Skip to content

Instantly share code, notes, and snippets.

@rickwillcox
Last active September 14, 2022 15:11
Show Gist options
  • Save rickwillcox/419cd5605cb438ec474c35fc5accf1b6 to your computer and use it in GitHub Desktop.
Save rickwillcox/419cd5605cb438ec474c35fc5accf1b6 to your computer and use it in GitHub Desktop.
extends Node
var si = ServerInterface
class_name Serializer
# Packet description from server to client. Default type is ENTITY_ID
static func get_server_client_descriptor():
var server_to_client_packet_descriptor = {
ServerInterface.Opcodes.TAKE_DAMAGE :
PacketBundle.create_packet_descriptor([["attacker"], ["victim"], ["damage"]]),
ServerInterface.Opcodes.INVENTORY_OK : [],
ServerInterface.Opcodes.INVENTORY_NOK : [],
ServerInterface.Opcodes.REMOVE_ITEM :
PacketBundle.create_packet_descriptor([["item_id"]]),
ServerInterface.Opcodes.ATTACK_SWING :
PacketBundle.create_packet_descriptor([["attacker"], ["victim"]]),
ServerInterface.Opcodes.PLAYER_SPAWN :
PacketBundle.create_packet_descriptor([["player_id"],
["position", PacketBundle.FieldType.VECTOR2],
["username", PacketBundle.FieldType.SHORT_STRING]]),
ServerInterface.Opcodes.PLAYER_INITIAL_INVENTORY:
PacketBundle.create_packet_descriptor([["player_id"],
["head"],
["chest"],
["legs"],
["feet"],
["hands"]]),
ServerInterface.Opcodes.PLAYER_DESPAWN :
PacketBundle.create_packet_descriptor([["player_id"]]),
ServerInterface.Opcodes.PLAYER_DEAD :
PacketBundle.create_packet_descriptor([
["player_id"],
["player_position", PacketBundle.FieldType.VECTOR2]]),
ServerInterface.Opcodes.PLAYER_INVENTORY_UPDATE :
PacketBundle.create_packet_descriptor([["player_id"],
["slot", PacketBundle.FieldType.INT8],
["item_id"]]),
ServerInterface.Opcodes.CHAT :
PacketBundle.create_packet_descriptor([["player_id"],
["content", PacketBundle.FieldType.SHORT_STRING]]),
ServerInterface.Opcodes.ENEMY_SPAWN:
PacketBundle.create_packet_descriptor([["enemy_id"],
["enemy_state", PacketBundle.FieldType.INT8],
["enemy_type", PacketBundle.FieldType.INT8],
["health"],
["position", PacketBundle.FieldType.VECTOR2]]),
ServerInterface.Opcodes.ENEMY_DIED:
PacketBundle.create_packet_descriptor([["enemy_id"]]),
ServerInterface.Opcodes.ENEMY_DESPAWN:
PacketBundle.create_packet_descriptor([["enemy_id"]]),
ServerInterface.Opcodes.ITEM_CRAFT_OK:
PacketBundle.create_packet_descriptor([["slot", PacketBundle.FieldType.INT8],
["item_id"]]),
ServerInterface.Opcodes.ITEM_CRAFT_NOK: [],
ServerInterface.Opcodes.SMELTER_STARTED: [],
ServerInterface.Opcodes.SMELTER_STOPPED: [],
ServerInterface.Opcodes.INVENTORY_SLOT_UPDATE:
PacketBundle.create_packet_descriptor([
["slot", PacketBundle.FieldType.INT8],
["item_id"],
["amount", PacketBundle.FieldType.INT8]]),
}
return server_to_client_packet_descriptor
const INT8_SIZE = 1
const INT64_SIZE = 8
class PacketBundle extends Object:
const COMPRESSION_MODE = 1
var buffer : PoolByteArray
var itr = 0
# this allows automatic serialization/descerialization
enum FieldType {INT8 = 0, INT16, INT32, INT64 = 3, ENTITY_ID = 3, FLOAT, VECTOR2, SHORT_STRING, LONG_STRING}
func _serialize_packet(data, op_code, input_packet_descriptor):
var descriptor = input_packet_descriptor[op_code]
for field in descriptor:
var field_name = field["name"]
match field["type"]:
FieldType.INT8:
serialize_int_8(data[field_name])
FieldType.INT64:
serialize_int_64(data[field_name])
FieldType.FLOAT:
serialize_float(data[field_name])
FieldType.SHORT_STRING:
serialize_string(data[field_name])
FieldType.VECTOR2:
serialize_vec2(data[field_name])
_:
assert(false)
func _deserialize_packet(op_code, output_packet_descriptor) -> Dictionary:
var descriptor = output_packet_descriptor[op_code]
var data = { "op_code" : op_code }
for field in descriptor:
var field_name = field["name"]
match field["type"]:
FieldType.INT8:
data[field_name] = deserialize_int_8()
FieldType.INT64:
data[field_name] = deserialize_int_64()
FieldType.FLOAT:
data[field_name] = deserialize_float()
FieldType.VECTOR2:
data[field_name] = deserialize_vec2()
FieldType.SHORT_STRING:
data[field_name] = deserialize_string()
_:
assert(false)
return data
func _init():
buffer = PoolByteArray()
func serialize_int_64(x : int):
for i in range(INT64_SIZE):
buffer.append((x >> (8 * i)) & 0xff)
# max length 255
func serialize_string(x : String):
var x_utf = x.to_utf8()
serialize_int_8(x_utf.size())
buffer.append_array(x_utf)
func serialize_float(x : float):
# multiply x by power of 2. later when its cast to int
# we retain some of the decimal places
var x_int = int(x * pow(2, 10))
serialize_int_64(x_int)
func deserialize_float():
var x_int = deserialize_int_64()
return float(x_int) / pow(2, 10)
func serialize_vec2(x : Vector2):
serialize_float(x.x)
serialize_float(x.y)
func deserialize_vec2():
var x = deserialize_float()
var y = deserialize_float()
return Vector2(x, y)
func compress():
buffer = buffer.compress(COMPRESSION_MODE)
func decompress(original_size : int):
var new_buffer = buffer.decompress(original_size, COMPRESSION_MODE)
buffer = new_buffer
func serialize_int_8(x : int):
buffer.append(x & 0xff)
func deserialize_int_8():
var res = int(buffer[itr])
itr += 1
return res
func deserialize_int_64() -> int:
var res = 0
for i in range(INT64_SIZE):
res += buffer[itr + i] << (i * INT64_SIZE)
itr += INT64_SIZE
return res
func deserialize_string() -> String:
var string_length = deserialize_int_8()
var content = buffer.subarray(itr, itr+string_length -1).get_string_from_utf8()
itr += string_length
return content
func serialize_packet_into_bundle(data, packet_descriptor = {}):
# Opcodes that dont have payload dont need to be serialized further
var op_code = data["op_code"]
serialize_int_8(data["op_code"])
match op_code:
# space for custom packet serialization
_:
_serialize_packet(data, op_code, packet_descriptor)
func serialize_packets(packets : Array, packet_descriptor):
for packet in packets:
serialize_packet_into_bundle(packet, packet_descriptor)
func deserialize_packet_from_bundle(packet_descriptor = {}) -> Dictionary:
if (itr + INT8_SIZE) > buffer.size():
return {}
var op_code = deserialize_int_8()
var data
match op_code:
# space for custom packet deserialization
_:
data = _deserialize_packet(op_code, packet_descriptor)
data["op_code"] = op_code
return data
func deserialize_packets(packet_descriptor) -> Array:
var packets = []
var has_packets = true
while has_packets:
var res = deserialize_packet_from_bundle(packet_descriptor)
if res.empty():
has_packets = false
break
packets.append(res)
return packets
static func create_packet_descriptor(fields = []):
var field_dicts = []
for field in fields:
var type = PacketBundle.FieldType.ENTITY_ID
if ((field as Array).size() > 1):
type = field[1]
field_dicts.append({"name" : field[0], "type":type})
return field_dicts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment