diff --git a/addons/matcha/MatchaPeer.gd b/addons/matcha/MatchaPeer.gd index f628cb4..0835be6 100644 --- a/addons/matcha/MatchaPeer.gd +++ b/addons/matcha/MatchaPeer.gd @@ -22,6 +22,8 @@ var _answered := false var _type: String var _local_sdp: String var _remote_sdp: String +var _event_channel: WebRTCDataChannel +var _event_listener := {} # We store event callback functions in here var is_connected: get: return _state == State.CONNECTED @@ -56,10 +58,10 @@ func _init(type: String, offer_id: String, remote_sdp=""): session_description_created.connect(self._on_session_description_created) ice_candidate_created.connect(self._on_ice_candidate_created) - var err := initialize({"iceServers":[{"urls":["stun:stun.l.google.com:19302"]}]}) - if err != OK: + if initialize({"iceServers":[{"urls":["stun:stun.l.google.com:19302"]}]}) != OK: push_error("Initializing failed") _state = State.CLOSED + _event_channel = create_data_channel("events", {"id": 555, "negotiated": true}) # Public methods func start() -> Error: @@ -116,8 +118,33 @@ func mark_as_announced() -> Error: _announced = true return Error.OK +# Allows you to send an event. +func send_event(event_name: String, event_args:=[]) -> Error: + var pack_array = [event_name] + if event_args.size() > 0: + pack_array.append(event_args) + return _event_channel.put_packet(Seriously.pack_to_bytes(pack_array)) + +# Allows you to listen to an event just one time. If the event was triggered the listener is removed. +func once_event(event_name: String, callback: Callable) -> Callable: + return on_event(event_name, callback, true) + +# Allows you to listen to an event. The return function can be used to remove that listener. +func on_event(event_name: String, callback: Callable, once:=false) -> Callable: + if not event_name in _event_listener: _event_listener[event_name] = [] + + var listener = [callback, once] + _event_listener[event_name].append(listener) + + return off_event.bind(event_name, callback, once) + +# Unregister an listener on an event +func off_event(event_name: String, callback: Callable, once:=false) -> void: + if not event_name in _event_listener: return + _event_listener[event_name] = _event_listener[event_name].filter(func(e): return e[0] != callback and e[1] != once) + # Private methods -func __poll(): +func __poll() -> void: if _state == State.NEW or _state == State.CLOSED: return poll() @@ -146,7 +173,17 @@ func __poll(): __close() return -func __close(): + # Read all event packets + while _event_channel.get_available_packet_count(): + var buffer := _event_channel.get_packet() + var args = Seriously.unpack_from_bytes(buffer) + if typeof(args) != TYPE_ARRAY or args.size() < 1 or typeof(args[0]) != TYPE_STRING: + continue + if args.size() == 2 and typeof(args[1]) != TYPE_ARRAY: + continue + _emit_event.callv(args) + +func __close() -> void: if _state == State.CLOSED: return @@ -160,10 +197,26 @@ func __close(): _state = State.CLOSED closed.emit() +# Handle an event +func _emit_event(event_name: String, event_args:=[]) -> void: + if not event_name in _event_listener: return + + # Remove null instance callbacks + _event_listener[event_name] = _event_listener[event_name].filter(func(e): return e[0].get_object() != null) + + for listener in _event_listener[event_name]: + listener[0].callv(event_args) + + # Remove once listeners + _event_listener[event_name] = _event_listener[event_name].filter(func(e): return not e[1]) + + if _event_listener[event_name].size() == 0: + _event_listener.erase(event_name) + # Callbacks -func _on_session_description_created(type: String, sdp: String): +func _on_session_description_created(type: String, sdp: String) -> void: _local_sdp = sdp set_local_description(type, sdp) -func _on_ice_candidate_created(media: String, index: int, name: String): +func _on_ice_candidate_created(media: String, index: int, name: String) -> void: _local_sdp += "a=%s\r\n" % [name] diff --git a/addons/matcha/MatchaRoom.gd b/addons/matcha/MatchaRoom.gd index 4b0f5d5..c76f841 100644 --- a/addons/matcha/MatchaRoom.gd +++ b/addons/matcha/MatchaRoom.gd @@ -127,6 +127,13 @@ func find_peer(filter:={}, allow_multiple_results:=false) -> MatchaPeer: if matches.size() == 0: return null return matches[0] +# Broadcast an event to everybody in this room or just specific peers. (List of peer_id) +func send_event(event_name: String, event_args:=[], target_peer_ids:=[]): + for peer: MatchaPeer in _peers: + if not peer.is_connected: continue + if target_peer_ids.size() > 0 and not target_peer_ids.has(peer.peer_id): continue + peer.send_event(event_name, event_args) + # Private methods func __poll(): poll() diff --git a/addons/matcha/lib/Seriously.gd b/addons/matcha/lib/Seriously.gd new file mode 100644 index 0000000..6b91789 --- /dev/null +++ b/addons/matcha/lib/Seriously.gd @@ -0,0 +1,595 @@ +# Source: https://github.com/freehuntx/godot-seriously +class_name Seriously extends RefCounted + +const OBJECT_AS_DICT := false # Should objects be serialized as dict? (more performance?) +const INT_MAX = 9223372036854775807 +const INT_MIN = -9223372036854775808 + +enum CustomTypes { + # Ensure the values dont overlap with an existing TYPE_* + UINT8 = 50, + INT8 = 51, + UINT16 = 52, + INT16 = 53, + UINT32 = 54, + INT32 = 55, + UINT64 = 56, + INT64 = 57, + TYPED_ARRAY = 58 +} + +static var _serializer = { + TYPE_NIL: { + "pack": func(_value, stream: StreamPeerBuffer) -> StreamPeerBuffer: + return stream, + "unpack": func(_stream: StreamPeerBuffer): + return null, + }, + TYPE_BOOL: { + "pack": func(value, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_u8(1 if value else 0) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> bool: + return stream.get_u8() > 0, + }, + TYPE_INT: { + "pack": func(value: int, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_32(value) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> int: + return stream.get_32(), + }, + CustomTypes.UINT8: { + "pack": func(value: int, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_u8(value) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> int: + return stream.get_u8(), + }, + CustomTypes.INT8: { + "pack": func(value: int, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_8(value) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> int: + return stream.get_8(), + }, + CustomTypes.UINT16: { + "pack": func(value: int, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_u16(value) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> int: + return stream.get_u16(), + }, + CustomTypes.INT16: { + "pack": func(value: int, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_16(value) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> int: + return stream.get_16(), + }, + CustomTypes.UINT32: { + "pack": func(value: int, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_u32(value) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> int: + return stream.get_u32(), + }, + CustomTypes.INT32: { + "pack": func(value: int, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_32(value) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> int: + return stream.get_32(), + }, + CustomTypes.UINT64: { + "pack": func(value: int, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_u64(value) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> int: + return stream.get_u64(), + }, + CustomTypes.INT64: { + "pack": func(value: int, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_64(value) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> int: + return stream.get_64(), + }, + TYPE_FLOAT: { + "pack": func(value: float, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_double(value) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> float: + return stream.get_double(), + }, + TYPE_STRING: { + "pack": func(value: String, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_u16(value.length()) + stream.put_data(value.to_utf8_buffer()) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> String: + return stream.get_utf8_string(stream.get_u16()), + }, + TYPE_VECTOR2: { + "pack": func(value: Vector2, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_float(value.x) + stream.put_float(value.y) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Vector2: + return Vector2(stream.get_float(), stream.get_float()), + }, + TYPE_VECTOR2I: { + "pack": func(value: Vector2i, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_32(value.x) + stream.put_32(value.y) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Vector2i: + return Vector2i(stream.get_32(), stream.get_32()), + }, + TYPE_RECT2: { + "pack": func(value: Rect2, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_float(value.position.x) + stream.put_float(value.position.y) + stream.put_float(value.size.x) + stream.put_float(value.size.y) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Rect2: + return Rect2(stream.get_float(), stream.get_float(), stream.get_float(), stream.get_float()), + }, + TYPE_RECT2I: { + "pack": func(value: Rect2i, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_32(value.position.x) + stream.put_32(value.position.y) + stream.put_32(value.size.x) + stream.put_32(value.size.y) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Rect2i: + return Rect2i(stream.get_32(), stream.get_32(), stream.get_32(), stream.get_32()), + }, + TYPE_VECTOR3: { + "pack": func(value: Vector3, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_float(value.x) + stream.put_float(value.y) + stream.put_float(value.z) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Vector3: + return Vector3(stream.get_float(), stream.get_float(), stream.get_float()), + }, + TYPE_VECTOR3I: { + "pack": func(value: Vector3i, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_32(value.x) + stream.put_32(value.y) + stream.put_32(value.z) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Vector3i: + return Vector3i(stream.get_32(), stream.get_32(), stream.get_32()), + }, + TYPE_TRANSFORM2D: { + "pack": func(value: Transform2D, stream: StreamPeerBuffer) -> StreamPeerBuffer: + pack(value.x, stream, TYPE_VECTOR2) + pack(value.y, stream, TYPE_VECTOR2) + pack(value.origin, stream, TYPE_VECTOR2) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Transform2D: + return Transform2D(unpack(stream, TYPE_VECTOR2), unpack(stream, TYPE_VECTOR2), unpack(stream, TYPE_VECTOR2)), + }, + TYPE_VECTOR4: { + "pack": func(value: Vector4, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_float(value.x) + stream.put_float(value.y) + stream.put_float(value.z) + stream.put_float(value.w) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Vector4: + return Vector4(stream.get_float(), stream.get_float(), stream.get_float(), stream.get_float()), + }, + TYPE_VECTOR4I: { + "pack": func(value: Vector4i, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_32(value.x) + stream.put_32(value.y) + stream.put_32(value.z) + stream.put_32(value.w) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Vector4i: + return Vector4i(stream.get_32(), stream.get_32(), stream.get_32(), stream.get_32()), + }, + TYPE_PLANE: { + "pack": func(value: Plane, stream: StreamPeerBuffer) -> StreamPeerBuffer: + pack(value.normal, stream, TYPE_VECTOR3) + stream.put_float(value.d) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Plane: + return Plane(unpack(stream, TYPE_VECTOR3), stream.get_float()), + }, + TYPE_QUATERNION: { + "pack": func(value: Quaternion, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_float(value.x) + stream.put_float(value.y) + stream.put_float(value.z) + stream.put_float(value.w) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Quaternion: + return Quaternion(stream.get_float(), stream.get_float(), stream.get_float(), stream.get_float()), + }, + TYPE_AABB: { + "pack": func(value: AABB, stream: StreamPeerBuffer) -> StreamPeerBuffer: + pack(value.position, stream, TYPE_VECTOR3) + pack(value.size, stream, TYPE_VECTOR3) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> AABB: + return AABB(unpack(stream, TYPE_VECTOR3), unpack(stream, TYPE_VECTOR3)), + }, + TYPE_BASIS: { + "pack": func(value: Basis, stream: StreamPeerBuffer) -> StreamPeerBuffer: + pack(value.x, stream, TYPE_VECTOR3) + pack(value.y, stream, TYPE_VECTOR3) + pack(value.z, stream, TYPE_VECTOR3) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Basis: + return Basis(unpack(stream, TYPE_VECTOR3), unpack(stream, TYPE_VECTOR3), unpack(stream, TYPE_VECTOR3)), + }, + TYPE_TRANSFORM3D: { + "pack": func(value: Transform3D, stream: StreamPeerBuffer) -> StreamPeerBuffer: + pack(value.basis, stream, TYPE_BASIS) + pack(value.origin, stream, TYPE_VECTOR3) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Transform3D: + return Transform3D(unpack(stream, TYPE_BASIS), unpack(stream, TYPE_VECTOR3)), + }, + TYPE_PROJECTION: { + "pack": func(value: Projection, stream: StreamPeerBuffer) -> StreamPeerBuffer: + pack(value.x, stream, TYPE_VECTOR4) + pack(value.y, stream, TYPE_VECTOR4) + pack(value.z, stream, TYPE_VECTOR4) + pack(value.w, stream, TYPE_VECTOR4) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Projection: + return Projection(unpack(stream, TYPE_VECTOR4), unpack(stream, TYPE_VECTOR4), unpack(stream, TYPE_VECTOR4), unpack(stream, TYPE_VECTOR4)), + }, + TYPE_COLOR: { + "pack": func(value: Color, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_u32(value.to_rgba32()) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Color: + return Color(stream.get_u32()), + }, + TYPE_STRING_NAME: { + "pack": func(value: StringName, stream: StreamPeerBuffer) -> StreamPeerBuffer: + pack(str(value), stream, TYPE_STRING) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> StringName: + return StringName(unpack(stream, TYPE_STRING)), + }, + TYPE_NODE_PATH: { + "pack": func(value: NodePath, stream: StreamPeerBuffer) -> StreamPeerBuffer: + pack(str(value), stream, TYPE_STRING) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> NodePath: + return NodePath(unpack(stream, TYPE_STRING)), + }, + TYPE_RID: { + "pack": func(_value: RID, _stream: StreamPeerBuffer) -> StreamPeerBuffer: + push_error("[Seriously] TYPE_RID is not supported") + return null, + "unpack": func(_stream: StreamPeerBuffer): + push_error("[Seriously] TYPE_RID is not supported") + return null, + }, + TYPE_OBJECT: { + "pack": func(value: Object, stream: StreamPeerBuffer) -> StreamPeerBuffer: + var prop_names = value.get_property_list().filter(func(p): return p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE).map(func(p): return p.name) + + stream.put_u16(prop_names.size()) + + for name in prop_names: + pack(name, stream, TYPE_STRING) + pack(value.get(name), stream) + + return stream, + "unpack": func(stream: StreamPeerBuffer): + var object_size := stream.get_u16() + var dict := {} + + for j in object_size: + var name = unpack(stream, TYPE_STRING) + dict[name] = unpack(stream) + + if OBJECT_AS_DICT: + return dict + + # Create dynamic object (bad performance?) + var source_code := "extends RefCounted\n" + + for name in dict.keys(): + source_code += "var %s\n" % [name] + + var dynamic_object := GDScript.new() + dynamic_object.source_code = source_code + dynamic_object.reload() + + var object = dynamic_object.new() + for name in dict.keys(): + object.set(name, dict[name]) + + return object, + }, + TYPE_CALLABLE: { + "pack": func(_value: Callable, _stream: StreamPeerBuffer) -> StreamPeerBuffer: + push_error("[Seriously] TYPE_CALLABLE type pack requested. This is not possible!") + return null, + "unpack": func(_stream: StreamPeerBuffer): + push_error("[Seriously] TYPE_CALLABLE type unpack requested. This is not possible!") + return null, + }, + TYPE_SIGNAL: { + "pack": func(_value: Signal, _stream: StreamPeerBuffer) -> StreamPeerBuffer: + push_error("[Seriously] TYPE_SIGNAL type pack requested. This is not possible!") + return null, + "unpack": func(_stream: StreamPeerBuffer): + push_error("[Seriously] TYPE_SIGNAL type unpack requested. This is not possible!") + return null, + }, + TYPE_DICTIONARY: { + "pack": func(value: Dictionary, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_u16(value.size()) + + for key in value.keys(): + pack(key, stream, TYPE_STRING) + pack(value[key], stream) + + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Dictionary: + var dictionary_size = stream.get_u16() + var dictionary = {} + + for j in dictionary_size: + var name = unpack(stream, TYPE_STRING) + dictionary[name] = unpack(stream) + + return dictionary, + }, + TYPE_ARRAY: { + "pack": func(value: Array, stream: StreamPeerBuffer) -> StreamPeerBuffer: + var array_size := value.size() + + stream.put_u16(array_size) + + for i in array_size: + pack(value[i], stream) + + return stream, + "unpack": func(stream: StreamPeerBuffer) -> Array: + var array_size = stream.get_u16() + var array = [] + + for i in array_size: + array.append(unpack(stream)) + + return array, + }, + TYPE_PACKED_BYTE_ARRAY: { + "pack": func(value: PackedByteArray, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_u16(value.size()) + stream.put_data(value) + return stream, + "unpack": func(stream: StreamPeerBuffer) -> PackedByteArray: + var array_size := stream.get_u16() + var data = stream.get_data(array_size)[1] + return PackedByteArray(data), + }, + TYPE_PACKED_INT32_ARRAY: { + "pack": func(value: PackedInt32Array, stream: StreamPeerBuffer) -> StreamPeerBuffer: + var buffer = value.to_byte_array() + + stream.put_u16(buffer.size()) + stream.put_data(buffer) + + return stream, + "unpack": func(stream: StreamPeerBuffer) -> PackedInt32Array: + return PackedByteArray(stream.get_data(stream.get_u16())).to_int32_array(), + }, + TYPE_PACKED_INT64_ARRAY: { + "pack": func(value: PackedInt64Array, stream: StreamPeerBuffer) -> StreamPeerBuffer: + var buffer = value.to_byte_array() + + stream.put_u16(buffer.size()) + stream.put_data(buffer) + + return stream, + "unpack": func(stream: StreamPeerBuffer) -> PackedInt64Array: + return PackedByteArray(stream.get_data(stream.get_u16())).to_int64_array(), + }, + TYPE_PACKED_FLOAT32_ARRAY: { + "pack": func(value: PackedFloat32Array, stream: StreamPeerBuffer) -> StreamPeerBuffer: + var buffer = value.to_byte_array() + + stream.put_u16(buffer.size()) + stream.put_data(buffer) + + return stream, + "unpack": func(stream: StreamPeerBuffer) -> PackedFloat32Array: + return PackedByteArray(stream.get_data(stream.get_u16())).to_float32_array(), + }, + TYPE_PACKED_FLOAT64_ARRAY: { + "pack": func(value: PackedFloat64Array, stream: StreamPeerBuffer) -> StreamPeerBuffer: + var buffer = value.to_byte_array() + + stream.put_u16(buffer.size()) + stream.put_data(buffer) + + return stream, + "unpack": func(stream: StreamPeerBuffer) -> PackedFloat64Array: + return PackedByteArray(stream.get_data(stream.get_u16())).to_float64_array(), + }, + TYPE_PACKED_STRING_ARRAY: { + "pack": func(value: PackedStringArray, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_u16(value.size()) + + for string in value: + pack(string, stream, TYPE_STRING) + + return stream, + "unpack": func(stream: StreamPeerBuffer) -> PackedStringArray: + var array_size := stream.get_u16() + var array := PackedStringArray() + + for i in array_size: + array.append(unpack(stream, TYPE_STRING)) + + return array, + }, + TYPE_PACKED_VECTOR2_ARRAY: { + "pack": func(value: PackedVector2Array, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_u16(value.size()) + + for vector in value: + pack(vector, stream, TYPE_VECTOR2) + + return stream, + "unpack": func(stream: StreamPeerBuffer) -> PackedVector2Array: + var array_size := stream.get_u16() + var array := PackedVector2Array() + + for i in array_size: + array.append(unpack(stream, TYPE_VECTOR2)) + + return array, + }, + TYPE_PACKED_VECTOR3_ARRAY: { + "pack": func(value: PackedVector3Array, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_u16(value.size()) + + for vector in value: + pack(vector, stream, TYPE_VECTOR3) + + return stream, + "unpack": func(stream: StreamPeerBuffer) -> PackedVector3Array: + var array_size := stream.get_u16() + var array := PackedVector3Array() + + for i in array_size: + array.append(unpack(stream, TYPE_VECTOR3)) + + return array, + }, + TYPE_PACKED_COLOR_ARRAY: { + "pack": func(value: PackedColorArray, stream: StreamPeerBuffer) -> StreamPeerBuffer: + stream.put_u16(value.size()) + + for vector in value: + pack(vector, stream, TYPE_COLOR) + + return stream, + "unpack": func(stream: StreamPeerBuffer) -> PackedColorArray: + var array_size := stream.get_u16() + var array := PackedColorArray() + + for i in array_size: + array.append(unpack(stream, TYPE_COLOR)) + + return array, + }, + CustomTypes.TYPED_ARRAY: { + "pack": func(array: Array, stream: StreamPeerBuffer) -> StreamPeerBuffer: + var item_type: int = TYPE_NIL + + if _can_array_be_typed(array): + item_type = typeof(array[0]) + + stream.put_u16(array.size()) + stream.put_u8(item_type) + + for item in array: + pack(item, stream, item_type) + + return stream, + "unpack": func(stream: StreamPeerBuffer): + var array_size = stream.get_u16() + var item_type = stream.get_u8() + + if item_type == TYPE_NIL: + return null + + var array = [] + for i in array_size: + array.append(unpack(stream, item_type)) + + return array, + } +} + +static func pack_to_bytes(value) -> PackedByteArray: + return pack(value).data_array + +static func unpack_from_bytes(bytes: PackedByteArray): + var stream := StreamPeerBuffer.new() + stream.data_array = bytes + return unpack(stream) + +static func pack(value, stream:=StreamPeerBuffer.new(), type:=-1, add_type_prefix:=false) -> StreamPeerBuffer: + add_type_prefix = add_type_prefix or type == -1 # If type is unknown we add a type prefix to identify it + + if type == -1: + type = typeof(value) # Check the generic type of the value + + # If the type is an array, lets try to make it typed (to save data) + if type == TYPE_ARRAY and _can_array_be_typed(value): + type = CustomTypes.TYPED_ARRAY + if type == TYPE_INT: + type = _get_int_type(value) + if type == TYPE_OBJECT and value == null: + type = TYPE_NIL + + if not type in _serializer: + push_error("[Seriously] Unknown type: ", type) + return null + + if add_type_prefix: + stream.put_u8(type) + + return _serializer[type].pack.call(value, stream) + +static func unpack(stream: StreamPeerBuffer, type:=-1): + if type == -1: # If we dont define a type we try to read a type prefix + type = stream.get_u8() + + if not type in _serializer: + push_error("[Seriously] Unknown type: ", type) + return null + + return _serializer[type].unpack.call(stream) + +# Private methods +static func _can_array_be_typed(array: Array) -> bool: + if array.size() == 0: # Typing empty arrays makes no sense + return false + if array.size() == 1: + return true # If an array has just 1 entry thats 1 type. So it CAN be typed + + var array_type: int = typeof(array[0]) # Lets check if all other entries have the same type + for entry in array: + if typeof(entry) != array_type: return false # This array cannot be typed because there is a mismatch + + return true + +static func _get_int_type(value: int) -> int: + var unsigned = value >= 0 + var bit_size = 8 + if abs(value) <= 0xFF: + bit_size = 8 + elif abs(value) <= 0xFFFF: + bit_size = 16 + elif abs(value) <= 0xFFFFFFFF: + bit_size = 32 + elif value >= INT_MIN and value <= INT_MAX: + bit_size = 64 + else: + push_error("[Seriously] Unsupported integer: ", value) + return -1 + + var type_name := "%sINT%s" % ["U" if unsigned else "", bit_size] + if not type_name in CustomTypes: + push_error("[Seriously] Unsupported integer: ", value) + return -1 + + return CustomTypes[type_name] diff --git a/examples/bobble/bobble.gd b/examples/bobble/bobble.gd index dbb820b..8067708 100644 --- a/examples/bobble/bobble.gd +++ b/examples/bobble/bobble.gd @@ -2,34 +2,64 @@ extends Node2D const PlayerComponent := preload("./components/player.tscn") var matcha_room := MatchaRoom.create_mesh_room() +var players := {} var local_player: get: - if not $Players.has_node(matcha_room.peer_id): return null - return $Players.get_node(matcha_room.peer_id) + if not matcha_room.peer_id in players: return null + return players[matcha_room.peer_id] + +func _init(): + matcha_room.peer_joined.connect(self._on_peer_joined) + matcha_room.peer_left.connect(self._on_peer_left) func _enter_tree(): multiplayer.multiplayer_peer = matcha_room func _ready(): - _create_player(matcha_room.peer_id, multiplayer.get_unique_id()) - - matcha_room.peer_joined.connect(func(id: int, peer: MatchaPeer): - _create_player(peer.peer_id, id) - ) - matcha_room.peer_left.connect(func(_id: int, peer: MatchaPeer): - if $Players.has_node(peer.peer_id): # Remove the player if it exists - $Players.remove_child($Players.get_node(peer.peer_id)) - ) + _add_player(matcha_room.peer_id, multiplayer.get_unique_id()) # Add ourself -func _create_player(peer_id: String, authority_id: int): - if $Players.has_node(peer_id): return # That peer is already known +func _add_player(peer_id: String, authority_id: int) -> void: + if peer_id in players: return # That peer is already known var node := PlayerComponent.instantiate() node.name = peer_id # The node must have the same name for every person. Otherwise syncing it will fail because path mismatch node.position = Vector2(100, 100) + players[peer_id] = node $Players.add_child(node) node.set_multiplayer_authority(authority_id) -func _on_line_edit_text_submitted(new_text): - $UI/LineEdit.text = "" +func _remove_player(peer_id: String) -> void: + if not peer_id in players: return # That peer is not known + $Players.remove_child($Players.get_node(peer_id)) + +# Peer callbacks +func _on_peer_joined(id: int, peer: MatchaPeer) -> void: + # Listen to events the other peer may send + peer.on_event("chat", self._on_peer_chat.bind(peer)) + peer.on_event("secret", self._on_peer_secret.bind(peer)) + _add_player(peer.peer_id, id) # Create the player + +func _on_peer_left(_id: int, peer: MatchaPeer) -> void: + _remove_player(peer.peer_id) + +func _on_peer_chat(message: String, peer: MatchaPeer) -> void: + $UI/chat_history.text += "\n%s: %s" % [peer.peer_id, message] + players[peer.peer_id].set_message(message) + +func _on_peer_secret(peer: MatchaPeer) -> void: + var sprite: Sprite2D = players[peer.peer_id].get_node("Sprite2D") + sprite.modulate = Color.from_hsv((randi() % 12) / 12.0, 1, 1) + +# UI Callbacks +func _on_line_edit_text_submitted(new_text) -> void: + if new_text == "": return + $UI/chat_input.text = "" + $UI/chat_history.text += "\n%s (Me): %s" % [matcha_room.peer_id, new_text] local_player.set_message(new_text) + matcha_room.send_event("chat", [new_text]) + +func _on_secret_button_pressed() -> void: + matcha_room.send_event("secret") + +func _on_chat_send_pressed() -> void: + _on_line_edit_text_submitted($UI/chat_input.text) diff --git a/examples/bobble/bobble.tscn b/examples/bobble/bobble.tscn index 6fac381..ccd4854 100644 --- a/examples/bobble/bobble.tscn +++ b/examples/bobble/bobble.tscn @@ -9,18 +9,47 @@ script = ExtResource("1_67bh4") layout_mode = 3 anchors_preset = 0 offset_left = -1.0 -offset_top = 577.0 -offset_right = 1152.0 -offset_bottom = 650.0 +offset_top = -2.0 +offset_right = 1196.0 +offset_bottom = 800.0 -[node name="LineEdit" type="LineEdit" parent="UI"] +[node name="chat_history" type="RichTextLabel" parent="UI"] +offset_left = 20.0 +offset_top = 597.0 +offset_right = 1024.0 +offset_bottom = 726.0 +text = " + +" +scroll_following = true + +[node name="chat_input" type="LineEdit" parent="UI"] layout_mode = 0 -offset_left = 63.0 -offset_top = 10.0 -offset_right = 1071.0 -offset_bottom = 54.0 +offset_left = 18.0 +offset_top = 734.0 +offset_right = 1109.0 +offset_bottom = 778.0 placeholder_text = "Type a chat message..." +[node name="secret_button" type="Button" parent="UI"] +layout_mode = 0 +offset_left = 1043.0 +offset_top = 622.0 +offset_right = 1186.0 +offset_bottom = 716.0 +text = "Secret button +What will it do? :)" + +[node name="chat_send" type="Button" parent="UI"] +layout_mode = 0 +offset_left = 1116.0 +offset_top = 734.0 +offset_right = 1186.0 +offset_bottom = 778.0 +text = "Send" + [node name="Players" type="Node2D" parent="."] -[connection signal="text_submitted" from="UI/LineEdit" to="." method="_on_line_edit_text_submitted"] +[connection signal="text_submitted" from="UI/chat_input" to="." method="_on_line_edit_text_submitted"] +[connection signal="pressed" from="UI/secret_button" to="." method="_on_secret_button_pressed"] +[connection signal="pressed" from="UI/chat_send" to="." method="_on_chat_send_pressed"] diff --git a/examples/bobble/components/player.gd b/examples/bobble/components/player.gd index 8836e68..f8f9fed 100644 --- a/examples/bobble/components/player.gd +++ b/examples/bobble/components/player.gd @@ -4,7 +4,6 @@ const SPEED = 300.0 var chat_message_time: float func set_message(message: String) -> void: - if get_multiplayer_authority() != multiplayer.get_unique_id(): return $Label.text = message chat_message_time = Time.get_unix_time_from_system()