-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbinary_serialize.lua
238 lines (226 loc) · 6.85 KB
/
binary_serialize.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
local class_registry = require "love-util.class_registry"
local struct = require "love-util.struct"
local binary_serialize = {}
local function collect_tables(tab, out)
if type(tab) ~= "table" then return end
if out[tab] then return end
if class_registry[tab] then
table.insert(out, 1, tab)
else
out[#out + 1] = tab
end
out[tab] = true
if class_registry[tab] then return end
collect_tables(getmetatable(tab), out)
for k, v in pairs(tab) do
collect_tables(k, out)
collect_tables(v, out)
end
end
local function collect_strings(tables, out)
for i = 1, #tables do
local tab = tables[i]
if class_registry[tab] then
local name = class_registry[tab]
if not out[name] then
out[name] = true
out[#out + 1] = name
end
else
for k, v in pairs(tab) do
if type(k) == "string" and not out[k] then
out[k] = true
out[#out + 1] = k
end
if type(v) == "string" and not out[v] then
out[v] = true
out[#out + 1] = v
end
end
end
end
end
local function write_uint32(out, int)
local addr = #out + 1
struct.packIE(out, int)
return addr
end
local function write_string(out, str)
write_uint32(out, #str)
local addr = #out + 1
for i = 1, #str do
out[addr + i - 1] = string.char(str:byte(i))
end
return addr
end
local type_to_char = {
["nil"] = "N",
["boolean"] = "b",
["number"] = "n",
["string"] = "s",
["table"] = "t",
["function"] = "f",
["userdata"] = "u",
["thread"] = "d",
[true] = "T",
[false] = "F",
[0] = "0",
}
local function write_any(out, tabs, strings, v)
local vtype = type(v)
if v ~= nil and vtype ~= "string" and type_to_char[v] ~= nil then
out[#out + 1] = type_to_char[v]
return
end
out[#out + 1] = type_to_char[vtype]
if vtype == "string" then
write_uint32(out, strings[v])
elseif vtype == "table" then
write_uint32(out, tabs[v])
elseif vtype == "number" then
struct.packdE(out, v)
elseif vtype == "nil" then
-- nothing to do
elseif vtype == "b" then
error "This is not to happen"
else
error "todo"
end
end
---serializes the data to a stream of bytes using a table - use table.concat to get a string
---@param tab table
---@return table stream of bytes
function binary_serialize:serialize(tab)
local tabs, strings = {}, {}
collect_tables(tab, tabs)
for i = 1, #tabs do
local t = tabs[i]
tabs[t] = i
end
collect_strings(tabs, strings)
local out = {}
write_uint32(out, #strings)
for i = 1, #strings do
local s = strings[i]
strings[s] = i
write_string(out, s)
end
write_uint32(out, #tabs)
for i = 1, #tabs do
local t = tabs[i]
if class_registry[t] then
write_uint32(out, 0xffffffff)
write_uint32(out, strings[class_registry[t]])
else
local mt = getmetatable(t)
-- write metatable address or 0 if no metatable
if not mt then
write_uint32(out, 0)
else
write_uint32(out, tabs[mt])
end
-- write table length
write_uint32(out, #t)
for i = 1, #t do
local v = t[i]
write_any(out, tabs, strings, v)
end
local kcnt = 0
for k, v in pairs(t) do
if type(k) ~= "number" or k < 1 or k > #t then
kcnt = kcnt + 1
end
end
write_uint32(out, kcnt)
for k, v in pairs(t) do
if type(k) ~= "number" or k < 1 or k > #t then
write_any(out, tabs, strings, k)
write_any(out, tabs, strings, v)
end
end
end
end
write_uint32(out, tabs[tab])
return out
end
local function read_any(bytes, pos, tabs, strings)
local vtype = bytes:sub(pos, pos)
local value
pos = pos + 1
if vtype == "n" then
value = struct.unpackd(bytes, pos)
pos = pos + 8
elseif vtype == "s" then
value = strings[struct.unpackI(bytes, pos)]
pos = pos + 4
elseif vtype == "t" then
value = tabs[struct.unpackI(bytes, pos)]
pos = pos + 4
elseif vtype == "F" then
value = false
elseif vtype == "T" then
value = true
elseif vtype == "0" then
value = 0
elseif vtype == "N" then
value = nil
else
error("unknown code: " .. vtype .. " @" .. (pos - 1))
end
return pos, value
end
function binary_serialize:deserialize(bytes)
local tabs, strings = {}, {}
local pos = 5
for i = 1, struct.unpackI(bytes, 1) do
local len = struct.unpackI(bytes, pos)
strings[i] = bytes:sub(pos + 4, pos + 3 + len)
pos = pos + len + 4
end
local tab_cnt = struct.unpackI(bytes, pos)
pos = pos + 4
for i = 1, tab_cnt do
tabs[i] = {}
end
-- since meta tables can mess with writing and reading (unless using rawset/rawget,
-- which I don't want to use), we delay metatable assignments
local meta_tabs = {}
for i = 1, tab_cnt do
local mt_addr = struct.unpackI(bytes, pos)
if mt_addr == 0xffffffff then
local mt_name = struct.unpackI(bytes, pos + 4)
pos = pos + 8
local class_name = strings[mt_name]
local class = class_registry[class_name]
if not class then
error("unknown class in serialization data: " .. class_name)
end
tabs[i] = class
else
pos = pos + 4
local t = tabs[i]
if mt_addr ~= 0 then
local mt = tabs[mt_addr]
meta_tabs[t] = mt
end
local len = struct.unpackI(bytes, pos)
pos = pos + 4
for j = 1, len do
pos, t[j] = read_any(bytes, pos, tabs, strings)
end
local kcnt = struct.unpackI(bytes, pos)
pos = pos + 4
for j = 1, kcnt do
local k
pos, k = read_any(bytes, pos, tabs, strings)
pos, t[k] = read_any(bytes, pos, tabs, strings)
end
end
end
for t, mt in pairs(meta_tabs) do
setmetatable(t, mt)
end
local root_index = struct.unpackI(bytes, pos)
return tabs[root_index]
end
return binary_serialize