Skip to content

Commit

Permalink
add feature to register custom userdata handlers.
Browse files Browse the repository at this point in the history
  • Loading branch information
Adrien Bertrand committed Feb 12, 2021
1 parent 66042a5 commit 0e3417c
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 1 deletion.
61 changes: 61 additions & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
* [`bitser.loadLoveFile`](#loadlovefile)
* [`bitser.register`](#register)
* [`bitser.registerClass`](#registerclass)
* [`bitser.registerUserdata`](#registerUserdata)
* [`bitser.unregister`](#unregister)
* [`bitser.unregisterClass`](#unregisterclass)
* [`bitser.unregisterUserdata`](#unregisterUserdata)
* [`bitser.reserveBuffer`](#reservebuffer)
* [`bitser.clearBuffer`](#clearbuffer)

Expand Down Expand Up @@ -193,6 +195,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 the seen data (the buffer is positioned at 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.

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(seen, env)
local len = env.deserialize_value(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
Expand All @@ -214,6 +265,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
Expand Down
37 changes: 36 additions & 1 deletion bitser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ local buf_is_writable = true
local writable_buf = nil
local writable_buf_size = nil
local SEEN_LEN = {}
local shared_env = {} -- for userdata callbacks

local function Buffer_prereserve(min_size)
if buf_size < min_size then
Expand Down Expand Up @@ -138,6 +139,7 @@ end

local resource_registry = {}
local resource_name_registry = {}
local userdata_registry = {}
local class_registry = {}
local class_name_registry = {}
local classkey_registry = {}
Expand Down Expand Up @@ -242,7 +244,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
Expand Down Expand Up @@ -385,11 +400,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(seen, 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
Expand Down Expand Up @@ -488,4 +517,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}

0 comments on commit 0e3417c

Please sign in to comment.