mirror of
https://github.com/gvx/bitser.git
synced 2024-11-24 06:54:20 +00:00
add feature to register custom userdata handlers.
This commit is contained in:
parent
1d3ebe04cf
commit
dff687147c
61
USAGE.md
61
USAGE.md
@ -10,8 +10,10 @@
|
|||||||
* [`bitser.includeMetatables`](#includeMetatables)
|
* [`bitser.includeMetatables`](#includeMetatables)
|
||||||
* [`bitser.register`](#register)
|
* [`bitser.register`](#register)
|
||||||
* [`bitser.registerClass`](#registerclass)
|
* [`bitser.registerClass`](#registerclass)
|
||||||
|
* [`bitser.registerUserdata`](#registerUserdata)
|
||||||
* [`bitser.unregister`](#unregister)
|
* [`bitser.unregister`](#unregister)
|
||||||
* [`bitser.unregisterClass`](#unregisterclass)
|
* [`bitser.unregisterClass`](#unregisterclass)
|
||||||
|
* [`bitser.unregisterUserdata`](#unregisterUserdata)
|
||||||
* [`bitser.reserveBuffer`](#reservebuffer)
|
* [`bitser.reserveBuffer`](#reservebuffer)
|
||||||
* [`bitser.clearBuffer`](#clearbuffer)
|
* [`bitser.clearBuffer`](#clearbuffer)
|
||||||
|
|
||||||
@ -202,6 +204,55 @@ Returns the registered class as a convenience.
|
|||||||
|
|
||||||
See also: [`bitser.unregisterClass`](#unregisterclass).
|
See also: [`bitser.unregisterClass`](#unregisterclass).
|
||||||
|
|
||||||
|
## registerUserdata
|
||||||
|
|
||||||
|
```lua
|
||||||
|
bitser.registerUserdata(id, matcher, serializer, deserializer)
|
||||||
|
```
|
||||||
|
|
||||||
|
This is an advanved-users-only feature, allowing you to register a custom userdata handler.
|
||||||
|
This makes it possible for bitser to serialize and deserialize userdata with your own callbacks.
|
||||||
|
|
||||||
|
- The `id` parameter has to be a number from 0 to 255 and unique, and identifies your userdata handler.
|
||||||
|
It must be unique per userdata "type".
|
||||||
|
|
||||||
|
- The `matcher` function takes the userdata value as parameter, and must return a boolean: whether the
|
||||||
|
current userdata value is (matches) the one you registered the handler for. This could be done via metatable
|
||||||
|
lookup, for instance, or any other way that your userdata provides.
|
||||||
|
|
||||||
|
- The `serializer` function takes the value (your userdata) and an `env` table as parameters.
|
||||||
|
The `env` table provides access to common internal functions useful for (de)serializing, and the `seen` data.
|
||||||
|
|
||||||
|
- The `deserializer` function takes an `env` table as parameter.
|
||||||
|
The `env` table provides access to common internal functions useful for (de)serializing, and the `seen` data.
|
||||||
|
|
||||||
|
Full usage example with a `ByteArray` class as userdata, which under the hood, stores bytes sequentially:
|
||||||
|
(The internal format would be `[len][data...]`)
|
||||||
|
```lua
|
||||||
|
bitser.registerUserdata(1,
|
||||||
|
function(value)
|
||||||
|
return getmetatable(value) == ByteArray
|
||||||
|
end,
|
||||||
|
function(value, env)
|
||||||
|
local len = #value
|
||||||
|
env.serialize_value(len, env.seen)
|
||||||
|
env.Buffer_write_raw(ffi.string(ffi.cast("uint8_t*", value:getDataPtr()), len), len)
|
||||||
|
end,
|
||||||
|
function(env)
|
||||||
|
local len = env.deserialize_value(env.seen)
|
||||||
|
local ba = ByteArray(len)
|
||||||
|
env.Buffer_read_raw(ffi.cast("uint8_t*", ba:getDataPtr()), len)
|
||||||
|
return ba
|
||||||
|
end)
|
||||||
|
|
||||||
|
local test = ByteArray({ 0x01, 0x02, 0x00, 0x04 })
|
||||||
|
local test2 = bitser.loads(bitser.dumps(test))
|
||||||
|
|
||||||
|
print(test:tostring("base64") == test2:tostring("base64")) -- true
|
||||||
|
```
|
||||||
|
|
||||||
|
See also: [`bitser.unregisterUserdata`](#unregisterUserdata).
|
||||||
|
|
||||||
## unregister
|
## unregister
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
@ -223,6 +274,16 @@ which is useful in a context where you don't have a reference to the class you w
|
|||||||
|
|
||||||
See also: [`bitser.registerClass`](#registerclass).
|
See also: [`bitser.registerClass`](#registerclass).
|
||||||
|
|
||||||
|
## unregisterUserdata
|
||||||
|
|
||||||
|
```lua
|
||||||
|
bitser.unregisterUserdata(id)
|
||||||
|
```
|
||||||
|
|
||||||
|
Deregisters the previously registered userdata with the id `id`.
|
||||||
|
|
||||||
|
See also: [`bitser.registerUserdata`](#registerUserdata).
|
||||||
|
|
||||||
## reserveBuffer
|
## reserveBuffer
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
|
39
bitser.lua
39
bitser.lua
@ -32,6 +32,7 @@ local writable_buf = nil
|
|||||||
local writable_buf_size = nil
|
local writable_buf_size = nil
|
||||||
local includeMetatables = true -- togglable with bitser.includeMetatables(false)
|
local includeMetatables = true -- togglable with bitser.includeMetatables(false)
|
||||||
local SEEN_LEN = {}
|
local SEEN_LEN = {}
|
||||||
|
local shared_env = {} -- for userdata callbacks
|
||||||
|
|
||||||
local function Buffer_prereserve(min_size)
|
local function Buffer_prereserve(min_size)
|
||||||
if buf_size < min_size then
|
if buf_size < min_size then
|
||||||
@ -139,6 +140,7 @@ end
|
|||||||
|
|
||||||
local resource_registry = {}
|
local resource_registry = {}
|
||||||
local resource_name_registry = {}
|
local resource_name_registry = {}
|
||||||
|
local userdata_registry = {}
|
||||||
local class_registry = {}
|
local class_registry = {}
|
||||||
local class_name_registry = {}
|
local class_name_registry = {}
|
||||||
local classkey_registry = {}
|
local classkey_registry = {}
|
||||||
@ -243,7 +245,20 @@ local function write_cdata(value, seen)
|
|||||||
Buffer_write_raw(ffi.typeof('$[1]', ty)(value), len)
|
Buffer_write_raw(ffi.typeof('$[1]', ty)(value), len)
|
||||||
end
|
end
|
||||||
|
|
||||||
local types = {number = write_number, string = write_string, table = write_table, boolean = write_boolean, ["nil"] = write_nil, cdata = write_cdata}
|
local function write_userdata(value, seen)
|
||||||
|
for ud_id, ud_handler in pairs(userdata_registry) do
|
||||||
|
if ud_handler.match(value) then
|
||||||
|
Buffer_write_byte(254)
|
||||||
|
Buffer_write_byte(ud_id)
|
||||||
|
shared_env.seen = seen
|
||||||
|
ud_handler.serialize(value, shared_env)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
error("cannot serialize this userdata")
|
||||||
|
end
|
||||||
|
|
||||||
|
local types = {number = write_number, string = write_string, table = write_table, boolean = write_boolean, ["nil"] = write_nil, cdata = write_cdata, userdata = write_userdata}
|
||||||
|
|
||||||
serialize_value = function(value, seen)
|
serialize_value = function(value, seen)
|
||||||
if seen[value] then
|
if seen[value] then
|
||||||
@ -259,7 +274,7 @@ serialize_value = function(value, seen)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
local t = type(value)
|
local t = type(value)
|
||||||
if t ~= 'number' and t ~= 'boolean' and t ~= 'nil' and t ~= 'cdata' then
|
if t ~= 'number' and t ~= 'boolean' and t ~= 'nil' and t ~= 'cdata' and t ~= 'userdata' then
|
||||||
seen[value] = seen[SEEN_LEN]
|
seen[value] = seen[SEEN_LEN]
|
||||||
seen[SEEN_LEN] = seen[SEEN_LEN] + 1
|
seen[SEEN_LEN] = seen[SEEN_LEN] + 1
|
||||||
end
|
end
|
||||||
@ -388,11 +403,25 @@ local function deserialize_value(seen)
|
|||||||
local read_into = ffi.typeof('$[1]', ctype)()
|
local read_into = ffi.typeof('$[1]', ctype)()
|
||||||
Buffer_read_raw(read_into, len)
|
Buffer_read_raw(read_into, len)
|
||||||
return ctype(read_into[0])
|
return ctype(read_into[0])
|
||||||
|
elseif t == 254 then
|
||||||
|
--userdata
|
||||||
|
local ud_id = Buffer_read_byte()
|
||||||
|
local ud_handler = userdata_registry[ud_id]
|
||||||
|
if ud_handler then
|
||||||
|
shared_env.seen = seen
|
||||||
|
return ud_handler.deserialize(shared_env)
|
||||||
|
end
|
||||||
|
error("unsupported serialized userdata id " .. ud_id)
|
||||||
else
|
else
|
||||||
error("unsupported serialized type " .. t)
|
error("unsupported serialized type " .. t)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
shared_env.serialize_value = serialize_value
|
||||||
|
shared_env.deserialize_value = deserialize_value
|
||||||
|
shared_env.Buffer_write_raw = Buffer_write_raw
|
||||||
|
shared_env.Buffer_read_raw = Buffer_read_raw
|
||||||
|
|
||||||
local function deserialize_MiddleClass(instance, class)
|
local function deserialize_MiddleClass(instance, class)
|
||||||
return setmetatable(instance, class.__instanceDict)
|
return setmetatable(instance, class.__instanceDict)
|
||||||
end
|
end
|
||||||
@ -493,4 +522,10 @@ end, unregisterClass = function(name)
|
|||||||
classkey_registry[name] = nil
|
classkey_registry[name] = nil
|
||||||
class_deserialize_registry[name] = nil
|
class_deserialize_registry[name] = nil
|
||||||
class_registry[name] = nil
|
class_registry[name] = nil
|
||||||
|
end, registerUserdata = function(id, matcher, serializer, deserializer)
|
||||||
|
assert(type(id) == "number" and id >= 0 and id <= 255, "registerUserdata: id must be a number between 0 and 255")
|
||||||
|
assert(not userdata_registry[id], "registerUserdata: id " .. id .. " already registered")
|
||||||
|
userdata_registry[id] = { match = matcher, serialize = serializer, deserialize = deserializer}
|
||||||
|
end, unregisterUserdata = function(id)
|
||||||
|
userdata_registry[id] = nil
|
||||||
end, reserveBuffer = Buffer_prereserve, clearBuffer = Buffer_clear, version = VERSION}
|
end, reserveBuffer = Buffer_prereserve, clearBuffer = Buffer_clear, version = VERSION}
|
||||||
|
@ -274,7 +274,6 @@ describe("bitser", function()
|
|||||||
assert.has_error(function() bitser.registerClass('Horse', {mane = 'majestic'}) end, "no deserializer given for unsupported class library")
|
assert.has_error(function() bitser.registerClass('Horse', {mane = 'majestic'}) end, "no deserializer given for unsupported class library")
|
||||||
end)
|
end)
|
||||||
it("cannot deserialize values from unassigned type bytes", function()
|
it("cannot deserialize values from unassigned type bytes", function()
|
||||||
assert.has_error(function() bitser.loads("\254") end, "unsupported serialized type 254")
|
|
||||||
assert.has_error(function() bitser.loads("\255") end, "unsupported serialized type 255")
|
assert.has_error(function() bitser.loads("\255") end, "unsupported serialized type 255")
|
||||||
end)
|
end)
|
||||||
it("can load from raw data", function()
|
it("can load from raw data", function()
|
||||||
|
Loading…
Reference in New Issue
Block a user