Browse Source

Add Websocket chat demo

Fabio Alessandrelli 6 years ago
parent
commit
e9f02ff05f

+ 73 - 0
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

+ 130 - 0
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"]

+ 59 - 0
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()

+ 62 - 0
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

BIN
networking/websocket_chat/icon.png


+ 34 - 0
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

+ 28 - 0
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=[  ]

+ 74 - 0
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)

+ 129 - 0
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"]

+ 67 - 0
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())

+ 11 - 0
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")