From 3f0cdaca6822ad82759eda900e1098760cbb664c Mon Sep 17 00:00:00 2001 From: Devin Ivy Date: Thu, 9 Apr 2026 16:38:46 -0400 Subject: [PATCH] feat: cross-worker serialization for tuple, string, bytes Co-Authored-By: Claude Sonnet 4.6 --- src/shared/reconstruct.ts | 9 +++ test/fixtures/shared-new.ts | 9 +++ test/shared/cross-worker-new.test.ts | 83 ++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 test/fixtures/shared-new.ts create mode 100644 test/shared/cross-worker-new.test.ts diff --git a/src/shared/reconstruct.ts b/src/shared/reconstruct.ts index 7bfc1f6..dfd914f 100644 --- a/src/shared/reconstruct.ts +++ b/src/shared/reconstruct.ts @@ -1,6 +1,7 @@ import { SharedStruct } from './shared-struct.ts'; import { Bytes } from './bytes.ts'; import { SharedString } from './string.ts'; +import { Tuple } from './tuple.ts'; const SHARED = Symbol.for('moroutine.shared'); @@ -20,6 +21,10 @@ export function serializeArg(arg: unknown): unknown { } return { __shared__: 'SharedStruct', fields: serializedFields }; } + if (data.tag === 'Tuple') { + const serializedElements = (data.elements as any[]).map((el: any) => serializeStructField(el)); + return { __shared__: 'Tuple', elements: serializedElements }; + } return { __shared__: data.tag, buffer: data.buffer, byteOffset: data.byteOffset, ...(data.size !== undefined && { size: data.size }), ...(data.maxBytes !== undefined && { maxBytes: data.maxBytes }) }; } return arg; @@ -55,6 +60,10 @@ export function deserializeArg(arg: unknown): unknown { } return new SharedStruct(reconstructed as any); } + if (data.__shared__ === 'Tuple') { + const elements = (data.elements as unknown[]).map((el) => deserializeArg(el)); + return new Tuple(elements as any); + } const typedData = data as { __shared__: string; buffer: SharedArrayBuffer; byteOffset: number }; const ctor = registry.get(typedData.__shared__); if (ctor) return new ctor(typedData.buffer, typedData.byteOffset); diff --git a/test/fixtures/shared-new.ts b/test/fixtures/shared-new.ts new file mode 100644 index 0000000..9946627 --- /dev/null +++ b/test/fixtures/shared-new.ts @@ -0,0 +1,9 @@ +import { mo } from 'moroutine'; + +export const readValue = mo(import.meta, (s: any): unknown => { + return s.load(); +}); + +export const writeValue = mo(import.meta, (s: any, values: any): void => { + s.store(values); +}); diff --git a/test/shared/cross-worker-new.test.ts b/test/shared/cross-worker-new.test.ts new file mode 100644 index 0000000..0cf39fb --- /dev/null +++ b/test/shared/cross-worker-new.test.ts @@ -0,0 +1,83 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { workerPool, shared, int32, string, bool, bytes } from 'moroutine'; +import { readValue, writeValue } from '../fixtures/shared-new.ts'; + +describe('new types across workers', () => { + it('struct via shared() works across workers', async () => { + const point = shared({ x: int32, y: int32 }); + point.store({ x: 1, y: 2 }); + + const pool = workerPool(1); + try { + const result = await pool(readValue(point)); + assert.deepEqual(result, { x: 1, y: 2 }); + } finally { + pool[Symbol.dispose](); + } + }); + + it('worker can write to struct via shared()', async () => { + const point = shared({ x: int32, y: int32 }); + + const pool = workerPool(1); + try { + await pool(writeValue(point, { x: 10, y: 20 })); + assert.deepEqual(point.load(), { x: 10, y: 20 }); + } finally { + pool[Symbol.dispose](); + } + }); + + it('tuple via shared() works across workers', async () => { + const t = shared([int32, int32]); + t.store([1, 2]); + + const pool = workerPool(1); + try { + const result = await pool(readValue(t)); + assert.deepEqual(result, [1, 2]); + } finally { + pool[Symbol.dispose](); + } + }); + + it('string via shared() works across workers', async () => { + const s = shared(string(32)); + s.store('hello'); + + const pool = workerPool(1); + try { + const result = await pool(readValue(s)); + assert.equal(result, 'hello'); + } finally { + pool[Symbol.dispose](); + } + }); + + it('bytes via shared() works across workers', async () => { + const b = shared(bytes(4)); + b.store(new Uint8Array([1, 2, 3, 4])); + + const pool = workerPool(1); + try { + const result = await pool(readValue(b)) as Uint8Array; + assert.deepEqual([...result], [1, 2, 3, 4]); + } finally { + pool[Symbol.dispose](); + } + }); + + it('nested struct with string across workers', async () => { + const entity = shared({ name: string(16), hp: int32 }); + entity.store({ name: 'goblin', hp: 50 }); + + const pool = workerPool(1); + try { + const result = await pool(readValue(entity)); + assert.deepEqual(result, { name: 'goblin', hp: 50 }); + } finally { + pool[Symbol.dispose](); + } + }); +}); -- 2.51.2