diff options
Diffstat (limited to 'addons/gdUnit4/src/network')
23 files changed, 527 insertions, 0 deletions
diff --git a/addons/gdUnit4/src/network/GdUnitServer.gd b/addons/gdUnit4/src/network/GdUnitServer.gd new file mode 100644 index 0000000..4ce2f48 --- /dev/null +++ b/addons/gdUnit4/src/network/GdUnitServer.gd @@ -0,0 +1,41 @@ +@tool +extends Node + +var _client_id: int + +@onready var _server: GdUnitTcpServer = $TcpServer + + +@warning_ignore("return_value_discarded") +func _ready() -> void: + var result := _server.start() + if result.is_error(): + push_error(result.error_message()) + return + var server_port :int = result.value() + Engine.set_meta("gdunit_server_port", server_port) + _server.client_connected.connect(_on_client_connected) + _server.client_disconnected.connect(_on_client_disconnected) + _server.rpc_data.connect(_receive_rpc_data) + + +func _on_client_connected(client_id: int) -> void: + _client_id = client_id + GdUnitSignals.instance().gdunit_client_connected.emit(client_id) + + +func _on_client_disconnected(client_id: int) -> void: + GdUnitSignals.instance().gdunit_client_disconnected.emit(client_id) + + +func _receive_rpc_data(p_rpc: RPC) -> void: + if p_rpc is RPCMessage: + var rpc_message: RPCMessage = p_rpc + GdUnitSignals.instance().gdunit_message.emit(rpc_message.message()) + return + if p_rpc is RPCGdUnitEvent: + var rpc_event: RPCGdUnitEvent = p_rpc + var event := rpc_event.event() + GdUnitSignals.instance().gdunit_event.emit(event) + if event.type() == GdUnitEvent.SESSION_CLOSE and _server != null: + _server.disconnect_client(_client_id) diff --git a/addons/gdUnit4/src/network/GdUnitServer.gd.uid b/addons/gdUnit4/src/network/GdUnitServer.gd.uid new file mode 100644 index 0000000..b44864e --- /dev/null +++ b/addons/gdUnit4/src/network/GdUnitServer.gd.uid @@ -0,0 +1 @@ +uid://cu75icxofcpxv diff --git a/addons/gdUnit4/src/network/GdUnitServer.tscn b/addons/gdUnit4/src/network/GdUnitServer.tscn new file mode 100644 index 0000000..4dbe8c4 --- /dev/null +++ b/addons/gdUnit4/src/network/GdUnitServer.tscn @@ -0,0 +1,10 @@ +[gd_scene load_steps=3 format=3 uid="uid://cn5mp3tmi2gb1"] + +[ext_resource type="Script" path="res://addons/gdUnit4/src/network/GdUnitServer.gd" id="1"] +[ext_resource type="Script" path="res://addons/gdUnit4/src/network/GdUnitTcpServer.gd" id="2"] + +[node name="Control" type="Node"] +script = ExtResource("1") + +[node name="TcpServer" type="Node" parent="."] +script = ExtResource("2") diff --git a/addons/gdUnit4/src/network/GdUnitServerConstants.gd b/addons/gdUnit4/src/network/GdUnitServerConstants.gd new file mode 100644 index 0000000..d31eee7 --- /dev/null +++ b/addons/gdUnit4/src/network/GdUnitServerConstants.gd @@ -0,0 +1,6 @@ +class_name GdUnitServerConstants +extends RefCounted + +const DEFAULT_SERVER_START_RETRY_TIMES :int = 5 +const GD_TEST_SERVER_PORT :int = 31002 +const JSON_RESPONSE_DELIMITER :String = "<<JRD>>" diff --git a/addons/gdUnit4/src/network/GdUnitServerConstants.gd.uid b/addons/gdUnit4/src/network/GdUnitServerConstants.gd.uid new file mode 100644 index 0000000..b847741 --- /dev/null +++ b/addons/gdUnit4/src/network/GdUnitServerConstants.gd.uid @@ -0,0 +1 @@ +uid://cg7ssosnfdrcn diff --git a/addons/gdUnit4/src/network/GdUnitTask.gd b/addons/gdUnit4/src/network/GdUnitTask.gd new file mode 100644 index 0000000..e0188a0 --- /dev/null +++ b/addons/gdUnit4/src/network/GdUnitTask.gd @@ -0,0 +1,25 @@ +class_name GdUnitTask +extends RefCounted + +const TASK_NAME = "task_name" +const TASK_ARGS = "task_args" + +var _task_name :String +var _fref :Callable + + +func _init(task_name :String,instance :Object,func_name :String) -> void: + _task_name = task_name + if not instance.has_method(func_name): + push_error("Can't create GdUnitTask, Invalid func name '%s' for instance '%s'" % [instance, func_name]) + _fref = Callable(instance, func_name) + + +func name() -> String: + return _task_name + + +func execute(args :Array) -> GdUnitResult: + if args.is_empty(): + return _fref.call() + return _fref.callv(args) diff --git a/addons/gdUnit4/src/network/GdUnitTask.gd.uid b/addons/gdUnit4/src/network/GdUnitTask.gd.uid new file mode 100644 index 0000000..99a1242 --- /dev/null +++ b/addons/gdUnit4/src/network/GdUnitTask.gd.uid @@ -0,0 +1 @@ +uid://dapl4oygj1lal diff --git a/addons/gdUnit4/src/network/GdUnitTcpClient.gd b/addons/gdUnit4/src/network/GdUnitTcpClient.gd new file mode 100644 index 0000000..d6c8a42 --- /dev/null +++ b/addons/gdUnit4/src/network/GdUnitTcpClient.gd @@ -0,0 +1,126 @@ +@tool +class_name GdUnitTcpClient +extends GdUnitTcpNode + +signal connection_succeeded(message: String) +signal connection_failed(message: String) + + +var _client_name: String +var _debug := false +var _host: String +var _port: int +var _client_id: int +var _connected: bool +var _stream: StreamPeerTCP + + +func _init(client_name := "GdUnit4 TCP Client", debug := false) -> void: + _client_name = client_name + _debug = debug + + +func _ready() -> void: + _connected = false + _stream = StreamPeerTCP.new() + #_stream.set_big_endian(true) + + +func stop() -> void: + console("Disconnecting from server") + if _stream != null: + rpc_send(_stream, RPCClientDisconnect.new().with_id(_client_id)) + if _stream != null: + _stream.disconnect_from_host() + _connected = false + + +func start(host: String, port: int) -> GdUnitResult: + _host = host + _port = port + if _connected: + return GdUnitResult.warn("Client already connected ... %s:%d" % [_host, _port]) + + # Connect client to server + if _stream.get_status() != StreamPeerTCP.STATUS_CONNECTED: + var err := _stream.connect_to_host(host, port) + #prints("connect_to_host", host, port, err) + if err != OK: + return GdUnitResult.error("GdUnit4: Can't establish client, error code: %s" % err) + return GdUnitResult.success("GdUnit4: Client connected checked port %d" % port) + + +func _process(_delta: float) -> void: + match _stream.get_status(): + StreamPeerTCP.STATUS_NONE: + return + + StreamPeerTCP.STATUS_CONNECTING: + set_process(false) + # wait until client is connected to server + for retry in 10: + @warning_ignore("return_value_discarded") + _stream.poll() + console("Waiting to connect ..") + if _stream.get_status() == StreamPeerTCP.STATUS_CONNECTING: + await get_tree().create_timer(0.500).timeout + if _stream.get_status() == StreamPeerTCP.STATUS_CONNECTED: + set_process(true) + return + set_process(true) + _stream.disconnect_from_host() + console("Connection failed") + connection_failed.emit("Connect to TCP Server %s:%d faild!" % [_host, _port]) + + StreamPeerTCP.STATUS_CONNECTED: + if not _connected: + var rpc_data :RPC = null + set_process(false) + while rpc_data == null: + await get_tree().create_timer(0.500).timeout + rpc_data = rpc_receive() + set_process(true) + _client_id = (rpc_data as RPCClientConnect).client_id() + console("Connected to Server: %d" % _client_id) + connection_succeeded.emit("Connect to TCP Server %s:%d success." % [_host, _port]) + _connected = true + process_rpc() + + StreamPeerTCP.STATUS_ERROR: + console("Connection failed") + _stream.disconnect_from_host() + connection_failed.emit("Connect to TCP Server %s:%d faild!" % [_host, _port]) + return + + +func is_client_connected() -> bool: + return _connected + + +func process_rpc() -> void: + if _stream.get_available_bytes() > 0: + var rpc_data := rpc_receive() + if rpc_data is RPCClientDisconnect: + console("RPCClientDisconnect") + GdUnitSignals.instance().gdunit_test_session_terminate.emit.call_deferred() + + +func send(data: RPC) -> void: + rpc_send(_stream, data) + + +func rpc_receive() -> RPC: + return receive_packages(_stream).front() + + +func console(value: Variant) -> void: + if _debug: + print(_client_name, ": ", value) + + +func _on_connection_failed(message: String) -> void: + console("Connection faild by: " + message) + + +func _on_connection_succeeded(message: String) -> void: + console("Connected: " + message) diff --git a/addons/gdUnit4/src/network/GdUnitTcpClient.gd.uid b/addons/gdUnit4/src/network/GdUnitTcpClient.gd.uid new file mode 100644 index 0000000..7166e61 --- /dev/null +++ b/addons/gdUnit4/src/network/GdUnitTcpClient.gd.uid @@ -0,0 +1 @@ +uid://bitvc4op4ulwh diff --git a/addons/gdUnit4/src/network/GdUnitTcpNode.gd b/addons/gdUnit4/src/network/GdUnitTcpNode.gd new file mode 100644 index 0000000..156c764 --- /dev/null +++ b/addons/gdUnit4/src/network/GdUnitTcpNode.gd @@ -0,0 +1,73 @@ +class_name GdUnitTcpNode +extends Node + + +func rpc_send(stream: StreamPeerTCP, data: RPC) -> void: + var package_buffer := StreamPeerBuffer.new() + var buffer := data.serialize().to_utf16_buffer() + package_buffer.put_u32(0xDEADBEEF) + package_buffer.put_u32(buffer.size()) + var status_code := package_buffer.put_data(buffer) + if status_code != OK: + push_error("'rpc_send:' Can't put_data(), error: %s" % error_string(status_code)) + return + stream.put_data(package_buffer.data_array) + + +func receive_packages(stream: StreamPeerTCP, rpc_cb: Callable = noop) -> Array[RPC]: + var received_packages: Array[RPC] = [] + var package_buffer := StreamPeerBuffer.new() + if stream.get_status() != StreamPeerTCP.STATUS_CONNECTED: + return received_packages + + while stream.get_status() == StreamPeerTCP.STATUS_CONNECTED and stream.get_available_bytes() > 0: + var buffer := stream.get_data(8) + var status_code: int = buffer[0] + if status_code != OK: + push_error("'receive_packages:' Can't get_data(%d) for available_bytes, error: %s" + % [stream.get_available_bytes(), error_string(status_code)]) + return received_packages + + var data_package: PackedByteArray + package_buffer.data_array = buffer[1] + package_buffer.seek(0) + + if package_buffer.get_u32() == 0xDEADBEEF: + var size := package_buffer.get_u32() + if stream.get_status() != StreamPeerTCP.STATUS_CONNECTED: + return received_packages + if stream.get_available_bytes() < size: + prints("size check:", + package_buffer.get_size(), ":", + package_buffer.get_position(), + "to read:", + size, + "available size:", + stream.get_available_bytes()) + push_error("'receive_packages:' Can't receive data get_data(%d) for package, error: %s" % [size, error_string(status_code)]) + return received_packages + + buffer = stream.get_data(size) + package_buffer.data_array = buffer[1] + + var rpc_data := package_buffer.get_data(size) + status_code = rpc_data[0] + if status_code != OK: + push_error("'receive_packages:' Can't get_data(%d) for package, error: %s" % [size, error_string(status_code)]) + continue + data_package = rpc_data[1] + else: + data_package = buffer[1] + + var json := data_package.get_string_from_utf16() + if json.is_empty(): + push_warning("json is empty, can't process data") + continue + var data := RPC.deserialize(json) + received_packages.append(data) + rpc_cb.call(data) + return received_packages + + +static func noop(_rpc_data: RPC) -> void: + pass diff --git a/addons/gdUnit4/src/network/GdUnitTcpNode.gd.uid b/addons/gdUnit4/src/network/GdUnitTcpNode.gd.uid new file mode 100644 index 0000000..4483fe2 --- /dev/null +++ b/addons/gdUnit4/src/network/GdUnitTcpNode.gd.uid @@ -0,0 +1 @@ +uid://c8bwg30yoifsu diff --git a/addons/gdUnit4/src/network/GdUnitTcpServer.gd b/addons/gdUnit4/src/network/GdUnitTcpServer.gd new file mode 100644 index 0000000..997d6e3 --- /dev/null +++ b/addons/gdUnit4/src/network/GdUnitTcpServer.gd @@ -0,0 +1,140 @@ +@tool +class_name GdUnitTcpServer +extends Node + +signal client_connected(client_id: int) +signal client_disconnected(client_id: int) +@warning_ignore("unused_signal") +signal rpc_data(rpc_data: RPC) + +var _server: TCPServer +var _server_name: String + +class TcpConnection extends GdUnitTcpNode: + var _id: int + var _stream: StreamPeerTCP + + + func _init(tcp_server: TCPServer) -> void: + _stream = tcp_server.take_connection() + #_stream.set_big_endian(true) + _id = _stream.get_instance_id() + rpc_send(_stream, RPCClientConnect.new().with_id(_id)) + + + func _ready() -> void: + server().client_connected.emit(_id) + + + func close() -> void: + if _stream != null and _stream.get_status() == StreamPeerTCP.STATUS_CONNECTED: + _stream.disconnect_from_host() + queue_free() + + + func id() -> int: + return _id + + + func server() -> GdUnitTcpServer: + return get_parent() + + + func _process(_delta: float) -> void: + if _stream == null or _stream.get_status() != StreamPeerTCP.STATUS_CONNECTED: + return + receive_packages(_stream, func(rpc_data: RPC) -> void: + server().rpc_data.emit(rpc_data) + # is client disconnecting we close the server after a timeout of 1 second + if rpc_data is RPCClientDisconnect: + close() + ) + + + func disconnect_from_server() -> void: + if _stream == null or _stream.get_status() != StreamPeerTCP.STATUS_CONNECTED: + return + rpc_send(_stream, RPCClientDisconnect.new().with_id(_id)) + + + func console(_value: Variant) -> void: + #print_debug("TCP Server: ", value) + pass + + +func _init(server_name := "GdUnit4 TCP Server") -> void: + _server_name = server_name + GdUnitSignals.instance().gdunit_test_session_terminate.connect(func() -> void: + for connection in get_children(): + if connection is TcpConnection: + (connection as TcpConnection).disconnect_from_server() + ) + + +func _ready() -> void: + _server = TCPServer.new() + client_connected.connect(_on_client_connected) + client_disconnected.connect(_on_client_disconnected) + + +func _notification(what: int) -> void: + if what == NOTIFICATION_PREDELETE: + stop() + + +func start(server_port := GdUnitServerConstants.GD_TEST_SERVER_PORT) -> GdUnitResult: + var err := OK + for retry in GdUnitServerConstants.DEFAULT_SERVER_START_RETRY_TIMES: + err = _server.listen(server_port, "127.0.0.1") + if err != OK: + prints("GdUnit4: Can't establish server checked port: %d, Error: %s" % [server_port, error_string(err)]) + server_port += 1 + prints("GdUnit4: Retry (%d) ..." % retry) + else: + break + if err != OK: + if err == ERR_ALREADY_IN_USE: + return GdUnitResult.error("GdUnit4: Can't establish server, the server is already in use. Error: %s, " % error_string(err)) + return GdUnitResult.error("GdUnit4: Can't establish server. Error: %s." % error_string(err)) + console("Successfully started checked port: %d" % server_port) + return GdUnitResult.success(server_port) + + +func stop() -> void: + if _server: + _server.stop() + for connection in get_children(): + if connection is TcpConnection: + @warning_ignore("unsafe_method_access") + connection.close() + remove_child(connection) + _server = null + + +func disconnect_client(client_id: int) -> void: + client_disconnected.emit(client_id) + + +func _process(_delta: float) -> void: + if _server != null and not _server.is_listening(): + return + # check if connection is ready to be used + if _server != null and _server.is_connection_available(): + add_child(TcpConnection.new(_server)) + + +func _on_client_connected(client_id: int) -> void: + console("Client connected %d" % client_id) + + +func _on_client_disconnected(client_id: int) -> void: + for connection in get_children(): + @warning_ignore("unsafe_method_access") + if connection is TcpConnection and connection.id() == client_id: + @warning_ignore("unsafe_method_access") + connection.close() + remove_child(connection) + + +func console(value: Variant) -> void: + print(_server_name, ": ", value) diff --git a/addons/gdUnit4/src/network/GdUnitTcpServer.gd.uid b/addons/gdUnit4/src/network/GdUnitTcpServer.gd.uid new file mode 100644 index 0000000..76f0fc0 --- /dev/null +++ b/addons/gdUnit4/src/network/GdUnitTcpServer.gd.uid @@ -0,0 +1 @@ +uid://csm58erfvsxld diff --git a/addons/gdUnit4/src/network/rpc/RPC.gd b/addons/gdUnit4/src/network/rpc/RPC.gd new file mode 100644 index 0000000..6569cb1 --- /dev/null +++ b/addons/gdUnit4/src/network/rpc/RPC.gd @@ -0,0 +1,37 @@ +class_name RPC +extends RefCounted + + +var _data: Dictionary = {} + + +func _init(obj: Object = null) -> void: + if obj != null: + if obj.has_method("serialize"): + _data = obj.call("serialize") + else: + _data = inst_to_dict(obj) + + +func get_data() -> Object: + return dict_to_inst(_data) + + +func serialize() -> String: + return JSON.stringify(inst_to_dict(self)) + + +# using untyped version see comments below +static func deserialize(json_value: String) -> Object: + var json := JSON.new() + var err := json.parse(json_value) + if err != OK: + push_error("Can't deserialize JSON, error at line %d:\n error: %s \n json: '%s'" + % [json.get_error_line(), json.get_error_message(), json_value]) + return null + var result: Dictionary = json.get_data() + if not typeof(result) == TYPE_DICTIONARY: + push_error("Can't deserialize JSON. Expecting dictionary, error at line %d:\n error: %s \n json: '%s'" + % [result.error_line, result.error_string, json_value]) + return null + return dict_to_inst(result) diff --git a/addons/gdUnit4/src/network/rpc/RPC.gd.uid b/addons/gdUnit4/src/network/rpc/RPC.gd.uid new file mode 100644 index 0000000..40c58dc --- /dev/null +++ b/addons/gdUnit4/src/network/rpc/RPC.gd.uid @@ -0,0 +1 @@ +uid://na86vw3ufqjo diff --git a/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd b/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd new file mode 100644 index 0000000..6b494cf --- /dev/null +++ b/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd @@ -0,0 +1,13 @@ +class_name RPCClientConnect +extends RPC + +var _client_id: int + + +func with_id(id: int) -> RPCClientConnect: + _client_id = id + return self + + +func client_id() -> int: + return _client_id diff --git a/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd.uid b/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd.uid new file mode 100644 index 0000000..016194f --- /dev/null +++ b/addons/gdUnit4/src/network/rpc/RPCClientConnect.gd.uid @@ -0,0 +1 @@ +uid://e0qdfuxqc6y0 diff --git a/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd b/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd new file mode 100644 index 0000000..7445b9d --- /dev/null +++ b/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd @@ -0,0 +1,13 @@ +class_name RPCClientDisconnect +extends RPC + +var _client_id: int + + +func with_id(id: int) -> RPCClientDisconnect: + _client_id = id + return self + + +func client_id() -> int: + return _client_id diff --git a/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd.uid b/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd.uid new file mode 100644 index 0000000..3114387 --- /dev/null +++ b/addons/gdUnit4/src/network/rpc/RPCClientDisconnect.gd.uid @@ -0,0 +1 @@ +uid://brg4jjgbo7okh diff --git a/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd b/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd new file mode 100644 index 0000000..dbf55c6 --- /dev/null +++ b/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd @@ -0,0 +1,14 @@ +class_name RPCGdUnitEvent +extends RPC + + +static func of(p_event: GdUnitEvent) -> RPCGdUnitEvent: + return RPCGdUnitEvent.new(p_event) + + +func event() -> GdUnitEvent: + return GdUnitEvent.new().deserialize(_data) + + +func _to_string() -> String: + return "RPCGdUnitEvent: " + str(_data) diff --git a/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd.uid b/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd.uid new file mode 100644 index 0000000..2543b41 --- /dev/null +++ b/addons/gdUnit4/src/network/rpc/RPCGdUnitEvent.gd.uid @@ -0,0 +1 @@ +uid://co8lhd0kirsur diff --git a/addons/gdUnit4/src/network/rpc/RPCMessage.gd b/addons/gdUnit4/src/network/rpc/RPCMessage.gd new file mode 100644 index 0000000..1db0470 --- /dev/null +++ b/addons/gdUnit4/src/network/rpc/RPCMessage.gd @@ -0,0 +1,18 @@ +class_name RPCMessage +extends RPC + +var _message: String + + +static func of(msg :String) -> RPCMessage: + var rpc := RPCMessage.new() + rpc._message = msg + return rpc + + +func message() -> String: + return _message + + +func _to_string() -> String: + return "RPCMessage: " + _message diff --git a/addons/gdUnit4/src/network/rpc/RPCMessage.gd.uid b/addons/gdUnit4/src/network/rpc/RPCMessage.gd.uid new file mode 100644 index 0000000..d470686 --- /dev/null +++ b/addons/gdUnit4/src/network/rpc/RPCMessage.gd.uid @@ -0,0 +1 @@ +uid://muf7ucwnbkuk |
