Initial Commit

Initial commit of Code Base.
This commit is contained in:
Mario Steele 2025-06-12 14:31:14 -05:00
parent 293b1213e1
commit c11a4ebbc2
653 changed files with 36893 additions and 1 deletions

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.1"
viewBox="0 0 16 16"
id="svg4"
sodipodi:docname="buffered-http-icon.svg"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs4">
<rect
x="0.0625"
y="10.625"
width="17.5"
height="10"
id="rect30" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect5"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,1,1,0,0.35632304,0,1 @ F,0,1,1,0,0.35632304,0,1 @ F,0,1,1,0,0.35632304,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,1,1,0,0.35632304,0,1 @ F,0,1,1,0,0.35632304,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.35632304,0,1 | F,0,0,1,0,0.32388529,0,1 @ F,0,0,1,0,0.30607722,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.21339985,0,1 @ F,0,0,1,0,0.41714928,0,1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect4"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0.59205278,0,1 @ F,0,0,1,0,0.62915907,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 | F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1 @ F,0,0,1,0,0,0,1"
is_visible="true"
lpeversion="1"
radius="0"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<sodipodi:namedview
id="namedview4"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="45.254834"
inkscape:cx="8.6068153"
inkscape:cy="15.733126"
inkscape:window-width="1999"
inkscape:window-height="1360"
inkscape:window-x="712"
inkscape:window-y="1440"
inkscape:window-maximized="0"
inkscape:current-layer="svg4" />
<rect
style="fill:#e0e0e0;fill-opacity:1;stroke:#e0e0e0;stroke-width:1.10201;stroke-linecap:butt;stroke-dasharray:none;stroke-opacity:1"
id="rect29"
width="6.1411905"
height="6.1411905"
x="6.0629044"
y="-2.3126719"
transform="matrix(0.92518611,0.37951371,-0.92518611,0.37951371,0,0)" />
<rect
style="fill:#e0e0e0;fill-opacity:1;stroke:#e0e0e0;stroke-width:1.10201;stroke-linecap:butt;stroke-dasharray:none;stroke-opacity:1"
id="rect29-2"
width="6.1411905"
height="6.1411905"
x="7.9945164"
y="-0.38105631"
transform="matrix(0.92518611,0.37951371,-0.92518611,0.37951371,0,0)" />
<rect
style="fill:#e0e0e0;fill-opacity:1;stroke:#e0e0e0;stroke-width:1.10201;stroke-linecap:butt;stroke-dasharray:none;stroke-opacity:1"
id="rect29-7"
width="6.1411905"
height="6.1411905"
x="9.9507313"
y="1.5751607"
transform="matrix(0.92518611,0.37951371,-0.92518611,0.37951371,0,0)" />
<path
style="fill:#9dff70;fill-opacity:1;stroke:none;stroke-width:1.36221;stroke-linecap:butt;stroke-dasharray:none;stroke-opacity:1"
d="M 3.4588334,2.784233 C 3.6019977,3.1259911 5.9999986,8.4422283 5.9999986,8.4422283 L 8.5411634,2.784233"
id="path29"
sodipodi:nodetypes="ccc" />
<path
style="fill:#ff7070;fill-opacity:1;stroke:none;stroke-width:1.36221;stroke-linecap:butt;stroke-dasharray:none;stroke-opacity:1"
d="M 7.4588334,8.4422283 C 7.6019977,8.1004702 9.9999986,2.784233 9.9999986,2.784233 l 2.5411684,5.6579953"
id="path29-8"
sodipodi:nodetypes="ccc" />
<path
d="m 1,10 v 2 1 2 h 1 v -2 h 1 v 2 H 4 V 10 H 3 v 2 H 2 v -2 z m 4,0 v 1 h 1 v 4 h 1 v -4 h 1 v -1 z m 4,0 v 1 h 1 v 4 h 1 v -4 h 1 v -1 z m 4,0 v 2 1 2 h 1 v -2 h 1 1 v -1 -2 h -2 z m 1,1 h 1 v 1 h -1 z"
style="fill:#e0e0e0;fill-opacity:0.99608"
id="path1" />
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://517onfw47ulp"
path="res://.godot/imported/buffered-http-icon.svg-338b6a52134b1dc8e217747ec727a304.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/twitcher/lib/http/buffered-http-icon.svg"
dest_files=["res://.godot/imported/buffered-http-icon.svg-338b6a52134b1dc8e217747ec727a304.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View file

@ -0,0 +1,204 @@
@icon("./buffered-http-icon.svg")
@tool
extends Twitcher
## Http client that bufferes the requests and sends them sequentialy
class_name BufferedHTTPClient
## Will be send when a new request was added to queue
signal request_added(request: RequestData)
## Will be send when a request is done.
signal request_done(response: ResponseData)
## Contains the request data to be send
class RequestData extends RefCounted:
## The client that the request belongs too
var client: BufferedHTTPClient
## The request node that is executing the request
var http_request: HTTPRequest
## Path of the request
var path: String
## The method that is used to call request
var method: int
## The request headers
var headers: Dictionary
## The body that is requested (TODO does it make more sense to make a Byte Array out of it?)
var body: String = ""
## Amount of retries
var retry: int
## When you are done free the request
func queue_free() -> void:
http_request.queue_free()
## Contains the response data
class ResponseData extends RefCounted:
## Result of the request see `HTTPRequest.Result`
var result: int
## Response code from the request like 200 for OK
var response_code: int
## the initial request data
var request_data: RequestData
## The body of the response as byte array
var response_data: PackedByteArray
## The response header as dictionary, where multiple keys are concatenated with ';'
var response_header: Dictionary
## Had the response an error
var error: bool
## When you are done free the request
func queue_free() -> void:
request_data.queue_free()
## When a request fails max_error_count then cancel that request -1 for endless amount of tries.
@export var max_error_count : int = -1
@export var custom_header : Dictionary[String, String] = { "Accept": "*/*" }
var requests : Array[RequestData] = []
var current_request : RequestData
var current_response_data : PackedByteArray = PackedByteArray()
var responses : Dictionary = {}
var error_count : int
## Only one poll at a time so block for all other tries to call it
var polling: bool
var processing: bool:
get: return not requests.is_empty() || current_request != null
## Starts a request that will be handled as soon as the client gets free.
## Use HTTPClient.METHOD_* for the method.
func request(path: String, method: int, headers: Dictionary, body: String) -> RequestData:
logInfo("[%s] start request " % [ path ])
headers = headers.duplicate()
headers.merge(custom_header)
var req = RequestData.new()
req.path = path
req.method = method
req.body = body
req.headers = headers
req.client = self
req.http_request = HTTPRequest.new()
req.http_request.use_threads = true
req.http_request.timeout = 30
req.http_request.request_completed.connect(_on_request_completed.bind(req))
add_child(req.http_request)
var err : Error = req.http_request.request(req.path, _pack_headers(req.headers), req.method, req.body)
if err != OK: logError("Problems with request to %s cause of %s" % [path, error_string(err)])
requests.append(req)
request_added.emit(req)
logDebug("[%s] request started " % [ path ])
return req
## When the response is available return it otherwise wait for the response
func wait_for_request(request_data: RequestData) -> ResponseData:
if responses.has(request_data):
var response = responses[request_data]
responses.erase(request_data)
request_data.queue_free()
logDebug("response cached return directly from wait")
return response
var latest_response : ResponseData = null
while (latest_response == null || request_data != latest_response.request_data):
latest_response = await request_done
logDebug("response received return from wait")
responses.erase(request_data)
request_data.queue_free()
return latest_response
func _on_request_completed(result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray, request_data: RequestData) -> void:
var response_data : ResponseData = ResponseData.new()
if result != HTTPRequest.Result.RESULT_SUCCESS:
logInfo("[%s] problems with result \n\t> response code: %s \n\t> body: %s" % [request_data.path, response_code, body.get_string_from_utf8()])
response_data.error = true
if result == HTTPRequest.Result.RESULT_CONNECTION_ERROR || result == HTTPRequest.Result.RESULT_TLS_HANDSHAKE_ERROR:
if request_data.retry == max_error_count:
printerr("Maximum amount of retries for the request. Abort request: %s" % [request_data.path])
return
var wait_time = pow(2, request_data.retry)
wait_time = min(wait_time, 30)
logDebug("Error happend during connection. Wait for %s" % wait_time)
await get_tree().create_timer(wait_time, true, false, true).timeout
var http_request: HTTPRequest = request_data.http_request.duplicate()
add_child(http_request)
request_data.http_request = http_request
request_data.retry += 1
http_request.request(request_data.path, _pack_headers(request_data.headers), request_data.method, request_data.body)
http_request.request_completed.connect(_on_request_completed.bind(http_request))
response_data.result = result
response_data.request_data = request_data
response_data.response_data = body
response_data.response_code = response_code
response_data.response_header = _get_response_headers_as_dictionary(headers)
responses[request_data] = response_data
logInfo("[%s] request done with result HTTPRequest.Result[%s] " % [ request_data.path, result])
request_done.emit(response_data)
func _get_response_headers_as_dictionary(headers: PackedStringArray) -> Dictionary:
var header_dict: Dictionary = {}
if headers == null:
return header_dict
for header in headers:
var header_data = header.split(":", true, 1)
var key = header_data[0]
var val = header_data[1]
if header_dict.has(key):
header_dict[key] += "; " + val
else:
header_dict[key] = val
return header_dict
func _pack_headers(headers: Dictionary) -> PackedStringArray:
var result: PackedStringArray = []
for header_key in headers:
var header_value = headers[header_key]
result.append("%s: %s" % [header_key, header_value])
return result
## The amount of requests that are pending
func queued_request_size() -> int:
var requests_size: int = requests.size()
if current_request != null:
requests_size += 1
return requests_size
func empty_response(request_data: RequestData) -> ResponseData:
var response_data = ResponseData.new()
response_data.request_data = request_data
response_data.response_data = []
response_data.response_code = 0
response_data.response_header = {}
response_data.result = 0
return response_data
# === LOGGER ===
static var logger: Dictionary = {}
static func set_logger(error: Callable, info: Callable, debug: Callable) -> void:
logger.debug = debug
logger.info = info
logger.error = error
static func logDebug(text: String) -> void:
if logger.has("debug"): logger.debug.call(text)
static func logInfo(text: String) -> void:
if logger.has("info"): logger.info.call(text)
static func logError(text: String) -> void:
if logger.has("error"): logger.error.call(text)

View file

@ -0,0 +1 @@
uid://b7i5j62lmuh71

View file

@ -0,0 +1,58 @@
extends Control
@onready var clients: Tree = %Clients
## Key: BufferedHTTPClient | value: TreeItem
var client_map : Dictionary[BufferedHTTPClient, TreeItem] = {}
## Key: RequestData | value: TreeItem
var request_map : Dictionary[BufferedHTTPClient.RequestData, TreeItem] = {}
func _ready() -> void:
get_tree().root.child_entered_tree.connect(_on_child_enter)
_add_http_clients(get_tree().root)
func _add_http_clients(parent: Node) -> void:
for child in parent.get_children():
_on_child_enter(child)
_add_http_clients(child)
func _on_child_enter(node: Node) -> void:
if node is BufferedHTTPClient:
_new_client(node)
func _new_client(client: BufferedHTTPClient):
var parent = clients.create_item()
parent.set_text(0, client.name)
client_map[client] = parent
client.request_added.connect(_on_add_request.bind(parent))
client.request_done.connect(_on_done_request)
for request in client.requests:
_on_add_request(request, parent)
func _on_add_request(request: BufferedHTTPClient.RequestData, http_item: TreeItem):
var request_item = clients.create_item(http_item)
request_item.set_text(0, request.path)
request_item.set_text(1, "Queued")
request_map[request] = request_item
func _on_done_request(response: BufferedHTTPClient.ResponseData):
var request_item = request_map[response.request_data] as TreeItem
request_item.set_text(1, "DONE")
await get_tree().create_timer(60, true, false, true).timeout
if request_item != null: request_item.free()
func _close_client(client: BufferedHTTPClient):
var http_item = client_map[client] as TreeItem
client_map.erase(client)
http_item.set_text(1, "CLOSED")
await get_tree().create_timer(60, true, false, true).timeout
http_item.free()

View file

@ -0,0 +1 @@
uid://bpvg80y7vsysx

View file

@ -0,0 +1,25 @@
[gd_scene load_steps=2 format=3 uid="uid://b0ebhuv2yaow0"]
[ext_resource type="Script" uid="uid://bpvg80y7vsysx" path="res://addons/twitcher/lib/http/debug_buffered_http_client.gd" id="1_ij68s"]
[node name="DebugBufferedHttpClient" type="VBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("1_ij68s")
[node name="Label" type="Label" parent="."]
layout_mode = 2
text = "Debug HTTP Clients"
horizontal_alignment = 1
[node name="Clients" type="Tree" parent="."]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
columns = 2
hide_root = true

View file

@ -0,0 +1,177 @@
@tool
extends Node
## Provides a simple HTTP Service to serve web stuff
class_name HTTPServer
## Key: int | Value: WeakRef(Server)
static var _servers : Dictionary = {}
class Server extends TCPServer:
var _bind_address: String
var _port: int
var _clients: Array[Client] = []
var _listeners: int
signal request_received(client: Client)
signal client_connected(client: Client)
signal client_disconnected(client: Client)
signal client_error_occured(client: Client, error: Error)
func _init(bind_address: String, port: int) -> void:
_bind_address = bind_address
_port = port
HTTPServer._servers[_port] = weakref(self)
func start_listening() -> void:
_listeners += 1
if !is_listening():
var status: Error = listen(_port, _bind_address)
Engine.get_main_loop().process_frame.connect(_process)
if status != OK:
HTTPServer.logError("Could not listen to port %d: %s" % [_port, error_string(status)])
else:
HTTPServer.logInfo("{%s:%s} listening" % [ _bind_address, _port ])
else:
HTTPServer.logDebug("{%s:%s} already listening" % [ _bind_address, _port ])
func stop_listening() -> void:
_listeners -= 1
HTTPServer.logDebug("{%s:%s} listener node detached %s left" % [ _bind_address, _port, _listeners ])
if _listeners <= 0:
_stop_server()
func _stop_server() -> void:
HTTPServer.logInfo("{%s:%s} stop" % [ _bind_address, _port ])
Engine.get_main_loop().process_frame.disconnect(_process)
for client in _clients:
client.peer.disconnect_from_host()
stop()
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE:
HTTPServer.logInfo("{%s:%s} removed" % [ _bind_address, _port ])
HTTPServer._servers.erase(_port)
func _process() -> void:
if !is_listening(): return
if is_connection_available():
_handle_connect()
for client in _clients:
_process_request(client)
_handle_disconnect(client)
func _process_request(client: Client) -> void:
var peer := client.peer
if peer.get_status() == StreamPeerTCP.STATUS_CONNECTED:
var error = peer.poll()
if error != OK:
HTTPServer.logError("Could not poll client %d: %s" % [_port, error_string(error)])
client_error_occured.emit(client, error)
elif peer.get_available_bytes() > 0:
request_received.emit(client)
func _handle_connect() -> void:
var peer := take_connection()
var client := Client.new()
client.peer = peer
_clients.append(client)
client_connected.emit(client)
HTTPServer.logInfo("{%s:%s} client connected" % [ _bind_address, _port ])
func _handle_disconnect(client: Client) -> void:
if client.peer.get_status() != StreamPeerTCP.STATUS_CONNECTED:
client_disconnected.emit(client)
HTTPServer.logInfo("{%s:%s} client disconnected" % [ _bind_address, _port ])
_clients.erase(client)
class Client extends RefCounted:
var peer: StreamPeerTCP
## Called when a new request was made
signal request_received(client: Client)
@export var _port: int
@export var _bind_address: String
var _server : Server
var _listening: bool
static func create(port: int, bind_address: String = "*") -> HTTPServer:
var server = HTTPServer.new()
server._bind_address = bind_address
server._port = port
return server
func _ready() -> void:
if _servers.has(_port) && _servers[_port] != null:
_server = _servers[_port].get_ref()
else:
_server = Server.new(_bind_address, _port)
_server.request_received.connect(_on_request_received)
logInfo("{%s:%s} start" % [ _bind_address, _port ])
func _on_request_received(client: Client) -> void:
request_received.emit(client)
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE:
stop_listening()
func start_listening() -> void:
_listening = true
_server.start_listening()
func stop_listening() -> void:
if _listening:
_listening = false
_server.stop_listening()
func send_response(client: Client, response_code : String, body : PackedByteArray) -> void:
var peer = client.peer
peer.put_data(("HTTP/1.1 %s\r\n" % response_code).to_utf8_buffer())
peer.put_data("Server: Godot Engine (Twitcher)\r\n".to_utf8_buffer())
peer.put_data(("Content-Length: %d\r\n"% body.size()).to_utf8_buffer())
peer.put_data("Connection: close\r\n".to_utf8_buffer())
peer.put_data("Content-Type: text/html charset=UTF-8\r\n".to_utf8_buffer())
peer.put_data("\r\n".to_utf8_buffer())
peer.put_data(body)
# === LOGGER ===
static var logger: Dictionary = {}
static func set_logger(error: Callable, info: Callable, debug: Callable) -> void:
logger.debug = debug
logger.info = info
logger.error = error
static func logDebug(text: String) -> void:
if logger.has("debug"): logger.debug.call(text)
static func logInfo(text: String) -> void:
if logger.has("info"): logger.info.call(text)
static func logError(text: String) -> void:
if logger.has("error"): logger.error.call(text)

View file

@ -0,0 +1 @@
uid://bnepo370sikkb

View file

@ -0,0 +1,25 @@
extends Object
## Parses a query string and returns a dictionary with the parameters.
static func parse_query(query: String) -> Dictionary:
var parameters = Dictionary()
# Split the query by '&' to separate different parameters.
var pairs = query.split("&")
# Iterate over each pair of key-value.
for pair in pairs:
# Split the pair by '=' to separate the key from the value.
var kv = pair.split("=")
if kv.size() == 2:
var key = kv[0].strip_edges()
var value = kv[1].strip_edges()
var decoded_key = key.uri_decode()
var decoded_value = value.uri_decode()
parameters[decoded_key] = decoded_value
return parameters
## Method to set all logger within this package
static func set_logger(error: Callable, info: Callable, debug: Callable) -> void:
BufferedHTTPClient.set_logger(error, info, debug)
HTTPServer.set_logger(error, info, debug)
WebsocketClient.set_logger(error, info, debug)

View file

@ -0,0 +1 @@
uid://5esrbr8ikth8

View file

@ -0,0 +1,145 @@
@tool
extends Node
## Advanced websocket client that automatically reconnects to the server
class_name WebsocketClient
## Called as soon the websocket got a connection
signal connection_established
## Called as soon the websocket closed the connection
signal connection_closed
## Called when a complete message got received
signal message_received(message: PackedByteArray)
## Called when the state of the websocket changed
signal connection_state_changed(state : WebSocketPeer.State)
@export var connection_url: String:
set(val):
_logDebug("Set connection to %s" % val)
connection_url = val
var connection_state : WebSocketPeer.State = WebSocketPeer.STATE_CLOSED:
set(new_state):
if new_state != connection_state:
connection_state_changed.emit(new_state)
if new_state == WebSocketPeer.STATE_OPEN: connection_established.emit()
if new_state == WebSocketPeer.STATE_CLOSED: connection_closed.emit()
connection_state = new_state
## Determines if a connection should be established or not
@export var auto_reconnect: bool:
set(val):
auto_reconnect = val
_logDebug("New auto_reconnect value: %s" % val)
## True if currently connecting to prevent 2 connectionen processes at the same time
var _is_already_connecting: bool
var is_open: bool:
get(): return _peer.get_ready_state() == WebSocketPeer.STATE_OPEN
var is_closed: bool:
get(): return _peer.get_ready_state() == WebSocketPeer.STATE_CLOSED
var _peer: WebSocketPeer = WebSocketPeer.new()
var _tries: int
func open_connection() -> void:
if not is_closed: return
auto_reconnect = true
_logInfo("Open connection")
await _establish_connection()
func wait_connection_established() -> void:
if is_open: return
await connection_established
func _establish_connection() -> void:
if _is_already_connecting || not is_closed: return
_is_already_connecting = true
var wait_time = pow(2, _tries)
_logDebug("Wait %s before connecting" % [wait_time])
await get_tree().create_timer(wait_time, true, false, true).timeout
_logInfo("Connecting to %s" % connection_url)
var err = _peer.connect_to_url(connection_url)
if err != OK:
logError("Couldn't connect cause of %s" % [error_string(err)])
_tries += 1
_is_already_connecting = false
func _enter_tree() -> void:
if auto_reconnect: open_connection()
func _exit_tree() -> void:
if not is_open: return
_peer.close(1000, "resource got freed")
func _process(delta: float) -> void:
_poll()
func _poll() -> void:
if connection_url == "": return
var state := _peer.get_ready_state()
if state == WebSocketPeer.STATE_CLOSED and auto_reconnect:
_establish_connection()
_peer.poll()
_handle_state_changes(state)
connection_state = state
if state == WebSocketPeer.STATE_OPEN:
_read_data()
func _handle_state_changes(state: WebSocketPeer.State) -> void:
if connection_state != WebSocketPeer.STATE_OPEN && state == WebSocketPeer.STATE_OPEN:
_logInfo("connected")
_tries = 0
if connection_state != WebSocketPeer.STATE_CLOSED && state == WebSocketPeer.STATE_CLOSED:
_logInfo("connection was closed [%s]: %s" % [_peer.get_close_code(), _peer.get_close_reason()])
func _read_data() -> void:
while (_peer.get_available_packet_count()):
message_received.emit(_peer.get_packet())
func send_text(message: String) -> Error:
return _peer.send_text(message)
func close(status: int = 1000, message: String = "Normal Closure") -> void:
_logDebug("Websocket activly closed")
auto_reconnect = false
_peer.close(status, message)
# === LOGGER ===
static var logger: Dictionary = {}
static func set_logger(error: Callable, info: Callable, debug: Callable) -> void:
logger.debug = debug
logger.info = info
logger.error = error
func _logDebug(text: String) -> void:
logDebug("[%s]: %s" % [connection_url, text])
static func logDebug(text: String) -> void:
if logger.has("debug"): logger.debug.call(text)
func _logInfo(text: String) -> void:
logInfo("[%s]: %s" % [connection_url, text])
static func logInfo(text: String) -> void:
if logger.has("info"): logger.info.call(text)
static func logError(text: String) -> void:
if logger.has("error"): logger.error.call(text)

View file

@ -0,0 +1 @@
uid://buqbforpa7b8a