mirror of
https://github.com/gvx/bitser.git
synced 2024-11-11 01:44:21 +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.register`](#register)
|
||||
* [`bitser.registerClass`](#registerclass)
|
||||
* [`bitser.registerUserdata`](#registerUserdata)
|
||||
* [`bitser.unregister`](#unregister)
|
||||
* [`bitser.unregisterClass`](#unregisterclass)
|
||||
* [`bitser.unregisterUserdata`](#unregisterUserdata)
|
||||
* [`bitser.reserveBuffer`](#reservebuffer)
|
||||
* [`bitser.clearBuffer`](#clearbuffer)
|
||||
|
||||
@ -202,6 +204,55 @@ Returns the registered class as a convenience.
|
||||
|
||||
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
|
||||
|
||||
```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).
|
||||
|
||||
## unregisterUserdata
|
||||
|
||||
```lua
|
||||
bitser.unregisterUserdata(id)
|
||||
```
|
||||
|
||||
Deregisters the previously registered userdata with the id `id`.
|
||||
|
||||
See also: [`bitser.registerUserdata`](#registerUserdata).
|
||||
|
||||
## reserveBuffer
|
||||
|
||||
```lua
|
||||
|
39
bitser.lua
39
bitser.lua
@ -32,6 +32,7 @@ local writable_buf = nil
|
||||
local writable_buf_size = nil
|
||||
local includeMetatables = true -- togglable with bitser.includeMetatables(false)
|
||||
local SEEN_LEN = {}
|
||||
local shared_env = {} -- for userdata callbacks
|
||||
|
||||
local function Buffer_prereserve(min_size)
|
||||
if buf_size < min_size then
|
||||
@ -139,6 +140,7 @@ end
|
||||
|
||||
local resource_registry = {}
|
||||
local resource_name_registry = {}
|
||||
local userdata_registry = {}
|
||||
local class_registry = {}
|
||||
local class_name_registry = {}
|
||||
local classkey_registry = {}
|
||||
@ -243,7 +245,20 @@ local function write_cdata(value, seen)
|
||||
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}
|
||||
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)
|
||||
if seen[value] then
|
||||
@ -259,7 +274,7 @@ serialize_value = function(value, seen)
|
||||
return
|
||||
end
|
||||
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[SEEN_LEN] = seen[SEEN_LEN] + 1
|
||||
end
|
||||
@ -388,11 +403,25 @@ local function deserialize_value(seen)
|
||||
local read_into = ffi.typeof('$[1]', ctype)()
|
||||
Buffer_read_raw(read_into, len)
|
||||
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
|
||||
error("unsupported serialized type " .. t)
|
||||
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)
|
||||
return setmetatable(instance, class.__instanceDict)
|
||||
end
|
||||
@ -493,4 +522,10 @@ end, unregisterClass = function(name)
|
||||
classkey_registry[name] = nil
|
||||
class_deserialize_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}
|
||||
|
@ -274,7 +274,6 @@ describe("bitser", function()
|
||||
assert.has_error(function() bitser.registerClass('Horse', {mane = 'majestic'}) end, "no deserializer given for unsupported class library")
|
||||
end)
|
||||
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")
|
||||
end)
|
||||
it("can load from raw data", function()
|
||||
|
Loading…
Reference in New Issue
Block a user