diff --git a/libs/binser.lua b/libs/binser.lua index 421da6e..00958f0 100644 --- a/libs/binser.lua +++ b/libs/binser.lua @@ -1,7 +1,7 @@ -- binser.lua --[[ -Copyright (c) 2016 Calvin Rose +Copyright (c) 2016-2019 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 @@ -27,7 +27,6 @@ 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 @@ -52,25 +51,6 @@ if not frexp then 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 @@ -166,25 +146,36 @@ end -- 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 not b then error("Expected more bytes of input.") end if b < 128 then return b - 27, index + 1 elseif b < 192 then - return byte(str, index + 1) + 0x100 * (b - 128) - 8192, index + 2 + local b2 = byte(str, index + 1) + if not b2 then error("Expected more bytes of input.") end + return b2 + 0x100 * (b - 128) - 8192, index + 2 end local b1, b2, b3, b4, b5, b6, b7, b8 = byte(str, index + 1, index + 8) + if (not b1) or (not b2) or (not b3) or (not b4) or + (not b5) or (not b6) or (not b7) or (not b8) then + error("Expected more bytes of input.") + end 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 + 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 + if b ~= 203 then + error("Expected number") + 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 @@ -207,483 +198,553 @@ local function number_from_str(str, index) return n, index + 9 end -local types = {} -types["nil"] = function(x, visited, accum) - accum[#accum + 1] = "\202" -end +local function newbinser() -function types.number(x, visited, accum) - accum[#accum + 1] = number_to_str(x) -end + -- unique table key for getting next value + local NEXT = {} + local CTORSTACK = {} -function types.boolean(x, visited, accum) - accum[#accum + 1] = x and "\204" or "\205" -end + -- NIL = 202 + -- FLOAT = 203 + -- TRUE = 204 + -- FALSE = 205 + -- STRING = 206 + -- TABLE = 207 + -- REFERENCE = 208 + -- CONSTRUCTOR = 209 + -- FUNCTION = 210 + -- RESOURCE = 211 + -- INT64 = 212 + -- TABLE WITH META = 213 -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 + local mts = {} + local ids = {} + local serializers = {} + local deserializers = {} + local resources = {} + local resources_by_name = {} + local types = {} + + types["nil"] = function(x, visited, accum) + accum[#accum + 1] = "\202" 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 + function types.number(x, visited, accum) + accum[#accum + 1] = number_to_str(x) end - local mt = getmetatable(x) - local id = mt and ids[mt] - if id then - if x == visited.temp then - error("Infinite loop in constructor.") + + 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 - 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) + 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 key_count = 0 - for k in pairs(x) do - if not_array_index(k, xlen) then - key_count = key_count + 1 + local mt = getmetatable(x) + local id = mt and ids[mt] + if id then + local constructing = visited[CTORSTACK] + if constructing[x] then + error("Infinite loop in constructor.") end + constructing[x] = true + 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 + -- We finished constructing + constructing[x] = nil + return true 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) + 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 + local mt = getmetatable(x) + if mt then + accum[#accum + 1] = "\213" + types.table(mt, visited, accum) + else + accum[#accum + 1] = "\207" + end + 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 -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) + types["function"] = function(x, visited, accum) + if visited[x] then + accum[#accum + 1] = "\208" + accum[#accum + 1] = number_to_str(visited[x]) else - extras[part[i]] = nil - len = len + 1 - argaccum[len] = x[part[i]] + 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 - 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) + types.cdata = function(x, visited, accum) + if visited[x] then + accum[#accum + 1] = "\208" + accum[#accum + 1] = number_to_str(visited[x]) else - ret[name] = values[vindex] - vindex = vindex + 1 + if check_custom_type(x, visited, #accum) then return end + error("Cannot serialize this cdata.") 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(...) + 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 nil, index end + if t < 128 then + return t - 27, index + 1 + elseif t < 192 then + local b2 = byte(str, index + 1) + if not b2 then error("Expected more bytes of input.") end + return b2 + 0x100 * (t - 128) - 8192, index + 2 + elseif t == 202 then + return nil, index + 1 + elseif t == 203 or t == 212 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 = number_from_str(str, index + 1) + local nextindex = dataindex + length + if not (length >= 0) then error("Bad string length") end + if #str < nextindex - 1 then error("Expected more bytes of string") end + local substr = sub(str, dataindex, nextindex - 1) + visited[#visited + 1] = substr + return substr, nextindex + elseif t == 207 or t == 213 then + local mt, count, nextindex + local ret = {} + visited[#visited + 1] = ret + nextindex = index + 1 + if t == 213 then + mt, nextindex = deserialize_value(str, nextindex, visited) + if type(mt) ~= "table" then error("Expected table metatable") end + end + count, nextindex = number_from_str(str, nextindex) + for i = 1, count do + local oldindex = nextindex + ret[i], nextindex = deserialize_value(str, nextindex, visited) + if nextindex == oldindex then error("Expected more bytes of input.") end + end + count, nextindex = number_from_str(str, nextindex) + for i = 1, count do + local k, v + local oldindex = nextindex + k, nextindex = deserialize_value(str, nextindex, visited) + if nextindex == oldindex then error("Expected more bytes of input.") end + oldindex = nextindex + v, nextindex = deserialize_value(str, nextindex, visited) + if nextindex == oldindex then error("Expected more bytes of input.") end + if k == nil then error("Can't have nil table keys") end + ret[k] = v + end + if mt then setmetatable(ret, mt) 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 + local oldindex = nextindex + args[i], nextindex = deserialize_value(str, nextindex, visited) + if nextindex == oldindex then error("Expected more bytes of input.") end + end + if not name or not deserializers[name] then + error(("Cannot deserialize class '%s'"):format(tostring(name))) + end + local ret = deserializers[name](unpack(args)) + visited[#visited + 1] = ret + return ret, nextindex + elseif t == 210 then + local length, dataindex = number_from_str(str, index + 1) + local nextindex = dataindex + length + if not (length >= 0) then error("Bad string length") end + if #str < nextindex - 1 then error("Expected more bytes of string") end + local ret = loadstring(sub(str, dataindex, nextindex - 1)) + visited[#visited + 1] = ret + return ret, nextindex + elseif t == 211 then + local resname, nextindex = deserialize_value(str, index + 1, visited) + if resname == nil then error("Got nil resource name") end + local res = resources_by_name[resname] + if res == nil then + error(("No resources found for name '%s'"):format(tostring(resname))) + end + return res, nextindex + else + error("Could not deserialize type byte " .. t .. ".") + end + end + + local function serialize(...) + local visited = {[NEXT] = 1, [CTORSTACK] = {}} + 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, [CTORSTACK] = {}} + 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 true do + local nextindex + val, nextindex = deserialize_value(str, index, visited) + if nextindex > index then + len = len + 1 + vals[len] = val + index = nextindex + else + break + 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 len < n do + local nextindex + val, nextindex = deserialize_value(str, index, visited) + if nextindex > index then + len = len + 1 + vals[len] = val + index = nextindex + else + break + end + end + vals[len + 1] = index + return unpack(vals, 1, n + 1) + end + + local function readFile(path) + local file, err = io.open(path, "rb") + assert(file, err) + local str = file:read("*all") + file:close() + return deserialize(str) + end + + -- Resources + + 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 + + -- Templating + + local function normalize_template(template) local ret = {} - local len = select("#", ...) - local args = {...} - templatepart_deserialize(ret, template, args, 1) - return setmetatable(ret, metatable) + 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 -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) + 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 + local name + if type(part[i]) == "table" then + name = part[i][1] + len = templatepart_serialize(part[i][2], argaccum, x[name], len) + else + name = part[i] + len = len + 1 + argaccum[len] = x[part[i]] + end + if extras[name] ~= nil then + extracount = extracount - 1 + extras[name] = nil + end + end + if extracount > 0 then + argaccum[len + 1] = extras else - serialize = metatable + 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) + local argaccum = {} + local len = templatepart_serialize(template, argaccum, x, 0) + return unpack(argaccum, 1, len) + end, function(...) + local ret = {} + local args = {...} + templatepart_deserialize(ret, template, args, 1) + return setmetatable(ret, 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 + -- Used to serialize classes withh custom serializers and deserializers. + -- If no _serialize or _deserialize (or no _template) value is found in the + -- metatable, then the metatable is registered as a resources. + local function register(metatable, name, serialize, deserialize) + if type(metatable) == "table" then + name = name or metatable.name + serialize = serialize or metatable._serialize + deserialize = deserialize or metatable._deserialize + if (not serialize) or (not deserialize) then + if metatable._template then + -- Register as template + local t = normalize_template(metatable._template) + serialize, deserialize = template_serializer_and_deserializer(metatable, t) + else + -- Register the metatable as a resource. This is semantically + -- similar and more flexible (handles cycles). + registerResource(metatable, name) + return + end + end + elseif type(metatable) == "string" then + name = name or metatable + end + type_check(name, "string", "name") + type_check(serialize, "function", "serialize") + type_check(deserialize, "function", "deserialize") + assert((not ids[metatable]) and (not resources[metatable]), + "Metatable already registered.") + assert((not mts[name]) and (not resources_by_name[name]), + ("Name %q already registered."):format(name)) + mts[name] = metatable + ids[metatable] = name + serializers[name] = serialize + deserializers[name] = deserialize + return metatable 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) + 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") + mts[name] = nil + if (metatable) then + resources[metatable] = nil + ids[metatable] = nil + end + serializers[name] = nil + deserializers[name] = nil + resources_by_name[name] = nil; + return metatable end - return class + + 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 + + return { + VERSION = "0.0-8", + -- 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, + + newbinser = newbinser + } 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 -} +return newbinser() diff --git a/main.lua b/main.lua index d82acc3..06fb7a3 100644 --- a/main.lua +++ b/main.lua @@ -1,6 +1,6 @@ if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE")=="1" then - LLDEBUGGER=require('lldebugger') - LLDEBUGGER.start() + LLDEBUGGER=require('lldebugger') + LLDEBUGGER.start() end function love.load() @@ -17,7 +17,7 @@ function love.load() require "load.version" require "funcs" - TOUCH_SETTINGS = require 'mobile_libs.settings' + TOUCH_SETTINGS = require 'mobile_libs.touch_settings' BUTTON = require 'mobile_libs.simple-button' BUTTON.setDefaultOption{ draw = function(self) @@ -62,6 +62,8 @@ function love.load() font=font_3x5_2, } require 'mobile_libs.vctrl' + VCTRL.new(TOUCH_SETTINGS.bind[1]) + VCTRL.toggle(true) loadSave() require "scene" @@ -136,6 +138,7 @@ function love.draw() love.graphics.scale(scale_factor) scene:render() + if scene.title ~= TouchConfigScene.title then VCTRL.draw() end if config.gamesettings.display_gamemode == 1 or scene.title == "Title" then love.graphics.setFont(font_3x5_2) @@ -335,10 +338,51 @@ function love.joystickhat(joystick, hat, direction) end end +function love.mousepressed(x, y, b, isTouch, presses) + if isTouch then return end + local x,y=GLOBAL_TRANSFORM:inverseTransformPoint(x,y) + if scene.title ~= 'Touchscreen configuration' and VCTRL.press(x, y, 1) then + return -- Avoid duplicate + end + scene:onInputPress{type = "mouse", x = x, y = y, presses = presses} +end +function love.mousereleased(x, y, b, isTouch, presses) + if isTouch then return end + local x,y=GLOBAL_TRANSFORM:inverseTransformPoint(x,y) + if scene.title ~= 'Touchscreen configuration' and VCTRL.release(1) then + return -- Avoid duplicate + end + scene:onInputRelease{type = "mouse", x = x, y = y, presses = presses} +end +function love.mousemoved(x, y, dx, dy, isTouch) + if isTouch then return end + local x,y=GLOBAL_TRANSFORM:inverseTransformPoint(x,y) + local dx,dy=dx/SCREEN_SCALE_FACTOR,dy/SCREEN_SCALE_FACTOR + scene:onInputMove{type = "mouse", x = x, y = y, dx = dx, dy = dy} +end function love.wheelmoved(x, y) scene:onInputPress({input=nil, type="wheel", x=x, y=y}) end +function love.touchpressed(id,x,y) + local x,y=GLOBAL_TRANSFORM:inverseTransformPoint(x,y) + if scene.title ~= 'Touchscreen configuration' and VCTRL.press(x, y, id) then + return -- Avoid duplicate + end + scene:onInputPress{type = "touch", x = x, y = y, dx = 0, dy = 0, id = id} +end +function love.touchreleased(id,x,y) + local x,y=GLOBAL_TRANSFORM:inverseTransformPoint(x,y) + if scene.title ~= 'Touchscreen configuration' and VCTRL.release(id) then + return -- Avoid duplicate + end + scene:onInputRelease{type = "touch", x = x, y = y, dx = 0, dy = 0, id = id} +end +function love.touchmoved(id,x,y,dx,dy) + local x,y=GLOBAL_TRANSFORM:inverseTransformPoint(x,y) + scene:onInputMove{type = "touch", x = x, y = y, dx = dx, dy = dy, id = id} +end + function love.resize(w, h) GLOBAL_CANVAS:release() GLOBAL_CANVAS = love.graphics.newCanvas(w, h) diff --git a/mobile_libs/bitser.lua b/mobile_libs/bitser.lua new file mode 100644 index 0000000..b76a9d7 --- /dev/null +++ b/mobile_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/mobile_libs/file.lua b/mobile_libs/file.lua index 44619b3..c6abbc1 100644 --- a/mobile_libs/file.lua +++ b/mobile_libs/file.lua @@ -1,6 +1,6 @@ local FILE = {} local binser = require "libs.binser" --- local bitser = require "libs.bitser" +local bitser = require "mobile_libs.bitser" local serializer_used @@ -37,5 +37,5 @@ end return function(lib_name) assert(lib_name == 'bitser' or lib_name == 'binser', '[lib_name] must be "bitser" or "binser"') serializer_used = lib_name - _G.FILE = FILE + return FILE end \ No newline at end of file diff --git a/mobile_libs/settings.lua b/mobile_libs/settings.lua deleted file mode 100644 index b6186c1..0000000 --- a/mobile_libs/settings.lua +++ /dev/null @@ -1,38 +0,0 @@ -local fs = love.filesystem - -local CONFIG_FILE = '/mobile/touch_config.txt' -local _settings = fs.read(CONFIG_FILE) ~= nil and FILE.read(CONFIG_FILE) or {} -local _defaultSettings = { - firstTime = true, - __default__={ - {type='button',x= 70,y=280,key= 'up',r=45,iconSize=60,alpha=0.4}, - {type='button',x= 70,y=430,key= 'down',r=45,iconSize=60,alpha=0.4}, - {type='button',x= -5,y=355,key= 'left',r=45,iconSize=60,alpha=0.4}, - {type='button',x= 145,y=355,key= 'right',r=45,iconSize=60,alpha=0.4}, - {type='button',x=640- -5,y=355,key= 'rotate_left',r=45,iconSize=60,alpha=0.4}, - {type='button',x=640-145,y=355,key= 'rotate_left2',r=45,iconSize=60,alpha=0.4}, - {type='button',x=640- 70,y=430,key= 'rotate_right',r=45,iconSize=60,alpha=0.4}, - {type='button',x=640- 70,y=280,key='rotate_right2',r=45,iconSize=60,alpha=0.4}, - {type='button',x=320, y=420,key= 'restart',r=35,iconSize=60,alpha=0.4}, - }, - - ---@type table[] - bind = {}, -} - -return setmetatable( - {__default__ = _defaultSettings}, - { - __index = function(_, k) - if _settings[k] == nil then - _settings[k] = _defaultSettings[k] - FILE.write(CONFIG_FILE,_settings) - end - return _settings[k] - end, - __newindex = function(_, k, v) - _settings[k] = v - FILE.write(CONFIG_FILE,_settings) - end - } -) \ No newline at end of file diff --git a/mobile_libs/touch_settings.lua b/mobile_libs/touch_settings.lua new file mode 100644 index 0000000..f205efe --- /dev/null +++ b/mobile_libs/touch_settings.lua @@ -0,0 +1,67 @@ +local fs = love.filesystem +local FILE=require'mobile_libs.file''binser' + +local CONFIG_FILE = 'mobile/touch_config.txt' +love.filesystem.createDirectory('mobile') +local _settings = fs.read(CONFIG_FILE) ~= nil and FILE.read(CONFIG_FILE) or {} +local _defaultSettings = { + firstTime = true, + + ---@type table[] + bind = { + { + {type='button',x= 1 * 70 + 120,y = 1 * 70 + 120, r = 30, key= 'up',iconSize=50}, + {type='button',x= 2 * 70 + 120,y = 1 * 70 + 120, r = 30, key= 'down',iconSize=50}, + {type='button',x= 3 * 70 + 120,y = 1 * 70 + 120, r = 30, key= 'left',iconSize=50}, + {type='button',x= 4 * 70 + 120,y = 1 * 70 + 120, r = 30, key= 'right',iconSize=50}, + {type='button',x= 5 * 70 + 120,y = 1 * 70 + 120, r = 30, key= 'menu_decide',iconSize=50}, + + {type='button',x= 1 * 70 + 120,y = 2 * 70 + 120, r = 30, key= 'rotate_left',iconSize=50}, + {type='button',x= 2 * 70 + 120,y = 2 * 70 + 120, r = 30, key= 'rotate_left2',iconSize=50}, + {type='button',x= 3 * 70 + 120,y = 2 * 70 + 120, r = 30, key= 'rotate_right',iconSize=50}, + {type='button',x= 4 * 70 + 120,y = 2 * 70 + 120, r = 30, key= 'rotate_right2',iconSize=50}, + {type='button',x= 5 * 70 + 120,y = 2 * 70 + 120, r = 30, key= 'retry',iconSize=50}, + + {type='button',x= 1 * 70 + 120,y = 3 * 70 + 120, r = 30, key= '1',iconSize=50}, + {type='button',x= 2 * 70 + 120,y = 3 * 70 + 120, r = 30, key= '2',iconSize=50}, + {type='button',x= 3 * 70 + 120,y = 3 * 70 + 120, r = 30, key= '3',iconSize=50}, + {type='button',x= 4 * 70 + 120,y = 3 * 70 + 120, r = 30, key= '4',iconSize=50}, + {type='button',x= 5 * 70 + 120,y = 3 * 70 + 120, r = 30, key= '5',iconSize=50}, + + -- {type='button',x= 1 * 70 + 120,y = 4 * 70 + 120, r = 30, key= 'up',iconSize=50}, + {type='button',x= 2 * 70 + 120,y = 4 * 70 + 120, r = 30, key='touch_settings',iconSize=50}, + {type='button',x= 3 * 70 + 120,y = 4 * 70 + 120, r = 30, key= 'align_view',iconSize=50}, + {type='button',x= 4 * 70 + 120,y = 4 * 70 + 120, r = 30, key= 'menu_back',iconSize=50}, + -- {type='button',x= 5 * 70 + 120,y = 4 * 70 + 120, r = 30, key= 'up',iconSize=50}, + + }, -- 1 + { + {type='button',x= 70,y=280,key= 'up',r=45,iconSize=60,alpha=0.4,shape='circle'}, + {type='button',x= 70,y=430,key= 'down',r=45,iconSize=60,alpha=0.4,shape='circle'}, + {type='button',x= -5,y=355,key= 'left',r=45,iconSize=60,alpha=0.4,shape='circle'}, + {type='button',x= 145,y=355,key= 'right',r=45,iconSize=60,alpha=0.4,shape='circle'}, + {type='button',x=640- -5,y=355,key= 'rotate_left',r=45,iconSize=60,alpha=0.4,shape='circle'}, + {type='button',x=640-145,y=355,key= 'rotate_left2',r=45,iconSize=60,alpha=0.4,shape='circle'}, + {type='button',x=640- 70,y=430,key= 'rotate_right',r=45,iconSize=60,alpha=0.4,shape='circle'}, + {type='button',x=640- 70,y=280,key='rotate_right2',r=45,iconSize=60,alpha=0.4,shape='circle'}, + {type='button',x=320, y=420,key= 'retry',r=35,iconSize=60,alpha=0.4,shape='circle'}, + } -- 2 + }, +} + +return setmetatable( + {__default__ = _defaultSettings}, + { + __index = function(_, k) + if _settings[k] == nil then + _settings[k] = _defaultSettings[k] + FILE.write(CONFIG_FILE,_settings) + end + return _settings[k] + end, + __newindex = function(_, k, v) + _settings[k] = v + FILE.write(CONFIG_FILE,_settings) + end + } +) \ No newline at end of file diff --git a/mobile_libs/vctrl.lua b/mobile_libs/vctrl.lua index ad601af..dcb2649 100644 --- a/mobile_libs/vctrl.lua +++ b/mobile_libs/vctrl.lua @@ -15,34 +15,34 @@ local function mDrawQ(obj,quad,x,y,a,k) love.graphics.draw(obj,quad,x,y,a,k,nil,w*.5,h*.5) end -local empty_quad +local alternativePfunction={} +local alternativeRfunction={ + touch_settings=function() + if ( + scene.title ~= 'Game' and + scene.title ~= TouchConfigScene.title + ) then + scene = TouchConfigScene() + end + end +} + +local empty_quad=gc_newQuad(1,1,1,1,1,1) -- A table containing quads used to draw icons for virtual control system. --- local virtual_quad=setmetatable((function() --- local t={} --- local w=180 --- empty_quad=gc_newQuad(0,0,1,1,5*w,7*w) --- for i,name in next,{ --- 'left','right','up','down','', --- 'rotate_right','rotate_left','','','', --- '','','','','', --- '','','','','', --- '','','menu_back','','', --- '','','','','', --- '','','','','menu_decide', --- } do if #name>0 then t[name]=gc_newQuad((i-1)%5*w,math.floor((i-1)/5)*w,w,w,5*w,7*w) end end --- t.rotate_right2, t.rotate_left2 = t.rotate_right, t.rotate_left --- return t --- end)(),{ --- __index=function() return empty_quad end --- }) local virtual_quad=setmetatable((function() local t={} local w=180 - empty_quad=gc_newQuad(0,0,1,1,5*w,2*w) + empty_quad=gc_newQuad(0,0,1,1,5*w,7*w) for i,name in next,{ - 'left','right','up','down','restart', - 'rotate_right','rotate_left','rotate_right2','rotate_left2' - } do if #name>0 then t[name]=gc_newQuad((i-1)%5*w,math.floor((i-1)/5)*w,w,w,5*w,2*w) end end + 'left','right','up','down','', + 'rotate_right','rotate_left','rotate_180','hold','align_view', + '','','','','', + '1','2','3','4','5', + '','retry','','menu_back','', + 'touch_settings','','','','', + '','','','','menu_decide', + } do if #name>0 then t[name]=gc_newQuad((i-1)%5*w,math.floor((i-1)/5)*w,w,w,5*w,7*w) end end + t.rotate_right2, t.rotate_left2 = t.rotate_right, t.rotate_left return t end)(),{ __index=function() return empty_quad end @@ -61,11 +61,11 @@ function control_type.button:new(data) x=data.x or 320, y=data.y or 240, r=data.r or 80, -- size - shape=data.shape or 'circle', + shape=data.shape or 'square', key=data.key or 'X', iconSize=data.iconSize or 60, alpha=data.alpha or 0.75, - quad=virtual_quad[data.key] + quad=virtual_quad[data.key], },self) end function control_type.button:export() @@ -78,7 +78,7 @@ function control_type.button:export() shape = self.shape, key = self.key, iconSize = self.iconSize, - alpha = self.alpha + alpha = self.alpha, } end function control_type.button:reset() @@ -91,13 +91,21 @@ function control_type.button:press(_,_,id) self.lastPressTime=love.timer.getTime() self.pressingID=id -- love.keypressed(self.key, love.keyboard.getScancodeFromKey(self.key)) - SCENE:onInputPress{input=self.key,type="virtual"} + if alternativePfunction[self.key] then + alternativePfunction[self.key]() + else + scene:onInputPress{input=self.key,type="virtual"} + end end function control_type.button:release() self.pressed=false self.pressingID=false -- love.keyreleased(self.key,love.keyboard.getScancodeFromKey(self.key)) - SCENE:onInputRelease{input=self.key,type="virtual"} + if alternativeRfunction[self.key] then + alternativeRfunction[self.key]() + else + scene:onInputRelease{input=self.key,type="virtual"} + end end function control_type.button:drag(dx,dy) self.x,self.y=self.x+dx,self.y+dy @@ -158,6 +166,8 @@ VCTRL.hasChanged = false ---@field iconSize? number ---@field alpha? number ---@field show? boolean +---@field pressFunc function|nil +---@field releaseFunc function|nil ---@param ... VCTRL.data[] ---Adding (multiple) virtual button(s) @@ -165,6 +175,14 @@ function VCTRL.new(...) for _,d in pairs(...) do table.insert(VCTRL,control_type[d.type]:new(d)) end end +---@param key string +---@param pfunc function|nil +---@param rfunc function|nil +function VCTRL.setAltFunction(key,pfunc,rfunc) + alternativePfunction[key] = pfunc + alternativeRfunction[key] = rfunc +end + ---@param toggle boolean|false ---Enabling virtual control or not function VCTRL.toggle(toggle) diff --git a/mobile_libs/vctrlTexture.png b/mobile_libs/vctrlTexture.png index dcd8574..cb09330 100644 Binary files a/mobile_libs/vctrlTexture.png and b/mobile_libs/vctrlTexture.png differ diff --git a/mobile_scene/touch_config.lua b/mobile_scene/touch_config.lua index 7752a24..70d080e 100644 --- a/mobile_scene/touch_config.lua +++ b/mobile_scene/touch_config.lua @@ -1,6 +1,6 @@ ---@diagnostic disable: cast-local-type local TouchConfigScene = Scene:extend() -TouchConfigScene.title = "Touchscreen config" +TouchConfigScene.title = "Touchscreen configuration" local function roundUnit(n,u) local u = u or 1 @@ -12,6 +12,12 @@ local function drawText(text, x, y, size, orientation, color) love.graphics.setColor(color) love.graphics.printf(text, x, y, size*2, orientation, nil, 0.5) end +local function math_clamp(x, min, max) + if max < min then + min, max = max, min + end + return x < min and min or (x > max and max or x) +end local buttonList local sliderList = {} @@ -25,9 +31,9 @@ local function exitSceneFunc(saved) VCTRL.release() BUTTON.release(buttonList) if TOUCH_SETTINGS.firstTime and not saved then - SCENE = InputConfigScene(true) + scene = InputConfigScene(true) else - SCENE = TitleScene() + scene = TitleScene() TOUCH_SETTINGS.firstTime = false end end @@ -56,7 +62,7 @@ buttonList = { -- codeWhenReleased = function() -- VCTRL.release() -- BUTTON.release(buttonList) - -- SCENE = TouchConfigPreviewScene() + -- scene = TouchConfigPreviewScene() -- end -- }, resetAll = BUTTON.new{ @@ -72,8 +78,8 @@ buttonList = { VCTRL.focus = nil; focusingButton = nil VCTRL.hasChanged = false VCTRL.clearAll() - VCTRL.new(TOUCH_SETTINGS.__default__) - TOUCH_SETTINGS.bind[1] = TOUCH_SETTINGS.__default__ + VCTRL.new(TOUCH_SETTINGS.__default__.bind[1]) + TOUCH_SETTINGS.bind[1] = TOUCH_SETTINGS.__default__.bind[1] end end }, @@ -94,7 +100,7 @@ buttonList = { exitSceneFunc(true) elseif selection == 2 then VCTRL.clearAll() - VCTRL.new(TOUCH_SETTINGS.bind[1]) + VCTRL.new(TOUCH_SETTINGS.bind[1] or {}) -- love.window.showMessageBox("Discarded!", "Your changes was discarded!") exitSceneFunc() @@ -239,16 +245,12 @@ function TouchConfigScene:render() for _, v in ipairs(VCTRL) do if v ~= focusingButton then - v:draw( - focusingButton and - (v.show and 0.5 or 0.1) or - (v.show and 1 or 0.5) - ) + v:draw(v.alpha * (focusingButton and 0.25 or 1)) end end if focusingButton then focusingButton:draw( - math.clamp( + math_clamp( math.sin(love.timer.getTime()*4)*.5+0.1, focusingButton.show and 1 or 0.1, 1 ) @@ -259,7 +261,7 @@ function TouchConfigScene:render() BUTTON.draw(buttonList) end ----@param e SCENE_onInput + function TouchConfigScene:onInputMove(e) if e.type == "touch" or (e.type == "mouse" and love.mouse.isDown(1)) then if VCTRL.drag(e.dx, e.dy, e.id or 1) then VCTRL.hasChanged = true end @@ -267,7 +269,7 @@ function TouchConfigScene:onInputMove(e) BUTTON.checkHovering(buttonList, e.x, e.y) end end ----@param e SCENE_onInput + function TouchConfigScene:onInputPress(e) if e.type == "mouse" or e.type == "touch" then if not ( @@ -281,7 +283,7 @@ function TouchConfigScene:onInputPress(e) end end end ----@param e SCENE_onInput + function TouchConfigScene:onInputRelease(e) if e.type == "mouse" or e.type == "touch" then if not BUTTON.release(buttonList, e.x, e.y, e.id) then diff --git a/scene.lua b/scene.lua index b5347ef..53bf76c 100644 --- a/scene.lua +++ b/scene.lua @@ -5,8 +5,9 @@ Scene = Object:extend() function Scene:new() end function Scene:update() end function Scene:render() end -function Scene:onInputPress() end -function Scene:onInputRelease() end +function Scene:onInputMove(e) end +function Scene:onInputPress(e) end +function Scene:onInputRelease(e) end ExitScene = require "scene.exit" GameScene = require "scene.game"