Project commit
This commit is contained in:
269
addons/bson/bson.gd
Normal file
269
addons/bson/bson.gd
Normal file
@ -0,0 +1,269 @@
|
||||
# COPYRIGHT 2025 Colormatic Studios and contributors.
|
||||
# This file is the BSON serializer for the Godot Engine,
|
||||
# published under the MIT license. https://opensource.org/license/MIT
|
||||
|
||||
class_name BSON
|
||||
|
||||
|
||||
static func to_bson(data: Dictionary) -> PackedByteArray:
|
||||
var document := dictionary_to_bytes(data)
|
||||
document.append(0x00)
|
||||
var buffer := PackedByteArray()
|
||||
buffer.append_array(int32_to_bytes(document.size() + 4))
|
||||
buffer.append_array(document)
|
||||
return buffer
|
||||
|
||||
static func get_byte_type(value: Variant) -> int:
|
||||
match typeof(value):
|
||||
TYPE_STRING:
|
||||
return 0x02
|
||||
TYPE_INT:
|
||||
if abs(value as int) < 2147483647: # 32 bit signed integer limit
|
||||
return 0x10
|
||||
else:
|
||||
return 0x12
|
||||
TYPE_FLOAT:
|
||||
return 0x01
|
||||
TYPE_ARRAY:
|
||||
return 0x04
|
||||
TYPE_DICTIONARY:
|
||||
return 0x03
|
||||
TYPE_BOOL:
|
||||
return 0x08
|
||||
_:
|
||||
push_error("BSON serialization error: Unsupported type: ", typeof(value))
|
||||
return 0x00
|
||||
|
||||
static func int16_to_bytes(value: int) -> PackedByteArray:
|
||||
var buffer := PackedByteArray()
|
||||
buffer.resize(2)
|
||||
buffer.encode_s16(0, value)
|
||||
return buffer
|
||||
|
||||
static func int32_to_bytes(value: int) -> PackedByteArray:
|
||||
var buffer := PackedByteArray()
|
||||
buffer.resize(4)
|
||||
buffer.encode_s32(0, value)
|
||||
return buffer
|
||||
|
||||
static func int64_to_bytes(value: int) -> PackedByteArray:
|
||||
var buffer := PackedByteArray()
|
||||
buffer.resize(8)
|
||||
buffer.encode_s64(0, value)
|
||||
return buffer
|
||||
|
||||
static func float_to_bytes(value: float) -> PackedByteArray:
|
||||
var buffer := PackedByteArray()
|
||||
buffer.resize(4)
|
||||
buffer.encode_float(0, value)
|
||||
return buffer
|
||||
|
||||
static func double_to_bytes(value: float) -> PackedByteArray:
|
||||
var buffer := PackedByteArray()
|
||||
buffer.resize(8)
|
||||
buffer.encode_double(0, value)
|
||||
return buffer
|
||||
|
||||
static func dictionary_to_bytes(dict: Dictionary) -> PackedByteArray:
|
||||
var buffer := PackedByteArray()
|
||||
|
||||
for key: String in dict.keys():
|
||||
buffer.append(get_byte_type(dict[key]))
|
||||
var key_string_bytes := key.to_utf8_buffer()
|
||||
buffer.append_array(key_string_bytes)
|
||||
buffer.append(0x00)
|
||||
buffer.append_array(serialize_variant(dict[key]))
|
||||
|
||||
return buffer
|
||||
|
||||
static func array_to_bytes(array: Array[Variant]) -> PackedByteArray:
|
||||
var buffer := PackedByteArray()
|
||||
|
||||
for index: int in range(array.size()):
|
||||
buffer.append(get_byte_type(array[index]))
|
||||
# For whatever reason, BSON wants array indexes to be strings. This makes no sense.
|
||||
var s_index := str(index)
|
||||
buffer.append_array(s_index.to_utf8_buffer())
|
||||
buffer.append(0x00)
|
||||
|
||||
buffer.append_array(serialize_variant(array[index]))
|
||||
|
||||
return buffer
|
||||
|
||||
static func serialize_variant(data: Variant) -> PackedByteArray:
|
||||
var buffer := PackedByteArray()
|
||||
match typeof(data):
|
||||
TYPE_DICTIONARY:
|
||||
var document := dictionary_to_bytes(data as Dictionary)
|
||||
buffer.append_array(int32_to_bytes(document.size()))
|
||||
buffer.append_array(document)
|
||||
buffer.append(0x00)
|
||||
TYPE_ARRAY:
|
||||
var b_array := array_to_bytes(data as Array[Variant])
|
||||
buffer.append_array(int32_to_bytes(b_array.size()))
|
||||
buffer.append_array(b_array)
|
||||
buffer.append(0x00)
|
||||
TYPE_STRING:
|
||||
var str_as_bytes := (data as String).to_utf8_buffer()
|
||||
buffer.append_array(int32_to_bytes(str_as_bytes.size() + 1))
|
||||
buffer.append_array(str_as_bytes)
|
||||
buffer.append(0x00)
|
||||
TYPE_INT:
|
||||
if abs(data as int) < 2147483647: # 32 bit signed integer limit
|
||||
buffer.append_array(int32_to_bytes(data as int))
|
||||
else:
|
||||
buffer.append_array(int64_to_bytes(data as int))
|
||||
TYPE_FLOAT:
|
||||
buffer.append_array(double_to_bytes(data as float))
|
||||
TYPE_BOOL:
|
||||
buffer.append((data as bool) if 0x01 else 0x00)
|
||||
_:
|
||||
buffer.append(0x00)
|
||||
|
||||
return buffer
|
||||
|
||||
|
||||
static func from_bson(data: PackedByteArray) -> Dictionary:
|
||||
return Deserializer.new(data).read_dictionary()
|
||||
|
||||
|
||||
class Deserializer:
|
||||
var buffer: PackedByteArray
|
||||
var read_position := 0
|
||||
|
||||
func _init(buffer: PackedByteArray):
|
||||
self.buffer = buffer
|
||||
|
||||
func get_int8() -> int:
|
||||
var value := buffer[read_position]
|
||||
read_position += 1
|
||||
return value
|
||||
|
||||
func get_int16() -> int:
|
||||
var value := buffer.decode_s16(read_position)
|
||||
read_position += 2
|
||||
return value
|
||||
|
||||
func get_int32() -> int:
|
||||
var value := buffer.decode_s32(read_position)
|
||||
read_position += 4
|
||||
return value
|
||||
|
||||
func get_int64() -> int:
|
||||
var value := buffer.decode_s64(read_position)
|
||||
read_position += 8
|
||||
return value
|
||||
|
||||
func get_float() -> float:
|
||||
var value := buffer.decode_float(read_position)
|
||||
read_position += 4
|
||||
return value
|
||||
|
||||
func get_double() -> float:
|
||||
var value := buffer.decode_double(read_position)
|
||||
read_position += 8
|
||||
return value
|
||||
|
||||
func get_string() -> String:
|
||||
var expected_size = get_int32()
|
||||
var s_value: String
|
||||
var iter := 0
|
||||
while true:
|
||||
iter += 1
|
||||
var b_char := get_int8()
|
||||
if b_char == 0x00: break
|
||||
s_value += char(b_char)
|
||||
if expected_size != iter: # Check if the string is terminated with 0x00
|
||||
push_error("BSON deserialization error: String was the wrong size."
|
||||
+ " Position: "
|
||||
+ str(read_position - iter)
|
||||
+ ", stated size: "
|
||||
+ str(expected_size)
|
||||
+ ", actual size: "
|
||||
+ str(iter))
|
||||
return s_value
|
||||
|
||||
func get_bool() -> bool:
|
||||
return (get_int8() == 1)
|
||||
|
||||
func read_dictionary() -> Dictionary:
|
||||
var object = {}
|
||||
|
||||
var expected_size := get_int32()
|
||||
|
||||
var iter := 0
|
||||
while true:
|
||||
iter += 1
|
||||
var type := get_int8()
|
||||
if type == 0x00: break
|
||||
|
||||
var key := ""
|
||||
|
||||
while true:
|
||||
var k_char := get_int8()
|
||||
if k_char == 0x00: break
|
||||
key += char(k_char)
|
||||
|
||||
match type:
|
||||
0x02: object[key] = get_string()
|
||||
0x10: object[key] = get_int32()
|
||||
0x12: object[key] = get_int64()
|
||||
0x01: object[key] = get_double()
|
||||
0x08: object[key] = get_bool()
|
||||
0x04: object[key] = read_array()
|
||||
0x03: object[key] = read_dictionary()
|
||||
_:
|
||||
push_error("BSON deserialization error: Unsupported type "
|
||||
+ str(type)
|
||||
+ " at byte "
|
||||
+ str(read_position - 1))
|
||||
|
||||
if iter > expected_size:
|
||||
push_warning("BSON deserialization warning: Dictionary is the wrong length."
|
||||
+ " Expected dictionary length: "
|
||||
+ str(expected_size)
|
||||
+ ", Actual dictionary length: "
|
||||
+ str(iter))
|
||||
return object
|
||||
|
||||
func read_array() -> Array:
|
||||
var array: Array
|
||||
|
||||
var expected_size := get_int32()
|
||||
|
||||
var iter := 0
|
||||
while true:
|
||||
iter += 1
|
||||
var type := get_int8()
|
||||
if type == 0x00: break
|
||||
|
||||
var key: String
|
||||
|
||||
while true:
|
||||
var k_char := get_int8()
|
||||
if k_char == 0x00: break
|
||||
key += char(k_char)
|
||||
|
||||
# IMPORTANT: Since the array is being deserialized sequentially, we can
|
||||
# use the Array.append() function. It would be better to set the index
|
||||
# directly, but that is not possible. (It *could* cause null holes)
|
||||
# The BSON specification unfortunately allows for null holes, but
|
||||
# this deserializer will just remove any gaps. This could cause an
|
||||
# index desynchronization between two programs communicating with BSON,
|
||||
# but that means the other program has a buggy serializer.
|
||||
match type:
|
||||
0x02: array.append(get_string())
|
||||
0x10: array.append(get_int32())
|
||||
0x12: array.append(get_int64())
|
||||
0x01: array.append(get_double())
|
||||
0x08: array.append(get_bool())
|
||||
0x04: array.append(read_array())
|
||||
0x03: array.append(read_dictionary())
|
||||
_: push_error("BSON deserialization error: Unsupported type: " + str(type))
|
||||
if iter > expected_size:
|
||||
push_warning("BSON deserialization warning: Array is the wrong length."
|
||||
+ " Expected array length: "
|
||||
+ str(expected_size)
|
||||
+ ", Actual array length: "
|
||||
+ str(iter))
|
||||
return array
|
7
addons/bson/plugin.cfg
Normal file
7
addons/bson/plugin.cfg
Normal file
@ -0,0 +1,7 @@
|
||||
[plugin]
|
||||
|
||||
name="BSON"
|
||||
description="A BSON class to serialize and deserialize BSON in GDScript."
|
||||
author="Colormatic Studios"
|
||||
version=""
|
||||
script="plugin.gd"
|
20
addons/bson/plugin.gd
Normal file
20
addons/bson/plugin.gd
Normal file
@ -0,0 +1,20 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
|
||||
const AUTOLOAD_NAME = "BSON"
|
||||
|
||||
|
||||
#func _enter_tree() -> void:
|
||||
# Initialization of the plugin goes here.
|
||||
#pass
|
||||
|
||||
#func _exit_tree() -> void:
|
||||
# Clean-up of the plugin goes here.
|
||||
#pass
|
||||
|
||||
func _enable_plugin() -> void:
|
||||
add_autoload_singleton(AUTOLOAD_NAME, "res://addons/bson/bson.gd")
|
||||
|
||||
func _disable_plugin() -> void:
|
||||
remove_autoload_singleton(AUTOLOAD_NAME)
|
Reference in New Issue
Block a user