Add EuphRoom
This commit is contained in:
parent
69515c6bc1
commit
1eafc745d7
3 changed files with 101 additions and 0 deletions
123
EuphRoom/EuphPacket.gd
Normal file
123
EuphRoom/EuphPacket.gd
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
class_name EuphPacket
|
||||
|
||||
static var _last_id := 0
|
||||
|
||||
#region Fields
|
||||
|
||||
## Client-generated id for associating replies with commands.
|
||||
## Optional.
|
||||
var id
|
||||
|
||||
## The name of the command, reply, or event.
|
||||
var type: String
|
||||
|
||||
## The payload of the command, reply, or event.
|
||||
var data: Dictionary
|
||||
|
||||
## This field appears in replies if a command fails.
|
||||
## Optional.
|
||||
var error
|
||||
|
||||
## This field appears in replies to warn the client that it may be flooding.
|
||||
## The client should slow down its command rate.
|
||||
var throttled: bool
|
||||
|
||||
## If throttled is true, this field describes why.
|
||||
## Optional.
|
||||
var throttled_reason
|
||||
|
||||
#endregion
|
||||
|
||||
@warning_ignore("shadowed_variable")
|
||||
func _init(type: String, data: Dictionary = {}):
|
||||
self.type = type
|
||||
self.data = data
|
||||
|
||||
## Generate a unique id if one is not already present.
|
||||
func gen_id():
|
||||
if id != null: return
|
||||
id = str(_last_id)
|
||||
_last_id += 1
|
||||
|
||||
#region Parse
|
||||
|
||||
## Returns null if packet could not be parsed.
|
||||
static func parse_dict(dict: Dictionary) -> EuphPacket:
|
||||
var packet = EuphPacket.new("placeholder")
|
||||
|
||||
packet.id = dict.get("id")
|
||||
if not (packet.id == null or typeof(packet.id) == TYPE_STRING):
|
||||
push_error("packet.id must be null or a string")
|
||||
return null
|
||||
|
||||
packet.type = dict["type"]
|
||||
if typeof(packet.type) != TYPE_STRING:
|
||||
push_error("packet.type must be a string")
|
||||
return null
|
||||
|
||||
packet.data = dict.get("data", {})
|
||||
if typeof(packet.data) != TYPE_DICTIONARY:
|
||||
push_error("packet.data must be a json object")
|
||||
return null
|
||||
|
||||
packet.error = dict.get("error")
|
||||
if not (packet.error == null or typeof(packet.error) == TYPE_STRING):
|
||||
push_error("packet.error must be null or a string")
|
||||
return null
|
||||
|
||||
packet.throttled = dict.get("throttled", false)
|
||||
if typeof(packet.throttled) != TYPE_BOOL:
|
||||
push_error("packet.throttled must be a bool")
|
||||
return null
|
||||
|
||||
packet.throttled_reason = dict.get("throttled_reason")
|
||||
if not (packet.throttled_reason == null or typeof(packet.throttled_reason) == TYPE_STRING):
|
||||
push_error("packet.throttled_reason must be null or a string")
|
||||
return null
|
||||
|
||||
return packet
|
||||
|
||||
## Returns null if packet could not be parsed.
|
||||
static func parse_json_string(json_string: String) -> EuphPacket:
|
||||
var packet: Variant = JSON.parse_string(json_string)
|
||||
|
||||
if packet == null:
|
||||
push_error("packet must be valid json")
|
||||
return null
|
||||
|
||||
if typeof(packet) != TYPE_DICTIONARY:
|
||||
push_error("packet must be a json object")
|
||||
return null
|
||||
|
||||
return parse_dict(packet)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Format
|
||||
|
||||
func dictify() -> Dictionary:
|
||||
var packet := {}
|
||||
|
||||
if id != null:
|
||||
packet["id"] = id
|
||||
|
||||
packet["type"] = type
|
||||
|
||||
if data:
|
||||
packet["data"] = data
|
||||
|
||||
if error != null:
|
||||
packet["error"] = error
|
||||
|
||||
if throttled:
|
||||
packet["throttled"] = throttled
|
||||
|
||||
if throttled_reason != null:
|
||||
packet["throttled_reason"] = throttled_reason
|
||||
|
||||
return packet
|
||||
|
||||
func json_stringify() -> String:
|
||||
return JSON.stringify(dictify())
|
||||
|
||||
#endregion
|
||||
91
EuphRoom/EuphRoom.gd
Normal file
91
EuphRoom/EuphRoom.gd
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
extends Node
|
||||
|
||||
signal joined()
|
||||
signal packet(packet: EuphPacket)
|
||||
|
||||
@export var domain := "euphoria.leet.nu"
|
||||
@export var room := "test"
|
||||
@export var nick := "godot"
|
||||
@export var autoconnect := false
|
||||
|
||||
@onready var _reconnect_timer := $ReconnectTimer
|
||||
|
||||
var _socket: WebSocketPeer
|
||||
var _joined: bool
|
||||
|
||||
func status() -> String:
|
||||
if not _socket:
|
||||
if _reconnect_timer.is_stopped(): return "stopped"
|
||||
return "waiting"
|
||||
match _socket.get_ready_state():
|
||||
WebSocketPeer.STATE_CONNECTING: return "connecting"
|
||||
WebSocketPeer.STATE_OPEN when _joined: return "joined"
|
||||
WebSocketPeer.STATE_OPEN : return "joining"
|
||||
WebSocketPeer.STATE_CLOSING: return "closing"
|
||||
WebSocketPeer.STATE_CLOSED: return "closed"
|
||||
return "unknown"
|
||||
|
||||
func reconnect():
|
||||
# Assumes gc will close the previous socket, if any
|
||||
var url := "wss://%s/room/%s/ws" % [domain, room]
|
||||
_socket = WebSocketPeer.new()
|
||||
_socket.connect_to_url(url)
|
||||
_joined = false
|
||||
|
||||
func send(p: EuphPacket):
|
||||
print("=> ", p.json_stringify())
|
||||
if not _socket:
|
||||
push_warning("Tried to send ", p.type, " while not connected")
|
||||
return
|
||||
|
||||
_socket.send_text(p.json_stringify())
|
||||
|
||||
func _on_packet(p: EuphPacket):
|
||||
print("<= ", p.json_stringify())
|
||||
if p.type == "ping-event":
|
||||
send(EuphPacket.new("ping-reply", {"time": p.data.time}))
|
||||
|
||||
if p.type == "snapshot-event":
|
||||
send(EuphPacket.new("nick", {"name": nick}))
|
||||
_joined = true
|
||||
joined.emit()
|
||||
|
||||
packet.emit(p)
|
||||
|
||||
func _on_raw_packet(raw_packet: PackedByteArray, is_text: bool):
|
||||
if not is_text:
|
||||
push_error("packet must be text")
|
||||
return
|
||||
|
||||
var text := raw_packet.get_string_from_utf8()
|
||||
var p := EuphPacket.parse_json_string(text)
|
||||
if p:
|
||||
_on_packet(p)
|
||||
|
||||
func _ready():
|
||||
if autoconnect:
|
||||
reconnect()
|
||||
|
||||
func _process(_delta):
|
||||
if not _socket: return
|
||||
|
||||
_socket.poll()
|
||||
|
||||
var state := _socket.get_ready_state()
|
||||
|
||||
# Reconnect if necessary
|
||||
if state == WebSocketPeer.STATE_CLOSED:
|
||||
_reconnect_timer.start()
|
||||
_socket = null
|
||||
return
|
||||
|
||||
if state != WebSocketPeer.STATE_OPEN:
|
||||
return
|
||||
|
||||
while _socket.get_available_packet_count() > 0:
|
||||
var raw_packet := _socket.get_packet()
|
||||
var is_text := _socket.was_string_packet()
|
||||
_on_raw_packet(raw_packet, is_text)
|
||||
|
||||
func _on_reconnect_timer_timeout():
|
||||
reconnect()
|
||||
10
EuphRoom/EuphRoom.tscn
Normal file
10
EuphRoom/EuphRoom.tscn
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[gd_scene load_steps=2 format=3 uid="uid://dmuubcbriw7uu"]
|
||||
|
||||
[ext_resource type="Script" path="res://EuphRoom/EuphRoom.gd" id="1_0hbbt"]
|
||||
|
||||
[node name="EuphRoom" type="Node"]
|
||||
script = ExtResource("1_0hbbt")
|
||||
|
||||
[node name="ReconnectTimer" type="Timer" parent="."]
|
||||
wait_time = 10.0
|
||||
one_shot = true
|
||||
Loading…
Add table
Add a link
Reference in a new issue