Skip to content

Instantly share code, notes, and snippets.

@theraot
Last active July 11, 2024 23:17
Show Gist options
  • Save theraot/31515e28e2d8bfea33f6c6d5bcd852f6 to your computer and use it in GitHub Desktop.
Save theraot/31515e28e2d8bfea33f6c6d5bcd852f6 to your computer and use it in GitHub Desktop.
BinarySerializer for Godot 4.2+
@tool
class_name BinarySerializer
## Utility class to save and load data from StreamPeerBuffer.
## Number of bytes in 32 bits.
const BITS32 := 4
## Number of bytes in 64 bits.
const BITS64 := 8
## Size in bytes of a bool.
const TYPE_BOOL_SIZE := 1
## Size in bytes of an int.
const TYPE_INT_SIZE := BITS64
## Size in bytes of a float.
const TYPE_FLOAT_SIZE := BITS32 # limited to single precision
## Size in bytes of a Vector2.
const TYPE_VECTOR2_SIZE := TYPE_FLOAT_SIZE * 2
## Size in bytes of a Vector2i.
const TYPE_VECTOR2I_SIZE := BITS32 * 2
## Size in bytes of a Rect2.
const TYPE_RECT2_SIZE := TYPE_VECTOR2_SIZE * 2
## Size in bytes of a Rect2i.
const TYPE_RECT2I_SIZE := TYPE_VECTOR2I_SIZE * 2
## Size in bytes of a Vector3.
const TYPE_VECTOR3_SIZE := TYPE_FLOAT_SIZE * 3
## Size in bytes of a Vector3i.
const TYPE_VECTOR3I_SIZE := BITS32 * 3
## Size in bytes of a Transform2D.
const TYPE_TRANSFORM2D_SIZE := TYPE_VECTOR2_SIZE * 3
## Size in bytes of a Vector4.
const TYPE_VECTOR4_SIZE := TYPE_FLOAT_SIZE * 4
## Size in bytes of a Vector4i.
const TYPE_VECTOR4I_SIZE := BITS32 * 4
## Size in bytes of a Plane.
const TYPE_PLANE_SIZE := TYPE_FLOAT_SIZE * 4
## Size in bytes of a Quaternion.
const TYPE_QUATERNION_SIZE := TYPE_FLOAT_SIZE * 4
## Size in bytes of an AABB.
const TYPE_AABB_SIZE := TYPE_VECTOR3_SIZE * 2
## Size in bytes of a Basis.
const TYPE_BASIS_SIZE := TYPE_VECTOR3_SIZE * 3
## Size in bytes of a Transform3D.
const TYPE_TRANSFORM3D_SIZE := TYPE_VECTOR3_SIZE * 4
## Size in bytes of a Projection.
const TYPE_PROJECTION_SIZE := TYPE_VECTOR4_SIZE * 4
## Size in bytes of a Color.
const TYPE_COLOR_SIZE := TYPE_FLOAT_SIZE * 4
# ---------------------
# BOOL
# ---------------------
## Saves a bool to the StreamPeerBuffer.
static func save_bool(buffer:StreamPeerBuffer, input:bool) -> void:
if input:
buffer.put_u8(1)
else:
buffer.put_u8(0)
## Loads a bool from the StreamPeerBuffer.
static func load_bool(buffer:StreamPeerBuffer) -> bool:
return buffer.get_u8() != 0
# ---------------------
# INT
# ---------------------
## Saves an int to the StreamPeerBuffer.
static func save_int(buffer:StreamPeerBuffer, input:int) -> void:
buffer.put_64(input)
## Loads an int from the StreamPeerBuffer.
static func load_int(buffer:StreamPeerBuffer) -> int:
return buffer.get_64()
# ---------------------
# FLOAT
# ---------------------
## Saves a float to the StreamPeerBuffer.
static func save_float(buffer:StreamPeerBuffer, input:float) -> void:
buffer.put_float(input)
## Loads a float from the StreamPeerBuffer.
static func load_float(buffer:StreamPeerBuffer) -> float:
return buffer.get_float()
# ---------------------
# VECTOR2
# ---------------------
## Saves a Vector2 to the StreamPeerBuffer.
static func save_vector2(buffer:StreamPeerBuffer, input:Vector2) -> void:
save_float(buffer, input.x)
save_float(buffer, input.y)
## Loads a Vector2 from the StreamPeerBuffer.
static func load_vector2(buffer:StreamPeerBuffer) -> Vector2:
return Vector2(load_float(buffer), load_float(buffer))
# ---------------------
# VECTOR2I
# ---------------------
## Saves a Vector2i to the StreamPeerBuffer.
static func save_vector2i(buffer:StreamPeerBuffer, input:Vector2i) -> void:
buffer.put_32(input.x)
buffer.put_32(input.y)
## Loads a Vector2i from the StreamPeerBuffer.
static func load_vector2i(buffer:StreamPeerBuffer) -> Vector2i:
return Vector2i(buffer.get_32(), buffer.get_32())
# ---------------------
# RECT2
# ---------------------
## Saves a Rect2 to the StreamPeerBuffer.
static func save_rect2(buffer:StreamPeerBuffer, input:Rect2) -> void:
save_vector2(buffer, input.position)
save_vector2(buffer, input.size)
## Loads a Rect2 from the StreamPeerBuffer.
static func load_rect2(buffer:StreamPeerBuffer) -> Rect2:
return Rect2(load_vector2(buffer), load_vector2(buffer))
# ---------------------
# RECT2I
# ---------------------
## Saves a Rect2i to the StreamPeerBuffer.
static func save_rect2i(buffer:StreamPeerBuffer, input:Rect2i) -> void:
save_vector2i(buffer, input.position)
save_vector2i(buffer, input.size)
## Loads a Rect2i from the StreamPeerBuffer.
static func load_rect2i(buffer:StreamPeerBuffer) -> Rect2i:
return Rect2i(load_vector2i(buffer), load_vector2i(buffer))
# ---------------------
# VECTOR3
# ---------------------
## Saves a Vector3 to the StreamPeerBuffer.
static func save_vector3(buffer:StreamPeerBuffer, input:Vector3) -> void:
save_float(buffer, input.x)
save_float(buffer, input.y)
save_float(buffer, input.z)
## Loads a Vector3 from the StreamPeerBuffer.
static func load_vector3(buffer:StreamPeerBuffer) -> Vector3:
return Vector3(load_float(buffer), load_float(buffer), load_float(buffer))
# ---------------------
# VECTOR3I
# ---------------------
## Saves a Vector3i to the StreamPeerBuffer.
static func save_vector3i(buffer:StreamPeerBuffer, input:Vector3i) -> void:
buffer.put_32(input.x)
buffer.put_32(input.y)
buffer.put_32(input.z)
## Loads a Vector3i from the StreamPeerBuffer.
static func load_vector3i(buffer:StreamPeerBuffer) -> Vector3i:
return Vector3i(buffer.get_32(), buffer.get_32(), buffer.get_32())
# ---------------------
# TRANSFORM2D
# ---------------------
## Saves a Transform2D to the StreamPeerBuffer.
static func save_transform2d(buffer:StreamPeerBuffer, input:Transform2D) -> void:
save_vector2(buffer, input.x)
save_vector2(buffer, input.y)
save_vector2(buffer, input.origin)
## Loads a Transform2D from the StreamPeerBuffer.
static func load_transform2d(buffer:StreamPeerBuffer) -> Transform2D:
return Transform2D(load_vector2(buffer), load_vector2(buffer), load_vector2(buffer))
# ---------------------
# VECTOR4
# ---------------------
## Saves a Vector4 to the StreamPeerBuffer.
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)
## Loads a Vector4 from the StreamPeerBuffer.
static func load_vector4(buffer:StreamPeerBuffer) -> Vector4:
return Vector4(load_float(buffer), load_float(buffer), load_float(buffer), load_float(buffer))
# ---------------------
# VECTOR4I
# ---------------------
## Saves a Vector4i to the StreamPeerBuffer.
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)
## Loads a Vector4i from the StreamPeerBuffer.
static func load_vector4i(buffer:StreamPeerBuffer) -> Vector4i:
return Vector4i(buffer.get_32(), buffer.get_32(), buffer.get_32(), buffer.get_32())
# ---------------------
# PLANE
# ---------------------
## Saves a Plane to the StreamPeerBuffer.
static func save_plane(buffer:StreamPeerBuffer, input:Plane) -> void:
save_vector3(buffer, input.normal)
save_float(buffer, input.d)
## Loads a Plane from the StreamPeerBuffer.
static func load_plane(buffer:StreamPeerBuffer) -> Plane:
return Plane(load_vector3(buffer), load_float(buffer))
# ---------------------
# QUATERNION
# ---------------------
## Saves a Quaternion to the StreamPeerBuffer.
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)
## Loads a Quaternion from the StreamPeerBuffer.
static func load_quaternion(buffer:StreamPeerBuffer) -> Quaternion:
return Quaternion(load_float(buffer), load_float(buffer), load_float(buffer), load_float(buffer))
# ---------------------
# AABB
# ---------------------
## Saves a AABB to the StreamPeerBuffer.
static func save_aabb(buffer:StreamPeerBuffer, input:AABB) -> void:
save_vector3(buffer, input.position)
save_vector3(buffer, input.size)
## Loads a AABB from the StreamPeerBuffer.
static func load_aabb(buffer:StreamPeerBuffer) -> AABB:
return AABB(load_vector3(buffer), load_vector3(buffer))
# ---------------------
# BASIS
# ---------------------
## Saves a Basis to the StreamPeerBuffer.
static func save_basis(buffer:StreamPeerBuffer, input:Basis) -> void:
save_vector3(buffer, input.x)
save_vector3(buffer, input.y)
save_vector3(buffer, input.z)
## Loads a Basis from the StreamPeerBuffer.
static func load_basis(buffer:StreamPeerBuffer) -> Basis:
return Basis(load_vector3(buffer), load_vector3(buffer), load_vector3(buffer))
# ---------------------
# TRANSFORM3D
# ---------------------
## Saves a Transform3D to the StreamPeerBuffer.
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)
## Loads a Transform3D from the StreamPeerBuffer.
static func load_transform3d(buffer:StreamPeerBuffer) -> Transform3D:
return Transform3D(load_vector3(buffer), load_vector3(buffer), load_vector3(buffer), load_vector3(buffer))
# ---------------------
# PROJECTION
# ---------------------
## Saves a Projection to the StreamPeerBuffer.
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)
## Loads a Projection from the StreamPeerBuffer.
static func load_projection(buffer:StreamPeerBuffer) -> Projection:
return Projection(load_vector4(buffer), load_vector4(buffer), load_vector4(buffer), load_vector4(buffer))
# ---------------------
# COLOR
# ---------------------
## Saves a Color to the StreamPeerBuffer.
static func save_color(buffer:StreamPeerBuffer, input:Color) -> void:
save_float(buffer, input.r)
save_float(buffer, input.g)
save_float(buffer, input.b)
save_float(buffer, input.a)
## Loads a Color from the StreamPeerBuffer.
static func load_color(buffer:StreamPeerBuffer) -> Color:
return Color(load_float(buffer), load_float(buffer), load_float(buffer), load_float(buffer))
# ---------------------
# STRING
# ---------------------
## Saves a String to the StreamPeerBuffer.
static func save_string(buffer:StreamPeerBuffer, input:String) -> void:
save_byte_array(buffer, input.to_utf8_buffer())
## Loads a String from the StreamPeerBuffer.
static func load_string(buffer:StreamPeerBuffer) -> String:
var utf8 := load_byte_array(buffer)
return utf8.get_string_from_utf8()
# ---------------------
# NODEPATH
# ---------------------
## Saves a StringName to the StreamPeerBuffer.
static func save_string_name(buffer:StreamPeerBuffer, input:StringName) -> void:
save_string(buffer, str(input))
## Loads a StringName from the StreamPeerBuffer.
static func load_string_name(buffer:StreamPeerBuffer) -> StringName:
return StringName(load_string(buffer))
# ---------------------
# NODEPATH
# ---------------------
## Saves a NodePath to the StreamPeerBuffer.
static func save_node_path(buffer:StreamPeerBuffer, input:NodePath) -> void:
save_string(buffer, str(input))
## Loads a NodePath from the StreamPeerBuffer.
static func load_node_path(buffer:StreamPeerBuffer) -> NodePath:
return NodePath(load_string(buffer))
# ---------------------
# RID
# ---------------------
## Saves a RID to the StreamPeerBuffer.
## @deprecated: not supported.
static func save_rid(_buffer:StreamPeerBuffer, _input:RID) -> void:
pass
## Loads a RID from the StreamPeerBuffer.
## @deprecated: not supported.
static func load_rid(_buffer:StreamPeerBuffer) -> RID:
return RID()
# ---------------------
# OBJECT
# ---------------------
## Saves a reference to a sub-object to the StreamPeerBuffer.
static func save_sub_object_reference(buffer:StreamPeerBuffer, input:Object, objects_to_save:Dictionary, can_save_class:Callable) -> void:
var obj := input as Object
if is_instance_valid(obj):
var name_of_class := obj.get_class()
if not can_save_class.is_valid() or can_save_class.call(name_of_class):
var x := obj.get_instance_id()
save_int(buffer, -2 * x - 1 if x < 0 else x * 2)
objects_to_save[obj] = true
return
save_int(buffer, -1)
## Loads a reference to a sub-object from the StreamPeerBuffer.
static func load_sub_object_reference(setter:Callable, buffer:StreamPeerBuffer, objects_to_load:Dictionary) -> void:
var id := load_int(buffer)
if objects_to_load.has(id):
var settersArray:Array[Callable] = objects_to_load[id]
settersArray.append(setter)
else:
var setters:Array[Callable] = [setter]
objects_to_load[id] = setters
# -------------------
## Saves an object to the StreamPeerBuffer.
static func save_object(buffer:StreamPeerBuffer, input:Object, can_save_class:Callable) -> bool:
var saved_objects := {}
var objects_to_save := {}
if not save_sub_object(buffer, input, saved_objects, objects_to_save, can_save_class):
return false
while not objects_to_save.is_empty():
var object:Object = objects_to_save.keys()[0]
if not save_sub_object(buffer, object, saved_objects, objects_to_save, can_save_class):
return false
return true
## Saves a sub-object to the StreamPeerBuffer.
static func save_sub_object(buffer:StreamPeerBuffer, input:Object, saved_objects:Dictionary, objects_to_save:Dictionary, can_save_class:Callable) -> bool:
if input == null:
return false
objects_to_save.erase(input)
if saved_objects.has(input):
return true
saved_objects[input] = true
var name_of_class := input.get_class()
if can_save_class.is_valid() and not can_save_class.call(name_of_class):
return false
@warning_ignore("unsafe_cast")
var found_script := input.get_script() as GDScript
if found_script != null:
name_of_class = ScriptHelper.get_class_name_by_script(found_script)
var sub_buffer := StreamPeerBuffer.new()
var x := input.get_instance_id()
var id := -2 * x - 1 if x < 0 else x * 2
save_int(sub_buffer, id)
save_string(sub_buffer, name_of_class)
var properties := input.get_property_list()
for property in properties:
var type:Variant.Type = property["type"]
if 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 == "resource_local_to_scene"
or name == "resource_name"
)
)
):
continue
var value:Variant = input.get(name)
save_string(sub_buffer, name)
save_sub_value(sub_buffer, value, type, objects_to_save, can_save_class)
save_byte_array(buffer, sub_buffer.data_array)
return true
# -------------------
## Loads an object from the StreamPeerBuffer.
static func load_object(buffer:StreamPeerBuffer, can_load_class:Callable) -> Object:
var loaded_objects := {}
var objects_to_load := {}
var result := load_sub_object(buffer, loaded_objects, objects_to_load, can_load_class)
if result == null:
return null
while (
not objects_to_load.is_empty()
and buffer.get_available_bytes() > 0
):
load_sub_object(buffer, loaded_objects, objects_to_load, can_load_class)
return result
## Loads a sub-object from the StreamPeerBuffer.
static func load_sub_object(buffer:StreamPeerBuffer, loaded_objects:Dictionary, objects_to_load:Dictionary, can_load_class:Callable) -> Object:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
if sub_buffer.get_size() == 0:
return null
var id := load_int(sub_buffer)
if loaded_objects.has(id):
var object:Object = loaded_objects[id]
return object
var name_of_class := load_string(sub_buffer)
if can_load_class.is_valid() and not can_load_class.call(name_of_class):
return null
var result:Object = null
if ClassDB.class_exists(name_of_class):
result = ClassDB.instantiate(name_of_class)
else:
var script := ScriptHelper.get_script_by_class_name(name_of_class)
if script is GDScript:
result = (script as GDScript).new()
else:
return null
var seen_properties := PackedStringArray([])
while sub_buffer.get_available_bytes() > 0:
var name := load_string(sub_buffer)
seen_properties.append(name)
if (
not name in result
or name == "script"
or (
result is Resource
and (
name == "resource_local_to_scene"
or name == "resource_name"
)
)
):
skip_var(sub_buffer)
else:
load_sub_value(
sub_buffer,
func(value:Variant) -> void:
result.set(name, value),
objects_to_load
)
var properties := result.get_property_list()
for property in properties:
var name := str(property["name"])
if seen_properties.has(name):
continue
var type:Variant.Type = property["type"]
if type == TYPE_NIL:
continue
var usage:PropertyUsageFlags = property["usage"]
if usage & PROPERTY_USAGE_STORAGE == 0:
continue
if (
name == "script"
or (
result is Resource
and (
name == "resource_local_to_scene"
or name == "resource_name"
)
)
):
continue
result.set(name, result.get(name))
loaded_objects[id] = result
if objects_to_load.has(id):
var setters:Array[Callable] = objects_to_load[id]
for setter in setters:
(setter as Callable).call(result)
objects_to_load.erase(id)
return result
# ---------------------
# Callable
# ---------------------
## Saves a Callable to the StreamPeerBuffer.
static func save_sub_callable(buffer:StreamPeerBuffer, input:Callable, objects_to_save:Dictionary, can_save_class:Callable) -> void:
save_string(buffer, input.get_method())
save_sub_array(buffer, input.get_bound_arguments(), objects_to_save, can_save_class)
save_sub_object_reference(buffer, input.get_object(), objects_to_save, can_save_class)
## Loads a Callable from the StreamPeerBuffer.
static func load_sub_callable(setter:Callable, buffer:StreamPeerBuffer, objects_to_load:Dictionary) -> void:
var name := load_string(buffer)
var bound_arguments := load_sub_array(buffer, objects_to_load)
load_sub_object_reference(
func(value:Object) -> void:
setter.call(Callable(value, name).bindv(bound_arguments)),
buffer,
objects_to_load
)
# ---------------------
# RID
# ---------------------
## Saves a sub-signal to the StreamPeerBuffer.
static func save_sub_signal(buffer:StreamPeerBuffer, input:Signal, objects_to_save:Dictionary, can_save_class:Callable) -> void:
save_string(buffer, input.get_name())
save_sub_object_reference(buffer, input.get_object(), objects_to_save, can_save_class)
## Loads a sub-signal from the StreamPeerBuffer.
static func load_sub_signal(setter:Callable, buffer:StreamPeerBuffer, objects_to_load:Dictionary) -> void:
var name := load_string(buffer)
load_sub_object_reference(
func(value:Object) -> void:
setter.call(Signal(value, name)),
buffer,
objects_to_load
)
# ---------------------
# DICTIONARY
# ---------------------
## Loads a Dictionary to the StreamPeerBuffer.
static func save_dictionary(buffer:StreamPeerBuffer, input:Dictionary, can_save_class:Callable) -> bool:
var saved_objects := {}
var objects_to_save := {}
save_sub_dictionary(buffer, input, objects_to_save, can_save_class)
while not objects_to_save.is_empty():
var object:Object = objects_to_save.keys()[0]
if not save_sub_object(buffer, object, saved_objects, objects_to_save, can_save_class):
return false
return true
## Loads a Dictionary from the StreamPeerBuffer.
static func load_dictionary(buffer:StreamPeerBuffer, can_load_class:Callable) -> Dictionary:
var loaded_objects := {}
var objects_to_load := {}
var result:Array = [{}]
var setter := func(value:Variant) -> void:
result[0] = value
load_sub_dictionary(setter, buffer, objects_to_load)
while (
not objects_to_load.is_empty()
and buffer.get_available_bytes() > 0
):
load_sub_object(buffer, loaded_objects, objects_to_load, can_load_class)
return result[0]
## Saves a Dictionary of a specific type to the StreamPeerBuffer.
static func save_dictionary_of(buffer:StreamPeerBuffer, input:Dictionary, value_type:Variant.Type) -> void:
match value_type:
TYPE_OBJECT:
return
TYPE_CALLABLE:
return
TYPE_SIGNAL:
return
TYPE_DICTIONARY:
return
TYPE_ARRAY:
return
var sub_buffer := StreamPeerBuffer.new()
for key:Variant in input.keys():
var value:Variant = input[key]
save_string(sub_buffer, str(key))
save_simple_value(sub_buffer, value, value_type)
save_byte_array(buffer, sub_buffer.data_array)
## Loads a Dictionary of a specific type from the StreamPeerBuffer.
static func load_dictionary_of(buffer:StreamPeerBuffer, value_type:Variant.Type) -> Dictionary:
match value_type:
TYPE_OBJECT:
return {}
TYPE_CALLABLE:
return {}
TYPE_SIGNAL:
return {}
TYPE_DICTIONARY:
return {}
TYPE_ARRAY:
return {}
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var result := {}
while sub_buffer.get_available_bytes() > 0:
var key := load_string(sub_buffer)
result[key] = load_simple_value(sub_buffer, value_type)
return result
## Saves a Dictionary to the StreamPeerBuffer.
static func save_sub_dictionary(buffer:StreamPeerBuffer, input:Dictionary, objects_to_save:Dictionary, can_save_class:Callable) -> void:
var sub_buffer := StreamPeerBuffer.new()
for key:Variant in input.keys():
var value:Variant = input[key]
save_string(sub_buffer, str(key))
save_sub_value(sub_buffer, value, -1, objects_to_save, can_save_class)
save_byte_array(buffer, sub_buffer.data_array)
## Loads a Dictionary from the StreamPeerBuffer.
static func load_sub_dictionary(setter:Callable, buffer:StreamPeerBuffer, objects_to_load:Dictionary) -> void:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var result := {}
var count:Array[int] = [0]
while sub_buffer.get_available_bytes() > 0:
var key := load_string(sub_buffer)
count[0] += 1
load_sub_value(
sub_buffer,
func(value:Variant) -> void:
result[key] = value
count[0] -= 1
if count[0] == 0:
setter.call(result)
,
objects_to_load
)
# ---------------------
# ARRAY
# ---------------------
## Saves an Array to the StreamPeerBuffer.
static func save_array(buffer:StreamPeerBuffer, input:Array, can_save_class:Callable) -> bool:
var saved_objects := {}
var objects_to_save := {}
save_sub_array(buffer, input, objects_to_save, can_save_class)
while not objects_to_save.is_empty():
var object:Object = objects_to_save.keys()[0]
if not save_sub_object(buffer, object, saved_objects, objects_to_save, can_save_class):
return false
return true
## Loads an Array from the StreamPeerBuffer.
static func load_array(buffer:StreamPeerBuffer, can_load_class:Callable) -> Array:
var loaded_objects := {}
var objects_to_load := {}
var result := load_sub_array(buffer, objects_to_load)
while (
not objects_to_load.is_empty()
and buffer.get_available_bytes() > 0
):
load_sub_object(buffer, loaded_objects, objects_to_load, can_load_class)
return result
## Saves an Array of a specific type to the StreamPeerBuffer.
static func save_array_of(buffer:StreamPeerBuffer, input:Array, value_type:Variant.Type) -> void:
match value_type:
TYPE_OBJECT:
return
TYPE_CALLABLE:
return
TYPE_SIGNAL:
return
TYPE_DICTIONARY:
return
TYPE_ARRAY:
return
var sub_buffer := StreamPeerBuffer.new()
for item:Variant in input:
save_simple_value(sub_buffer, item, -1)
save_byte_array(buffer, sub_buffer.data_array)
## Loads an Array of a specific type from the StreamPeerBuffer.
static func load_array_of(buffer:StreamPeerBuffer, value_type:Variant.Type) -> Array:
match value_type:
TYPE_OBJECT:
return []
TYPE_CALLABLE:
return []
TYPE_SIGNAL:
return []
TYPE_DICTIONARY:
return []
TYPE_ARRAY:
return []
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var result := []
while sub_buffer.get_available_bytes() > 0:
var index := result.size()
result.resize(index + 1)
result[index] = load_simple_value(sub_buffer, value_type)
return result
## Saves an Array to the StreamPeerBuffer.
static func save_sub_array(buffer:StreamPeerBuffer, input:Array, objects_to_save:Dictionary, can_save_class:Callable) -> void:
var sub_buffer := StreamPeerBuffer.new()
for item:Variant in input:
save_sub_value(sub_buffer, item, -1, objects_to_save, can_save_class)
save_byte_array(buffer, sub_buffer.data_array)
## Loads an Array from the StreamPeerBuffer.
static func load_sub_array(buffer:StreamPeerBuffer, objects_to_load:Dictionary) -> Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var result := []
while sub_buffer.get_available_bytes() > 0:
var index := result.size()
result.resize(index + 1)
load_sub_value(
sub_buffer,
func(value:Variant) -> void:
result[index] = value,
objects_to_load
)
return result
# ---------------------
# BYTE ARRAY
# ---------------------
## Saves a PackedByteArray to the StreamPeerBuffer.
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: " + error_string(err))
## Loads a PackedByteArray from the StreamPeerBuffer.
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 result := buffer.data_array.slice(position, position + size)
buffer.seek(position + size)
return result
## Skips a byte array in the StreamPeerBuffer.
static func skip_byte_array(buffer:StreamPeerBuffer) -> bool:
var size := mini(buffer.get_32(), buffer.get_available_bytes())
var position := buffer.get_position()
if position >= buffer.data_array.size():
return false
buffer.seek(position + size)
return true
# ---------------------
# INT32 ARRAY
# ---------------------
## Saves a PackedInt32Array to the StreamPeerBuffer.
static func save_int32_array(buffer:StreamPeerBuffer, input:PackedInt32Array) -> void:
buffer.put_32(input.size() * BITS32)
for item in input:
buffer.put_32(item)
## Loads a PackedInt32Array from the StreamPeerBuffer.
static func load_int32_array(buffer:StreamPeerBuffer) -> PackedInt32Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var result := PackedInt32Array()
@warning_ignore("integer_division")
result.resize(sub_buffer.data_array.size() / BITS32)
for index in result.size():
result[index] = buffer.get_32()
return result
# ---------------------
# INT64 ARRAY
# ---------------------
## Saves a PackedInt64Array to the StreamPeerBuffer.
static func save_int64_array(buffer:StreamPeerBuffer, input:PackedInt64Array) -> void:
buffer.put_32(input.size() * BITS64)
for item in input:
buffer.put_64(item)
## Loads a PackedInt64Array from the StreamPeerBuffer.
static func load_int64_array(buffer:StreamPeerBuffer) -> PackedInt64Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var result := PackedInt64Array()
@warning_ignore("integer_division")
result.resize(sub_buffer.data_array.size() / BITS64)
for index in result.size():
result[index] = buffer.get_64()
return result
# ---------------------
# FLOAT32 ARRAY
# ---------------------
## Saves a PackedFloat32Array to the StreamPeerBuffer.
static func save_float32_array(buffer:StreamPeerBuffer, input:PackedFloat32Array) -> void:
buffer.put_32(input.size() * BITS32)
for item in input:
buffer.put_float(item)
## Loads a PackedFloat32Array from the StreamPeerBuffer.
static func load_float32_array(buffer:StreamPeerBuffer) -> PackedFloat32Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var result := PackedFloat32Array()
@warning_ignore("integer_division")
result.resize(sub_buffer.data_array.size() / BITS32)
for index in result.size():
result[index] = buffer.get_float()
return result
# ---------------------
# FLOAT64 ARRAY
# ---------------------
## Saves a PackedFloat64Array to the StreamPeerBuffer.
static func save_float64_array(buffer:StreamPeerBuffer, input:PackedFloat64Array) -> void:
buffer.put_32(input.size() * BITS64)
for item in input:
buffer.put_double(item)
## Loads a PackedFloat64Array from the StreamPeerBuffer.
static func load_float64_array(buffer:StreamPeerBuffer) -> PackedFloat64Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var result := PackedFloat64Array()
@warning_ignore("integer_division")
result.resize(sub_buffer.data_array.size() / BITS64)
for index in result.size():
result[index] = buffer.get_double()
return result
# ---------------------
# STRING ARRAY
# ---------------------
## Saves a PackedStringArray to the StreamPeerBuffer.
static func save_string_array(buffer:StreamPeerBuffer, input:PackedStringArray) -> void:
var sub_buffer := StreamPeerBuffer.new()
for item in input:
save_string(sub_buffer, str(item))
save_byte_array(buffer, sub_buffer.data_array)
## Loads a PackedStringArray from the StreamPeerBuffer.
static func load_string_array(buffer:StreamPeerBuffer) -> PackedStringArray:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var result := PackedStringArray()
while sub_buffer.get_available_bytes() > 0:
result.append(load_string(sub_buffer))
return result
# ---------------------
# VECTOR2 ARRAY
# ---------------------
## Saves a PackedVector2Array to the StreamPeerBuffer.
static func save_vector2_array(buffer:StreamPeerBuffer, input:PackedVector2Array) -> void:
buffer.put_32(input.size() * TYPE_VECTOR2_SIZE)
for item in input:
save_vector2(buffer, item)
## Loads a PackedVector2Array from the StreamPeerBuffer.
static func load_vector2_array(buffer:StreamPeerBuffer) -> PackedVector2Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var result := PackedVector2Array()
@warning_ignore("integer_division")
result.resize(sub_buffer.data_array.size() / TYPE_VECTOR2_SIZE)
for index in result.size():
result[index] = load_vector2(buffer)
return result
# ---------------------
# VECTOR3 ARRAY
# ---------------------
## Saves a PackedVector3Array to the StreamPeerBuffer.
static func save_vector3_array(buffer:StreamPeerBuffer, input:PackedVector3Array) -> void:
buffer.put_32(input.size() * TYPE_VECTOR3_SIZE)
for item in input:
save_vector3(buffer, item)
## Loads a PackedVector3Array from the StreamPeerBuffer.
static func load_vector3_array(buffer:StreamPeerBuffer) -> PackedVector3Array:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var result := PackedVector3Array()
@warning_ignore("integer_division")
result.resize(sub_buffer.data_array.size() / TYPE_VECTOR3_SIZE)
for index in result.size():
result[index] = load_vector3(buffer)
return result
# ---------------------
# COLOR ARRAY
# ---------------------
## Saves a PackedColorArray to the StreamPeerBuffer.
static func save_color_array(buffer:StreamPeerBuffer, input:PackedColorArray) -> void:
buffer.put_32(input.size() * TYPE_COLOR_SIZE)
for item in input:
save_color(buffer, item)
## Loads a PackedColorArray from the StreamPeerBuffer.
static func load_color_array(buffer:StreamPeerBuffer) -> PackedColorArray:
var sub_buffer := StreamPeerBuffer.new()
sub_buffer.data_array = load_byte_array(buffer)
var result := PackedColorArray()
@warning_ignore("integer_division")
result.resize(sub_buffer.data_array.size() / TYPE_COLOR_SIZE)
for index in result.size():
result[index] = load_color(buffer)
return result
# ---------------------
# VAR
# ---------------------
## Saves a simple value to the StreamPeerBuffer.
static func save_simple_value(buffer:StreamPeerBuffer, input:Variant, type:int) -> void:
match type:
TYPE_NIL:
pass
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:
var value:RID = input
save_rid(buffer, value)
TYPE_OBJECT:
pass
TYPE_CALLABLE:
pass
TYPE_SIGNAL:
pass
TYPE_DICTIONARY:
pass
TYPE_ARRAY:
pass
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)
_:
pass
## Saves a sub-value to the StreamPeerBuffer.
static func save_sub_value(buffer:StreamPeerBuffer, input:Variant, type:int, objects_to_save:Dictionary, can_save_class:Callable) -> void:
if type == -1:
type = typeof(input)
buffer.put_32(type)
match type:
TYPE_OBJECT:
var value:Object = input
save_sub_object_reference(buffer, value, objects_to_save, can_save_class)
TYPE_CALLABLE:
var value:Callable = input
save_sub_callable(buffer, value, objects_to_save, can_save_class)
TYPE_SIGNAL:
var value:Signal = input
save_sub_signal(buffer, value, objects_to_save, can_save_class)
TYPE_DICTIONARY:
var value:Dictionary = input
save_sub_dictionary(buffer, value, objects_to_save, can_save_class)
TYPE_ARRAY:
var value:Array = input
save_sub_array(buffer, value, objects_to_save, can_save_class)
_:
save_simple_value(buffer, input, type)
## Loads a simple value from the StreamPeerBuffer.
static func load_simple_value(buffer:StreamPeerBuffer, type:Variant.Type) -> Variant:
match type:
TYPE_NIL:
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:
return load_rid(buffer)
TYPE_OBJECT:
return null
TYPE_CALLABLE:
return null
TYPE_SIGNAL:
return null
TYPE_DICTIONARY:
return null
TYPE_ARRAY:
return null
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)
_:
return null
## Loads a sub-value from the StreamPeerBuffer.
static func load_sub_value(buffer:StreamPeerBuffer, setter:Callable, objects_to_load:Dictionary) -> void:
var type := buffer.get_32()
match type:
TYPE_NIL:
return
TYPE_OBJECT:
load_sub_object_reference(setter, buffer, objects_to_load)
TYPE_CALLABLE:
load_sub_callable(setter, buffer, objects_to_load)
TYPE_SIGNAL:
load_sub_signal(setter, buffer, objects_to_load)
TYPE_DICTIONARY:
load_sub_dictionary(setter, buffer, objects_to_load)
_:
setter.call(load_simple_value(buffer, type))
## Skips a variable in the StreamPeerBuffer.
static func skip_var(buffer:StreamPeerBuffer) -> void:
var type := buffer.get_32()
var position := buffer.get_position()
var size := 0
match type:
TYPE_NIL:
pass
TYPE_BOOL:
size = TYPE_BOOL_SIZE
TYPE_INT:
size = TYPE_INT_SIZE
TYPE_FLOAT:
size = TYPE_FLOAT_SIZE
TYPE_STRING:
size = buffer.get_32()
TYPE_VECTOR2:
size = TYPE_VECTOR2_SIZE
TYPE_VECTOR2I:
size = TYPE_VECTOR2I_SIZE
TYPE_RECT2:
size = TYPE_RECT2_SIZE
TYPE_RECT2I:
size = TYPE_RECT2I_SIZE
TYPE_VECTOR3:
size = TYPE_VECTOR3_SIZE
TYPE_VECTOR3I:
size = TYPE_VECTOR3I_SIZE
TYPE_TRANSFORM2D:
size = TYPE_TRANSFORM2D_SIZE
TYPE_VECTOR4:
size = TYPE_VECTOR4_SIZE
TYPE_VECTOR4I:
size = TYPE_VECTOR4I_SIZE
TYPE_PLANE:
size = TYPE_PLANE_SIZE
TYPE_QUATERNION:
size = TYPE_QUATERNION_SIZE
TYPE_AABB:
size = TYPE_AABB_SIZE
TYPE_BASIS:
size = TYPE_BASIS_SIZE
TYPE_TRANSFORM3D:
size = TYPE_TRANSFORM3D_SIZE
TYPE_PROJECTION:
size = TYPE_PROJECTION_SIZE
TYPE_COLOR:
size = TYPE_COLOR_SIZE
TYPE_NODE_PATH:
size = buffer.get_32()
TYPE_RID:
pass
TYPE_OBJECT:
size = buffer.get_32()
TYPE_CALLABLE:
pass
TYPE_SIGNAL:
pass
TYPE_DICTIONARY:
size = buffer.get_32()
TYPE_ARRAY:
size = buffer.get_32()
TYPE_PACKED_BYTE_ARRAY:
size = buffer.get_32()
TYPE_PACKED_INT32_ARRAY:
size = buffer.get_32()
TYPE_PACKED_INT64_ARRAY:
size = buffer.get_32()
TYPE_PACKED_FLOAT32_ARRAY:
size = buffer.get_32()
TYPE_PACKED_FLOAT64_ARRAY:
size = buffer.get_32()
TYPE_PACKED_STRING_ARRAY:
size = buffer.get_32()
TYPE_PACKED_VECTOR2_ARRAY:
size = buffer.get_32()
TYPE_PACKED_VECTOR3_ARRAY:
size = buffer.get_32()
TYPE_PACKED_COLOR_ARRAY:
size = buffer.get_32()
_:
pass
size = mini(size, buffer.get_available_bytes())
buffer.seek(position + size)
@tool
class_name ScriptHelper
## Utility to facilitate script and class name management within Godot projects.
static var _populated_dictionaries := false
static var _script_path_by_class_name := {}
static var _class_name_by_script_path := {}
## Retrieves the script path associated with a given class name.
static func get_script_path_by_class_name(name_of_class:String) -> String:
if ClassDB.class_exists(name_of_class):
return ""
if name_of_class.begins_with("res://"):
if ResourceLoader.exists(name_of_class, "Script"):
return name_of_class
else:
return ""
_ensure_dictionaries_are_populated()
if _script_path_by_class_name.has(name_of_class):
var path:String = _script_path_by_class_name[name_of_class]
return path
return ""
## Retrieves the Script resource associated with a given class name.
static func get_script_by_class_name(name_of_class:String) -> Script:
if ClassDB.class_exists(name_of_class):
return null
var path := get_script_path_by_class_name(name_of_class)
if path.is_empty():
return null
var result := ResourceLoader.load(path, "Script") as Script
return result
## Retrieves the class name associated with a given Script resource.
static func get_class_name_by_script(script:Script) -> String:
if script == null:
return ""
_ensure_dictionaries_are_populated()
var path := script.resource_path
if _class_name_by_script_path.has(path):
var name_of_class:String = _class_name_by_script_path[path]
return name_of_class
return script.resource_path
## Determines whether a class is derived from another class, either directly or indirectly.
## This method supports both built-in Godot classes and custom script classes.
static func is_class_derived(name_of_class:String, name_of_parent_class:String) -> bool:
if ClassDB.class_exists(name_of_class):
if ClassDB.class_exists(name_of_parent_class):
return name_of_class == name_of_parent_class or ClassDB.is_parent_class(name_of_class, name_of_parent_class)
return false
var class_script := get_script_by_class_name(name_of_class)
if class_script == null:
return false
if ClassDB.class_exists(name_of_parent_class):
name_of_class = class_script.get_instance_base_type()
return name_of_class == name_of_parent_class or ClassDB.is_parent_class(name_of_class, name_of_parent_class)
return is_script_derived(class_script, get_script_by_class_name(name_of_parent_class))
## Determines whether a script is derived from another script, either directly or indirectly.
static func is_script_derived(script:Script, parent_script:Script) -> bool:
# MUST CHECK FOR NULL
if script == null or parent_script == null:
return false
while script != null:
if script == parent_script:
return true
script = script.get_base_script()
return false
static func _ensure_dictionaries_are_populated() -> void:
if _populated_dictionaries:
return
for global_class in ProjectSettings.get_global_class_list():
var found_name_of_class:String = global_class["class"]
var found_path:String = global_class["path"]
_script_path_by_class_name[found_name_of_class] = found_path
_class_name_by_script_path[found_path] = found_name_of_class
_populated_dictionaries = true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment