diff --git a/networking/websocket_chat/client/client.gd b/networking/websocket_chat/client/client.gd new file mode 100644 index 00000000..16b44dec --- /dev/null +++ b/networking/websocket_chat/client/client.gd @@ -0,0 +1,73 @@ +extends Node + +onready var _log_dest = get_parent().get_node("Panel/VBoxContainer/RichTextLabel") + +var _client = WebSocketClient.new() +var _write_mode = WebSocketPeer.WRITE_MODE_BINARY +var _use_multiplayer = true +var last_connected_client = 0 + +func _init(): + _client.connect("connection_established", self, "_client_connected") + _client.connect("connection_error", self, "_client_disconnected") + _client.connect("connection_closed", self, "_client_disconnected") + _client.connect("server_close_request", self, "_client_close_request") + _client.connect("data_received", self, "_client_received") + + _client.connect("peer_packet", self, "_client_received") + _client.connect("peer_connected", self, "_peer_connected") + _client.connect("connection_succeeded", self, "_client_connected", ["multiplayer_protocol"]) + _client.connect("connection_failed", self, "_client_disconnected") + +func _client_close_request(code, reason): + Utils._log(_log_dest, "Close code: %d, reason: %s" % [code, reason]) + +func _peer_connected(id): + Utils._log(_log_dest, "%s: Client just connected" % id) + last_connected_client = id + +func _exit_tree(): + _client.disconnect_from_host(1001, "Bye bye!") + +func _process(delta): + if _client.get_connection_status() == WebSocketClient.CONNECTION_DISCONNECTED: + return + + _client.poll() + +func _client_connected(protocol): + Utils._log(_log_dest, "Client just connected with protocol: %s" % protocol) + _client.get_peer(1).set_write_mode(_write_mode) + +func _client_disconnected(clean=true): + Utils._log(_log_dest, "Client just disconnected. Was clean: %s" % clean) + +func _client_received(p_id = 1): + if _use_multiplayer: + var peer_id = _client.get_packet_peer() + var packet = _client.get_packet() + Utils._log(_log_dest, "MPAPI: From %s Data: %s" % [str(peer_id), Utils.decode_data(packet, false)]) + else: + var packet = _client.get_peer(1).get_packet() + var is_string = _client.get_peer(1).was_string_packet() + Utils._log(_log_dest, "Received data. BINARY: %s: %s" % [not is_string, Utils.decode_data(packet, is_string)]) + +func connect_to_url(host, protocols, multiplayer): + _use_multiplayer = multiplayer + if _use_multiplayer: + _write_mode = WebSocketPeer.WRITE_MODE_BINARY + return _client.connect_to_url(host, protocols, multiplayer) + +func disconnect_from_host(): + _client.disconnect_from_host(1000, "Bye bye!") + +func send_data(data, dest): + _client.get_peer(1).set_write_mode(_write_mode) + if _use_multiplayer: + _client.set_target_peer(dest) + _client.put_packet(Utils.encode_data(data, _write_mode)) + else: + _client.get_peer(1).put_packet(Utils.encode_data(data, _write_mode)) + +func set_write_mode(mode): + _write_mode = mode diff --git a/networking/websocket_chat/client/client.tscn b/networking/websocket_chat/client/client.tscn new file mode 100644 index 00000000..f20edaad --- /dev/null +++ b/networking/websocket_chat/client/client.tscn @@ -0,0 +1,130 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://client/client_ui.gd" type="Script" id=1] +[ext_resource path="res://client/client.gd" type="Script" id=2] + +[node name="Client" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Panel" type="Panel" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { + +} + +[node name="VBoxContainer" type="VBoxContainer" parent="Panel"] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { + +} + +[node name="Connect" type="HBoxContainer" parent="Panel/VBoxContainer"] +margin_right = 1024.0 +margin_bottom = 24.0 +__meta__ = { + +} + +[node name="Host" type="LineEdit" parent="Panel/VBoxContainer/Connect"] +margin_right = 956.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +text = "ws://localhost:8000/test/" +placeholder_text = "ws://my.server/path/" +__meta__ = { + +} + +[node name="Connect" type="Button" parent="Panel/VBoxContainer/Connect"] +margin_left = 960.0 +margin_right = 1024.0 +margin_bottom = 24.0 +toggle_mode = true +text = "Connect" +__meta__ = { + +} + +[node name="Settings" type="HBoxContainer" parent="Panel/VBoxContainer"] +margin_top = 28.0 +margin_right = 1024.0 +margin_bottom = 52.0 +__meta__ = { + +} + +[node name="Mode" type="OptionButton" parent="Panel/VBoxContainer/Settings"] +margin_right = 41.0 +margin_bottom = 24.0 +__meta__ = { + +} + +[node name="Multiplayer" type="CheckBox" parent="Panel/VBoxContainer/Settings"] +margin_left = 45.0 +margin_right = 171.0 +margin_bottom = 24.0 +pressed = true +text = "Multiplayer API" +__meta__ = { + +} + +[node name="Destination" type="OptionButton" parent="Panel/VBoxContainer/Settings"] +margin_left = 175.0 +margin_right = 216.0 +margin_bottom = 24.0 +__meta__ = { + +} + +[node name="Send" type="HBoxContainer" parent="Panel/VBoxContainer"] +margin_top = 56.0 +margin_right = 1024.0 +margin_bottom = 80.0 +__meta__ = { + +} + +[node name="LineEdit" type="LineEdit" parent="Panel/VBoxContainer/Send"] +margin_right = 977.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +placeholder_text = "Enter some text to send..." +__meta__ = { + +} + +[node name="Send" type="Button" parent="Panel/VBoxContainer/Send"] +margin_left = 981.0 +margin_right = 1024.0 +margin_bottom = 24.0 +text = "Send" +__meta__ = { + +} + +[node name="RichTextLabel" type="RichTextLabel" parent="Panel/VBoxContainer"] +margin_top = 84.0 +margin_right = 1024.0 +margin_bottom = 600.0 +size_flags_vertical = 3 +__meta__ = { + +} + +[node name="Client" type="Node" parent="."] +script = ExtResource( 2 ) +__meta__ = { + +} +[connection signal="toggled" from="Panel/VBoxContainer/Connect/Connect" to="." method="_on_Connect_toggled"] +[connection signal="item_selected" from="Panel/VBoxContainer/Settings/Mode" to="." method="_on_Mode_item_selected"] +[connection signal="pressed" from="Panel/VBoxContainer/Send/Send" to="." method="_on_Send_pressed"] diff --git a/networking/websocket_chat/client/client_ui.gd b/networking/websocket_chat/client/client_ui.gd new file mode 100644 index 00000000..071562c4 --- /dev/null +++ b/networking/websocket_chat/client/client_ui.gd @@ -0,0 +1,59 @@ +extends Control + +onready var _client = get_node("Client") +onready var _log_dest = get_node("Panel/VBoxContainer/RichTextLabel") +onready var _line_edit = get_node("Panel/VBoxContainer/Send/LineEdit") +onready var _host = get_node("Panel/VBoxContainer/Connect/Host") +onready var _multiplayer = get_node("Panel/VBoxContainer/Settings/Multiplayer") +onready var _write_mode = get_node("Panel/VBoxContainer/Settings/Mode") +onready var _destination = get_node("Panel/VBoxContainer/Settings/Destination") + +func _ready(): + _write_mode.clear() + _write_mode.add_item("BINARY") + _write_mode.set_item_metadata(0, WebSocketPeer.WRITE_MODE_BINARY) + _write_mode.add_item("TEXT") + _write_mode.set_item_metadata(1, WebSocketPeer.WRITE_MODE_TEXT) + + _destination.add_item("Broadcast") + _destination.set_item_metadata(0, 0) + _destination.add_item("Last connected") + _destination.set_item_metadata(1, 1) + _destination.add_item("All But last connected") + _destination.set_item_metadata(2, -1) + _destination.select(0) + +func _on_Mode_item_selected( ID ): + _client.set_write_mode(_write_mode.get_selected_metadata()) + +func _on_Send_pressed(): + if _line_edit.text == "": + return + + var dest = _destination.get_selected_metadata() + if dest > 0: + dest = _client.last_connected_client + elif dest < 0: + dest = -_client.last_connected_client + + Utils._log(_log_dest, "Sending data %s to %s" % [_line_edit.text, dest]) + _client.send_data(_line_edit.text, dest) + _line_edit.text = "" + +func _on_Connect_toggled( pressed ): + if pressed: + var multiplayer = _multiplayer.pressed + if multiplayer: + _write_mode.disabled = true + else: + _destination.disabled = true + _multiplayer.disabled = true + if _host.text != "": + Utils._log(_log_dest, "Connecting to host: %s" % [_host.text]) + var supported_protocols = PoolStringArray(["my-protocol2", "my-protocol", "binary"]) + _client.connect_to_url(_host.text, supported_protocols, multiplayer) + else: + _destination.disabled = false + _write_mode.disabled = false + _multiplayer.disabled = false + _client.disconnect_from_host() diff --git a/networking/websocket_chat/combo/combo.tscn b/networking/websocket_chat/combo/combo.tscn new file mode 100644 index 00000000..8940a854 --- /dev/null +++ b/networking/websocket_chat/combo/combo.tscn @@ -0,0 +1,62 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://server/server.tscn" type="PackedScene" id=1] +[ext_resource path="res://client/client.tscn" type="PackedScene" id=2] + +[node name="Combo" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +mouse_filter = 1 +__meta__ = { + +} + +[node name="Box" type="HBoxContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +custom_constants/separation = 20 +__meta__ = { + +} + +[node name="ServerControl" parent="Box" instance=ExtResource( 1 )] +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 502.0 +margin_bottom = 600.0 +size_flags_horizontal = 3 + +[node name="VBoxContainer" type="VBoxContainer" parent="Box"] +margin_left = 522.0 +margin_right = 1024.0 +margin_bottom = 600.0 +size_flags_horizontal = 3 +__meta__ = { + +} + +[node name="Client" parent="Box/VBoxContainer" instance=ExtResource( 2 )] +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_right = 502.0 +margin_bottom = 197.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Client2" parent="Box/VBoxContainer" instance=ExtResource( 2 )] +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 201.0 +margin_right = 502.0 +margin_bottom = 398.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Client3" parent="Box/VBoxContainer" instance=ExtResource( 2 )] +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_top = 402.0 +margin_right = 502.0 +margin_bottom = 600.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 diff --git a/networking/websocket_chat/icon.png b/networking/websocket_chat/icon.png new file mode 100644 index 00000000..a0b64eee Binary files /dev/null and b/networking/websocket_chat/icon.png differ diff --git a/networking/websocket_chat/icon.png.import b/networking/websocket_chat/icon.png.import new file mode 100644 index 00000000..96cbf462 --- /dev/null +++ b/networking/websocket_chat/icon.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="StreamTexture" +path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.png" +dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] + +[params] + +compress/mode=0 +compress/lossy_quality=0.7 +compress/hdr_mode=0 +compress/bptc_ldr=0 +compress/normal_map=0 +flags/repeat=0 +flags/filter=true +flags/mipmaps=false +flags/anisotropic=false +flags/srgb=2 +process/fix_alpha_border=true +process/premult_alpha=false +process/HDR_as_SRGB=false +process/invert_color=false +stream=false +size_limit=0 +detect_3d=true +svg/scale=1.0 diff --git a/networking/websocket_chat/project.godot b/networking/websocket_chat/project.godot new file mode 100644 index 00000000..5a9ca2cf --- /dev/null +++ b/networking/websocket_chat/project.godot @@ -0,0 +1,28 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=4 + +_global_script_classes=[ ] +_global_script_class_icons={ + +} + +[application] + +config/name="Websocket Chat Demo" +run/main_scene="res://combo/combo.tscn" +config/icon="res://icon.png" + +[autoload] + +Utils="*res://utils.gd" + +[gdnative] + +singletons=[ ] diff --git a/networking/websocket_chat/server/server.gd b/networking/websocket_chat/server/server.gd new file mode 100644 index 00000000..790df3e5 --- /dev/null +++ b/networking/websocket_chat/server/server.gd @@ -0,0 +1,74 @@ +extends Node + +onready var _log_dest = get_parent().get_node("Panel/VBoxContainer/RichTextLabel") + +var _server = WebSocketServer.new() +var _clients = {} +var _write_mode = WebSocketPeer.WRITE_MODE_BINARY +var _use_multiplayer = true +var last_connected_client = 0 + +func _init(): + _server.connect("client_connected", self, "_client_connected") + _server.connect("client_disconnected", self, "_client_disconnected") + _server.connect("client_close_request", self, "_client_close_request") + _server.connect("data_received", self, "_client_receive") + + _server.connect("peer_packet", self, "_client_receive") + _server.connect("peer_connected", self, "_client_connected", ["multiplayer_protocol"]) + _server.connect("peer_disconnected", self, "_client_disconnected") + +func _exit_tree(): + _clients.clear() + _server.stop() + +func _process(delta): + if _server.is_listening(): + _server.poll() + +func _client_close_request(id, code, reason): + print(reason == "Bye bye!") + Utils._log(_log_dest, "Client %s close code: %d, reason: %s" % [id, code, reason]) + +func _client_connected(id, protocol): + _clients[id] = _server.get_peer(id) + _clients[id].set_write_mode(_write_mode) + last_connected_client = id + Utils._log(_log_dest, "%s: Client connected with protocol %s" % [id, protocol]) + +func _client_disconnected(id, clean = true): + Utils._log(_log_dest, "Client %s disconnected. Was clean: %s" % [id, clean]) + if _clients.has(id): + _clients.erase(id) + +func _client_receive(id): + if _use_multiplayer: + var peer_id = _server.get_packet_peer() + var packet = _server.get_packet() + Utils._log(_log_dest, "MPAPI: From %s data: %s" % [peer_id, Utils.decode_data(packet, false)]) + else: + var packet = _server.get_peer(id).get_packet() + var is_string = _server.get_peer(id).was_string_packet() + Utils._log(_log_dest, "Data from %s BINARY: %s: %s" % [id, not is_string, Utils.decode_data(packet, is_string)]) + +func send_data(data, dest): + if _use_multiplayer: + _server.set_target_peer(dest) + _server.put_packet(Utils.encode_data(data, _write_mode)) + else: + for id in _clients: + _server.get_peer(id).put_packet(Utils.encode_data(data, _write_mode)) + +func listen(port, supported_protocols, multiplayer): + _use_multiplayer = multiplayer + if _use_multiplayer: + set_write_mode(WebSocketPeer.WRITE_MODE_BINARY) + return _server.listen(port, supported_protocols, multiplayer) + +func stop(): + _server.stop() + +func set_write_mode(mode): + _write_mode = mode + for c in _clients: + _clients[c].set_write_mode(_write_mode) diff --git a/networking/websocket_chat/server/server.tscn b/networking/websocket_chat/server/server.tscn new file mode 100644 index 00000000..6c14493d --- /dev/null +++ b/networking/websocket_chat/server/server.tscn @@ -0,0 +1,129 @@ +[gd_scene load_steps=3 format=2] + +[ext_resource path="res://server/server_ui.gd" type="Script" id=1] +[ext_resource path="res://server/server.gd" type="Script" id=2] + +[node name="ServerControl" type="Control"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 1 ) +__meta__ = { + +} + +[node name="Server" type="Node" parent="."] +script = ExtResource( 2 ) +__meta__ = { + +} + +[node name="Panel" type="Panel" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { + +} + +[node name="VBoxContainer" type="VBoxContainer" parent="Panel"] +anchor_right = 1.0 +anchor_bottom = 1.0 +__meta__ = { + +} + +[node name="HBoxContainer" type="HBoxContainer" parent="Panel/VBoxContainer"] +margin_right = 1024.0 +margin_bottom = 24.0 +__meta__ = { + +} + +[node name="Port" type="SpinBox" parent="Panel/VBoxContainer/HBoxContainer"] +margin_right = 74.0 +margin_bottom = 24.0 +min_value = 1.0 +max_value = 65535.0 +value = 8000.0 +__meta__ = { + +} + +[node name="Listen" type="Button" parent="Panel/VBoxContainer/HBoxContainer"] +margin_left = 78.0 +margin_right = 129.0 +margin_bottom = 24.0 +toggle_mode = true +text = "Listen" +__meta__ = { + +} + +[node name="HBoxContainer2" type="HBoxContainer" parent="Panel/VBoxContainer"] +margin_top = 28.0 +margin_right = 1024.0 +margin_bottom = 52.0 +__meta__ = { + +} + +[node name="WriteMode" type="OptionButton" parent="Panel/VBoxContainer/HBoxContainer2"] +margin_right = 41.0 +margin_bottom = 24.0 +__meta__ = { + +} + +[node name="MPAPI" type="CheckBox" parent="Panel/VBoxContainer/HBoxContainer2"] +margin_left = 45.0 +margin_right = 171.0 +margin_bottom = 24.0 +pressed = true +text = "Multiplayer API" +__meta__ = { + +} + +[node name="Destination" type="OptionButton" parent="Panel/VBoxContainer/HBoxContainer2"] +margin_left = 175.0 +margin_right = 216.0 +margin_bottom = 24.0 +__meta__ = { + +} + +[node name="HBoxContainer3" type="HBoxContainer" parent="Panel/VBoxContainer"] +margin_top = 56.0 +margin_right = 1024.0 +margin_bottom = 80.0 +__meta__ = { + +} + +[node name="LineEdit" type="LineEdit" parent="Panel/VBoxContainer/HBoxContainer3"] +margin_right = 977.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +__meta__ = { + +} + +[node name="Send" type="Button" parent="Panel/VBoxContainer/HBoxContainer3"] +margin_left = 981.0 +margin_right = 1024.0 +margin_bottom = 24.0 +text = "Send" +__meta__ = { + +} + +[node name="RichTextLabel" type="RichTextLabel" parent="Panel/VBoxContainer"] +margin_top = 84.0 +margin_right = 1024.0 +margin_bottom = 600.0 +size_flags_vertical = 3 +__meta__ = { + +} +[connection signal="toggled" from="Panel/VBoxContainer/HBoxContainer/Listen" to="." method="_on_Listen_toggled"] +[connection signal="item_selected" from="Panel/VBoxContainer/HBoxContainer2/WriteMode" to="." method="_on_WriteMode_item_selected"] +[connection signal="pressed" from="Panel/VBoxContainer/HBoxContainer3/Send" to="." method="_on_Send_pressed"] diff --git a/networking/websocket_chat/server/server_ui.gd b/networking/websocket_chat/server/server_ui.gd new file mode 100644 index 00000000..dfad08fb --- /dev/null +++ b/networking/websocket_chat/server/server_ui.gd @@ -0,0 +1,67 @@ +extends Control + +onready var _server = get_node("Server") +onready var _port = get_node("Panel/VBoxContainer/HBoxContainer/Port") +onready var _line_edit = get_node("Panel/VBoxContainer/HBoxContainer3/LineEdit") +onready var _write_mode = get_node("Panel/VBoxContainer/HBoxContainer2/WriteMode") +onready var _log_dest = get_node("Panel/VBoxContainer/RichTextLabel") +onready var _multiplayer = get_node("Panel/VBoxContainer/HBoxContainer2/MPAPI") +onready var _destination = get_node("Panel/VBoxContainer/HBoxContainer2/Destination") + +func _ready(): + _write_mode.clear() + _write_mode.add_item("BINARY") + _write_mode.set_item_metadata(0, WebSocketPeer.WRITE_MODE_BINARY) + _write_mode.add_item("TEXT") + _write_mode.set_item_metadata(1, WebSocketPeer.WRITE_MODE_TEXT) + _write_mode.select(0) + + _destination.add_item("Broadcast") + _destination.set_item_metadata(0, 0) + _destination.add_item("Last connected") + _destination.set_item_metadata(1, 1) + _destination.add_item("All But last connected") + _destination.set_item_metadata(2, -1) + _destination.select(0) + +func _on_Listen_toggled( pressed ): + if pressed: + var use_multiplayer = _multiplayer.pressed + _multiplayer.disabled = true + var supported_protocols = PoolStringArray(["my-protocol", "binary"]) + var port = int(_port.value) + if use_multiplayer: + _write_mode.disabled = true + _write_mode.select(0) + else: + _destination.disabled = true + _destination.select(0) + if _server.listen(port, supported_protocols, use_multiplayer) == OK: + Utils._log(_log_dest, "Listing on port %s" % port) + if not use_multiplayer: + Utils._log(_log_dest, "Supported protocols: %s" % supported_protocols) + else: + Utils._log(_log_dest, "Error listening on port %s" % port) + else: + _server.stop() + _multiplayer.disabled = false + _write_mode.disabled = false + _destination.disabled = false + Utils._log(_log_dest, "Server stopped") + +func _on_Send_pressed(): + if _line_edit.text == "": + return + + var dest = _destination.get_selected_metadata() + if dest > 0: + dest = _server.last_connected_client + elif dest < 0: + dest = -_server.last_connected_client + + Utils._log(_log_dest, "Sending data %s to %s" % [_line_edit.text, dest]) + _server.send_data(_line_edit.text, dest) + _line_edit.text = "" + +func _on_WriteMode_item_selected( ID ): + _server.set_write_mode(_write_mode.get_selected_metadata()) diff --git a/networking/websocket_chat/utils.gd b/networking/websocket_chat/utils.gd new file mode 100644 index 00000000..0c1ffcfb --- /dev/null +++ b/networking/websocket_chat/utils.gd @@ -0,0 +1,11 @@ +extends Node + +func encode_data(data, mode): + return data.to_utf8() if mode == WebSocketPeer.WRITE_MODE_TEXT else var2bytes(data) + +func decode_data(data, is_string): + return data.get_string_from_utf8() if is_string else bytes2var(data) + +func _log(node, msg): + print(msg) + node.add_text(str(msg) + "\n")