diff --git a/game/gamemode.lua b/game/gamemode.lua index 6970bed..d3d7f78 100644 --- a/game/gamemode.lua +++ b/game/gamemode.lua @@ -1,7 +1,7 @@ local Object = require 'libs.classic' local bit = require("bit") local lualzw = require 'libs.lualzw' -local binser = require 'libs.binser' +local bitser = require 'libs.bitser' require 'funcs' require 'load.save' @@ -141,10 +141,8 @@ function GameMode:new(player_name, input_file, replay_grade) end function GameMode:readGradeHistory() - outfile = love.filesystem.newFile(SAVE_DIR..self.player_name.."_grade_history.sav", 'r') - if outfile ~= nil then - self.grade_history = binser.deserialize(outfile:read('a'))[1] - outfile:close() + if love.filesystem.getInfo(SAVE_DIR..self.player_name.."_grade_history.sav") then + self.grade_history = bitser.loadLoveFile(SAVE_DIR..self.player_name.."_grade_history.sav") else self.grade_history = {1,2,0,0} end @@ -153,17 +151,13 @@ function GameMode:readGradeHistory() if self.grade > 1 then temp_grade = copy(self.grade_history) temp_grade[2] = 0 - gradefile = love.filesystem.newFile(SAVE_DIR..self.player_name.."_grade_history.sav", 'w') - gradefile:write(binser.serialize(temp_grade)) - gradefile:close() - end + bitser.dumpLoveFile(SAVE_DIR..self.player_name.."_grade_history.sav", temp_grade) + end end function GameMode:readHiScores() - outfile = love.filesystem.newFile(HIscoreFILE, 'r') - if outfile ~= nil then - self.hi_scores = binser.deserialize(outfile:read())[1] - outfile:close() + if love.filesystem.getInfo(HIscoreFILE) then + self.hi_scores = bitser.loadLoveFile(HIscoreFILE) else self.hi_scores = {"TRO",0,"MIT",0,"ROM",0,"ITR",0,"OMI",0} end @@ -231,9 +225,7 @@ function GameMode:updateHiScores() self.hi_scores[score_position] = self.grade_score hiscore_pos = {score_position-1, score_position} end - local scoresfile = love.filesystem.newFile(HIscoreFILE, 'w') - scoresfile:write(binser.serialize(self.hi_scores)) - scoresfile:close() + bitser.dumpLoveFile(HIscoreFILE, self.hi_scores) return hiscore_pos end @@ -718,9 +710,7 @@ function GameMode:onGameOver() self.grade_score = self.grade_score + self.speed_level promo_string = self:updateGradeHistory() hiscore_pos = self:updateHiScores() - gradefile = love.filesystem.newFile(SAVE_DIR..self.player_name.."_grade_history.sav", 'w') - gradefile:write(binser.serialize(self.grade_history)) - gradefile:close() + bitser.dumpLoveFile(SAVE_DIR..self.player_name.."_grade_history.sav", self.grade_history) self.did_grades = true end self:drawEndScoringInfo() diff --git a/libs/binser.lua b/libs/binser.lua deleted file mode 100644 index 421da6e..0000000 --- a/libs/binser.lua +++ /dev/null @@ -1,689 +0,0 @@ --- binser.lua - ---[[ -Copyright (c) 2016 Calvin Rose - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -]] - -local assert = assert -local error = error -local select = select -local pairs = pairs -local getmetatable = getmetatable -local setmetatable = setmetatable -local tonumber = tonumber -local type = type -local loadstring = loadstring or load -local concat = table.concat -local char = string.char -local byte = string.byte -local format = string.format -local sub = string.sub -local dump = string.dump -local floor = math.floor -local frexp = math.frexp -local unpack = unpack or table.unpack - --- Lua 5.3 frexp polyfill --- From https://github.com/excessive/cpml/blob/master/modules/utils.lua -if not frexp then - local log, abs, floor = math.log, math.abs, math.floor - local log2 = log(2) - frexp = function(x) - if x == 0 then return 0, 0 end - local e = floor(log(abs(x)) / log2 + 1) - return x / 2 ^ e, e - end -end - --- NIL = 202 --- FLOAT = 203 --- TRUE = 204 --- FALSE = 205 --- STRING = 206 --- TABLE = 207 --- REFERENCE = 208 --- CONSTRUCTOR = 209 --- FUNCTION = 210 --- RESOURCE = 211 --- INT64 = 212 - -local mts = {} -local ids = {} -local serializers = {} -local deserializers = {} -local resources = {} -local resources_by_name = {} - -local function pack(...) - return {...}, select("#", ...) -end - -local function not_array_index(x, len) - return type(x) ~= "number" or x < 1 or x > len or x ~= floor(x) -end - -local function type_check(x, tp, name) - assert(type(x) == tp, - format("Expected parameter %q to be of type %q.", name, tp)) -end - -local bigIntSupport = false -local isInteger -if math.type then -- Detect Lua 5.3 - local mtype = math.type - bigIntSupport = loadstring[[ - local char = string.char - return function(n) - local nn = n < 0 and -(n + 1) or n - local b1 = nn // 0x100000000000000 - local b2 = nn // 0x1000000000000 % 0x100 - local b3 = nn // 0x10000000000 % 0x100 - local b4 = nn // 0x100000000 % 0x100 - local b5 = nn // 0x1000000 % 0x100 - local b6 = nn // 0x10000 % 0x100 - local b7 = nn // 0x100 % 0x100 - local b8 = nn % 0x100 - if n < 0 then - b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4 - b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8 - end - return char(212, b1, b2, b3, b4, b5, b6, b7, b8) - end]]() - isInteger = function(x) - return mtype(x) == 'integer' - end -else - isInteger = function(x) - return floor(x) == x - end -end - --- Copyright (C) 2012-2015 Francois Perrad. --- number serialization code modified from https://github.com/fperrad/lua-MessagePack --- Encode a number as a big-endian ieee-754 double, big-endian signed 64 bit integer, or a small integer -local function number_to_str(n) - if isInteger(n) then -- int - if n <= 100 and n >= -27 then -- 1 byte, 7 bits of data - return char(n + 27) - elseif n <= 8191 and n >= -8192 then -- 2 bytes, 14 bits of data - n = n + 8192 - return char(128 + (floor(n / 0x100) % 0x100), n % 0x100) - elseif bigIntSupport then - return bigIntSupport(n) - end - end - local sign = 0 - if n < 0.0 then - sign = 0x80 - n = -n - end - local m, e = frexp(n) -- mantissa, exponent - if m ~= m then - return char(203, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) - elseif m == 1/0 then - if sign == 0 then - return char(203, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) - else - return char(203, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00) - end - end - e = e + 0x3FE - if e < 1 then -- denormalized numbers - m = m * 2 ^ (52 + e) - e = 0 - else - m = (m * 2 - 1) * 2 ^ 52 - end - return char(203, - sign + floor(e / 0x10), - (e % 0x10) * 0x10 + floor(m / 0x1000000000000), - floor(m / 0x10000000000) % 0x100, - floor(m / 0x100000000) % 0x100, - floor(m / 0x1000000) % 0x100, - floor(m / 0x10000) % 0x100, - floor(m / 0x100) % 0x100, - m % 0x100) -end - --- Copyright (C) 2012-2015 Francois Perrad. --- number deserialization code also modified from https://github.com/fperrad/lua-MessagePack -local function number_from_str(str, index) - local b = byte(str, index) - if b < 128 then - return b - 27, index + 1 - elseif b < 192 then - return byte(str, index + 1) + 0x100 * (b - 128) - 8192, index + 2 - end - local b1, b2, b3, b4, b5, b6, b7, b8 = byte(str, index + 1, index + 8) - if b == 212 then - local flip = b1 >= 128 - if flip then -- negative - b1, b2, b3, b4 = 0xFF - b1, 0xFF - b2, 0xFF - b3, 0xFF - b4 - b5, b6, b7, b8 = 0xFF - b5, 0xFF - b6, 0xFF - b7, 0xFF - b8 - end - local n = ((((((b1 * 0x100 + b2) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8 - if flip then - return (-n) - 1, index + 9 - else - return n, index + 9 - end - end - local sign = b1 > 0x7F and -1 or 1 - local e = (b1 % 0x80) * 0x10 + floor(b2 / 0x10) - local m = ((((((b2 % 0x10) * 0x100 + b3) * 0x100 + b4) * 0x100 + b5) * 0x100 + b6) * 0x100 + b7) * 0x100 + b8 - local n - if e == 0 then - if m == 0 then - n = sign * 0.0 - else - n = sign * (m / 2 ^ 52) * 2 ^ -1022 - end - elseif e == 0x7FF then - if m == 0 then - n = sign * (1/0) - else - n = 0.0/0.0 - end - else - n = sign * (1.0 + m / 2 ^ 52) * 2 ^ (e - 0x3FF) - end - return n, index + 9 -end - -local types = {} - -types["nil"] = function(x, visited, accum) - accum[#accum + 1] = "\202" -end - -function types.number(x, visited, accum) - accum[#accum + 1] = number_to_str(x) -end - -function types.boolean(x, visited, accum) - accum[#accum + 1] = x and "\204" or "\205" -end - -function types.string(x, visited, accum) - local alen = #accum - if visited[x] then - accum[alen + 1] = "\208" - accum[alen + 2] = number_to_str(visited[x]) - else - visited[x] = visited.next - visited.next = visited.next + 1 - accum[alen + 1] = "\206" - accum[alen + 2] = number_to_str(#x) - accum[alen + 3] = x - end -end - -local function check_custom_type(x, visited, accum) - local res = resources[x] - if res then - accum[#accum + 1] = "\211" - types[type(res)](res, visited, accum) - return true - end - local mt = getmetatable(x) - local id = mt and ids[mt] - if id then - if x == visited.temp then - error("Infinite loop in constructor.") - end - visited.temp = x - accum[#accum + 1] = "\209" - types[type(id)](id, visited, accum) - local args, len = pack(serializers[id](x)) - accum[#accum + 1] = number_to_str(len) - for i = 1, len do - local arg = args[i] - types[type(arg)](arg, visited, accum) - end - visited[x] = visited.next - visited.next = visited.next + 1 - return true - end -end - -function types.userdata(x, visited, accum) - if visited[x] then - accum[#accum + 1] = "\208" - accum[#accum + 1] = number_to_str(visited[x]) - else - if check_custom_type(x, visited, accum) then return end - error("Cannot serialize this userdata.") - end -end - -function types.table(x, visited, accum) - if visited[x] then - accum[#accum + 1] = "\208" - accum[#accum + 1] = number_to_str(visited[x]) - else - if check_custom_type(x, visited, accum) then return end - visited[x] = visited.next - visited.next = visited.next + 1 - local xlen = #x - accum[#accum + 1] = "\207" - accum[#accum + 1] = number_to_str(xlen) - for i = 1, xlen do - local v = x[i] - types[type(v)](v, visited, accum) - end - local key_count = 0 - for k in pairs(x) do - if not_array_index(k, xlen) then - key_count = key_count + 1 - end - end - accum[#accum + 1] = number_to_str(key_count) - for k, v in pairs(x) do - if not_array_index(k, xlen) then - types[type(k)](k, visited, accum) - types[type(v)](v, visited, accum) - end - end - end -end - -types["function"] = function(x, visited, accum) - if visited[x] then - accum[#accum + 1] = "\208" - accum[#accum + 1] = number_to_str(visited[x]) - else - if check_custom_type(x, visited, accum) then return end - visited[x] = visited.next - visited.next = visited.next + 1 - local str = dump(x) - accum[#accum + 1] = "\210" - accum[#accum + 1] = number_to_str(#str) - accum[#accum + 1] = str - end -end - -types.cdata = function(x, visited, accum) - if visited[x] then - accum[#accum + 1] = "\208" - accum[#accum + 1] = number_to_str(visited[x]) - else - if check_custom_type(x, visited, #accum) then return end - error("Cannot serialize this cdata.") - end -end - -types.thread = function() error("Cannot serialize threads.") end - -local function deserialize_value(str, index, visited) - local t = byte(str, index) - if not t then return end - if t < 128 then - return t - 27, index + 1 - elseif t < 192 then - return byte(str, index + 1) + 0x100 * (t - 128) - 8192, index + 2 - elseif t == 202 then - return nil, index + 1 - elseif t == 203 then - return number_from_str(str, index) - elseif t == 204 then - return true, index + 1 - elseif t == 205 then - return false, index + 1 - elseif t == 206 then - local length, dataindex = deserialize_value(str, index + 1, visited) - local nextindex = dataindex + length - local substr = sub(str, dataindex, nextindex - 1) - visited[#visited + 1] = substr - return substr, nextindex - elseif t == 207 then - local count, nextindex = number_from_str(str, index + 1) - local ret = {} - visited[#visited + 1] = ret - for i = 1, count do - ret[i], nextindex = deserialize_value(str, nextindex, visited) - end - count, nextindex = number_from_str(str, nextindex) - for i = 1, count do - local k, v - k, nextindex = deserialize_value(str, nextindex, visited) - v, nextindex = deserialize_value(str, nextindex, visited) - ret[k] = v - end - return ret, nextindex - elseif t == 208 then - local ref, nextindex = number_from_str(str, index + 1) - return visited[ref], nextindex - elseif t == 209 then - local count - local name, nextindex = deserialize_value(str, index + 1, visited) - count, nextindex = number_from_str(str, nextindex) - local args = {} - for i = 1, count do - args[i], nextindex = deserialize_value(str, nextindex, visited) - end - local ret = deserializers[name](unpack(args)) - visited[#visited + 1] = ret - return ret, nextindex - elseif t == 210 then - local length, dataindex = deserialize_value(str, index + 1, visited) - local nextindex = dataindex + length - local ret = loadstring(sub(str, dataindex, nextindex - 1)) - visited[#visited + 1] = ret - return ret, nextindex - elseif t == 211 then - local res, nextindex = deserialize_value(str, index + 1, visited) - return resources_by_name[res], nextindex - elseif t == 212 then - return number_from_str(str, index) - else - error("Could not deserialize type byte " .. t .. ".") - end -end - -local function serialize(...) - local visited = {next = 1} - local accum = {} - for i = 1, select("#", ...) do - local x = select(i, ...) - types[type(x)](x, visited, accum) - end - return concat(accum) -end - -local function make_file_writer(file) - return setmetatable({}, { - __newindex = function(_, _, v) - file:write(v) - end - }) -end - -local function serialize_to_file(path, mode, ...) - local file, err = io.open(path, mode) - assert(file, err) - local visited = {next = 1} - local accum = make_file_writer(file) - for i = 1, select("#", ...) do - local x = select(i, ...) - types[type(x)](x, visited, accum) - end - -- flush the writer - file:flush() - file:close() -end - -local function writeFile(path, ...) - return serialize_to_file(path, "wb", ...) -end - -local function appendFile(path, ...) - return serialize_to_file(path, "ab", ...) -end - -local function deserialize(str, index) - assert(type(str) == "string", "Expected string to deserialize.") - local vals = {} - index = index or 1 - local visited = {} - local len = 0 - local val - while index do - val, index = deserialize_value(str, index, visited) - if index then - len = len + 1 - vals[len] = val - end - end - return vals, len -end - -local function deserializeN(str, n, index) - assert(type(str) == "string", "Expected string to deserialize.") - n = n or 1 - assert(type(n) == "number", "Expected a number for parameter n.") - assert(n > 0 and floor(n) == n, "N must be a poitive integer.") - local vals = {} - index = index or 1 - local visited = {} - local len = 0 - local val - while index and len < n do - val, index = deserialize_value(str, index, visited) - if index then - len = len + 1 - vals[len] = val - end - end - vals[len + 1] = index - return unpack(vals, 1, n + 1) -end - -local function readFile(path) - local file, err = io.open(path, "rb") - if file == nil then - return nil, 0 - end - local str = file:read("*all") - file:close() - return deserialize(str) -end - -local function default_deserialize(metatable) - return function(...) - local ret = {} - for i = 1, select("#", ...), 2 do - ret[select(i, ...)] = select(i + 1, ...) - end - return setmetatable(ret, metatable) - end -end - -local function default_serialize(x) - assert(type(x) == "table", - "Default serialization for custom types only works for tables.") - local args = {} - local len = 0 - for k, v in pairs(x) do - args[len + 1], args[len + 2] = k, v - len = len + 2 - end - return unpack(args, 1, len) -end - --- Templating - -local function normalize_template(template) - local ret = {} - for i = 1, #template do - ret[i] = template[i] - end - local non_array_part = {} - -- The non-array part of the template (nested templates) have to be deterministic, so they are sorted. - -- This means that inherently non deterministicly sortable keys (tables, functions) should NOT be used - -- in templates. Looking for way around this. - for k in pairs(template) do - if not_array_index(k, #template) then - non_array_part[#non_array_part + 1] = k - end - end - table.sort(non_array_part) - for i = 1, #non_array_part do - local name = non_array_part[i] - ret[#ret + 1] = {name, normalize_template(template[name])} - end - return ret -end - -local function templatepart_serialize(part, argaccum, x, len) - local extras = {} - local extracount = 0 - for k, v in pairs(x) do - extras[k] = v - extracount = extracount + 1 - end - for i = 1, #part do - extracount = extracount - 1 - if type(part[i]) == "table" then - extras[part[i][1]] = nil - len = templatepart_serialize(part[i][2], argaccum, x[part[i][1]], len) - else - extras[part[i]] = nil - len = len + 1 - argaccum[len] = x[part[i]] - end - end - if extracount > 0 then - argaccum[len + 1] = extras - else - argaccum[len + 1] = nil - end - return len + 1 -end - -local function templatepart_deserialize(ret, part, values, vindex) - for i = 1, #part do - local name = part[i] - if type(name) == "table" then - local newret = {} - ret[name[1]] = newret - vindex = templatepart_deserialize(newret, name[2], values, vindex) - else - ret[name] = values[vindex] - vindex = vindex + 1 - end - end - local extras = values[vindex] - if extras then - for k, v in pairs(extras) do - ret[k] = v - end - end - return vindex + 1 -end - -local function template_serializer_and_deserializer(metatable, template) - return function(x) - argaccum = {} - local len = templatepart_serialize(template, argaccum, x, 0) - return unpack(argaccum, 1, len) - end, function(...) - local ret = {} - local len = select("#", ...) - local args = {...} - templatepart_deserialize(ret, template, args, 1) - return setmetatable(ret, metatable) - end -end - -local function register(metatable, name, serialize, deserialize) - name = name or metatable.name - serialize = serialize or metatable._serialize - deserialize = deserialize or metatable._deserialize - if not serialize then - if metatable._template then - local t = normalize_template(metatable._template) - serialize, deserialize = template_serializer_and_deserializer(metatable, t) - elseif not deserialize then - serialize = default_serialize - deserialize = default_deserialize(metatable) - else - serialize = metatable - end - end - type_check(metatable, "table", "metatable") - type_check(name, "string", "name") - type_check(serialize, "function", "serialize") - type_check(deserialize, "function", "deserialize") - assert(not ids[metatable], "Metatable already registered.") - assert(not mts[name], ("Name %q already registered."):format(name)) - mts[name] = metatable - ids[metatable] = name - serializers[name] = serialize - deserializers[name] = deserialize - return metatable -end - -local function unregister(item) - local name, metatable - if type(item) == "string" then -- assume name - name, metatable = item, mts[item] - else -- assume metatable - name, metatable = ids[item], item - end - type_check(name, "string", "name") - type_check(metatable, "table", "metatable") - mts[name] = nil - ids[metatable] = nil - serializers[name] = nil - deserializers[name] = nil - return metatable -end - -local function registerClass(class, name) - name = name or class.name - if class.__instanceDict then -- middleclass - register(class.__instanceDict, name) - else -- assume 30log or similar library - register(class, name) - end - return class -end - -local function registerResource(resource, name) - type_check(name, "string", "name") - assert(not resources[resource], - "Resource already registered.") - assert(not resources_by_name[name], - format("Resource %q already exists.", name)) - resources_by_name[name] = resource - resources[resource] = name - return resource -end - -local function unregisterResource(name) - type_check(name, "string", "name") - assert(resources_by_name[name], format("Resource %q does not exist.", name)) - local resource = resources_by_name[name] - resources_by_name[name] = nil - resources[resource] = nil - return resource -end - -return { - -- aliases - s = serialize, - d = deserialize, - dn = deserializeN, - r = readFile, - w = writeFile, - a = appendFile, - - serialize = serialize, - deserialize = deserialize, - deserializeN = deserializeN, - readFile = readFile, - writeFile = writeFile, - appendFile = appendFile, - register = register, - unregister = unregister, - registerResource = registerResource, - unregisterResource = unregisterResource, - registerClass = registerClass -} diff --git a/libs/bitser.lua b/libs/bitser.lua new file mode 100644 index 0000000..b76a9d7 --- /dev/null +++ b/libs/bitser.lua @@ -0,0 +1,496 @@ +--[[ +Copyright (c) 2020, Jasmijn Wellner + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +]] + +local VERSION = '1.1' + +local floor = math.floor +local pairs = pairs +local type = type +local insert = table.insert +local getmetatable = getmetatable +local setmetatable = setmetatable + +local ffi = require("ffi") +local buf_pos = 0 +local buf_size = -1 +local buf = nil +local buf_is_writable = true +local writable_buf = nil +local writable_buf_size = nil +local includeMetatables = true -- togglable with bitser.includeMetatables(false) +local SEEN_LEN = {} + +local function Buffer_prereserve(min_size) + if buf_size < min_size then + buf_size = min_size + buf = ffi.new("uint8_t[?]", buf_size) + buf_is_writable = true + end +end + +local function Buffer_clear() + buf_size = -1 + buf = nil + buf_is_writable = true + writable_buf = nil + writable_buf_size = nil +end + +local function Buffer_makeBuffer(size) + if not buf_is_writable then + buf = writable_buf + buf_size = writable_buf_size + writable_buf = nil + writable_buf_size = nil + buf_is_writable = true + end + buf_pos = 0 + Buffer_prereserve(size) +end + +local function Buffer_newReader(str) + Buffer_makeBuffer(#str) + ffi.copy(buf, str, #str) +end + +local function Buffer_newDataReader(data, size) + if buf_is_writable then + writable_buf = buf + writable_buf_size = buf_size + end + buf_is_writable = false + buf_pos = 0 + buf_size = size + buf = ffi.cast("uint8_t*", data) +end + +local function Buffer_reserve(additional_size) + while buf_pos + additional_size > buf_size do + buf_size = buf_size * 2 + local oldbuf = buf + buf = ffi.new("uint8_t[?]", buf_size) + buf_is_writable = true + ffi.copy(buf, oldbuf, buf_pos) + end +end + +local function Buffer_write_byte(x) + Buffer_reserve(1) + buf[buf_pos] = x + buf_pos = buf_pos + 1 +end + +local function Buffer_write_raw(data, len) + Buffer_reserve(len) + ffi.copy(buf + buf_pos, data, len) + buf_pos = buf_pos + len +end + +local function Buffer_write_string(s) + Buffer_write_raw(s, #s) +end + +local function Buffer_write_data(ct, len, ...) + Buffer_write_raw(ffi.new(ct, ...), len) +end + +local function Buffer_ensure(numbytes) + if buf_pos + numbytes > buf_size then + error("malformed serialized data") + end +end + +local function Buffer_read_byte() + Buffer_ensure(1) + local x = buf[buf_pos] + buf_pos = buf_pos + 1 + return x +end + +local function Buffer_read_string(len) + Buffer_ensure(len) + local x = ffi.string(buf + buf_pos, len) + buf_pos = buf_pos + len + return x +end + +local function Buffer_read_raw(data, len) + ffi.copy(data, buf + buf_pos, len) + buf_pos = buf_pos + len + return data +end + +local function Buffer_read_data(ct, len) + return Buffer_read_raw(ffi.new(ct), len) +end + +local resource_registry = {} +local resource_name_registry = {} +local class_registry = {} +local class_name_registry = {} +local classkey_registry = {} +local class_deserialize_registry = {} + +local serialize_value + +local function write_number(value, _) + if floor(value) == value and value >= -2147483648 and value <= 2147483647 then + if value >= -27 and value <= 100 then + --small int + Buffer_write_byte(value + 27) + elseif value >= -32768 and value <= 32767 then + --short int + Buffer_write_byte(250) + Buffer_write_data("int16_t[1]", 2, value) + else + --long int + Buffer_write_byte(245) + Buffer_write_data("int32_t[1]", 4, value) + end + else + --double + Buffer_write_byte(246) + Buffer_write_data("double[1]", 8, value) + end +end + +local function write_string(value, _) + if #value < 32 then + --short string + Buffer_write_byte(192 + #value) + else + --long string + Buffer_write_byte(244) + write_number(#value) + end + Buffer_write_string(value) +end + +local function write_nil(_, _) + Buffer_write_byte(247) +end + +local function write_boolean(value, _) + Buffer_write_byte(value and 249 or 248) +end + +local function write_table(value, seen) + local classkey + local metatable = getmetatable(value) + local classname = (class_name_registry[value.class] -- MiddleClass + or class_name_registry[value.__baseclass] -- SECL + or class_name_registry[metatable] -- hump.class + or class_name_registry[value.__class__] -- Slither + or class_name_registry[value.__class]) -- Moonscript class + if classname then + classkey = classkey_registry[classname] + Buffer_write_byte(242) + serialize_value(classname, seen) + elseif includeMetatables and metatable then + Buffer_write_byte(253) + else + Buffer_write_byte(240) + end + local len = #value + write_number(len, seen) + for i = 1, len do + serialize_value(value[i], seen) + end + local klen = 0 + for k in pairs(value) do + if (type(k) ~= 'number' or floor(k) ~= k or k > len or k < 1) and k ~= classkey then + klen = klen + 1 + end + end + write_number(klen, seen) + for k, v in pairs(value) do + if (type(k) ~= 'number' or floor(k) ~= k or k > len or k < 1) and k ~= classkey then + serialize_value(k, seen) + serialize_value(v, seen) + end + end + if includeMetatables and metatable and not classname then + serialize_value(metatable, seen) + end +end + +local function write_cdata(value, seen) + local ty = ffi.typeof(value) + if ty == value then + -- ctype + Buffer_write_byte(251) + serialize_value(tostring(ty):sub(7, -2), seen) + return + end + -- cdata + Buffer_write_byte(252) + serialize_value(ty, seen) + local len = ffi.sizeof(value) + write_number(len) + Buffer_write_raw(ffi.typeof('$[1]', ty)(value), len) +end + +local types = {number = write_number, string = write_string, table = write_table, boolean = write_boolean, ["nil"] = write_nil, cdata = write_cdata} + +serialize_value = function(value, seen) + if seen[value] then + local ref = seen[value] + if ref < 64 then + --small reference + Buffer_write_byte(128 + ref) + else + --long reference + Buffer_write_byte(243) + write_number(ref, seen) + end + return + end + local t = type(value) + if t ~= 'number' and t ~= 'boolean' and t ~= 'nil' and t ~= 'cdata' then + seen[value] = seen[SEEN_LEN] + seen[SEEN_LEN] = seen[SEEN_LEN] + 1 + end + if resource_name_registry[value] then + local name = resource_name_registry[value] + if #name < 16 then + --small resource + Buffer_write_byte(224 + #name) + Buffer_write_string(name) + else + --long resource + Buffer_write_byte(241) + write_string(name, seen) + end + return + end + (types[t] or + error("cannot serialize type " .. t) + )(value, seen) +end + +local function serialize(value) + Buffer_makeBuffer(4096) + local seen = {[SEEN_LEN] = 0} + serialize_value(value, seen) +end + +local function add_to_seen(value, seen) + insert(seen, value) + return value +end + +local function reserve_seen(seen) + insert(seen, 42) + return #seen +end + +local function deserialize_value(seen) + local t = Buffer_read_byte() + if t < 128 then + --small int + return t - 27 + elseif t < 192 then + --small reference + return seen[t - 127] + elseif t < 224 then + --small string + return add_to_seen(Buffer_read_string(t - 192), seen) + elseif t < 240 then + --small resource + return add_to_seen(resource_registry[Buffer_read_string(t - 224)], seen) + elseif t == 240 or t == 253 then + --table + local v = add_to_seen({}, seen) + local len = deserialize_value(seen) + for i = 1, len do + v[i] = deserialize_value(seen) + end + len = deserialize_value(seen) + for _ = 1, len do + local key = deserialize_value(seen) + v[key] = deserialize_value(seen) + end + if t == 253 then + if includeMetatables then + setmetatable(v, deserialize_value(seen)) + end + end + return v + elseif t == 241 then + --long resource + local idx = reserve_seen(seen) + local value = resource_registry[deserialize_value(seen)] + seen[idx] = value + return value + elseif t == 242 then + --instance + local instance = add_to_seen({}, seen) + local classname = deserialize_value(seen) + local class = class_registry[classname] + local classkey = classkey_registry[classname] + local deserializer = class_deserialize_registry[classname] + local len = deserialize_value(seen) + for i = 1, len do + instance[i] = deserialize_value(seen) + end + len = deserialize_value(seen) + for _ = 1, len do + local key = deserialize_value(seen) + instance[key] = deserialize_value(seen) + end + if classkey then + instance[classkey] = class + end + return deserializer(instance, class) + elseif t == 243 then + --reference + return seen[deserialize_value(seen) + 1] + elseif t == 244 then + --long string + return add_to_seen(Buffer_read_string(deserialize_value(seen)), seen) + elseif t == 245 then + --long int + return Buffer_read_data("int32_t[1]", 4)[0] + elseif t == 246 then + --double + return Buffer_read_data("double[1]", 8)[0] + elseif t == 247 then + --nil + return nil + elseif t == 248 then + --false + return false + elseif t == 249 then + --true + return true + elseif t == 250 then + --short int + return Buffer_read_data("int16_t[1]", 2)[0] + elseif t == 251 then + --ctype + return ffi.typeof(deserialize_value(seen)) + elseif t == 252 then + local ctype = deserialize_value(seen) + local len = deserialize_value(seen) + local read_into = ffi.typeof('$[1]', ctype)() + Buffer_read_raw(read_into, len) + return ctype(read_into[0]) + else + error("unsupported serialized type " .. t) + end +end + +local function deserialize_MiddleClass(instance, class) + return setmetatable(instance, class.__instanceDict) +end + +local function deserialize_SECL(instance, class) + return setmetatable(instance, getmetatable(class)) +end + +local deserialize_humpclass = setmetatable + +local function deserialize_Slither(instance, class) + return getmetatable(class).allocate(instance) +end + +local function deserialize_Moonscript(instance, class) + return setmetatable(instance, class.__base) +end + +return {dumps = function(value) + serialize(value) + return ffi.string(buf, buf_pos) +end, dumpLoveFile = function(fname, value) + serialize(value) + assert(love.filesystem.write(fname, ffi.string(buf, buf_pos))) +end, loadLoveFile = function(fname) + local serializedData, error = love.filesystem.newFileData(fname) + assert(serializedData, error) + Buffer_newDataReader(serializedData:getPointer(), serializedData:getSize()) + local value = deserialize_value({}) + -- serializedData needs to not be collected early in a tail-call + -- so make sure deserialize_value returns before loadLoveFile does + return value +end, loadData = function(data, size) + if size == 0 then + error('cannot load value from empty data') + end + Buffer_newDataReader(data, size) + return deserialize_value({}) +end, loads = function(str) + if #str == 0 then + error('cannot load value from empty string') + end + Buffer_newReader(str) + return deserialize_value({}) +end, includeMetatables = function(bool) + includeMetatables = not not bool +end, register = function(name, resource) + assert(not resource_registry[name], name .. " already registered") + resource_registry[name] = resource + resource_name_registry[resource] = name + return resource +end, unregister = function(name) + resource_name_registry[resource_registry[name]] = nil + resource_registry[name] = nil +end, registerClass = function(name, class, classkey, deserializer) + if not class then + class = name + name = class.__name__ or class.name or class.__name + end + if not classkey then + if class.__instanceDict then + -- assume MiddleClass + classkey = 'class' + elseif class.__baseclass then + -- assume SECL + classkey = '__baseclass' + end + -- assume hump.class, Slither, Moonscript class or something else that doesn't store the + -- class directly on the instance + end + if not deserializer then + if class.__instanceDict then + -- assume MiddleClass + deserializer = deserialize_MiddleClass + elseif class.__baseclass then + -- assume SECL + deserializer = deserialize_SECL + elseif class.__index == class then + -- assume hump.class + deserializer = deserialize_humpclass + elseif class.__name__ then + -- assume Slither + deserializer = deserialize_Slither + elseif class.__base then + -- assume Moonscript class + deserializer = deserialize_Moonscript + else + error("no deserializer given for unsupported class library") + end + end + class_registry[name] = class + classkey_registry[name] = classkey + class_deserialize_registry[name] = deserializer + class_name_registry[class] = name + return class +end, unregisterClass = function(name) + class_name_registry[class_registry[name]] = nil + classkey_registry[name] = nil + class_deserialize_registry[name] = nil + class_registry[name] = nil +end, reserveBuffer = Buffer_prereserve, clearBuffer = Buffer_clear, version = VERSION} diff --git a/load/save.lua b/load/save.lua index 50c1bfa..5b2b67d 100644 --- a/load/save.lua +++ b/load/save.lua @@ -1,4 +1,4 @@ -local binser = require 'libs.binser' +local bitser = require 'libs.bitser' local fs = love.filesystem function loadSave() @@ -6,11 +6,10 @@ function loadSave() end function loadFromFile(filename) - local save_data = fs.read(filename) - if save_data == nil then + if fs.read(filename) == nil then return {} -- new object end - return binser.deserialize(save_data)[1] + return bitser.loadLoveFile(filename) end function initConfig() @@ -25,5 +24,5 @@ function initConfig() end function saveConfig() - fs.write(CONFIG_FILE,binser.serialize(config)) + bitser.dumpLoveFile(CONFIG_FILE, config) end diff --git a/scene/name_entry.lua b/scene/name_entry.lua index 0babb81..18c8200 100644 --- a/scene/name_entry.lua +++ b/scene/name_entry.lua @@ -1,6 +1,6 @@ local NameEntryScene = Scene:extend() local Grid = require 'game.grid' -local binser = require 'libs.binser' +local bitser = require 'libs.bitser' require 'load.save' NameEntryScene.title = "Game Start" @@ -30,9 +30,8 @@ function NameEntryScene:new() self.name_entry = {config['last_entry']:sub(1,1),config['last_entry']:sub(2,2),config['last_entry']:sub(3,3)} self.entry_pos = 3 end - score_file = love.filesystem.newFile(HIscoreFILE, 'r') - if score_file ~= nil then - self.hi_scores = binser.deserialize(score_file:read())[1] + if love.filesystem.getInfo(HIscoreFILE) then + self.hi_scores = bitser.loadLoveFile(HIscoreFILE) else self.hi_scores = {"TRO",0,"MIT",0,"ROM",0,"ITR",0,"OMI",0} end @@ -122,9 +121,8 @@ function NameEntryScene:onInputPress(e) else if self.entry_pos == 3 then name = string.lower(self.name_entry[1]..self.name_entry[2]..self.name_entry[3]) - grade_history = love.filesystem.newFile(SAVE_DIR..name.."_grade_history.sav", 'r') - if grade_history ~= nil then - grade_history = binser.deserialize(grade_history:read())[1] + if love.filesystem.getInfo((SAVE_DIR..name.."_grade_history.sav")) then + grade_history = bitser.loadLoveFile(SAVE_DIR..name.."_grade_history.sav") self.grade = grade_history[1] self.wins = grade_history[2] self.plays = grade_history[4]