From 291b1f988ac63bc30b6161887e8c9c310f12d18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Fri, 24 Oct 2025 12:51:51 +0200 Subject: [PATCH 1/9] fix(js): Clear command flags on response --- trimsock.js/package.json | 2 +- .../packages/trimsock-bun/package.json | 2 +- .../packages/trimsock-js/lib/reactor.ts | 71 ++++++---- trimsock.js/packages/trimsock-js/package.json | 2 +- .../trimsock-js/spec/reactor/exchange.spec.ts | 133 ++++++++++++++++-- .../packages/trimsock-node/package.json | 2 +- 6 files changed, 171 insertions(+), 41 deletions(-) diff --git a/trimsock.js/package.json b/trimsock.js/package.json index 9b86ec8..8cd4b44 100644 --- a/trimsock.js/package.json +++ b/trimsock.js/package.json @@ -1,7 +1,7 @@ { "name": "trimsock", "private": true, - "version": "0.18.0", + "version": "0.18.1", "type": "module", "scripts": { "docs": "typedoc", diff --git a/trimsock.js/packages/trimsock-bun/package.json b/trimsock.js/packages/trimsock-bun/package.json index 1017ea6..bc2ab59 100644 --- a/trimsock.js/packages/trimsock-bun/package.json +++ b/trimsock.js/packages/trimsock-bun/package.json @@ -1,6 +1,6 @@ { "name": "@foxssake/trimsock-bun", - "version": "0.18.0", + "version": "0.18.1", "module": "dist/index.js", "type": "module", "author": "Tamás Gálffy", diff --git a/trimsock.js/packages/trimsock-js/lib/reactor.ts b/trimsock.js/packages/trimsock-js/lib/reactor.ts index ca4602c..2ceb8d9 100644 --- a/trimsock.js/packages/trimsock-js/lib/reactor.ts +++ b/trimsock.js/packages/trimsock-js/lib/reactor.ts @@ -304,6 +304,11 @@ export class ReactorExchange implements Exchange { requestId: this.generateExchangeId(), }; + req.isSuccessResponse = undefined; + req.isErrorResponse = undefined; + req.isStreamChunk = undefined; + req.isStreamEnd = undefined; + return this.send(req); } @@ -315,15 +320,19 @@ export class ReactorExchange implements Exchange { this.requireRepliable(); this.requireOpen(); - this.write( - { - ...what, - name: "", - requestId: this.command?.requireId(), - isSuccessResponse: true, - }, - this.source, - ); + const reply = { + ...what, + name: "", + requestId: this.command?.requireId(), + isSuccessResponse: true, + }; + + reply.isRequest = undefined; + reply.isErrorResponse = undefined; + reply.isStreamChunk = undefined; + reply.isStreamEnd = undefined; + + this.write(reply, this.source); this.close(); } @@ -336,15 +345,19 @@ export class ReactorExchange implements Exchange { this.requireRepliable(); this.requireOpen(); - this.write( - { - ...what, - name: "", - requestId: this.command?.requireId(), - isErrorResponse: true, - }, - this.source, - ); + const reply = { + ...what, + name: "", + requestId: this.command?.requireId(), + isErrorResponse: true, + }; + + reply.isRequest = undefined; + reply.isSuccessResponse = undefined; + reply.isStreamChunk = undefined; + reply.isStreamEnd = undefined; + + this.write(reply, this.source); } failOrSend(what: CommandSpec): void { @@ -356,15 +369,19 @@ export class ReactorExchange implements Exchange { this.requireRepliable(); this.requireOpen(); - this.write( - { - ...what, - name: "", - streamId: this.command?.requireId(), - isStreamChunk: true, - }, - this.source, - ); + const stream = { + ...what, + name: "", + streamId: this.command?.requireId(), + isStreamChunk: true, + }; + + stream.isRequest = undefined; + stream.isSuccessResponse = undefined; + stream.isErrorResponse = undefined; + stream.isStreamEnd = undefined; + + this.write(stream, this.source); } finishStream(): void { diff --git a/trimsock.js/packages/trimsock-js/package.json b/trimsock.js/packages/trimsock-js/package.json index 3416e92..8b1e903 100644 --- a/trimsock.js/packages/trimsock-js/package.json +++ b/trimsock.js/packages/trimsock-js/package.json @@ -1,6 +1,6 @@ { "name": "@foxssake/trimsock-js", - "version": "0.18.0", + "version": "0.18.1", "module": "dist/index.js", "type": "module", "author": "Tamás Gálffy", diff --git a/trimsock.js/packages/trimsock-js/spec/reactor/exchange.spec.ts b/trimsock.js/packages/trimsock-js/spec/reactor/exchange.spec.ts index 89357c7..5bf0f3e 100644 --- a/trimsock.js/packages/trimsock-js/spec/reactor/exchange.spec.ts +++ b/trimsock.js/packages/trimsock-js/spec/reactor/exchange.spec.ts @@ -4,7 +4,7 @@ import { TestingExchange } from "./testing.exchange.js"; describe("Exchange", () => { describe("ReadableExchange", () => { - describe("onCommand", () => { + describe("onCommand()", () => { test("should return command", async () => { const exchange = new TestingExchange( "1", @@ -31,7 +31,7 @@ describe("Exchange", () => { expect(async () => await exchange.onCommand()).toThrow(); }); }); - describe("onReply", () => { + describe("onReply()", () => { test("should return response", async () => { const exchange = new TestingExchange( "1", @@ -94,7 +94,7 @@ describe("Exchange", () => { }); }); - describe("onStream", () => { + describe("onStream()", () => { test("should return chunk", async () => { const exchange = new TestingExchange( "1", @@ -225,7 +225,7 @@ describe("Exchange", () => { }); }); - describe("chunks", () => { + describe("chunks()", () => { test("should return chunks", async () => { const exchange = new TestingExchange( "1", @@ -360,7 +360,7 @@ describe("Exchange", () => { }); describe("WritableExchange", () => { - describe("send", () => { + describe("send()", () => { test("should send data", () => { const exchange = new TestingExchange(); exchange.send({ name: "command", text: "data" }); @@ -370,7 +370,7 @@ describe("Exchange", () => { }); }); - describe("request", () => { + describe("request()", () => { test("should send request with generated id", () => { const exchange = new TestingExchange(); exchange.request({ @@ -384,9 +384,31 @@ describe("Exchange", () => { expect(sent.isRequest).toBeTrue(); expect(sent.requestId).not.toBeNull(); }); + + test("should reset command flags", () => { + const exchange = new TestingExchange(); + exchange.request({ + name: "command", + text: "data", + isSuccessResponse: true, + isErrorResponse: true, + isStreamChunk: true, + isStreamEnd: true, + }); + + const sent = exchange.outbox[0][1]; + expect(sent.name).toEqual("command"); + expect(sent.text).toEqual("data"); + expect(sent.isRequest).toBeTrue(); + expect(sent.isSuccessResponse).toBeFalsy(); + expect(sent.isErrorResponse).toBeFalsy(); + expect(sent.isStreamChunk).toBeFalsy(); + expect(sent.isStreamEnd).toBeFalsy(); + expect(sent.requestId).not.toBeNull(); + }); }); - describe("reply", () => { + describe("reply()", () => { test("should reply to request", () => { const exchange = new TestingExchange( "1", @@ -411,6 +433,35 @@ describe("Exchange", () => { ], ]); }); + + test("should reset flags", () => { + const exchange = new TestingExchange( + "1", + new Command({ + name: "request", + text: "", + requestId: "1234", + isRequest: true, + }), + ); + exchange.reply({ + text: "foo", + isErrorResponse: true, + isStreamChunk: true, + isStreamEnd: true, + }); + + const sent = exchange.outbox[0][1]; + expect(sent.name).toEqual(""); + expect(sent.text).toEqual("foo"); + expect(sent.isRequest).toBeFalsy(); + expect(sent.isSuccessResponse).toBeTrue(); + expect(sent.isErrorResponse).toBeFalsy(); + expect(sent.isStreamChunk).toBeFalsy(); + expect(sent.isStreamEnd).toBeFalsy(); + expect(sent.requestId).toBe("1234"); + }); + test("should reply to stream", () => { const exchange = new TestingExchange( "1", @@ -459,7 +510,7 @@ describe("Exchange", () => { }); }); - describe("fail", () => { + describe("fail()", () => { test("should reply failure to request", () => { const exchange = new TestingExchange( "1", @@ -484,6 +535,36 @@ describe("Exchange", () => { ], ]); }); + + test("should reset flags", () => { + const exchange = new TestingExchange( + "1", + new Command({ + name: "request", + text: "", + requestId: "1234", + isRequest: true, + }), + ); + exchange.fail({ + text: "error", + isRequest: true, + isSuccessResponse: true, + isStreamChunk: true, + isStreamEnd: true, + }); + + const sent = exchange.outbox[0][1]; + expect(sent.name).toEqual(""); + expect(sent.text).toEqual("error"); + expect(sent.isRequest).toBeFalsy(); + expect(sent.isSuccessResponse).toBeFalsy(); + expect(sent.isErrorResponse).toBeTrue(); + expect(sent.isStreamChunk).toBeFalsy(); + expect(sent.isStreamEnd).toBeFalsy(); + expect(sent.requestId).toBe("1234"); + }); + test("should reply failure to stream", () => { const exchange = new TestingExchange( "1", @@ -532,7 +613,7 @@ describe("Exchange", () => { }); }); - describe("stream", () => { + describe("stream()", () => { test("should stream with stream id", () => { const exchange = new TestingExchange( "1", @@ -556,6 +637,36 @@ describe("Exchange", () => { ], ]); }); + + test("should reset flags", () => { + const exchange = new TestingExchange( + "1", + new Command({ + name: "stream", + text: "", + streamId: "1234", + isStreamChunk: true, + }), + ); + exchange.stream({ + text: "foo", + isRequest: true, + isSuccessResponse: true, + isErrorResponse: true, + isStreamEnd: true, + }); + + const sent = exchange.outbox[0][1]; + expect(sent.name).toEqual(""); + expect(sent.text).toEqual("foo"); + expect(sent.isRequest).toBeFalsy(); + expect(sent.isSuccessResponse).toBeFalsy(); + expect(sent.isErrorResponse).toBeFalsy(); + expect(sent.isStreamChunk).toBeTrue(); + expect(sent.isStreamEnd).toBeFalsy(); + expect(sent.streamId).toBe("1234"); + }); + test("should stream to request", () => { const exchange = new TestingExchange( "1", @@ -602,7 +713,7 @@ describe("Exchange", () => { }); }); - describe("finish stream", () => { + describe("finishStream()", () => { test("should finish stream", () => { const exchange = new TestingExchange( "1", @@ -626,6 +737,7 @@ describe("Exchange", () => { ], ]); }); + test("should finish to request", () => { const exchange = new TestingExchange( "1", @@ -649,6 +761,7 @@ describe("Exchange", () => { ], ]); }); + test("should throw without id", () => { const exchange = new TestingExchange( "1", diff --git a/trimsock.js/packages/trimsock-node/package.json b/trimsock.js/packages/trimsock-node/package.json index c6a9132..d4ce803 100644 --- a/trimsock.js/packages/trimsock-node/package.json +++ b/trimsock.js/packages/trimsock-node/package.json @@ -1,6 +1,6 @@ { "name": "@foxssake/trimsock-node", - "version": "0.18.0", + "version": "0.18.1", "module": "./dist/index.js", "type": "module", "author": "Tamás Gálffy", From 92599bf8489b1887c3ed6f82fbe1aad6be808a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Mon, 27 Oct 2025 13:35:05 +0100 Subject: [PATCH 2/9] mitata perf test --- trimsock.js/bun.lockb | Bin 20656 -> 21352 bytes .../packages/trimsock-js/lib/reader.ts | 2 +- trimsock.js/packages/trimsock-js/package.json | 4 +++- .../packages/trimsock-js/spec/reader.perf.ts | 20 ++++++++++++++++++ 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 trimsock.js/packages/trimsock-js/spec/reader.perf.ts diff --git a/trimsock.js/bun.lockb b/trimsock.js/bun.lockb index e052c3540e76bb7783eaf86effc77cc1f0525958..9d389819304032585532f3a0d8e21c343a31e9b8 100644 GIT binary patch delta 2347 zcmds2drVVj6u;l44Ybk%qF5}2K6tcZTl?ZwTG~bNQA&Y58gR0pVw{G7iV<-Rl`K9M zH^?DdW@aXTY|+g4>P$9V7TAU}W@bZYV5o^kmn-o$a=C34(6j@#Fl#Bs^Ui;z>1zeKwlIa$eZI^;(Rj?*HK zA!i~FB5ROOirkC52-|y*Gmx7_es=vnbYua?!M7~UVo-4$F28R-G7jH2j7&ak*s-I1 zcU$wUZ@~D6`j+LXR|`yo)?RzV(fo_PQblX|<%!y3mx|S$-7x<9z=ed4#Iyo*--AoA z_<=bioKGl#HkBd?J4&!a14D_$>;!zStWe!UTL5jsSYWb3m`q6Gmnh(GrC)AA&+6fc z(knlJ+KYlVidwCpnK5P&LEAN>T}CY|*wXP7Awg@DG{q^@L>>lh^J>^3x=v4dV zNATQU*sJ!k5tvd}@blDAmg47wYUo7TqmB)v9ARB@bkiZY8fQA9Q*6&=JQPKDudgd!BCB+Kw|7 z?mvGxc>B(^g(n+oz^Bu|ldKTa!D?N!KAWN@imYp0+sM)7RrL+0UH>TV-+%Psg~_c` z3E6tr!qe%>iG!;XpB&KK*em-!yRPQFp6<$&@bR~^`af9OvRU))m6NZJ=i;AW+;qw` zat`$CqD&7{_%%Rnc9a?66n;&>a-u93>hYTg=kc2l3VoCnK$AWSn+?hEjlPOmAk7d3 zjd3GXWmUlyV=^(b!Zu?RI!r3KX$&zt7)((x=c?dMQ;0cW1Y2Xd$>7edVlD_~MVTAF z5qM}d$7=Gjcog|O+SqbnXCx9h;&IwtWfJp_t{V*O9FzfCCM@b*{B^x&@9R?VoCu& zg8E(1?@BLoqeeYgE`?-3q5>q?C=)3$sIN$}k%ID|2!3< zl}9SiR2Bs%T*$N~Z9yv)Be1G)r3lJJ2kdmaDp#R~MVm`OP)5=vqN9-O6^J}U0fNfI z3X#cC%GOj`66NI*bkL}x%@!kQ<0YW;bo+8pGmBPy353|qwNWgoO`B#R@YN8Up$+rl znn&AZK}{M(|79tnaE@(mAK57|RQaUf(BF{8NS;V-T95qgp{Srt2L$4jvop- z_0Cvp(Xech^w3GqhdF*Tz~Nnx%Nm_-TZzrVNw1{z_Ly0t!?w)ka&vtU@n(9YSMKL2 z*VYH71YX7*wo)6#OnL`Tk7sy>9#@ad_VmJWZ>CmyH!G~ClD3RzKb-9!gb{Bh8-riH znOV}SJ8-O7v$Ai?*R!LNm7p)zus$d)5Ay~U^!PP!qI^DIsDe++55+A`e!mK~``%UnVvllFFdmmfK}?keaM!;SQY!+=&qQ;3O{}59my{ZaN5HC%qXtUbmiZvb**jA~B!O%juvhoFZ9Ml?~oolzwEMr}UQ7nmRR-KhA&(Zxy8LNSj zuo32b9!0(YZueQC-01!;suQ^R2bKr3}=?2X3yzx%qG?Vpg6LO=GijZ$@tY E3l8?r`2YX_ delta 1928 zcmdT_YiOHg6#m|?iA~aE$%=DHn_JVQZQAvcq)S`cd`VoLwP}{LDsh(|UN&auM%7v9 zgwcv%i(|!lz)R^+(G6tc>cSAbG5s+pRHU#U?jmlZ!!gC`#8R<3pYtVI1OD`v2cGvi zm+w8_dEf8kwEXzy0ox5cjWNMC-c+p zw4_RCD&vxW2u_V$d2GH^lFB4$ETxkqBlrv0>%ilBNpgVqgDb%gf^FbVuoYaLXCrtc z>}Bk~2Ye;Zck(u@Q?O|0L!29}D z?OkXoRl9XZi{s2m)i(((p=uWjn%;{t^RLpjiCtt{cUwMZqzbd5)uQrFa+o_sAH8Xg z$}gGdqFIqYGf{;_k(W)`9hR5HXbG;l84y5=dgVFB5je3b|AjekM?Md;7Ek~gh_R}H z(hcx~_W>NZKX3PdS(rKg02>fy-j}40$}j!TkC5Gd_ijCHa3)CSG|>TPLO5u>D^3|0@#VXdaBs*Lc`A*?=Hz*<9oPe#}&w|BXa|=6x-aTESmt1v&{{_(gd)N?F7I%xRaT=+qwXb2LSfn z1^9rH2$mqgPRtUu3qcYW$v*xGa7S{i6=0u*706;M{T%j?E^PNFVQvFb0N29Xa6ddn zcwX_WQtJs&GF++UR&S=^aA@mRXqa1)8Q_L@0$e}W%cXIRwE+M6w&a=1;U9x#GZx50 zVJ^eOr>Fqd$Exd*W|qzhU$eC`}hLc#=w;9^j%x!s0$jGz^^Ps`4iLw{vuy- zjQ#vccz`qle0;w8!ZU>m3O48CIw!(dAYbt$fEc=VW{29p*3kK`a=toMQgkF@vlpiH z^*=&SEmoJv8WCvfXyUK1tfR9L>*m5(H~;qfYcA!Rx7Qvh4E|{2`?`G>ol9$xPBJOB z%EBMT=O6S$?V0ws*5dmquGr*pJ?&DgB1OYWQl2r=e9TIhm2&xnk$zDoa-N{PYNQXN zK@p>?(GZ^95Id8P7%b7;LDEsu#bau_QcpZ)7|Z)VPG( { + reader.ingest(commands[idx]); + do_not_optimize([...reader.commands()]); + idx = (idx + 1) & commands.length; +}) + +await run(); From d47b14a50ec33da64bcab0542f8bb009b2c32c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Tue, 28 Oct 2025 21:06:26 +0100 Subject: [PATCH 3/9] serialization perf test --- .../packages/trimsock-js/perf/commands.perf.ts | 18 ++++++++++++++++++ .../trimsock-js/{spec => perf}/reader.perf.ts | 0 .../packages/trimsock-js/tsconfig.build.json | 2 +- trimsock.js/packages/trimsock-js/tsconfig.json | 2 +- 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 trimsock.js/packages/trimsock-js/perf/commands.perf.ts rename trimsock.js/packages/trimsock-js/{spec => perf}/reader.perf.ts (100%) diff --git a/trimsock.js/packages/trimsock-js/perf/commands.perf.ts b/trimsock.js/packages/trimsock-js/perf/commands.perf.ts new file mode 100644 index 0000000..27e72a2 --- /dev/null +++ b/trimsock.js/packages/trimsock-js/perf/commands.perf.ts @@ -0,0 +1,18 @@ +import { Command, type CommandSpec } from "@lib/command.js"; +import { run, bench, do_not_optimize } from "mitata"; + +let idx = 0; +const commands: CommandSpec[] = [ + { name: "whereami", isRequest: true, requestId: "" }, + { name: "session/set-game-id", isRequest: true, requestId: "", params: [ "RghrnyJK0mUA7cW05fCKo" ]}, + { name: "lobby/create", isRequest: true, requestId: "", params: [ "noray://example.com:8890/Q9VKjXiAlwVK" ], kvParams: [["name", "Cool Lobby"]]}, + { name: "lobby/lock", isRequest: true, requestId: "", text: "YuDqpQovXvpc" }, + { name: "lobby/delete", isRequest: true, requestId: "", params: [ "YuDqpQovXvpc" ]} +]; + +bench("serialize commands", () => { + do_not_optimize(Command.serialize(commands[idx])) + idx = (idx + 1) % commands.length +}) + +await run(); diff --git a/trimsock.js/packages/trimsock-js/spec/reader.perf.ts b/trimsock.js/packages/trimsock-js/perf/reader.perf.ts similarity index 100% rename from trimsock.js/packages/trimsock-js/spec/reader.perf.ts rename to trimsock.js/packages/trimsock-js/perf/reader.perf.ts diff --git a/trimsock.js/packages/trimsock-js/tsconfig.build.json b/trimsock.js/packages/trimsock-js/tsconfig.build.json index b98d28d..d084c31 100644 --- a/trimsock.js/packages/trimsock-js/tsconfig.build.json +++ b/trimsock.js/packages/trimsock-js/tsconfig.build.json @@ -1,6 +1,6 @@ { "extends": "./tsconfig.json", - "exclude": ["spec/*"], + "exclude": ["spec/*", "perf/*"], "compilerOptions": { "noEmit": false diff --git a/trimsock.js/packages/trimsock-js/tsconfig.json b/trimsock.js/packages/trimsock-js/tsconfig.json index 40de8c4..1a4a4e5 100644 --- a/trimsock.js/packages/trimsock-js/tsconfig.json +++ b/trimsock.js/packages/trimsock-js/tsconfig.json @@ -31,5 +31,5 @@ "outDir": "./dist/" }, - "include": ["lib/**/*.ts", "spec/**/*.ts", "./index.ts"] + "include": ["lib/**/*.ts", "spec/**/*.ts", "perf/**/*.ts", "./index.ts"] } From e3b2ddc7f646a0c36d2ebdcbf56ece220bd5a10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Tue, 28 Oct 2025 21:43:28 +0100 Subject: [PATCH 4/9] add godot tests --- trimsock.gd/addons/trimsock.gd/command.gd | 22 ++++++------- trimsock.gd/addons/trimsock.gd/reader.gd | 8 ++--- trimsock.gd/tests/command.perf.gd | 27 ++++++++++++++++ trimsock.gd/tests/command.perf.gd.uid | 1 + trimsock.gd/tests/command.test.gd | 12 +++---- trimsock.gd/tests/reactor.test.gd | 38 +++++++++++------------ trimsock.gd/tests/reader.perf.gd | 28 +++++++++++++++++ trimsock.gd/tests/reader.perf.gd.uid | 1 + trimsock.gd/tests/reader.test.gd | 4 +-- 9 files changed, 99 insertions(+), 42 deletions(-) create mode 100644 trimsock.gd/tests/command.perf.gd create mode 100644 trimsock.gd/tests/command.perf.gd.uid create mode 100644 trimsock.gd/tests/reader.perf.gd create mode 100644 trimsock.gd/tests/reader.perf.gd.uid diff --git a/trimsock.gd/addons/trimsock.gd/command.gd b/trimsock.gd/addons/trimsock.gd/command.gd index d0b2521..27fe53b 100644 --- a/trimsock.gd/addons/trimsock.gd/command.gd +++ b/trimsock.gd/addons/trimsock.gd/command.gd @@ -4,7 +4,7 @@ class_name TrimsockCommand class Chunk: var text: String var is_quoted: bool - + static func quoted(p_text: String) -> Chunk: var chunk := Chunk.new() chunk.is_quoted = true @@ -16,7 +16,7 @@ class Chunk: chunk.is_quoted = false chunk.text = p_text return chunk - + static func of_text(p_text: String) -> Chunk: var chunk := Chunk.new() chunk.is_quoted = p_text.contains(" ") @@ -65,7 +65,7 @@ static func simple(name: String, text: String = "") -> TrimsockCommand: command.name = name if text: command.chunks.append(Chunk.of_text(text)) - + return command static func request(name: String, exchange_id: String = "") -> TrimsockCommand: @@ -105,20 +105,20 @@ static func stream_finish(name: String, exchange_id: String = "") -> TrimsockCom static func error_from(command: TrimsockCommand, name: String, data) -> TrimsockCommand: var result := TrimsockCommand.new() - + if not result.is_simple(): result.name = "" result.type = Type.ERROR_RESPONSE result.exchange_id = command.exchange_id else: result.name = name - + if typeof(data) == TYPE_ARRAY: for param in data: result.params.append(str(param)) else: result.chunks.append(Chunk.of_text(str(data))) - + return result static func unescape(what: String) -> String: @@ -285,7 +285,7 @@ func serialize_to_stream(out: StreamPeer) -> void: if is_empty() and not is_raw: out.put_u8(_ord("\n")) return - + # Space after name out.put_u8(_ord(" ")) @@ -308,11 +308,11 @@ func serialize_to_stream(out: StreamPeer) -> void: elif not kv_pairs.is_empty() or not kv_map.is_empty() or not params.is_empty(): # Fall back to params if no chunks var tokens := PackedStringArray() - + # Print params first for param in params: tokens.append(_autoquoted_chunk(param)) - + # Print kv-params, either from `kv_pairs`, or `kv_map` if not kv_pairs.is_empty(): for pair in kv_pairs: @@ -340,10 +340,10 @@ func equals(what) -> bool: if not command.name == name or \ not command.type == type: return false - + if not is_simple() and exchange_id != command.exchange_id: return false - + if not is_raw: return text == command.text else: diff --git a/trimsock.gd/addons/trimsock.gd/reader.gd b/trimsock.gd/addons/trimsock.gd/reader.gd index 48d00d4..d3fe7c2 100644 --- a/trimsock.gd/addons/trimsock.gd/reader.gd +++ b/trimsock.gd/addons/trimsock.gd/reader.gd @@ -23,11 +23,11 @@ func _pop() -> TrimsockCommand: var data_size := int(_queued_raw.text) if not _line_reader.has_data(data_size): return null - + _queued_raw.raw = _line_reader.read_data(data_size) _queued_raw.text = "" _queued_raw.chunks.clear() - + var result := _queued_raw _queued_raw = null return result @@ -36,13 +36,13 @@ func _pop() -> TrimsockCommand: var line := _line_reader.read_text() if not line: return null - + var command := _line_parser.parse(line) if command.is_raw: # Command is raw, we'll keep it in the queue until we read the binary # data for it _queued_raw = command - + # Try getting it immediately, in case we already have the data in buffer return _pop() diff --git a/trimsock.gd/tests/command.perf.gd b/trimsock.gd/tests/command.perf.gd new file mode 100644 index 0000000..c210688 --- /dev/null +++ b/trimsock.gd/tests/command.perf.gd @@ -0,0 +1,27 @@ +extends VestTest + +var commands := [ + TrimsockCommand.request("whereami"), + TrimsockCommand.request("session/set-game-id").with_params(["RghrnyJK0mUA7cW05fCKo"]), + TrimsockCommand.request("lobby/create").with_params([ "noray://example.com:8890/Q9VKjXiAlwVK" ]).with_kv_map({ "name": "Cool Lobby" }), + TrimsockCommand.request("lobby/lock").with_text("YuDqpQovXvpc"), + TrimsockCommand.request("lobby/delete").with_text("YuDqpQovXvpc") +] as Array[TrimsockCommand] + +func get_suite_name(): + return "TrimsockCommand" + +func suite(): + test("Serialization", func(): + var idx := 0 + var peer := StreamPeerBuffer.new() + + benchmark("Predefined commands", func(__): + commands[idx].serialize_to_stream(peer) + peer.data_array.clear() + idx = (idx + 1) % commands.size() + )\ + .with_duration(4.0)\ + .with_batch_size(512)\ + .run() + ) diff --git a/trimsock.gd/tests/command.perf.gd.uid b/trimsock.gd/tests/command.perf.gd.uid new file mode 100644 index 0000000..07c6ce5 --- /dev/null +++ b/trimsock.gd/tests/command.perf.gd.uid @@ -0,0 +1 @@ +uid://dskylbpiwu1nd diff --git a/trimsock.gd/tests/command.test.gd b/trimsock.gd/tests/command.test.gd index 311d2af..e684ead 100644 --- a/trimsock.gd/tests/command.test.gd +++ b/trimsock.gd/tests/command.test.gd @@ -1,7 +1,7 @@ extends VestTest func get_suite_name(): - return "Command" + return "TrimsockCommand" func suite(): define("serialize", func(): @@ -11,23 +11,23 @@ func suite(): check_serialized("raw", TrimsockCommand.simple("command").as_raw(), "\rcommand 0\n\n") check_serialized("raw quoted", TrimsockCommand.simple("command name").as_raw(), "\r\"command name\" 0\n\n") ) - + define("request-response", func(): check_serialized("request", TrimsockCommand.request("command", "1234"), "command?1234\n") check_serialized("success", TrimsockCommand.success_response("command", "1234"), "command.1234\n") check_serialized("error", TrimsockCommand.error_response("command", "1234"), "command!1234\n") check_serialized("stream", TrimsockCommand.stream_chunk("command", "1234"), "command|1234\n") - + check_serialized("success without name", TrimsockCommand.success_response("", "1234"), ".1234\n") check_serialized("error without name", TrimsockCommand.error_response("", "1234"), "!1234\n") check_serialized("stream without name", TrimsockCommand.stream_chunk("", "1234"), "|1234\n") ) - + define("multiparam", func(): check_serialized("simple params", TrimsockCommand.simple("command").with_params(["foo", "bar"]), "command foo bar\n") check_serialized("quote params", TrimsockCommand.simple("command").with_params(["foo bar", "quix"]), "command \"foo bar\" quix\n") ) - + define("kv-params", func(): check_serialized("simple", TrimsockCommand.simple("command").with_kv_pairs([TrimsockCommand.pair_of("foo", "bar")]), "command foo=bar\n") check_serialized("quoted", TrimsockCommand.simple("command").with_kv_pairs([TrimsockCommand.pair_of("foo bar", "quix baz")]), "command \"foo bar\"=\"quix baz\"\n") @@ -35,7 +35,7 @@ func suite(): check_serialized("value-quoted", TrimsockCommand.simple("command").with_kv_pairs([TrimsockCommand.pair_of("foo", "quix baz")]), "command foo=\"quix baz\"\n") check_serialized("with params", TrimsockCommand.simple("command").with_kv_pairs([TrimsockCommand.pair_of("foo", "bar")]).with_params(["foo", "bar"]), "command foo bar foo=bar\n") ) - + define("kv-map", func(): check_serialized("simple", TrimsockCommand.simple("command").with_kv_map({ "foo": "bar" }), "command foo=bar\n") check_serialized("quoted", TrimsockCommand.simple("command").with_kv_map({ "foo bar": "quix baz"}), "command \"foo bar\"=\"quix baz\"\n") diff --git a/trimsock.gd/tests/reactor.test.gd b/trimsock.gd/tests/reactor.test.gd index fb92864..a58e468 100644 --- a/trimsock.gd/tests/reactor.test.gd +++ b/trimsock.gd/tests/reactor.test.gd @@ -20,38 +20,38 @@ func suite(): var handler := func(cmd, xchg): commands.append(cmd) reactor.on("command", handler) - + reactor.ingest_text(some_source, "command foo\n") reactor.poll() - + expect_not_empty(commands) ) - + test("should handle unknown", func(): var commands := [] var handler := func(cmd, xchg): commands.append(cmd) reactor.send(some_source, TrimsockCommand.simple("error")) reactor.on_unknown(handler) - + reactor.ingest_text(some_source, "unknown foo\n") reactor.poll() - + expect_not_empty(commands, "No commands handled!") expect_not_empty(reactor.outbox, "No commands sent!") ) - + test("should handle unknown by default", func(): # Flimsy test: it should ensure that the default handler is not broken # The test only breaks when ran in debug, but might catch an issue or # two simply by being here - + reactor.ingest_text(some_source, "unknown foo\n") reactor.poll() - + ok() ) - + test("should route to exchange", func(): var commands := [] var handler := func(cmd: TrimsockCommand, xchg: TrimsockExchange): @@ -59,23 +59,23 @@ func suite(): while xchg.is_open(): commands.append(await xchg.read()) reactor.on("command", handler) - + reactor.ingest_text(some_source, "command|1 foo\ncommand|1 bar\n") reactor.poll() - + expect_equal(commands.size(), 2) ) - + test("should reply with handler return value", func(): var commands := [] var handler := func(cmd, xchg): commands.append(cmd) return TrimsockCommand.simple("response") reactor.on("command", handler) - + reactor.ingest_text(some_source, "command foo\n") reactor.poll() - + expect_equal(commands.size(), 1) expect_equal(reactor.outbox[0].target, some_source) expect_equal(reactor.outbox[0].command, TrimsockCommand.simple("response")) @@ -84,18 +84,18 @@ func suite(): test("should fill ID on request", func(): var command := TrimsockCommand.simple("request") command.exchange_id = "" - + reactor.request(some_source, command) - + expect(reactor.outbox[0].command.is_request(), "Command was not a request!") expect_not_empty(reactor.outbox[0].command.exchange_id, "Request ID was empty!") ) - test("should fill ID on stream", func(): + test("should fill ID on stream", func(): var command := TrimsockCommand.simple("stream", "foo") command.exchange_id = "" - + reactor.stream(some_source, command) - + expect(reactor.outbox[0].command.is_stream(), "Command was not a stream!") expect_not_empty(reactor.outbox[0].command.exchange_id, "Stream ID was empty!") ) diff --git a/trimsock.gd/tests/reader.perf.gd b/trimsock.gd/tests/reader.perf.gd new file mode 100644 index 0000000..26b5079 --- /dev/null +++ b/trimsock.gd/tests/reader.perf.gd @@ -0,0 +1,28 @@ +extends VestTest + +const COMMANDS := [ + "whereami?\n", + "session/set-game-id RghrnyJK0mUA7cW05fCKo\n", + "lobby/create? noray://example.com:8890/Q9VKjXiAlwVK name=\"Cool Lobby\"\n", + "lobby/lock? YuDqpQovXvpc", + "lobby/delete? YuDqpQovXvpc" +] + +func get_suite_name(): + return "TrimsockReader" + +func suite(): + var reader := TrimsockReader.new() + var idx := 0 + + test("Parsing", func(): + benchmark("Predefined commands", func(__): + reader.ingest_text(COMMANDS[idx]) + while reader.read() != null: + pass + idx = (idx + 1) % COMMANDS.size() + )\ + .with_duration(4.)\ + .with_batch_size(128)\ + .run() + ) diff --git a/trimsock.gd/tests/reader.perf.gd.uid b/trimsock.gd/tests/reader.perf.gd.uid new file mode 100644 index 0000000..3e43c8c --- /dev/null +++ b/trimsock.gd/tests/reader.perf.gd.uid @@ -0,0 +1 @@ +uid://brvun1xvnsvqx diff --git a/trimsock.gd/tests/reader.test.gd b/trimsock.gd/tests/reader.test.gd index 6e0cbc4..98ce24e 100644 --- a/trimsock.gd/tests/reader.test.gd +++ b/trimsock.gd/tests/reader.test.gd @@ -1,7 +1,7 @@ extends VestTest func get_suite_name(): - return "Reader" + return "TrimsockReader" var reader: TrimsockReader @@ -9,7 +9,7 @@ func suite(): on_case_begin.connect(func(__): reader = TrimsockReader.new() ) - + test("should read raw message", func(): ok() reader.ingest_text("\rcommand 4\na\ncd\n") From 5dac23ed598e9351bf719f323a85183b91e88537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Tue, 28 Oct 2025 22:38:17 +0100 Subject: [PATCH 5/9] setup perf run --- trimsock.js/package.json | 5 +++-- trimsock.js/packages/trimsock-js/package.json | 3 ++- trimsock.js/packages/trimsock-js/perf/perf.ts | 2 ++ 3 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 trimsock.js/packages/trimsock-js/perf/perf.ts diff --git a/trimsock.js/package.json b/trimsock.js/package.json index 8cd4b44..2d0e415 100644 --- a/trimsock.js/package.json +++ b/trimsock.js/package.json @@ -7,7 +7,8 @@ "docs": "typedoc", "format": "bun --filter '*' check --write --unsafe", "fmt": "bun format", - "build": "bun --filter '*' build" + "build": "bun --filter '*' build", + "perf": "bun --filter '*' perf" }, "workspaces": [ "packages/*", @@ -17,4 +18,4 @@ "typedoc": "^0.28.8", "typedoc-plugin-markdown": "^4.8.0" } -} \ No newline at end of file +} diff --git a/trimsock.js/packages/trimsock-js/package.json b/trimsock.js/packages/trimsock-js/package.json index 327733a..c4ee38c 100644 --- a/trimsock.js/packages/trimsock-js/package.json +++ b/trimsock.js/packages/trimsock-js/package.json @@ -9,7 +9,8 @@ "format": "biome format", "lint": "biome lint", "check": "biome check", - "build": "tsc -p tsconfig.build.json" + "build": "tsc -p tsconfig.build.json", + "perf": "bun perf/perf.ts" }, "devDependencies": { "@biomejs/biome": "1.9.4", diff --git a/trimsock.js/packages/trimsock-js/perf/perf.ts b/trimsock.js/packages/trimsock-js/perf/perf.ts new file mode 100644 index 0000000..ce1625b --- /dev/null +++ b/trimsock.js/packages/trimsock-js/perf/perf.ts @@ -0,0 +1,2 @@ +import "./commands.perf.ts"; +import "./reader.perf.ts"; From 9cfb722f249d12d0f0ff6615851c0a2a2c682b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Tue, 28 Oct 2025 22:38:28 +0100 Subject: [PATCH 6/9] fmt --- .../trimsock-js/perf/commands.perf.ts | 30 ++++++++++++++----- .../packages/trimsock-js/perf/reader.perf.ts | 12 ++++---- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/trimsock.js/packages/trimsock-js/perf/commands.perf.ts b/trimsock.js/packages/trimsock-js/perf/commands.perf.ts index 27e72a2..cbf4cf8 100644 --- a/trimsock.js/packages/trimsock-js/perf/commands.perf.ts +++ b/trimsock.js/packages/trimsock-js/perf/commands.perf.ts @@ -1,18 +1,34 @@ import { Command, type CommandSpec } from "@lib/command.js"; -import { run, bench, do_not_optimize } from "mitata"; +import { bench, do_not_optimize, run } from "mitata"; let idx = 0; const commands: CommandSpec[] = [ { name: "whereami", isRequest: true, requestId: "" }, - { name: "session/set-game-id", isRequest: true, requestId: "", params: [ "RghrnyJK0mUA7cW05fCKo" ]}, - { name: "lobby/create", isRequest: true, requestId: "", params: [ "noray://example.com:8890/Q9VKjXiAlwVK" ], kvParams: [["name", "Cool Lobby"]]}, + { + name: "session/set-game-id", + isRequest: true, + requestId: "", + params: ["RghrnyJK0mUA7cW05fCKo"], + }, + { + name: "lobby/create", + isRequest: true, + requestId: "", + params: ["noray://example.com:8890/Q9VKjXiAlwVK"], + kvParams: [["name", "Cool Lobby"]], + }, { name: "lobby/lock", isRequest: true, requestId: "", text: "YuDqpQovXvpc" }, - { name: "lobby/delete", isRequest: true, requestId: "", params: [ "YuDqpQovXvpc" ]} + { + name: "lobby/delete", + isRequest: true, + requestId: "", + params: ["YuDqpQovXvpc"], + }, ]; bench("serialize commands", () => { - do_not_optimize(Command.serialize(commands[idx])) - idx = (idx + 1) % commands.length -}) + do_not_optimize(Command.serialize(commands[idx])); + idx = (idx + 1) % commands.length; +}); await run(); diff --git a/trimsock.js/packages/trimsock-js/perf/reader.perf.ts b/trimsock.js/packages/trimsock-js/perf/reader.perf.ts index 75c6e1c..a939d2b 100644 --- a/trimsock.js/packages/trimsock-js/perf/reader.perf.ts +++ b/trimsock.js/packages/trimsock-js/perf/reader.perf.ts @@ -1,20 +1,20 @@ import { TrimsockReader } from "@lib/reader.js"; -import { run, bench, do_not_optimize } from "mitata"; +import { bench, do_not_optimize, run } from "mitata"; const reader = new TrimsockReader(); -let idx = 0 +let idx = 0; const commands = [ "whereami?\n", "session/set-game-id RghrnyJK0mUA7cW05fCKo\n", - "lobby/create? noray://example.com:8890/Q9VKjXiAlwVK name=\"Cool Lobby\"\n", + 'lobby/create? noray://example.com:8890/Q9VKjXiAlwVK name="Cool Lobby"\n', "lobby/lock? YuDqpQovXvpc", - "lobby/delete? YuDqpQovXvpc" -] + "lobby/delete? YuDqpQovXvpc", +]; bench("ingest and parse", () => { reader.ingest(commands[idx]); do_not_optimize([...reader.commands()]); idx = (idx + 1) & commands.length; -}) +}); await run(); From f5f8c09630339020755fa64ba281eba1703cd548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Tue, 28 Oct 2025 22:39:26 +0100 Subject: [PATCH 7/9] ci --- .github/workflows/ci.js.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.js.yml b/.github/workflows/ci.js.yml index f99a4cb..0232077 100644 --- a/.github/workflows/ci.js.yml +++ b/.github/workflows/ci.js.yml @@ -40,4 +40,12 @@ jobs: run: cd trimsock.js && bun install - name: Test run: cd trimsock.js && bun test - + perf: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + - name: Install + run: cd trimsock.js && bun install + - name: Test + run: cd trimsock.js && bun perf From 5abb068cbda32132bcc38e214b107b7fcaffdc7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Tue, 28 Oct 2025 22:44:52 +0100 Subject: [PATCH 8/9] ci --- .github/workflows/ci.gd.yml | 2 ++ .github/workflows/ci.js.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/ci.gd.yml b/.github/workflows/ci.gd.yml index f7c2871..685b0ac 100644 --- a/.github/workflows/ci.gd.yml +++ b/.github/workflows/ci.gd.yml @@ -29,6 +29,8 @@ jobs: - name: Run tests run: cd trimsock.gd && sh/test.sh perf: + needs: + - test strategy: fail-fast: false matrix: diff --git a/.github/workflows/ci.js.yml b/.github/workflows/ci.js.yml index 0232077..651e49f 100644 --- a/.github/workflows/ci.js.yml +++ b/.github/workflows/ci.js.yml @@ -42,6 +42,8 @@ jobs: run: cd trimsock.js && bun test perf: runs-on: ubuntu-latest + needs: + - test steps: - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 From 0022447cd7ed1533701e50c38e7fafd85521759d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tam=C3=A1s=20G=C3=A1lffy?= Date: Tue, 28 Oct 2025 22:45:17 +0100 Subject: [PATCH 9/9] bv --- trimsock.gd/addons/trimsock.gd/plugin.cfg | 2 +- trimsock.js/package.json | 4 ++-- trimsock.js/packages/trimsock-bun/package.json | 2 +- trimsock.js/packages/trimsock-js/package.json | 2 +- trimsock.js/packages/trimsock-node/package.json | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/trimsock.gd/addons/trimsock.gd/plugin.cfg b/trimsock.gd/addons/trimsock.gd/plugin.cfg index 14bb659..3e8a335 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.0" +version="0.13.1" script="trimsock.gd" diff --git a/trimsock.js/package.json b/trimsock.js/package.json index 2d0e415..72001fe 100644 --- a/trimsock.js/package.json +++ b/trimsock.js/package.json @@ -1,7 +1,7 @@ { "name": "trimsock", "private": true, - "version": "0.18.1", + "version": "0.18.2", "type": "module", "scripts": { "docs": "typedoc", @@ -18,4 +18,4 @@ "typedoc": "^0.28.8", "typedoc-plugin-markdown": "^4.8.0" } -} +} \ No newline at end of file diff --git a/trimsock.js/packages/trimsock-bun/package.json b/trimsock.js/packages/trimsock-bun/package.json index bc2ab59..f345475 100644 --- a/trimsock.js/packages/trimsock-bun/package.json +++ b/trimsock.js/packages/trimsock-bun/package.json @@ -1,6 +1,6 @@ { "name": "@foxssake/trimsock-bun", - "version": "0.18.1", + "version": "0.18.2", "module": "dist/index.js", "type": "module", "author": "Tamás Gálffy", diff --git a/trimsock.js/packages/trimsock-js/package.json b/trimsock.js/packages/trimsock-js/package.json index c4ee38c..72d0196 100644 --- a/trimsock.js/packages/trimsock-js/package.json +++ b/trimsock.js/packages/trimsock-js/package.json @@ -1,6 +1,6 @@ { "name": "@foxssake/trimsock-js", - "version": "0.18.1", + "version": "0.18.2", "module": "dist/index.js", "type": "module", "author": "Tamás Gálffy", diff --git a/trimsock.js/packages/trimsock-node/package.json b/trimsock.js/packages/trimsock-node/package.json index d4ce803..2688c8d 100644 --- a/trimsock.js/packages/trimsock-node/package.json +++ b/trimsock.js/packages/trimsock-node/package.json @@ -1,6 +1,6 @@ { "name": "@foxssake/trimsock-node", - "version": "0.18.1", + "version": "0.18.2", "module": "./dist/index.js", "type": "module", "author": "Tamás Gálffy",