From 5bdb3e7b77ddbd95e53c8d7a99934ab5f954aa3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Wed, 29 Oct 2025 16:46:16 +0100 Subject: [PATCH 1/3] some docs --- trimsock.gd/addons/trimsock.gd/command.gd | 82 +++++++++++++++++-- trimsock.gd/addons/trimsock.gd/conventions.gd | 4 + trimsock.gd/addons/trimsock.gd/reader.gd | 22 +++++ 3 files changed, 102 insertions(+), 6 deletions(-) diff --git a/trimsock.gd/addons/trimsock.gd/command.gd b/trimsock.gd/addons/trimsock.gd/command.gd index 27fe53b..a1a683c 100644 --- a/trimsock.gd/addons/trimsock.gd/command.gd +++ b/trimsock.gd/addons/trimsock.gd/command.gd @@ -1,65 +1,91 @@ extends RefCounted class_name TrimsockCommand +## Represents a trimsock command + +## A chunk of command data class Chunk: + ## Text contained in the chunk var text: String + ## True if the chunk is quoted var is_quoted: bool + ## Create a quoted command data chunk static func quoted(p_text: String) -> Chunk: var chunk := Chunk.new() chunk.is_quoted = true chunk.text = p_text return chunk + ## Create an quoted command data chunk static func unquoted(p_text: String) -> Chunk: var chunk := Chunk.new() chunk.is_quoted = false chunk.text = p_text return chunk + ## Create a command data chunk from [param text], quoted as needed static func of_text(p_text: String) -> Chunk: var chunk := Chunk.new() chunk.is_quoted = p_text.contains(" ") chunk.text = p_text return chunk +## Key-value pair specified in a command +## +## Note that both key and value are always strings class Pair: + ## Key var key: String + ## Value var value: String +## Command type enum Type { - SIMPLE, - REQUEST, - SUCCESS_RESPONSE, - ERROR_RESPONSE, - STREAM_CHUNK, - STREAM_FINISH + SIMPLE, ## Simple command, without any conventions + REQUEST, ## Request command + SUCCESS_RESPONSE, ## Successful response command + ERROR_RESPONSE, ## Error response command + STREAM_CHUNK, ## Stream chunk command + STREAM_FINISH ## Stream finish command } # Core properties +## Command name var name: String = "" +## Text contents - empty string for raw commands var text: String = "" +## Chunks making up the command data var chunks: Array[Chunk] = [] +## True if the command is raw var is_raw: bool = false +## Raw data - empty for text commands var raw: PackedByteArray # Multiparam +## Command parameters var params: Array[String] # Key-value pairs +## Key-value params, in the form of pairs - a key may be specified multiple times var kv_pairs: Array[Pair] +## Key-value params, as a dictionary - for repeating keys, only the last value is retained var kv_map: Dictionary # Request-response + Stream +## Exchange ID - empty if not request, response, or stream var exchange_id: String +## Command type var type: Type = Type.SIMPLE +## Create a raw command from data buffer static func from_buffer(name: String, data: PackedByteArray) -> TrimsockCommand: var command := TrimsockCommand.new() command.is_raw = true command.raw = data return command +## Create a simple command from name and text content static func simple(name: String, text: String = "") -> TrimsockCommand: var command := TrimsockCommand.new() command.name = name @@ -68,6 +94,7 @@ static func simple(name: String, text: String = "") -> TrimsockCommand: return command +## Create a request command static func request(name: String, exchange_id: String = "") -> TrimsockCommand: var command := TrimsockCommand.new() command.name = name @@ -75,6 +102,7 @@ static func request(name: String, exchange_id: String = "") -> TrimsockCommand: command.exchange_id = exchange_id return command +## Create a success response command static func success_response(name: String, exchange_id: String = "") -> TrimsockCommand: var command := TrimsockCommand.new() command.name = name @@ -82,6 +110,7 @@ static func success_response(name: String, exchange_id: String = "") -> Trimsock command.exchange_id = exchange_id return command +## Create an error response command static func error_response(name: String, exchange_id: String = "") -> TrimsockCommand: var command := TrimsockCommand.new() command.name = name @@ -89,6 +118,7 @@ static func error_response(name: String, exchange_id: String = "") -> TrimsockCo command.exchange_id = exchange_id return command +## Create a stream chunk command static func stream_chunk(name: String, exchange_id: String = "") -> TrimsockCommand: var command := TrimsockCommand.new() command.name = name @@ -96,6 +126,7 @@ static func stream_chunk(name: String, exchange_id: String = "") -> TrimsockComm command.exchange_id = exchange_id return command +## Create a stream finish command static func stream_finish(name: String, exchange_id: String = "") -> TrimsockCommand: var command := TrimsockCommand.new() command.name = name @@ -103,6 +134,7 @@ static func stream_finish(name: String, exchange_id: String = "") -> TrimsockCom command.exchange_id = exchange_id return command +## Create an error response to the specified [param command] static func error_from(command: TrimsockCommand, name: String, data) -> TrimsockCommand: var result := TrimsockCommand.new() @@ -121,6 +153,7 @@ static func error_from(command: TrimsockCommand, name: String, data) -> Trimsock return result +## TODO: Move to reader static func unescape(what: String) -> String: return (what .replace("\\n", "\n") @@ -128,9 +161,11 @@ static func unescape(what: String) -> String: .replace("\\\"", "\"") ) +## TODO: Move to writer static func escape_quoted(what: String) -> String: return what.replace("\"", "\\\"") +## TODO: Move to writer static func escape_unquoted(what: String) -> String: return (what .replace("\n", "\\n") @@ -138,12 +173,14 @@ static func escape_unquoted(what: String) -> String: .replace("\"", "\\\"") ) +## Create a key-value pair for use with [member kv_pairs] static func pair_of(key: String, value: String) -> Pair: var pair := Pair.new() pair.key = key pair.value = value return pair +## Convert the command [param type] to a string static func type_string(type: Type) -> String: match type: Type.SIMPLE: return "Simple" @@ -155,33 +192,42 @@ static func type_string(type: Type) -> String: return "%d???" % [type] +## Return true if it's a simple command func is_simple() -> bool: return type == Type.SIMPLE +## Return true if it's a request command func is_request() -> bool: return type == Type.REQUEST +## Return true if it's a success response command func is_success() -> bool: return type == Type.SUCCESS_RESPONSE +## Return true if it's an error response command func is_error() -> bool: return type == Type.ERROR_RESPONSE +## Return true if it's a stream command - either a chunk or finish func is_stream() -> bool: return is_stream_chunk() or is_stream_end() +## Return true if it's a stream chunk command func is_stream_chunk() -> bool: return type == Type.STREAM_CHUNK +## Return true if it's a stream finish command func is_stream_end() -> bool: return type == Type.STREAM_FINISH +## Return true if the command has no data func is_empty() -> bool: if is_raw: return raw.is_empty() else: return text.is_empty() and chunks.is_empty() and params.is_empty() and kv_pairs.is_empty() and kv_map.is_empty() +## Clear command, resetting all its flags and data func clear(): raw.clear() chunks.clear() @@ -190,76 +236,99 @@ func clear(): kv_map.clear() text = "" +## Set the command's name before returning it func with_name(p_name: String) -> TrimsockCommand: name = p_name return self +## Set the command's text content before returning it func with_text(p_text: String) -> TrimsockCommand: text = p_text return self +## Set the command's data chunks before returning it func with_chunks(p_chunks: Array[Chunk]) -> TrimsockCommand: chunks += p_chunks return self +## Change the command into a raw command before returning it func as_raw() -> TrimsockCommand: is_raw = true text = "" chunks = [] return self +## Set the raw command data before returning it func with_data(data: PackedByteArray) -> TrimsockCommand: as_raw() raw = data return self +## Add more parameters to [member params] before returning the command func with_params(p_params: Array[String]) -> TrimsockCommand: params += p_params return self +## Add more pairs to [member kv_pairs] before returning the command func with_kv_pairs(p_kv_pairs: Array[Pair]) -> TrimsockCommand: kv_pairs += p_kv_pairs for pair in kv_pairs: kv_map[pair.key] = pair.value return self +## Merge [param p_kv_map] with [member p_kv_map] before returning the command func with_kv_map(p_kv_map: Dictionary) -> TrimsockCommand: for key in p_kv_map: var value = p_kv_map[key] + # TODO: Don't add pair if it was already present in the kv_map kv_pairs.append(pair_of(key, value)) kv_map.merge(p_kv_map, true) return self +## Set the exchange ID of the command before returning it func with_exchange_id(p_exchange_id: String) -> TrimsockCommand: exchange_id = p_exchange_id return self +## Set the command type to request before returning it func as_request() -> TrimsockCommand: type = Type.REQUEST return self +## Set the command type to success response before returning it func as_success_response() -> TrimsockCommand: type = Type.SUCCESS_RESPONSE return self +## Set the command type to error response before returning it func as_error_response() -> TrimsockCommand: type = Type.ERROR_RESPONSE return self +## Set the command type to stream before returning it +## [br][br] +## If the command is empty, it will become a stream finish command, otherwise a +## stream chunk. func as_stream() -> TrimsockCommand: type = Type.STREAM_FINISH if is_empty() else Type.STREAM_CHUNK return self +# TODO: Move to writer +## Serialize the command into a new [PackedByteArray] func serialize() -> PackedByteArray: var out := PackedByteArray() serialize_to_array(out) return out +# TODO: Move to writer +## Serialize the command into an existing [PackedByteArray] func serialize_to_array(out: PackedByteArray) -> void: var buffer := StreamPeerBuffer.new() serialize_to_stream(buffer) out.append_array(buffer.data_array) +# TODO: Move to writer +## Serialize the command into an existing [StreamPeer] func serialize_to_stream(out: StreamPeer) -> void: # Add raw marker if is_raw: @@ -331,6 +400,7 @@ func serialize_to_stream(out: StreamPeer) -> void: # Add closing NL out.put_u8(_ord("\n")) +## Return true if this command is considered equal to [param what] func equals(what) -> bool: if not what is TrimsockCommand: return false diff --git a/trimsock.gd/addons/trimsock.gd/conventions.gd b/trimsock.gd/addons/trimsock.gd/conventions.gd index fc9c9ea..6083349 100644 --- a/trimsock.gd/addons/trimsock.gd/conventions.gd +++ b/trimsock.gd/addons/trimsock.gd/conventions.gd @@ -1,11 +1,14 @@ extends Object class_name _TrimsockConventions +# Implements conventions on top of the core protocol spec +# Apply all conventions to command, in-place static func apply(command: TrimsockCommand) -> void: parse_type(command) parse_params(command) +# Figure out command type static func parse_type(command: TrimsockCommand) -> void: var at := 0 @@ -42,6 +45,7 @@ static func parse_type(command: TrimsockCommand) -> void: command.name = name command.exchange_id = id +# Parse params and kv-pairs static func parse_params(command: TrimsockCommand) -> void: if command.is_raw or command.chunks.is_empty(): return diff --git a/trimsock.gd/addons/trimsock.gd/reader.gd b/trimsock.gd/addons/trimsock.gd/reader.gd index d3fe7c2..6cc7b3c 100644 --- a/trimsock.gd/addons/trimsock.gd/reader.gd +++ b/trimsock.gd/addons/trimsock.gd/reader.gd @@ -1,16 +1,38 @@ extends RefCounted class_name TrimsockReader +## Ingests and parses incoming trimsock data +## +## As data arrives from a stream, call [method ingest_test] or +## [method ingest_bytes] to prepare the data for parsing. Internally, the data +## will be buffered. +## [br][br] +## After ingestion, call [method read] to extract commands from the ingested +## data. Data that is parsed is immediately freed from the internal buffer. + + var _line_reader: _TrimsockLineReader = _TrimsockLineReader.new() var _line_parser: _TrimsockLineParser = _TrimsockLineParser.new() var _queued_raw: TrimsockCommand = null +## Ingest incoming text +## [br][br] +## Returns [constant OK] on success, or [constant ERR_OUT_OF_MEMORY] if the +## internal buffer can't store the data. func ingest_text(text: String) -> Error: return _line_reader.ingest(text.to_utf8_buffer()) +## Ingest incoming binary data +## [br][br] +## Returns [constant OK] on success, or [constant ERR_OUT_OF_MEMORY] if the +## internal buffer can't store the data. func ingest_bytes(bytes: PackedByteArray) -> Error: return _line_reader.ingest(bytes) +## Try and extract a command from the ingested data +## [br][br] +## Returns a parsed command, or [code]null[/code] if no command is available +## yet. func read() -> TrimsockCommand: var command := _pop() if command: From f58b87c868cf9d81566b04e5fe73b264ce994e9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Thu, 30 Oct 2025 00:38:25 +0100 Subject: [PATCH 2/3] rest of everything --- trimsock.gd/addons/trimsock.gd/exchange.gd | 68 +++++++++++++++++++ .../id/incremental_id_generator.gd | 5 ++ .../trimsock.gd/id/random_id_generator.gd | 8 +++ .../addons/trimsock.gd/id_generator.gd | 6 ++ trimsock.gd/addons/trimsock.gd/reactor.gd | 60 ++++++++++++++++ .../trimsock.gd/reactors/client_reactor.gd | 18 +++++ .../reactors/tcp_client_reactor.gd | 9 +++ .../reactors/tcp_server_reactor.gd | 7 ++ .../reactors/tls_client_reactor.gd | 9 +++ 9 files changed, 190 insertions(+) diff --git a/trimsock.gd/addons/trimsock.gd/exchange.gd b/trimsock.gd/addons/trimsock.gd/exchange.gd index 8933f80..72248f6 100644 --- a/trimsock.gd/addons/trimsock.gd/exchange.gd +++ b/trimsock.gd/addons/trimsock.gd/exchange.gd @@ -1,6 +1,23 @@ extends RefCounted class_name TrimsockExchange +## Represents an exchange of commands, similar to a thread on forum +## +## Whenever a command is received by the [TrimsockReactor], it either creates +## a new exchange for it, or associates it to an already running exchange. +## [br][br] +## Exchanges are then used to send and receive data belonging to the same thread +## of commands. +## [br][br] +## Exchanges keep track of who sent the initial command, and may associate +## arbitrary data with the given sender's session using [method get_session] and +## [method set_session]. +## [br][br] +## Once an exchange sends or receives a message that concludes it ( e.g. an +## error response ), the exchange becomes closed. Once an exchange is closed, +## it won't send any further commands. + + var _source: Variant var _reactor: TrimsockReactor var _command: TrimsockCommand @@ -18,29 +35,42 @@ func _init(command: TrimsockCommand, source: Variant, reactor: TrimsockReactor): _reactor = reactor #region Properties +## Get the exchange's initiator func source() -> Variant: return _source +## Get the exchange's ID func id() -> String: return _command.exchange_id +## Get the session data associated with the exchange func session() -> Variant: return _reactor.get_session(_source) +## Set the session data associated with the exchange func set_session(data: Variant) -> void: _reactor.set_session(_source, data) +## Return true if the exchange has not been closed yet func is_open() -> bool: return _is_open +## Return true if the exchange can be replied to ( regardless if it's open ) func can_reply() -> bool: return _command.type != TrimsockCommand.Type.SIMPLE +## Close exchange, preventing it from sending further data +## [br][br] +## Note that the exchange is closed automatically when sending a closing +## command. func close() -> void: _is_open = false #endregion #region Write +## Send a command over the exchange +## [br][br] +## Returns true if the command was sent. func send(command: TrimsockCommand) -> bool: if not is_open(): return false @@ -48,6 +78,9 @@ func send(command: TrimsockCommand) -> bool: _reactor._write(_source, command) return true +## Send a command over the exchange and close it +## [br][br] +## Returns true if the command was sent. func send_and_close(command: TrimsockCommand) -> bool: if not send(command): return false @@ -55,6 +88,10 @@ func send_and_close(command: TrimsockCommand) -> bool: close() return true +## Send a reply over the exchange +## [br][br] +## The command will be changed if it's not a success response already. +## Returns true if the command was sent. func reply(command: TrimsockCommand) -> bool: if not can_reply() or not is_open(): return false @@ -67,6 +104,10 @@ func reply(command: TrimsockCommand) -> bool: close() return true +## Send an error response over the exchange +## [br][br] +## The command will be changed if it's not an error response already. +## Returns true if the command was sent. func fail(command: TrimsockCommand) -> bool: if not can_reply() or not is_open(): return false @@ -79,6 +120,10 @@ func fail(command: TrimsockCommand) -> bool: close() return true +## Send a stream command over the exchange +## [br][br] +## The command will be changed if it's not a stream command already. +## Returns true if the command was sent. func stream(command: TrimsockCommand) -> bool: if not can_reply() or not is_open(): return false @@ -90,6 +135,10 @@ func stream(command: TrimsockCommand) -> bool: send(command) return true +## Send a stream finish command over the exchange +## [br][br] +## The command will be changed if it's not a stream finish command already. +## Returns true if the command was sent. func stream_finish(command: TrimsockCommand) -> bool: if not can_reply() or not is_open(): return false @@ -103,6 +152,11 @@ func stream_finish(command: TrimsockCommand) -> bool: close() return true +## Send a reply or a simple command over the exchange +## [br][br] +## If the original command had an exchange ID, a success response will be sent. +## Otherwise, this method will fall back to a simple command. +## Returns true if the command was sent. func reply_or_send(command: TrimsockCommand) -> bool: if not is_open(): return false @@ -112,6 +166,11 @@ func reply_or_send(command: TrimsockCommand) -> bool: return true +## Send an error response or a simple command over the exchange +## [br][br] +## If the original command had an exchange ID, an error response will be sent. +## Otherwise, this method will fall back to a simple command. +## Returns true if the command was sent. func fail_or_send(command: TrimsockCommand) -> bool: if not is_open(): return false @@ -123,6 +182,10 @@ func fail_or_send(command: TrimsockCommand) -> bool: #endregion #region Read +## Push an incoming command into the exchange +## [br][br] +## This is called by [TrimsockReactor] when it receives a command that belongs +## to this exchange. func push(command: TrimsockCommand) -> void: match command.type: TrimsockCommand.Type.SUCCESS_RESPONSE,\ @@ -133,6 +196,11 @@ func push(command: TrimsockCommand) -> void: _queue.append(command) _on_command.emit(command) +## Get the next incoming command +## [br][br] +## Commands are queued when received. If there's already a command in the queue, +## it will be returned instantly. Otherwise, this method will wait for the next +## incoming command. func read() -> TrimsockCommand: while _queue.is_empty(): await _on_command diff --git a/trimsock.gd/addons/trimsock.gd/id/incremental_id_generator.gd b/trimsock.gd/addons/trimsock.gd/id/incremental_id_generator.gd index 2031985..5bb156f 100644 --- a/trimsock.gd/addons/trimsock.gd/id/incremental_id_generator.gd +++ b/trimsock.gd/addons/trimsock.gd/id/incremental_id_generator.gd @@ -1,10 +1,15 @@ extends TrimsockIDGenerator class_name IncrementalTrimsockIDGenerator +## Generates IDs by incrementing an internal counter +## +## The internal counter is converted to a hexadecimal string. + var _at := -1 +## Get the next ID func get_id() -> String: _at += 1 return "%x" % _at diff --git a/trimsock.gd/addons/trimsock.gd/id/random_id_generator.gd b/trimsock.gd/addons/trimsock.gd/id/random_id_generator.gd index dceebae..a47a260 100644 --- a/trimsock.gd/addons/trimsock.gd/id/random_id_generator.gd +++ b/trimsock.gd/addons/trimsock.gd/id/random_id_generator.gd @@ -1,8 +1,15 @@ extends TrimsockIDGenerator class_name RandomTrimsockIDGenerator +## Generates IDs by stringing together random characters from a predefined set +## of characters +## +## Uses an internal [RandomNumberGenerator] to pick characters. + +## Character set used for the individual characters in the ID var charset := "abcdeghijklmnopqrstuvwxyz" + "ABCDEFGHIJLKMNOPQRSTUVWXYZ" + "0123456789" +## Length of the ID, in characters var length := 8 var _rng := RandomNumberGenerator.new() @@ -13,6 +20,7 @@ func _init(p_length: int = 8, p_charset: String = ""): if p_charset: charset = p_charset +## Get the next ID func get_id() -> String: var id := "" for i in length: diff --git a/trimsock.gd/addons/trimsock.gd/id_generator.gd b/trimsock.gd/addons/trimsock.gd/id_generator.gd index 549a2e6..0655e88 100644 --- a/trimsock.gd/addons/trimsock.gd/id_generator.gd +++ b/trimsock.gd/addons/trimsock.gd/id_generator.gd @@ -1,5 +1,11 @@ extends RefCounted class_name TrimsockIDGenerator +## Base class for generating IDs +## +## Used by [TrimsockReactor] to generate exchange IDs. To implement a custom +## ID generator, extend this class and implement [method get_id]. + +## Get the next ID func get_id() -> String: return "" diff --git a/trimsock.gd/addons/trimsock.gd/reactor.gd b/trimsock.gd/addons/trimsock.gd/reactor.gd index 57f1095..b80bc29 100644 --- a/trimsock.gd/addons/trimsock.gd/reactor.gd +++ b/trimsock.gd/addons/trimsock.gd/reactor.gd @@ -1,6 +1,29 @@ extends RefCounted class_name TrimsockReactor +## Manages incoming and outgoing commands for multiple sources +## +## The reactor reads and ingests all incoming data during [method poll]. Once +## that's done, it parses incoming commands and delegates them to the +## appropriate command handler. These command handlers can be configured by +## calling [method on]. If there is no handler associated, the unknown command +## handler will be called, which can be configured using [method on_unknown]. +## [br][br] +## Note that every piece of incoming data belongs to a source. The reactor can +## be notified of a source being added or removed by calling [method attach] and +## [method detach]. While implementations in general do not require this, it is +## good practice. +## [br][br] +## The reactor can also be used to associate arbitrary session data to sources, +## by calling [method set_session]. Session data can be any value, and its +## meaning is entirely up to the user. +## [br][br] +## This base class by itself does not implement any kind of communication. That +## responsibility is up to individual implementations. To build a custom +## reactor, implement [method _poll] to grab and pass incoming data to [method +## _ingest], and [method _write] to send outgoing data. + + var _sources: Array = [] var _sessions: Dictionary = {} # source to session data var _readers: Dictionary = {} # source to reader @@ -10,10 +33,13 @@ var _unknown_handler: Callable = func(_cmd, _xchg): pass var _id_generator: TrimsockIDGenerator = RandomTrimsockIDGenerator.new(12) +## Emitted when a new source is attached to the reactor signal on_attach(source: Variant) +## Emitted when a known source is detached from the reactor signal on_detach(source: Variant) +## Poll all sources and process incoming data func poll() -> void: _poll() @@ -26,6 +52,9 @@ func poll() -> void: _handle(command, source) +## Send a command to the [param target] source +## [br][br] +## The returned exchange can be used for further commands. func send(target: Variant, command: TrimsockCommand) -> TrimsockExchange: # Send command _write(target, command) @@ -37,18 +66,31 @@ func send(target: Variant, command: TrimsockCommand) -> TrimsockExchange: return xchg +## Send a request command to the [param target] source +## [br][br] +## The returned exchange can be used for further commands. The [param command] +## will be modified if it's not a request already. func request(target: Variant, command: TrimsockCommand) -> TrimsockExchange: command.as_request() if not command.exchange_id: command.exchange_id = _id_generator.get_id() return send(target, command) +## Send a stream command to the [param target] source +## [br][br] +## The returned exchange can be used for further commands. The [param command] +## will be modified if it's not a stream already. func stream(target: Variant, command: TrimsockCommand) -> TrimsockExchange: command.as_stream() if not command.exchange_id: command.exchange_id = _id_generator.get_id() return send(target, command) +## Attach a source to the reactor +## [br][br] +## This explicitly notifies the reactor of the new source. While not required, +## this can enable specific implementations to optimize. If the source is +## already attached, nothing happens. func attach(source: Variant) -> void: if _sources.has(source): return @@ -57,6 +99,11 @@ func attach(source: Variant) -> void: _readers[source] = TrimsockReader.new() on_attach.emit(source) +## Detach a source to the reactor +## [br][br] +## This explicitly notifies the reactor of the source being freed. While not +## required, this can enable specific implementations to optimize, e.g. by +## freeing some resources. If the source is already detached, nothing happens. func detach(source: Variant) -> void: if not _sources.has(source): return @@ -66,19 +113,32 @@ func detach(source: Variant) -> void: _readers.erase(source) on_detach.emit(source) +## Set session data associated to a source func set_session(source: Variant, data: Variant) -> void: _sessions[source] = data +## Get session data associated to a source func get_session(source: Variant) -> Variant: return _sessions.get(source) +## Set the ID generator used to generate exchange IDs func set_id_generator(id_generator: TrimsockIDGenerator) -> void: _id_generator = id_generator +## Register a command handler +## [br][br] +## The [param handler] must accept a command and an exchange. Coroutines are +## supported. If a handler is already registered for the command, it will be +## replaced. func on(command_name: String, handler: Callable) -> TrimsockReactor: _handlers[command_name] = handler return self +## Register the unknown command handler +## [br][br] +## The [param handler] must accept a command and an exchange. Coroutines are +## supported. If an unknown command handler is already registered, it will be +## replaced. func on_unknown(handler: Callable) -> TrimsockReactor: _unknown_handler = handler return self diff --git a/trimsock.gd/addons/trimsock.gd/reactors/client_reactor.gd b/trimsock.gd/addons/trimsock.gd/reactors/client_reactor.gd index 8a58528..b704543 100644 --- a/trimsock.gd/addons/trimsock.gd/reactors/client_reactor.gd +++ b/trimsock.gd/addons/trimsock.gd/reactors/client_reactor.gd @@ -1,13 +1,31 @@ extends TrimsockReactor class_name TrimsockClientReactor +## A reactor that is connected to a single host source +## +## The client reactor implements convenience methods to send commands to a +## single host source. +## [br][br] +## By default, [TrimsockReactor] assumes that it may send commands to multiple +## sources. This can be cumbersome for implementing clients, where the reactor +## communicates with a single source. + +## Submit a command to host +## [br][br] +## See also: [method send] func submit(command: TrimsockCommand) -> TrimsockExchange: return send(_get_host(), command) +## Submit a request to host +## [br][br] +## See also: [method request] func submit_request(command: TrimsockCommand) -> TrimsockExchange: return request(_get_host(), command) +## Submit a stream to host +## [br][br] +## See also: [method stream] func submit_stream(command: TrimsockCommand) -> TrimsockExchange: return stream(_get_host(), command) diff --git a/trimsock.gd/addons/trimsock.gd/reactors/tcp_client_reactor.gd b/trimsock.gd/addons/trimsock.gd/reactors/tcp_client_reactor.gd index 8c2ac06..6599def 100644 --- a/trimsock.gd/addons/trimsock.gd/reactors/tcp_client_reactor.gd +++ b/trimsock.gd/addons/trimsock.gd/reactors/tcp_client_reactor.gd @@ -1,6 +1,15 @@ extends TrimsockClientReactor class_name TrimsockTCPClientReactor +## Client reactor communicating over TCP via [StreamPeerTCP] +## +## When creating a new instance, a [StreamPeerTCP] must be specified. This will +## be used as the host source, and will be polled every time when [method poll] +## is called. +## [br][br] +## See [TrimsockClientReactor] and [TrimsockReactor] for details. + + var _connection: StreamPeerTCP diff --git a/trimsock.gd/addons/trimsock.gd/reactors/tcp_server_reactor.gd b/trimsock.gd/addons/trimsock.gd/reactors/tcp_server_reactor.gd index 93bd3a1..21ff5dd 100644 --- a/trimsock.gd/addons/trimsock.gd/reactors/tcp_server_reactor.gd +++ b/trimsock.gd/addons/trimsock.gd/reactors/tcp_server_reactor.gd @@ -1,6 +1,13 @@ extends TrimsockReactor class_name TrimsockTCPServerReactor +## Reactor communicating over TCP via [TCPServer] +## +## It will accept incoming connections and poll them. +## [br][br] +## See [TrimsockClientReactor] and [TrimsockReactor] for details. + + var _server: TCPServer func _init(server: TCPServer): diff --git a/trimsock.gd/addons/trimsock.gd/reactors/tls_client_reactor.gd b/trimsock.gd/addons/trimsock.gd/reactors/tls_client_reactor.gd index af7bc87..8bbea29 100644 --- a/trimsock.gd/addons/trimsock.gd/reactors/tls_client_reactor.gd +++ b/trimsock.gd/addons/trimsock.gd/reactors/tls_client_reactor.gd @@ -1,6 +1,15 @@ extends TrimsockClientReactor class_name TrimsockTLSClientReactor +## Client reactor communicating securely over TCP via [StreamPeerTLS] +## +## When creating a new instance, a [StreamPeerTLS] must be specified. This will +## be used as the host source, and will be polled every time when [method poll] +## is called. +## [br][br] +## See [TrimsockClientReactor] and [TrimsockReactor] for details. + + var _connection: StreamPeerTLS From a6d6d455f64561e486359a48755e067a70f7e4c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Thu, 30 Oct 2025 00:43:09 +0100 Subject: [PATCH 3/3] bv --- trimsock.gd/addons/trimsock.gd/plugin.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trimsock.gd/addons/trimsock.gd/plugin.cfg b/trimsock.gd/addons/trimsock.gd/plugin.cfg index 3e8a335..a768d22 100644 --- a/trimsock.gd/addons/trimsock.gd/plugin.cfg +++ b/trimsock.gd/addons/trimsock.gd/plugin.cfg @@ -3,5 +3,5 @@ name="trimsock.gd" description="Godot implementation of the trimsock protocol" author="Tamás Gálffy" -version="0.13.1" +version="0.13.2" script="trimsock.gd"