I think table serialization is a pretty well-known token-saving technique but I hadn't seen anyone post one like this that just operates via peek
and poke
rather than using strings, so I thought I'd share my table serializer and deserializer here (originally written for PIZZA PANDA) in case this is useful to anyone.
The serializer writes binary data directly to memory so you can use the output however you want, e.g. you can use the "storing binary data as strings" technique to store it as a string, but you could also just store it anywhere in the cart's data.
The deserializer (the part you need to include in your final cart) is 170 tokens and it similarly reads bytes directly from memory.
The serialized format is pretty efficient and uses data types which are more specific than Lua itself, in order to save storage space; each one of the following is a separate "type":
- 8-bit integer
- 16-bit integer
- full "number" (32 bits)
- boolean true
- boolean false
- string
- empty table
- array
- table
This way if your table has a bunch of little 8-bit integers in it, you're not storing a bunch of whole 32-bit numbers for no reason.
The serialized format has the following limitations in order to keep the deserializer small:
- max 255 properties in a table
- max 255 characters in a string
- no "function" type support
Serialize Table
function serialize_table(addr, t, isarray) poke(addr, count_props(t)) addr += 1 if isarray then for v in all(t) do addr = serialize_value(addr, v) end else for k, v in pairs(t) do addr = serialize_value(addr, k) addr = serialize_value(addr, v) end end return addr end function serialize_value(addr, v) if type(v) == "number" then if v & 0x00ff == v then poke(addr, 0) addr += 1 poke(addr, v) return addr + 1 elseif v & 0xffff == v then poke(addr, 1) addr += 1 poke2(addr, v) return addr + 2 else poke(addr, 2) addr += 1 poke4(addr, v) return addr + 4 end elseif type(v) == "boolean" and v == true then poke(addr, 3) return addr + 1 elseif type(v) == "boolean" and v == false then poke(addr, 4) return addr + 1 elseif type(v) == "string" then poke(addr, 5) addr += 1 local len = #v assert(len <= 255, "string must be <= 255 chrs") poke(addr, len) addr += 1 for i = 1, len do poke(addr, ord(v[i])) addr += 1 end return addr elseif type(v) == "table" then if is_empty(v) then poke(addr, 6) return addr + 1 elseif is_array(v) then poke(addr, 7) return serialize_table(addr + 1, v, true) else poke(addr, 8) return serialize_table(addr + 1, v) end end end function count_props(t) local propcount = 0 for k, v in pairs(t) do propcount += 1 end return propcount end function is_array(t) return #t == count_props(t) end function is_empty(t) for k, v in pairs(t) do return false end return true end |
Deserialize Table
-- 170 tokens -- limitations: -- * max 255 properties in a table -- * max 255 characters in a string -- * no "function" type support function deserialize_table(addr, isarray) local t, propcount, k, v = {}, @addr addr += 1 for i = 1, propcount do if isarray then k = i else k, addr = deserialize_value(addr) end v, addr = deserialize_value(addr) t[k] = v end return t, addr end function deserialize_value(addr) local vtype, v = @addr addr += 1 if vtype == 0 then -- 8-bit integer return @addr, addr + 1 elseif vtype == 1 then -- 16-bit integer return %addr, addr + 2 elseif vtype == 2 then -- number return $addr, addr + 4 elseif vtype == 3 then -- boolean true return true, addr elseif vtype == 4 then -- boolean false return false, addr elseif vtype == 5 then -- string local len = @addr addr += 1 v = "" for i = 1, len do v ..= chr(@addr) addr += 1 end return v, addr elseif vtype == 6 then -- empty table return {}, addr elseif vtype == 7 then -- array return deserialize_table(addr, true) elseif vtype == 8 then -- table return deserialize_table(addr) end end |
btw if you want to see more context for how I used this in an actual project, here is the source code of that project: https://github.com/andmatand/pizza-panda
also here is a quick example:
-- step 1. do something like this in your build cart t1 = { -- put lots of stuff in here } -- this returns the address right after the end of what -- was stored so you can store multiple tables in a row addr = serialize_table(0x2000, t1) t2 = { -- another table } addr = serialize_table(addr, t2) -- etc. -- step 2. do something like this in your final published cart t1, addr = deserialize_table(0x2000) t2 = deserialize_table(addr) |
[Please log in to post a comment]