2016-02-16 01:35:43 +00:00
--[[
2020-04-28 14:51:41 +00:00
Copyright ( c ) 2020 , Jasmijn Wellner
2016-02-16 01:35:43 +00:00
Permission to use , copy , modify , and / or distribute this software for any
purpose with or without fee is hereby granted , provided that the above
copyright notice and this permission notice appear in all copies .
THE SOFTWARE IS PROVIDED " AS IS " AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS . IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT , INDIRECT , OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE , DATA OR PROFITS , WHETHER IN AN
ACTION OF CONTRACT , NEGLIGENCE OR OTHER TORTIOUS ACTION , ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE .
] ]
2020-04-29 18:37:17 +00:00
local VERSION = ' 1.1 '
2016-02-14 02:52:58 +00:00
local floor = math.floor
local pairs = pairs
local type = type
local insert = table.insert
local getmetatable = getmetatable
local setmetatable = setmetatable
local ffi = require ( " ffi " )
2016-02-23 11:44:46 +00:00
local buf_pos = 0
local buf_size = - 1
local buf = nil
2020-12-26 12:19:55 +00:00
local buf_is_writable = true
2016-02-23 11:44:46 +00:00
local writable_buf = nil
local writable_buf_size = nil
2021-02-14 18:53:02 +00:00
local includeMetatables = true -- togglable with bitser.includeMetatables(false)
2018-11-04 13:07:19 +00:00
local SEEN_LEN = { }
2016-02-23 11:44:46 +00:00
local function Buffer_prereserve ( min_size )
if buf_size < min_size then
buf_size = min_size
buf = ffi.new ( " uint8_t[?] " , buf_size )
2020-12-26 12:19:55 +00:00
buf_is_writable = true
2016-02-23 11:44:46 +00:00
end
end
2016-02-23 12:00:41 +00:00
local function Buffer_clear ( )
buf_size = - 1
buf = nil
2020-12-26 12:19:55 +00:00
buf_is_writable = true
2016-02-23 12:00:41 +00:00
writable_buf = nil
writable_buf_size = nil
end
2016-02-23 11:44:46 +00:00
local function Buffer_makeBuffer ( size )
2020-12-26 12:19:55 +00:00
if not buf_is_writable then
2016-02-23 11:44:46 +00:00
buf = writable_buf
buf_size = writable_buf_size
writable_buf = nil
writable_buf_size = nil
2020-12-26 12:19:55 +00:00
buf_is_writable = true
2016-02-23 11:44:46 +00:00
end
buf_pos = 0
Buffer_prereserve ( size )
end
2016-02-14 02:52:58 +00:00
2016-02-15 13:12:51 +00:00
local function Buffer_newReader ( str )
2016-02-23 11:44:46 +00:00
Buffer_makeBuffer ( # str )
2016-02-14 02:52:58 +00:00
ffi.copy ( buf , str , # str )
2016-02-16 01:08:07 +00:00
end
2016-02-16 01:45:24 +00:00
local function Buffer_newDataReader ( data , size )
2020-12-26 12:19:55 +00:00
if buf_is_writable then
writable_buf = buf
writable_buf_size = buf_size
end
buf_is_writable = false
2016-02-23 11:44:46 +00:00
buf_pos = 0
buf_size = size
buf = ffi.cast ( " uint8_t* " , data )
2016-02-14 02:52:58 +00:00
end
2016-02-23 11:44:46 +00:00
local function Buffer_reserve ( additional_size )
while buf_pos + additional_size > buf_size do
buf_size = buf_size * 2
local oldbuf = buf
buf = ffi.new ( " uint8_t[?] " , buf_size )
2020-12-26 12:19:55 +00:00
buf_is_writable = true
2016-02-23 11:44:46 +00:00
ffi.copy ( buf , oldbuf , buf_pos )
2016-02-14 02:52:58 +00:00
end
end
2016-02-23 11:44:46 +00:00
local function Buffer_write_byte ( x )
Buffer_reserve ( 1 )
buf [ buf_pos ] = x
buf_pos = buf_pos + 1
2016-02-14 02:52:58 +00:00
end
2020-04-28 14:51:41 +00:00
local function Buffer_write_raw ( data , len )
Buffer_reserve ( len )
ffi.copy ( buf + buf_pos , data , len )
buf_pos = buf_pos + len
end
2016-02-23 11:44:46 +00:00
local function Buffer_write_string ( s )
2020-04-28 14:51:41 +00:00
Buffer_write_raw ( s , # s )
2016-02-14 02:52:58 +00:00
end
2016-02-23 11:44:46 +00:00
local function Buffer_write_data ( ct , len , ... )
2020-04-28 14:51:41 +00:00
Buffer_write_raw ( ffi.new ( ct , ... ) , len )
2016-02-14 02:52:58 +00:00
end
2016-02-23 11:44:46 +00:00
local function Buffer_ensure ( numbytes )
if buf_pos + numbytes > buf_size then
2016-02-16 01:45:24 +00:00
error ( " malformed serialized data " )
end
end
2016-02-23 11:44:46 +00:00
local function Buffer_read_byte ( )
Buffer_ensure ( 1 )
local x = buf [ buf_pos ]
buf_pos = buf_pos + 1
2016-02-14 02:52:58 +00:00
return x
end
2016-02-23 11:44:46 +00:00
local function Buffer_read_string ( len )
Buffer_ensure ( len )
local x = ffi.string ( buf + buf_pos , len )
buf_pos = buf_pos + len
return x
2016-02-14 02:52:58 +00:00
end
2020-04-28 14:51:41 +00:00
local function Buffer_read_raw ( data , len )
ffi.copy ( data , buf + buf_pos , len )
2016-02-23 11:44:46 +00:00
buf_pos = buf_pos + len
2020-04-28 14:51:41 +00:00
return data
end
local function Buffer_read_data ( ct , len )
return Buffer_read_raw ( ffi.new ( ct ) , len )
2016-02-14 02:52:58 +00:00
end
local resource_registry = { }
local resource_name_registry = { }
local class_registry = { }
local class_name_registry = { }
local classkey_registry = { }
local class_deserialize_registry = { }
2021-03-30 15:14:14 +00:00
local extension_registry = { }
local extensions_by_type = { }
local EXTENSION_TYPE_KEY = ' bitser-type '
local EXTENSION_MATCH_KEY = ' bitser-match '
local EXTENSION_LOAD_KEY = ' bitser-load '
local EXTENSION_DUMP_KEY = ' bitser-dump '
2016-02-14 02:52:58 +00:00
local serialize_value
2016-02-23 11:44:46 +00:00
local function write_number ( value , _ )
2016-02-14 02:52:58 +00:00
if floor ( value ) == value and value >= - 2147483648 and value <= 2147483647 then
if value >= - 27 and value <= 100 then
--small int
2016-02-23 11:44:46 +00:00
Buffer_write_byte ( value + 27 )
2016-02-14 02:52:58 +00:00
elseif value >= - 32768 and value <= 32767 then
--short int
2016-02-23 11:44:46 +00:00
Buffer_write_byte ( 250 )
Buffer_write_data ( " int16_t[1] " , 2 , value )
2016-02-14 02:52:58 +00:00
else
--long int
2016-02-23 11:44:46 +00:00
Buffer_write_byte ( 245 )
Buffer_write_data ( " int32_t[1] " , 4 , value )
2016-02-14 02:52:58 +00:00
end
else
--double
2016-02-23 11:44:46 +00:00
Buffer_write_byte ( 246 )
Buffer_write_data ( " double[1] " , 8 , value )
2016-02-14 02:52:58 +00:00
end
end
2016-05-11 04:57:41 +00:00
local function write_string ( value , _ )
2016-02-14 02:52:58 +00:00
if # value < 32 then
--short string
2016-02-23 11:44:46 +00:00
Buffer_write_byte ( 192 + # value )
2016-02-14 02:52:58 +00:00
else
--long string
2016-02-23 11:44:46 +00:00
Buffer_write_byte ( 244 )
2016-05-11 04:57:41 +00:00
write_number ( # value )
2016-02-14 02:52:58 +00:00
end
2016-02-23 11:44:46 +00:00
Buffer_write_string ( value )
2016-02-14 02:52:58 +00:00
end
2016-02-23 11:44:46 +00:00
local function write_nil ( _ , _ )
Buffer_write_byte ( 247 )
2016-02-14 02:52:58 +00:00
end
2016-02-23 11:44:46 +00:00
local function write_boolean ( value , _ )
Buffer_write_byte ( value and 249 or 248 )
2016-02-14 02:52:58 +00:00
end
2016-02-23 11:44:46 +00:00
local function write_table ( value , seen )
2016-02-14 02:52:58 +00:00
local classkey
2020-12-26 13:22:57 +00:00
local metatable = getmetatable ( value )
2016-05-11 04:57:41 +00:00
local classname = ( class_name_registry [ value.class ] -- MiddleClass
2016-02-14 02:52:58 +00:00
or class_name_registry [ value.__baseclass ] -- SECL
2020-12-26 13:22:57 +00:00
or class_name_registry [ metatable ] -- hump.class
2017-01-08 01:18:11 +00:00
or class_name_registry [ value.__class__ ] -- Slither
or class_name_registry [ value.__class ] ) -- Moonscript class
2016-05-11 04:57:41 +00:00
if classname then
classkey = classkey_registry [ classname ]
2016-02-23 11:44:46 +00:00
Buffer_write_byte ( 242 )
2016-05-11 04:57:41 +00:00
serialize_value ( classname , seen )
2021-02-14 18:53:02 +00:00
elseif includeMetatables and metatable then
2020-12-26 13:22:57 +00:00
Buffer_write_byte ( 253 )
2016-02-14 02:52:58 +00:00
else
2016-02-23 11:44:46 +00:00
Buffer_write_byte ( 240 )
2016-02-14 02:52:58 +00:00
end
local len = # value
2016-02-23 11:44:46 +00:00
write_number ( len , seen )
2016-02-14 02:52:58 +00:00
for i = 1 , len do
2016-02-23 11:44:46 +00:00
serialize_value ( value [ i ] , seen )
2016-02-14 02:52:58 +00:00
end
local klen = 0
for k in pairs ( value ) do
if ( type ( k ) ~= ' number ' or floor ( k ) ~= k or k > len or k < 1 ) and k ~= classkey then
klen = klen + 1
end
end
2016-02-23 11:44:46 +00:00
write_number ( klen , seen )
2016-02-14 02:52:58 +00:00
for k , v in pairs ( value ) do
if ( type ( k ) ~= ' number ' or floor ( k ) ~= k or k > len or k < 1 ) and k ~= classkey then
2016-02-23 11:44:46 +00:00
serialize_value ( k , seen )
serialize_value ( v , seen )
2016-02-14 02:52:58 +00:00
end
end
2021-02-14 18:53:02 +00:00
if includeMetatables and metatable and not classname then
2020-12-26 13:22:57 +00:00
serialize_value ( metatable , seen )
end
2016-02-14 02:52:58 +00:00
end
2020-04-28 14:51:41 +00:00
local function write_cdata ( value , seen )
local ty = ffi.typeof ( value )
if ty == value then
-- ctype
Buffer_write_byte ( 251 )
serialize_value ( tostring ( ty ) : sub ( 7 , - 2 ) , seen )
return
end
-- cdata
Buffer_write_byte ( 252 )
serialize_value ( ty , seen )
local len = ffi.sizeof ( value )
write_number ( len )
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 }
2016-02-14 02:52:58 +00:00
2016-02-23 11:44:46 +00:00
serialize_value = function ( value , seen )
2016-02-14 02:52:58 +00:00
if seen [ value ] then
local ref = seen [ value ]
if ref < 64 then
--small reference
2016-02-23 11:44:46 +00:00
Buffer_write_byte ( 128 + ref )
2016-02-14 02:52:58 +00:00
else
--long reference
2016-02-23 11:44:46 +00:00
Buffer_write_byte ( 243 )
write_number ( ref , seen )
2016-02-14 02:52:58 +00:00
end
return
end
local t = type ( value )
2020-04-28 23:42:38 +00:00
if t ~= ' number ' and t ~= ' boolean ' and t ~= ' nil ' and t ~= ' cdata ' then
2018-11-04 13:07:19 +00:00
seen [ value ] = seen [ SEEN_LEN ]
seen [ SEEN_LEN ] = seen [ SEEN_LEN ] + 1
2016-02-14 02:52:58 +00:00
end
if resource_name_registry [ value ] then
local name = resource_name_registry [ value ]
if # name < 16 then
--small resource
2016-02-23 11:44:46 +00:00
Buffer_write_byte ( 224 + # name )
Buffer_write_string ( name )
2016-02-14 02:52:58 +00:00
else
--long resource
2016-02-23 11:44:46 +00:00
Buffer_write_byte ( 241 )
write_string ( name , seen )
2016-02-14 02:52:58 +00:00
end
return
end
2021-03-30 15:22:37 +00:00
if extensions_by_type [ t ] then
2021-03-30 15:14:14 +00:00
for extension_id , extension in pairs ( extensions_by_type [ t ] ) do
if extension [ EXTENSION_MATCH_KEY ] ( value ) then
-- extension
Buffer_write_byte ( 254 )
serialize_value ( extension_id , seen )
serialize_value ( extension [ EXTENSION_DUMP_KEY ] ( value ) , seen )
return
end
end
end
2016-02-14 02:52:58 +00:00
( types [ t ] or
error ( " cannot serialize type " .. t )
2016-02-23 11:44:46 +00:00
) ( value , seen )
2016-02-14 02:52:58 +00:00
end
local function serialize ( value )
2016-02-23 12:00:41 +00:00
Buffer_makeBuffer ( 4096 )
2018-11-04 13:07:19 +00:00
local seen = { [ SEEN_LEN ] = 0 }
2016-02-23 11:44:46 +00:00
serialize_value ( value , seen )
2016-02-14 02:52:58 +00:00
end
local function add_to_seen ( value , seen )
insert ( seen , value )
return value
end
local function reserve_seen ( seen )
insert ( seen , 42 )
return # seen
end
2016-02-23 11:44:46 +00:00
local function deserialize_value ( seen )
local t = Buffer_read_byte ( )
2016-02-14 02:52:58 +00:00
if t < 128 then
--small int
return t - 27
elseif t < 192 then
--small reference
return seen [ t - 127 ]
elseif t < 224 then
--small string
2016-02-23 11:44:46 +00:00
return add_to_seen ( Buffer_read_string ( t - 192 ) , seen )
2016-02-14 02:52:58 +00:00
elseif t < 240 then
--small resource
2016-02-23 11:44:46 +00:00
return add_to_seen ( resource_registry [ Buffer_read_string ( t - 224 ) ] , seen )
2020-12-26 13:22:57 +00:00
elseif t == 240 or t == 253 then
2016-02-14 02:52:58 +00:00
--table
local v = add_to_seen ( { } , seen )
2016-02-23 11:44:46 +00:00
local len = deserialize_value ( seen )
2016-02-14 02:52:58 +00:00
for i = 1 , len do
2016-02-23 11:44:46 +00:00
v [ i ] = deserialize_value ( seen )
2016-02-14 02:52:58 +00:00
end
2016-02-23 11:44:46 +00:00
len = deserialize_value ( seen )
2016-02-14 02:52:58 +00:00
for _ = 1 , len do
2016-02-23 11:44:46 +00:00
local key = deserialize_value ( seen )
v [ key ] = deserialize_value ( seen )
2016-02-14 02:52:58 +00:00
end
2020-12-26 13:22:57 +00:00
if t == 253 then
2021-02-14 18:53:02 +00:00
if includeMetatables then
setmetatable ( v , deserialize_value ( seen ) )
end
2020-12-26 13:22:57 +00:00
end
2016-02-14 02:52:58 +00:00
return v
elseif t == 241 then
--long resource
2016-02-15 14:24:07 +00:00
local idx = reserve_seen ( seen )
2016-02-23 11:44:46 +00:00
local value = resource_registry [ deserialize_value ( seen ) ]
2016-02-14 02:52:58 +00:00
seen [ idx ] = value
return value
elseif t == 242 then
--instance
local instance = add_to_seen ( { } , seen )
2016-02-23 11:44:46 +00:00
local classname = deserialize_value ( seen )
2016-02-14 02:52:58 +00:00
local class = class_registry [ classname ]
local classkey = classkey_registry [ classname ]
local deserializer = class_deserialize_registry [ classname ]
2016-02-23 11:44:46 +00:00
local len = deserialize_value ( seen )
2016-02-14 02:52:58 +00:00
for i = 1 , len do
2016-02-23 11:44:46 +00:00
instance [ i ] = deserialize_value ( seen )
2016-02-14 02:52:58 +00:00
end
2016-02-23 11:44:46 +00:00
len = deserialize_value ( seen )
2016-02-14 02:52:58 +00:00
for _ = 1 , len do
2016-02-23 11:44:46 +00:00
local key = deserialize_value ( seen )
instance [ key ] = deserialize_value ( seen )
2016-02-14 02:52:58 +00:00
end
if classkey then
instance [ classkey ] = class
end
return deserializer ( instance , class )
elseif t == 243 then
--reference
2016-02-23 11:44:46 +00:00
return seen [ deserialize_value ( seen ) + 1 ]
2016-02-14 02:52:58 +00:00
elseif t == 244 then
--long string
2016-02-23 11:44:46 +00:00
return add_to_seen ( Buffer_read_string ( deserialize_value ( seen ) ) , seen )
2016-02-14 02:52:58 +00:00
elseif t == 245 then
--long int
2016-02-23 11:44:46 +00:00
return Buffer_read_data ( " int32_t[1] " , 4 ) [ 0 ]
2016-02-14 02:52:58 +00:00
elseif t == 246 then
--double
2016-02-23 11:44:46 +00:00
return Buffer_read_data ( " double[1] " , 8 ) [ 0 ]
2016-02-14 02:52:58 +00:00
elseif t == 247 then
--nil
return nil
elseif t == 248 then
--false
return false
elseif t == 249 then
--true
return true
elseif t == 250 then
--short int
2016-02-23 11:44:46 +00:00
return Buffer_read_data ( " int16_t[1] " , 2 ) [ 0 ]
2020-04-28 14:51:41 +00:00
elseif t == 251 then
--ctype
return ffi.typeof ( deserialize_value ( seen ) )
elseif t == 252 then
local ctype = deserialize_value ( seen )
local len = deserialize_value ( seen )
local read_into = ffi.typeof ( ' $[1] ' , ctype ) ( )
Buffer_read_raw ( read_into , len )
return ctype ( read_into [ 0 ] )
2021-03-30 15:14:14 +00:00
elseif t == 254 then
--extension
local extension_id = deserialize_value ( seen )
return extension_registry [ extension_id ] [ EXTENSION_LOAD_KEY ] ( deserialize_value ( seen ) )
2016-02-14 02:52:58 +00:00
else
error ( " unsupported serialized type " .. t )
end
end
local function deserialize_MiddleClass ( instance , class )
return setmetatable ( instance , class.__instanceDict )
end
local function deserialize_SECL ( instance , class )
return setmetatable ( instance , getmetatable ( class ) )
end
local deserialize_humpclass = setmetatable
local function deserialize_Slither ( instance , class )
return getmetatable ( class ) . allocate ( instance )
end
2017-01-08 01:18:11 +00:00
local function deserialize_Moonscript ( instance , class )
return setmetatable ( instance , class.__base )
end
2016-02-16 01:08:07 +00:00
return { dumps = function ( value )
2016-02-23 12:00:41 +00:00
serialize ( value )
2016-02-23 12:15:53 +00:00
return ffi.string ( buf , buf_pos )
2016-02-23 12:00:41 +00:00
end , dumpLoveFile = function ( fname , value )
serialize ( value )
2018-02-20 17:38:47 +00:00
assert ( love.filesystem . write ( fname , ffi.string ( buf , buf_pos ) ) )
2016-02-23 12:00:41 +00:00
end , loadLoveFile = function ( fname )
2018-02-20 17:38:47 +00:00
local serializedData , error = love.filesystem . newFileData ( fname )
assert ( serializedData , error )
2016-02-23 12:00:41 +00:00
Buffer_newDataReader ( serializedData : getPointer ( ) , serializedData : getSize ( ) )
2018-11-30 13:17:27 +00:00
local value = deserialize_value ( { } )
-- serializedData needs to not be collected early in a tail-call
-- so make sure deserialize_value returns before loadLoveFile does
return value
2016-02-16 01:48:39 +00:00
end , loadData = function ( data , size )
2020-04-27 17:47:09 +00:00
if size == 0 then
error ( ' cannot load value from empty data ' )
end
2016-02-23 11:44:46 +00:00
Buffer_newDataReader ( data , size )
return deserialize_value ( { } )
2016-02-16 01:08:07 +00:00
end , loads = function ( str )
2020-04-27 17:47:09 +00:00
if # str == 0 then
error ( ' cannot load value from empty string ' )
end
2016-02-23 11:44:46 +00:00
Buffer_newReader ( str )
return deserialize_value ( { } )
2021-02-14 18:53:02 +00:00
end , includeMetatables = function ( bool )
includeMetatables = not not bool
2016-02-14 02:52:58 +00:00
end , register = function ( name , resource )
assert ( not resource_registry [ name ] , name .. " already registered " )
resource_registry [ name ] = resource
resource_name_registry [ resource ] = name
2016-02-15 13:39:41 +00:00
return resource
2016-02-14 02:52:58 +00:00
end , unregister = function ( name )
resource_name_registry [ resource_registry [ name ] ] = nil
resource_registry [ name ] = nil
2016-02-15 13:12:51 +00:00
end , registerClass = function ( name , class , classkey , deserializer )
2016-02-14 02:52:58 +00:00
if not class then
class = name
2017-01-08 01:18:11 +00:00
name = class.__name__ or class.name or class.__name
2016-02-14 02:52:58 +00:00
end
if not classkey then
if class.__instanceDict then
-- assume MiddleClass
classkey = ' class '
elseif class.__baseclass then
-- assume SECL
classkey = ' __baseclass '
end
2017-01-08 01:18:11 +00:00
-- assume hump.class, Slither, Moonscript class or something else that doesn't store the
2016-02-14 02:52:58 +00:00
-- class directly on the instance
end
2016-02-15 13:12:51 +00:00
if not deserializer then
2016-02-14 02:52:58 +00:00
if class.__instanceDict then
-- assume MiddleClass
2016-02-15 13:12:51 +00:00
deserializer = deserialize_MiddleClass
2016-02-14 02:52:58 +00:00
elseif class.__baseclass then
-- assume SECL
2016-02-15 13:12:51 +00:00
deserializer = deserialize_SECL
2016-02-14 02:52:58 +00:00
elseif class.__index == class then
-- assume hump.class
2016-02-15 13:12:51 +00:00
deserializer = deserialize_humpclass
2016-02-14 02:52:58 +00:00
elseif class.__name__ then
-- assume Slither
2016-02-15 13:12:51 +00:00
deserializer = deserialize_Slither
2017-01-08 01:18:11 +00:00
elseif class.__base then
-- assume Moonscript class
deserializer = deserialize_Moonscript
2016-02-14 02:52:58 +00:00
else
error ( " no deserializer given for unsupported class library " )
end
end
class_registry [ name ] = class
classkey_registry [ name ] = classkey
2016-02-15 13:12:51 +00:00
class_deserialize_registry [ name ] = deserializer
2016-02-14 02:52:58 +00:00
class_name_registry [ class ] = name
2016-02-15 13:39:41 +00:00
return class
2016-02-14 02:52:58 +00:00
end , unregisterClass = function ( name )
class_name_registry [ class_registry [ name ] ] = nil
classkey_registry [ name ] = nil
class_deserialize_registry [ name ] = nil
class_registry [ name ] = nil
2021-03-30 15:14:14 +00:00
end , registerExtension = function ( extension_id , extension )
assert ( not extension_registry [ extension_id ] , ' extension with id ' .. extension_id .. ' already registered ' )
local ty = extension [ EXTENSION_TYPE_KEY ]
assert ( type ( ty ) == ' string ' and type ( extension [ EXTENSION_MATCH_KEY ] ) == ' function ' and type ( extension [ EXTENSION_LOAD_KEY ] ) == ' function ' and type ( extension [ EXTENSION_DUMP_KEY ] ) == ' function ' , ' not a valid extension ' )
extension_registry [ extension_id ] = extension
if not extensions_by_type [ ty ] then
extensions_by_type [ ty ] = { }
end
extensions_by_type [ ty ] [ extension_id ] = extension
end , unregisterExtension = function ( extension_id )
extensions_by_type [ extension_registry [ extension_id ] [ EXTENSION_TYPE_KEY ] ] [ extension_id ] = nil
extension_registry [ extension_id ] = nil
2020-04-29 18:37:17 +00:00
end , reserveBuffer = Buffer_prereserve , clearBuffer = Buffer_clear , version = VERSION }