Skip to content

Instantly share code, notes, and snippets.

@theraot
Last active April 22, 2026 23:50
Show Gist options
  • Select an option

  • Save theraot/31515e28e2d8bfea33f6c6d5bcd852f6 to your computer and use it in GitHub Desktop.

Select an option

Save theraot/31515e28e2d8bfea33f6c6d5bcd852f6 to your computer and use it in GitHub Desktop.
BinarySerializer for Godot 4.6+
@tool
class_name BinarySerializer # RefCounted (Static)
## Comprehensive binary serialization system for built-in data types.
const _BITS08 := 1
const _BITS16 := 2
const _BITS32 := 4
const _BITS64 := 8
const _TYPE_BOOL_SIZE := _BITS08
const _TYPE_INT_SIZE := _BITS64
const _TYPE_FLOAT_SIZE := _BITS32
const _TYPE_VECTOR2_SIZE := _TYPE_FLOAT_SIZE * 2
const _TYPE_VECTOR2I_SIZE := _TYPE_INT_SIZE * 2
const _TYPE_RECT2_SIZE := _TYPE_VECTOR2_SIZE * 2
const _TYPE_RECT2I_SIZE := _TYPE_VECTOR2I_SIZE * 2
const _TYPE_VECTOR3_SIZE := _TYPE_FLOAT_SIZE * 3
const _TYPE_VECTOR3I_SIZE := _TYPE_INT_SIZE * 3
const _TYPE_TRANSFORM2D_SIZE := _TYPE_VECTOR2_SIZE * 3
const _TYPE_VECTOR4_SIZE := _TYPE_FLOAT_SIZE * 4
const _TYPE_VECTOR4I_SIZE := _TYPE_INT_SIZE * 4
const _TYPE_PLANE_SIZE := _TYPE_VECTOR3_SIZE + _TYPE_FLOAT_SIZE
const _TYPE_QUATERNION_SIZE := _TYPE_FLOAT_SIZE * 4
const _TYPE_AABB_SIZE := _TYPE_VECTOR3_SIZE * 2
const _TYPE_BASIS_SIZE := _TYPE_VECTOR3_SIZE * 3
const _TYPE_TRANSFORM3D_SIZE := _TYPE_VECTOR3_SIZE * 4
const _TYPE_PROJECTION_SIZE := _TYPE_VECTOR4_SIZE * 4
const _TYPE_COLOR_SIZE := _BITS16 * 4
const _RESOURCE_PROPERTIES := [
&"resource_local_to_scene", &"resource_name", &"resource_path", &"resource_scene_unique_id"
]
static func skip_byte_count(buffer: StreamPeerBuffer, count: int) -> void:
assert(count >= 0)
var new_position := buffer.get_position() + count
buffer.seek(new_position)
static func save_dictionary_of(
buffer: StreamPeerBuffer, input: Dictionary, keys_to_skip: Array = []
) -> void:
var key_type := input.get_typed_key_builtin()
var value_type := input.get_typed_value_builtin()
var sub_buffer := StreamPeerBuffer.new()
for key: Variant in input.keys():
if keys_to_skip.has(key):
continue
var value: Variant = input[key]
save_value(sub_buffer, key, key_type, {})
save_value(sub_buffer, value, value_type, {})
save_byte_array(buffer, sub_buffer.data_array)
static func load_dictionary_to(
buffer: StreamPeerBuffer, output: Dictionary, keys_to_skip: Array = []
) -> void:
var key_type := output.get_typed_key_builtin()
var value_type := output.get_typed_value_builtin()
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
while sub_buffer.get_available_bytes() > 0:
var key: Variant = load_value(sub_buffer, key_type, {})
if keys_to_skip.has(key):
skip_value(sub_buffer, value_type, {})
continue
var value: Variant = load_value(sub_buffer, value_type, {})
output[key] = value
#region BOOL
static func save_bool(buffer: StreamPeerBuffer, input: bool) -> void:
buffer.put_u8(0b11111111 if input else 0)
static func load_bool(buffer: StreamPeerBuffer) -> bool:
return buffer.get_u8() != 0
static func skip_bool(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_BOOL_SIZE)
#endregion
#region INT
static func save_int(buffer: StreamPeerBuffer, input: int) -> void:
buffer.put_64(input)
static func load_int(buffer: StreamPeerBuffer) -> int:
return buffer.get_64()
static func skip_int(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_INT_SIZE)
#endregion
#region FLOAT
static func save_float(buffer: StreamPeerBuffer, input: float) -> void:
buffer.put_float(input)
static func load_float(buffer: StreamPeerBuffer) -> float:
return buffer.get_float()
static func skip_float(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_FLOAT_SIZE)
#endregion
#region STRING
static func save_string(buffer: StreamPeerBuffer, input: String) -> void:
save_byte_array(buffer, input.to_utf8_buffer())
static func load_string(buffer: StreamPeerBuffer) -> String:
var utf8 := load_byte_array(buffer)
return utf8.get_string_from_utf8()
static func skip_string(buffer: StreamPeerBuffer) -> void:
skip_byte_array(buffer)
#endregion
#region VECTOR2
static func save_vector2(buffer: StreamPeerBuffer, input: Vector2) -> void:
save_float(buffer, input.x)
save_float(buffer, input.y)
static func load_vector2(buffer: StreamPeerBuffer) -> Vector2:
return Vector2(load_float(buffer), load_float(buffer))
static func skip_vector2(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_VECTOR2_SIZE)
#endregion
#region VECTOR2I
static func save_vector2i(buffer: StreamPeerBuffer, input: Vector2i) -> void:
buffer.put_32(input.x)
buffer.put_32(input.y)
static func load_vector2i(buffer: StreamPeerBuffer) -> Vector2i:
return Vector2i(buffer.get_32(), buffer.get_32())
static func skip_vector2i(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_VECTOR2I_SIZE)
#endregion
#region RECT2
static func save_rect2(buffer: StreamPeerBuffer, input: Rect2) -> void:
save_vector2(buffer, input.position)
save_vector2(buffer, input.size)
static func load_rect2(buffer: StreamPeerBuffer) -> Rect2:
return Rect2(load_vector2(buffer), load_vector2(buffer))
static func skip_rect2(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_RECT2_SIZE)
#endregion
#region RECT2I
static func save_rect2i(buffer: StreamPeerBuffer, input: Rect2i) -> void:
save_vector2i(buffer, input.position)
save_vector2i(buffer, input.size)
static func load_rect2i(buffer: StreamPeerBuffer) -> Rect2i:
return Rect2i(load_vector2i(buffer), load_vector2i(buffer))
static func skip_rect2i(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_RECT2I_SIZE)
#endregion
#region VECTOR3
static func save_vector3(buffer: StreamPeerBuffer, input: Vector3) -> void:
save_float(buffer, input.x)
save_float(buffer, input.y)
save_float(buffer, input.z)
static func load_vector3(buffer: StreamPeerBuffer) -> Vector3:
return Vector3(load_float(buffer), load_float(buffer), load_float(buffer))
static func skip_vector3(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_VECTOR3_SIZE)
#endregion
#region VECTOR3I
static func save_vector3i(buffer: StreamPeerBuffer, input: Vector3i) -> void:
buffer.put_32(input.x)
buffer.put_32(input.y)
buffer.put_32(input.z)
static func load_vector3i(buffer: StreamPeerBuffer) -> Vector3i:
return Vector3i(buffer.get_32(), buffer.get_32(), buffer.get_32())
static func skip_vector3i(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_VECTOR3I_SIZE)
#endregion
#region TRANSFORM2D
static func save_transform2d(buffer: StreamPeerBuffer, input: Transform2D) -> void:
save_vector2(buffer, input.x)
save_vector2(buffer, input.y)
save_vector2(buffer, input.origin)
static func load_transform2d(buffer: StreamPeerBuffer) -> Transform2D:
return Transform2D(load_vector2(buffer), load_vector2(buffer), load_vector2(buffer))
static func skip_transform2d(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_TRANSFORM2D_SIZE)
#endregion
#region VECTOR4
static func save_vector4(buffer: StreamPeerBuffer, input: Vector4) -> void:
save_float(buffer, input.x)
save_float(buffer, input.y)
save_float(buffer, input.z)
save_float(buffer, input.w)
static func load_vector4(buffer: StreamPeerBuffer) -> Vector4:
return Vector4(load_float(buffer), load_float(buffer), load_float(buffer), load_float(buffer))
static func skip_vector4(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_VECTOR4_SIZE)
#endregion
#region VECTOR4I
static func save_vector4i(buffer: StreamPeerBuffer, input: Vector4i) -> void:
buffer.put_32(input.x)
buffer.put_32(input.y)
buffer.put_32(input.z)
buffer.put_32(input.w)
static func load_vector4i(buffer: StreamPeerBuffer) -> Vector4i:
return Vector4i(buffer.get_32(), buffer.get_32(), buffer.get_32(), buffer.get_32())
static func skip_vector4i(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_VECTOR4I_SIZE)
#endregion
#region PLANE
static func save_plane(buffer: StreamPeerBuffer, input: Plane) -> void:
save_vector3(buffer, input.normal)
save_float(buffer, input.d)
static func load_plane(buffer: StreamPeerBuffer) -> Plane:
return Plane(load_vector3(buffer), load_float(buffer))
static func skip_plane(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_PLANE_SIZE)
#endregion
#region QUATERNION
static func save_quaternion(buffer: StreamPeerBuffer, input: Quaternion) -> void:
save_float(buffer, input.x)
save_float(buffer, input.y)
save_float(buffer, input.z)
save_float(buffer, input.w)
static func load_quaternion(buffer: StreamPeerBuffer) -> Quaternion:
return Quaternion(
load_float(buffer), load_float(buffer), load_float(buffer), load_float(buffer)
)
static func skip_quaternion(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_QUATERNION_SIZE)
#endregion
#region AABB
static func save_aabb(buffer: StreamPeerBuffer, input: AABB) -> void:
save_vector3(buffer, input.position)
save_vector3(buffer, input.size)
static func load_aabb(buffer: StreamPeerBuffer) -> AABB:
return AABB(load_vector3(buffer), load_vector3(buffer))
static func skip_aabb(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_AABB_SIZE)
#endregion
#region BASIS
static func save_basis(buffer: StreamPeerBuffer, input: Basis) -> void:
save_vector3(buffer, input.x)
save_vector3(buffer, input.y)
save_vector3(buffer, input.z)
static func load_basis(buffer: StreamPeerBuffer) -> Basis:
return Basis(load_vector3(buffer), load_vector3(buffer), load_vector3(buffer))
static func skip_basis(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_BASIS_SIZE)
#endregion
#region TRANSFORM3D
static func save_transform3d(buffer: StreamPeerBuffer, input: Transform3D) -> void:
save_vector3(buffer, input.basis.x)
save_vector3(buffer, input.basis.y)
save_vector3(buffer, input.basis.z)
save_vector3(buffer, input.origin)
static func load_transform3d(buffer: StreamPeerBuffer) -> Transform3D:
return Transform3D(
load_vector3(buffer), load_vector3(buffer), load_vector3(buffer), load_vector3(buffer)
)
static func skip_transform3d(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_TRANSFORM3D_SIZE)
#endregion
#region PROJECTION
static func save_projection(buffer: StreamPeerBuffer, input: Projection) -> void:
save_vector4(buffer, input.x)
save_vector4(buffer, input.y)
save_vector4(buffer, input.z)
save_vector4(buffer, input.w)
static func load_projection(buffer: StreamPeerBuffer) -> Projection:
return Projection(
load_vector4(buffer), load_vector4(buffer), load_vector4(buffer), load_vector4(buffer)
)
static func skip_projection(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_PROJECTION_SIZE)
#endregion
#region COLOR
static func save_color(buffer: StreamPeerBuffer, input: Color) -> void:
buffer.put_half(input.r)
buffer.put_half(input.g)
buffer.put_half(input.b)
buffer.put_half(input.a)
static func load_color(buffer: StreamPeerBuffer) -> Color:
return Color(buffer.get_half(), buffer.get_half(), buffer.get_half(), buffer.get_half())
static func skip_color(buffer: StreamPeerBuffer) -> void:
skip_byte_count(buffer, _TYPE_COLOR_SIZE)
#endregion
#region STRING_NAME
static func save_string_name(buffer: StreamPeerBuffer, input: StringName) -> void:
save_string(buffer, str(input))
static func load_string_name(buffer: StreamPeerBuffer) -> StringName:
return StringName(load_string(buffer))
static func skip_string_name(buffer: StreamPeerBuffer) -> void:
skip_byte_array(buffer)
#endregion
#region NODE_PATH
static func save_node_path(buffer: StreamPeerBuffer, input: NodePath) -> void:
save_string(buffer, str(input))
static func load_node_path(buffer: StreamPeerBuffer) -> NodePath:
return NodePath(load_string(buffer))
static func skip_node_path(buffer: StreamPeerBuffer) -> void:
skip_byte_array(buffer)
#endregion
#region CALLABLE
static func save_callable(
buffer: StreamPeerBuffer, input: Callable, saved_objects: Dictionary[int, Object]
) -> void:
save_string(buffer, input.get_method())
save_array(buffer, input.get_bound_arguments(), saved_objects)
save_object(buffer, input.get_object(), saved_objects)
static func load_callable(
buffer: StreamPeerBuffer, saved_objects: Dictionary[int, Object]
) -> Callable:
var method := load_string(buffer)
var arguments := load_array(buffer, saved_objects)
var object := load_object(buffer, saved_objects)
return Callable(object, method).bindv(arguments)
static func skip_callable(buffer: StreamPeerBuffer, saved_objects: Dictionary[int, Object]) -> void:
skip_string(buffer)
skip_array(buffer, saved_objects)
skip_object(buffer, saved_objects)
#endregion
#region SIGNAL
static func save_signal(
buffer: StreamPeerBuffer, input: Signal, saved_objects: Dictionary[int, Object]
) -> void:
save_string(buffer, input.get_name())
save_object(buffer, input.get_object(), saved_objects)
static func load_signal(buffer: StreamPeerBuffer, saved_objects: Dictionary[int, Object]) -> Signal:
var name := load_string(buffer)
var object := load_object(buffer, saved_objects)
return Signal(object, name)
static func skip_signal(buffer: StreamPeerBuffer, saved_objects: Dictionary[int, Object]) -> void:
skip_string(buffer)
skip_object(buffer, saved_objects)
#endregion
#region OBJECT
static func save_object(
buffer: StreamPeerBuffer, input: Object, saved_objects: Dictionary[int, Object]
) -> void:
if input == null:
buffer.put_32(0)
return
var id := input.get_instance_id()
buffer.put_32(id)
if saved_objects.has(id):
save_bool(buffer, true)
return
saved_objects[id] = input
save_bool(buffer, false)
var sub_buffer := StreamPeerBuffer.new()
var class_string := InstanceHelper.get_class_by_instance(input)
save_string(sub_buffer, class_string)
var properties := input.get_property_list()
for property in properties:
var value_type: Variant.Type = property["type"]
if value_type == TYPE_NIL:
continue
var usage: PropertyUsageFlags = property["usage"]
if usage & PROPERTY_USAGE_STORAGE == 0:
continue
var name := str(property["name"])
if name == &"script" or (input is Resource and name in _RESOURCE_PROPERTIES):
continue
var value: Variant = input.get(name)
save_string(sub_buffer, name)
sub_buffer.put_8(value_type)
save_value(sub_buffer, value, value_type, saved_objects)
save_byte_array(buffer, sub_buffer.data_array)
static func load_object(
buffer: StreamPeerBuffer, loaded_objects: Dictionary[int, Object]
) -> Object:
var id := buffer.get_32()
if id == 0:
return null
var is_placeholder := load_bool(buffer)
if is_placeholder:
if loaded_objects.has(id):
return loaded_objects[id]
push_error("Missing object id %d" % str(id))
return null
if loaded_objects.has(id):
push_error("Duplicate object id %d" % str(id))
return null
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
if sub_buffer.get_size() == 0:
loaded_objects[id] = null
return null
var class_string := load_string(sub_buffer)
var output: Object = InstanceHelper.instantiate_class(class_string)
loaded_objects[id] = output
var seen_properties := []
while sub_buffer.get_available_bytes() > 0:
var name := load_string(sub_buffer)
var value_type := sub_buffer.get_8()
seen_properties.append(name)
if (
name not in output
or name == &"script"
or (output is Resource and name in _RESOURCE_PROPERTIES)
):
skip_value(sub_buffer, value_type, loaded_objects)
else:
var value: Variant = load_value(sub_buffer, value_type, loaded_objects)
output.set(name, value)
return output
static func skip_object(buffer: StreamPeerBuffer, loaded_objects: Dictionary[int, Object]) -> void:
@warning_ignore("return_value_discarded")
load_object(buffer, loaded_objects)
#endregion
#region DICTIONARY
static func save_dictionary(
buffer: StreamPeerBuffer,
input: Dictionary,
saved_objects: Dictionary[int, Object],
keys_to_skip: Array = []
) -> void:
var sub_buffer := StreamPeerBuffer.new()
var key_type := input.get_typed_key_builtin()
var value_type := input.get_typed_value_builtin()
sub_buffer.put_8(key_type)
sub_buffer.put_8(value_type)
for key: Variant in input.keys():
if keys_to_skip.has(key):
continue
var value: Variant = input[key]
save_value(sub_buffer, key, key_type, saved_objects)
save_value(sub_buffer, value, value_type, saved_objects)
save_byte_array(buffer, sub_buffer.data_array)
static func load_dictionary(
buffer: StreamPeerBuffer, loaded_objects: Dictionary[int, Object], keys_to_skip: Array = []
) -> Dictionary:
var output := Dictionary()
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var key_type := sub_buffer.get_8()
var value_type := sub_buffer.get_8()
while sub_buffer.get_available_bytes() > 0:
var key: Variant = load_value(sub_buffer, key_type, loaded_objects)
if keys_to_skip.has(key):
skip_value(sub_buffer, value_type, loaded_objects)
continue
var value: Variant = load_value(sub_buffer, value_type, loaded_objects)
output[key] = value
return output
static func skip_dictionary(
buffer: StreamPeerBuffer, loaded_objects: Dictionary[int, Object]
) -> void:
@warning_ignore("return_value_discarded")
load_dictionary(buffer, loaded_objects)
#endregion
#region ARRAY
static func save_array(
buffer: StreamPeerBuffer, input: Array, saved_objects: Dictionary[int, Object]
) -> void:
var sub_buffer := StreamPeerBuffer.new()
var value_type := input.get_typed_builtin()
sub_buffer.put_8(value_type)
if value_type == TYPE_OBJECT:
save_string(sub_buffer, input.get_typed_class_name())
var script: GDScript = input.get_typed_script()
save_string(sub_buffer, ScriptHelper.get_class_by_script_or_empty(script))
for value: Variant in input:
save_value(sub_buffer, value, value_type, saved_objects)
save_byte_array(buffer, sub_buffer.data_array)
static func load_array(buffer: StreamPeerBuffer, loaded_objects: Dictionary[int, Object]) -> Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var value_type := sub_buffer.get_8()
var class_string := &""
var script: GDScript = null
if value_type == TYPE_OBJECT:
class_string = load_string(sub_buffer)
var class_script := load_string(sub_buffer)
script = ScriptHelper.get_script_by_class_or_empty(class_script)
var output := Array([], value_type, class_string, script)
while sub_buffer.get_available_bytes() > 0:
var value: Variant = load_value(sub_buffer, value_type, loaded_objects)
output.append(value)
return output
static func skip_array(buffer: StreamPeerBuffer, loaded_objects: Dictionary[int, Object]) -> void:
@warning_ignore("return_value_discarded")
load_array(buffer, loaded_objects)
#endregion
#region PACKED_BYTE_ARRAY
static func save_byte_array(buffer: StreamPeerBuffer, input: PackedByteArray) -> void:
buffer.put_32(input.size())
var err := buffer.put_data(input)
if err != OK:
push_error("Error saving data. Error: %s" % error_string(err))
static func load_byte_array(buffer: StreamPeerBuffer) -> PackedByteArray:
var size := mini(buffer.get_32(), buffer.get_available_bytes())
var position := buffer.get_position()
if position >= buffer.data_array.size():
return PackedByteArray()
var output := buffer.data_array.slice(position, position + size)
buffer.seek(position + size)
return output
static func skip_byte_array(buffer: StreamPeerBuffer) -> void:
var size := mini(buffer.get_32(), buffer.get_available_bytes())
skip_byte_count(buffer, size)
#endregion
#region PACKED_INT32_ARRAY
static func save_int32_array(buffer: StreamPeerBuffer, input: PackedInt32Array) -> void:
buffer.put_32(input.size() * _BITS32)
for input_entry in input:
buffer.put_32(input_entry)
static func load_int32_array(buffer: StreamPeerBuffer) -> PackedInt32Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var output := PackedInt32Array()
@warning_ignore("integer_division")
var err := output.resize(sub_buffer.data_array.size() / _BITS32)
if err != OK:
push_error("Error loading data. Error: %s" % error_string(err))
for index in output.size():
output[index] = sub_buffer.get_32()
return output
static func skip_int32_array(buffer: StreamPeerBuffer) -> void:
skip_byte_array(buffer)
#endregion
#region PACKED_INT64_ARRAY
static func save_int64_array(buffer: StreamPeerBuffer, input: PackedInt64Array) -> void:
buffer.put_32(input.size() * _BITS64)
for input_entry in input:
buffer.put_64(input_entry)
static func load_int64_array(buffer: StreamPeerBuffer) -> PackedInt64Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var output := PackedInt64Array()
@warning_ignore("integer_division")
var err := output.resize(sub_buffer.data_array.size() / _BITS64)
if err != OK:
push_error("Error loading data. Error: %s" % error_string(err))
for index in output.size():
output[index] = sub_buffer.get_64()
return output
static func skip_int64_array(buffer: StreamPeerBuffer) -> void:
skip_byte_array(buffer)
#endregion
#region PACKED_FLOAT32_ARRAY
static func save_float32_array(buffer: StreamPeerBuffer, input: PackedFloat32Array) -> void:
buffer.put_32(input.size() * _BITS32)
for input_entry in input:
buffer.put_float(input_entry)
static func load_float32_array(buffer: StreamPeerBuffer) -> PackedFloat32Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var output := PackedFloat32Array()
@warning_ignore("integer_division")
var err := output.resize(sub_buffer.data_array.size() / _BITS32)
if err != OK:
push_error("Error loading data. Error: %s" % error_string(err))
for index in output.size():
output[index] = sub_buffer.get_float()
return output
static func skip_float32_array(buffer: StreamPeerBuffer) -> void:
skip_byte_array(buffer)
#endregion
#region PACKED_FLOAT64_ARRAY
static func save_float64_array(buffer: StreamPeerBuffer, input: PackedFloat64Array) -> void:
buffer.put_32(input.size() * _BITS64)
for input_entry in input:
buffer.put_double(input_entry)
static func load_float64_array(buffer: StreamPeerBuffer) -> PackedFloat64Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var output := PackedFloat64Array()
@warning_ignore("integer_division")
var err := output.resize(sub_buffer.data_array.size() / _BITS64)
if err != OK:
push_error("Error loading data. Error: %s" % error_string(err))
for index in output.size():
output[index] = sub_buffer.get_double()
return output
static func skip_float64_array(buffer: StreamPeerBuffer) -> void:
skip_byte_array(buffer)
#endregion
#region PACKED_STRING_ARRAY
static func save_string_array(buffer: StreamPeerBuffer, input: PackedStringArray) -> void:
var sub_buffer := StreamPeerBuffer.new()
for input_entry in input:
save_string(sub_buffer, str(input_entry))
save_byte_array(buffer, sub_buffer.data_array)
static func load_string_array(buffer: StreamPeerBuffer) -> PackedStringArray:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var output := PackedStringArray()
while sub_buffer.get_available_bytes() > 0:
if output.append(load_string(sub_buffer)):
push_error("Error loading data. Error: %s" % error_string(ERR_OUT_OF_MEMORY))
return output
static func skip_string_array(buffer: StreamPeerBuffer) -> void:
skip_byte_array(buffer)
#endregion
#region PACKED_VECTOR2_ARRAY
static func save_vector2_array(buffer: StreamPeerBuffer, input: PackedVector2Array) -> void:
buffer.put_32(input.size() * _TYPE_VECTOR2_SIZE)
for input_entry in input:
save_vector2(buffer, input_entry)
static func load_vector2_array(buffer: StreamPeerBuffer) -> PackedVector2Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var output := PackedVector2Array()
@warning_ignore("integer_division")
var err := output.resize(sub_buffer.data_array.size() / _TYPE_VECTOR2_SIZE)
if err != OK:
push_error("Error loading data. Error: %s" % error_string(err))
for index in output.size():
output[index] = load_vector2(sub_buffer)
return output
static func skip_vector2_array(buffer: StreamPeerBuffer) -> void:
skip_byte_array(buffer)
#endregion
#region PACKED_VECTOR3_ARRAY
static func save_vector3_array(buffer: StreamPeerBuffer, input: PackedVector3Array) -> void:
buffer.put_32(input.size() * _TYPE_VECTOR3_SIZE)
for input_entry in input:
save_vector3(buffer, input_entry)
static func load_vector3_array(buffer: StreamPeerBuffer) -> PackedVector3Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var output := PackedVector3Array()
@warning_ignore("integer_division")
var err := output.resize(sub_buffer.data_array.size() / _TYPE_VECTOR3_SIZE)
if err != OK:
push_error("Error loading data. Error: %s" % error_string(err))
for index in output.size():
output[index] = load_vector3(sub_buffer)
return output
static func skip_vector3_array(buffer: StreamPeerBuffer) -> void:
skip_byte_array(buffer)
#endregion
#region PACKED_COLOR_ARRAY
static func save_color_array(buffer: StreamPeerBuffer, input: PackedColorArray) -> void:
buffer.put_32(input.size() * _TYPE_COLOR_SIZE)
for input_entry in input:
save_color(buffer, input_entry)
static func load_color_array(buffer: StreamPeerBuffer) -> PackedColorArray:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var output := PackedColorArray()
@warning_ignore("integer_division")
var err := output.resize(sub_buffer.data_array.size() / _TYPE_COLOR_SIZE)
if err != OK:
push_error("Error loading data. Error: %s" % error_string(err))
for index in output.size():
output[index] = load_color(sub_buffer)
return output
static func skip_color_array(buffer: StreamPeerBuffer) -> void:
skip_byte_array(buffer)
#endregion
#region PACKED_VECTOR4_ARRAY
static func save_vector4_array(buffer: StreamPeerBuffer, input: PackedVector4Array) -> void:
buffer.put_32(input.size() * _TYPE_VECTOR4_SIZE)
for input_entry in input:
save_vector4(buffer, input_entry)
static func load_vector4_array(buffer: StreamPeerBuffer) -> PackedVector4Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var output := PackedVector4Array()
@warning_ignore("integer_division")
var err := output.resize(sub_buffer.data_array.size() / _TYPE_VECTOR4_SIZE)
if err != OK:
push_error("Error loading data. Error: %s" % error_string(err))
for index in output.size():
output[index] = load_vector4(buffer)
return output
static func skip_vector4_array(buffer: StreamPeerBuffer) -> void:
skip_byte_array(buffer)
#endregion
#region VARIANT
static func save_variant(
buffer: StreamPeerBuffer, input: Variant, saved_objects: Dictionary[int, Object]
) -> void:
save_value(buffer, input, TYPE_NIL, saved_objects)
static func load_variant(
buffer: StreamPeerBuffer, loaded_objects: Dictionary[int, Object]
) -> Variant:
return load_value(buffer, TYPE_NIL, loaded_objects)
static func skip_variant(buffer: StreamPeerBuffer, loaded_objects: Dictionary[int, Object]) -> void:
@warning_ignore("return_value_discarded")
load_variant(buffer, loaded_objects)
#endregion
static func save_simple_value(
buffer: StreamPeerBuffer, input: Variant, known_type: Variant.Type
) -> void:
var type := typeof(input)
if known_type == TYPE_NIL:
buffer.put_8(type)
if known_type == TYPE_OBJECT and type == TYPE_NIL:
type = TYPE_OBJECT
elif type != known_type:
push_error(
(
"BinarySerializer.save_value(): Type mismatch. Expected: %s, Got: %s"
% [str(known_type), str(type)]
)
)
match type:
TYPE_NIL:
push_error("BinarySerializer.save_value(): TYPE_NIL is not supported.")
TYPE_BOOL:
var value: bool = input
save_bool(buffer, value)
TYPE_INT:
var value: int = input
save_int(buffer, value)
TYPE_FLOAT:
var value: float = input
save_float(buffer, value)
TYPE_STRING:
var value: String = input
save_string(buffer, value)
TYPE_VECTOR2:
var value: Vector2 = input
save_vector2(buffer, value)
TYPE_VECTOR2I:
var value: Vector2i = input
save_vector2i(buffer, value)
TYPE_RECT2:
var value: Rect2 = input
save_rect2(buffer, value)
TYPE_RECT2I:
var value: Rect2i = input
save_rect2i(buffer, value)
TYPE_VECTOR3:
var value: Vector3 = input
save_vector3(buffer, value)
TYPE_VECTOR3I:
var value: Vector3i = input
save_vector3i(buffer, value)
TYPE_TRANSFORM2D:
var value: Transform2D = input
save_transform2d(buffer, value)
TYPE_VECTOR4:
var value: Vector4 = input
save_vector4(buffer, value)
TYPE_VECTOR4I:
var value: Vector4i = input
save_vector4i(buffer, value)
TYPE_PLANE:
var value: Plane = input
save_plane(buffer, value)
TYPE_QUATERNION:
var value: Quaternion = input
save_quaternion(buffer, value)
TYPE_AABB:
var value: AABB = input
save_aabb(buffer, value)
TYPE_BASIS:
var value: Basis = input
save_basis(buffer, value)
TYPE_TRANSFORM3D:
var value: Transform3D = input
save_transform3d(buffer, value)
TYPE_PROJECTION:
var value: Projection = input
save_projection(buffer, value)
TYPE_COLOR:
var value: Color = input
save_color(buffer, value)
TYPE_STRING_NAME:
var value: StringName = input
save_string_name(buffer, value)
TYPE_NODE_PATH:
var value: NodePath = input
save_node_path(buffer, value)
TYPE_RID:
push_error("BinarySerializer.save_value(): TYPE_RID is not supported.")
TYPE_PACKED_BYTE_ARRAY:
var value: PackedByteArray = input
save_byte_array(buffer, value)
TYPE_PACKED_INT32_ARRAY:
var value: PackedInt32Array = input
save_int32_array(buffer, value)
TYPE_PACKED_INT64_ARRAY:
var value: PackedInt64Array = input
save_int64_array(buffer, value)
TYPE_PACKED_FLOAT32_ARRAY:
var value: PackedFloat32Array = input
save_float32_array(buffer, value)
TYPE_PACKED_FLOAT64_ARRAY:
var value: PackedFloat64Array = input
save_float64_array(buffer, value)
TYPE_PACKED_STRING_ARRAY:
var value: PackedStringArray = input
save_string_array(buffer, value)
TYPE_PACKED_VECTOR2_ARRAY:
var value: PackedVector2Array = input
save_vector2_array(buffer, value)
TYPE_PACKED_VECTOR3_ARRAY:
var value: PackedVector3Array = input
save_vector3_array(buffer, value)
TYPE_PACKED_COLOR_ARRAY:
var value: PackedColorArray = input
save_color_array(buffer, value)
TYPE_PACKED_VECTOR4_ARRAY:
var value: PackedVector4Array = input
save_vector4_array(buffer, value)
_:
push_error("BinarySerializer.save_value(): Unsupported type: %s" % str(type))
static func load_simple_value(buffer: StreamPeerBuffer, known_type: Variant.Type) -> Variant:
var type := TYPE_NIL
if known_type == TYPE_NIL:
type = buffer.get_8() as Variant.Type
else:
type = known_type
match type:
TYPE_NIL:
push_error("BinarySerializer.load_value(): TYPE_NIL is not supported.")
return null
TYPE_BOOL:
return load_bool(buffer)
TYPE_INT:
return load_int(buffer)
TYPE_FLOAT:
return load_float(buffer)
TYPE_STRING:
return load_string(buffer)
TYPE_VECTOR2:
return load_vector2(buffer)
TYPE_VECTOR2I:
return load_vector2i(buffer)
TYPE_RECT2:
return load_rect2(buffer)
TYPE_RECT2I:
return load_rect2i(buffer)
TYPE_VECTOR3:
return load_vector3(buffer)
TYPE_VECTOR3I:
return load_vector3i(buffer)
TYPE_TRANSFORM2D:
return load_transform2d(buffer)
TYPE_VECTOR4:
return load_vector4(buffer)
TYPE_VECTOR4I:
return load_vector4i(buffer)
TYPE_PLANE:
return load_plane(buffer)
TYPE_QUATERNION:
return load_quaternion(buffer)
TYPE_AABB:
return load_aabb(buffer)
TYPE_BASIS:
return load_basis(buffer)
TYPE_TRANSFORM3D:
return load_transform3d(buffer)
TYPE_PROJECTION:
return load_projection(buffer)
TYPE_COLOR:
return load_color(buffer)
TYPE_STRING_NAME:
return load_string_name(buffer)
TYPE_NODE_PATH:
return load_node_path(buffer)
TYPE_RID:
push_error("BinarySerializer.load_value(): TYPE_RID is not supported.")
return RID()
TYPE_PACKED_BYTE_ARRAY:
return load_byte_array(buffer)
TYPE_PACKED_INT32_ARRAY:
return load_int32_array(buffer)
TYPE_PACKED_INT64_ARRAY:
return load_int64_array(buffer)
TYPE_PACKED_FLOAT32_ARRAY:
return load_float32_array(buffer)
TYPE_PACKED_FLOAT64_ARRAY:
return load_float64_array(buffer)
TYPE_PACKED_STRING_ARRAY:
return load_string_array(buffer)
TYPE_PACKED_VECTOR2_ARRAY:
return load_vector2_array(buffer)
TYPE_PACKED_VECTOR3_ARRAY:
return load_vector3_array(buffer)
TYPE_PACKED_COLOR_ARRAY:
return load_color_array(buffer)
TYPE_PACKED_VECTOR4_ARRAY:
return load_vector4_array(buffer)
_:
push_error("BinarySerializer.load_value(): Unsupported type: %s" % type)
return null
static func skip_simple_value(buffer: StreamPeerBuffer, known_type: Variant.Type) -> void:
var type := TYPE_NIL
if known_type == TYPE_NIL:
type = buffer.get_8() as Variant.Type
else:
type = known_type
match type:
TYPE_NIL:
return
TYPE_BOOL:
skip_bool(buffer)
TYPE_INT:
skip_int(buffer)
TYPE_FLOAT:
skip_float(buffer)
TYPE_STRING:
skip_string(buffer)
TYPE_VECTOR2:
skip_vector2(buffer)
TYPE_VECTOR2I:
skip_vector2i(buffer)
TYPE_RECT2:
skip_rect2(buffer)
TYPE_RECT2I:
skip_rect2i(buffer)
TYPE_VECTOR3:
skip_vector3(buffer)
TYPE_VECTOR3I:
skip_vector3i(buffer)
TYPE_TRANSFORM2D:
skip_transform2d(buffer)
TYPE_VECTOR4:
skip_vector4(buffer)
TYPE_VECTOR4I:
skip_vector4i(buffer)
TYPE_PLANE:
skip_plane(buffer)
TYPE_QUATERNION:
skip_quaternion(buffer)
TYPE_AABB:
skip_aabb(buffer)
TYPE_BASIS:
skip_basis(buffer)
TYPE_TRANSFORM3D:
skip_transform3d(buffer)
TYPE_PROJECTION:
skip_projection(buffer)
TYPE_COLOR:
skip_color(buffer)
TYPE_STRING_NAME:
skip_string_name(buffer)
TYPE_NODE_PATH:
skip_node_path(buffer)
TYPE_RID:
return
TYPE_PACKED_BYTE_ARRAY:
skip_byte_array(buffer)
TYPE_PACKED_INT32_ARRAY:
skip_int32_array(buffer)
TYPE_PACKED_INT64_ARRAY:
skip_int64_array(buffer)
TYPE_PACKED_FLOAT32_ARRAY:
skip_float32_array(buffer)
TYPE_PACKED_FLOAT64_ARRAY:
skip_float64_array(buffer)
TYPE_PACKED_STRING_ARRAY:
skip_string_array(buffer)
TYPE_PACKED_VECTOR2_ARRAY:
skip_vector2_array(buffer)
TYPE_PACKED_VECTOR3_ARRAY:
skip_vector3_array(buffer)
TYPE_PACKED_COLOR_ARRAY:
skip_color_array(buffer)
TYPE_PACKED_VECTOR4_ARRAY:
skip_vector4_array(buffer)
_:
return
static func save_value(
buffer: StreamPeerBuffer,
input: Variant,
known_type: Variant.Type,
saved_objects: Dictionary[int, Object]
) -> void:
var type := typeof(input)
if known_type == TYPE_NIL:
buffer.put_8(type)
elif known_type == TYPE_OBJECT and type == TYPE_NIL:
type = TYPE_OBJECT
elif type != known_type:
push_error(
(
"BinarySerializer.save_value(): Type mismatch. Expected: %s, got: %s"
% [str(known_type), str(type)]
)
)
match type:
TYPE_NIL:
push_error("BinarySerializer.save_value(): TYPE_NIL is not supported.")
TYPE_OBJECT:
var value: Object = null
if is_instance_valid(input):
value = input
save_object(buffer, value, saved_objects)
TYPE_CALLABLE:
var value: Callable = input
save_callable(buffer, value, saved_objects)
TYPE_SIGNAL:
var value: Signal = input
save_signal(buffer, value, saved_objects)
TYPE_DICTIONARY:
var value: Dictionary = input
save_dictionary(buffer, value, saved_objects)
TYPE_ARRAY:
var value: Array = input
save_array(buffer, value, saved_objects)
_:
save_simple_value(buffer, input, type)
static func load_value(
buffer: StreamPeerBuffer, known_type: Variant.Type, loaded_objects: Dictionary[int, Object]
) -> Variant:
var type := TYPE_NIL
if known_type == TYPE_NIL:
type = buffer.get_8() as Variant.Type
else:
type = known_type
match type:
TYPE_NIL:
push_error("BinarySerializer.load_value(): TYPE_NIL is not supported.")
return null
TYPE_OBJECT:
return load_object(buffer, loaded_objects)
TYPE_CALLABLE:
return load_callable(buffer, loaded_objects)
TYPE_SIGNAL:
return load_signal(buffer, loaded_objects)
TYPE_DICTIONARY:
return load_dictionary(buffer, loaded_objects)
TYPE_ARRAY:
return load_array(buffer, loaded_objects)
_:
return load_simple_value(buffer, type)
static func skip_value(
buffer: StreamPeerBuffer, known_type: Variant.Type, loaded_objects: Dictionary[int, Object]
) -> void:
var type := TYPE_NIL
if known_type == TYPE_NIL:
type = buffer.get_8() as Variant.Type
else:
type = known_type
match type:
TYPE_NIL:
return
TYPE_OBJECT:
skip_object(buffer, loaded_objects)
TYPE_CALLABLE:
skip_callable(buffer, loaded_objects)
TYPE_SIGNAL:
skip_signal(buffer, loaded_objects)
TYPE_DICTIONARY:
skip_dictionary(buffer, loaded_objects)
TYPE_ARRAY:
skip_array(buffer, loaded_objects)
_:
skip_simple_value(buffer, type)
@tool
class_name ScriptHelper # RefCounted (Static)
## Utility functions for scripts, including inheritance checks, and global name resolution.
static var _script_path_by_class: Dictionary[StringName, StringName] = {}
static var _root_scripts: Array[StringName] = []
static func _static_init() -> void:
_rebuild_cache()
static func _rebuild_cache() -> void:
_script_path_by_class.clear()
_root_scripts.clear()
for global_class in ProjectSettings.get_global_class_list():
var found_class: String = global_class["class"]
var found_path: String = global_class["path"]
assert(not found_class.is_empty())
assert(not found_path.is_empty())
_script_path_by_class[found_class] = found_path
if found_class.ends_with("Root"):
_root_scripts.append(found_class)
static func _get_script_by_path(script_path: String) -> Script:
assert(not script_path.is_empty())
assert(ResourceLoader.exists(script_path, "Script"))
var script: Script = ResourceLoader.load(script_path, "Script") as Script
assert(script != null, "Failed to load script")
return script
static func _get_script_by_class(not_build_in_class_string: StringName) -> Script:
assert(not not_build_in_class_string.is_empty())
var script_path := ""
if is_resource_path(not_build_in_class_string):
script_path = not_build_in_class_string
else:
script_path = _script_path_by_class.get(not_build_in_class_string, &"")
if (
Engine.is_editor_hint()
and (
script_path.is_empty()
or not ResourceLoader.exists(script_path, "Script")
)
):
_rebuild_cache()
script_path = _script_path_by_class.get(not_build_in_class_string, "")
if script_path.is_empty() or not ResourceLoader.exists(script_path, "Script"):
push_error("Not found script for class %s" % not_build_in_class_string)
return null
if script_path.is_empty() or not ResourceLoader.exists(script_path, "Script"):
return null
return _get_script_by_path(script_path)
static func is_resource_path(string: String) -> bool:
if string.length() <= 6:
return false
var check_a := string.substr(3, 3)
if check_a != "://":
return false
var check_b := string.substr(0, 3)
if check_b not in ["res", "uid"]:
return false
return true
static func get_script_by_path(script_path: String) -> Script:
if script_path.is_empty() or not ResourceLoader.exists(script_path, "Script"):
return null
return _get_script_by_path(script_path)
static func get_script_by_class_name(name_of_class: String) -> Script:
assert(not name_of_class.is_empty())
var script_path: String = _script_path_by_class.get(name_of_class, "")
if (
Engine.is_editor_hint()
and (
script_path.is_empty()
or not ResourceLoader.exists(script_path, "Script")
)
):
_rebuild_cache()
script_path = _script_path_by_class.get(name_of_class, "")
if script_path.is_empty() or not ResourceLoader.exists(script_path, "Script"):
push_error("Not found script for class %s" % name_of_class)
return null
if script_path.is_empty() or not ResourceLoader.exists(script_path, "Script"):
return null
return _get_script_by_path(script_path)
static func get_script_by_class(class_string: StringName) -> Script:
assert(not class_string.is_empty())
assert(not ClassDB.class_exists(class_string))
return _get_script_by_class(class_string)
static func get_script_by_class_or_empty(class_string: StringName) -> Script:
if class_string.is_empty() or ClassDB.class_exists(class_string):
return null
return _get_script_by_class(class_string)
static func get_class_by_script(script: Script) -> String:
assert(script != null)
var global_name := script.get_global_name()
if not global_name.is_empty():
return global_name
return script.resource_path
static func get_class_by_script_or_empty(script: Script) -> String:
if script == null:
return ""
var global_name := script.get_global_name()
if not global_name.is_empty():
return global_name
return script.resource_path
static func is_node_class(class_string: StringName) -> bool:
assert(not class_string.is_empty())
if ClassDB.class_exists(class_string):
return class_string == &"Node" or ClassDB.is_parent_class(class_string, &"Node")
var class_script := _get_script_by_class(class_string)
if class_script == null:
return false
var found_class_string: StringName = class_script.get_instance_base_type()
return found_class_string == &"Node" or ClassDB.is_parent_class(found_class_string, &"Node")
static func is_class_derived(class_string: StringName, base_class_string: StringName) -> bool:
assert(not class_string.is_empty())
assert(not base_class_string.is_empty())
if ClassDB.class_exists(class_string):
if ClassDB.class_exists(base_class_string):
return (
class_string == base_class_string
or ClassDB.is_parent_class(class_string, base_class_string)
)
return false
var class_script := _get_script_by_class(class_string)
if class_script == null:
return false
if ClassDB.class_exists(base_class_string):
var found_class_string: StringName = class_script.get_instance_base_type()
return (
found_class_string == base_class_string
or ClassDB.is_parent_class(found_class_string, base_class_string)
)
return is_script_derived(class_script, _get_script_by_class(base_class_string))
static func is_script_derived(script: Script, base_script: Script) -> bool:
assert(base_script != null)
if script == null:
return false
while script != null:
if script == base_script:
return true
script = script.get_base_script()
return false
static func get_all_script_classes() -> Array[StringName]:
return _script_path_by_class.keys()
static func get_root_script_classes() -> Array[StringName]:
return _root_scripts
static func get_static_method_callable(
name_of_class: StringName, method_name: StringName
) -> Callable:
if name_of_class.is_empty():
return Callable()
if method_name.is_empty():
return Callable()
var script := _get_script_by_class(name_of_class)
if script == null:
return Callable()
if not script.has_method(method_name):
return Callable()
return Callable(script, method_name)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment