diff --git a/big-feature-phase/notes/constraints.md b/big-feature-phase/notes/constraints.md deleted file mode 100644 index fefcef6..0000000 --- a/big-feature-phase/notes/constraints.md +++ /dev/null @@ -1,58 +0,0 @@ -# Constraints — In-Browser Swactor Runtime - -## Threading Model - -- **wasm-threads is mandatory** — the runtime uses SharedArrayBuffer + WebAssembly atomics for multi-worker parallelism. There is no single-threaded degraded mode for MVP. -- Browsers must serve pages with COOP/COEP headers: - - `Cross-Origin-Opener-Policy: same-origin` - - `Cross-Origin-Embedder-Policy: require-corp` -- Build requires nightly Rust + `-Z build-std=std,panic_abort` + target features `+atomics,+bulk-memory,+mutable-globals`. - -## Architecture Rules - -- **Platform abstractions live in core swactor** (`src/`), gated by `#[cfg(target_arch = "wasm32")]`. They do not belong in the wasm crate. -- **Do not add new modules** to `src/` — modify existing files only (TASK.md style rule). -- **Do not restructure** existing module boundaries. The abstraction is a thin layer (type aliases, cfg-gated imports), not a trait-based HAL. -- The browser crate (`crates/wasm-browser/` or evolved `crates/wasm/`) is a **thin wasm-bindgen shell**. All scheduling, routing, and actor logic stays in core Rust. - -## Actor Model - -- **Rust-only actors** — actors are written in Rust and compiled to wasm. JavaScript does not define actor behavior. -- JS interacts through the wasm-bindgen API: create runtime, spawn actors (by registered type), send messages, receive results. -- Actor types are registered at compile time via Rust generics, not dynamically from JS. - -## Performance Priorities - -- Maximize throughput: auto-scheduling via `setTimeout(0)` tight loop, not `requestAnimationFrame` (which caps at display refresh rate). -- Web Worker count defaults to `navigator.hardwareConcurrency` for full core utilization. -- Zero-copy where possible: SharedArrayBuffer eliminates serialization between workers. -- Minimize JS↔Wasm boundary crossings — batch operations where feasible. - -## Feature Scope - -- All core features that compile for wasm32: spawn, send, receive, tick, actor lifecycle, watching, extensions. -- swactor-std features (naming, groups, monitoring) should work if they compile. -- Transport: WebSocket adapter for distributed clusters. STUN/TURN (WebRTC) deferred to later. -- Features that require OS primitives not available in wasm (filesystem, raw TCP) are excluded. - -## Testing - -- Tests must pass on both native (`cargo test`) and wasm targets. -- Wasm tests use `wasm-pack test --headless --chrome` or Node.js with `--experimental-wasm-threads`. -- No test-only code paths that diverge native vs wasm behavior — if it works differently, it's a bug. -- Prefer scenario tests over structural tests (per project testing rules). - -## Dependencies - -- `web-time` — drop-in replacement for `std::time::Instant` on wasm32 -- `wasm-bindgen` + `js-sys` + `web-sys` — browser API bindings (in the wasm crate only, not core) -- `gloo-timers` — optional, for ergonomic setTimeout/setInterval -- No new dependencies in core swactor beyond `web-time` (which is no-op on native) - -## What We Don't Do - -- No async/await runtime (tokio, async-std) — swactor is synchronous tick-based -- No Emscripten — target is `wasm32-unknown-unknown` only -- No WASI — browser environment, not server-side wasm -- No JS actor definitions — Rust only -- No polyfills for missing atomics — if SharedArrayBuffer isn't available, the runtime doesn't start diff --git a/big-feature-phase/notes/feature-stages/01-platform-abstraction.md b/big-feature-phase/notes/feature-stages/01-platform-abstraction.md deleted file mode 100644 index 9fa5954..0000000 --- a/big-feature-phase/notes/feature-stages/01-platform-abstraction.md +++ /dev/null @@ -1,110 +0,0 @@ -# Stage 1 — Platform Abstraction Layer - -**Priority**: P0 -**Depends on**: Nothing -**Enables**: All subsequent stages - -## Goal - -Make core swactor compile for `wasm32-unknown-unknown` with `+atomics,+bulk-memory,+mutable-globals` target features. No behavioral changes on native targets. No new modules — only modify existing files with `cfg` gates. - -## What Changes - -### 1. Instant → web_time::Instant - -**Files**: `src/runtime.rs`, `src/worker.rs` - -Add `web-time` to `[dependencies]` (it's a no-op on non-wasm targets). Replace: -```rust -use std::time::Instant; -``` -with: -```rust -use web_time::Instant; -``` - -`web-time` is a drop-in replacement. The `Instant` type has identical API on native (delegates to `std::time::Instant`) and on wasm32 (uses `performance.now()`). - -**Scope**: 2 `use` statements, 0 logic changes. - -### 2. Thread Parking → ParkHandle - -**Files**: `src/runtime.rs`, `src/delivery.rs`, `src/worker.rs` - -Currently uses `OnceLock` + `thread::park_timeout` + `Thread::unpark`. On wasm32, there's no `Thread` type accessible from Rust (workers are JS objects). But wasm-threads supports `Atomics.wait`/`Atomics.notify` through Rust's `std::sync::atomic` and futex primitives. - -Approach: Define a `ParkHandle` abstraction in `src/runtime.rs`: - -**Native**: -```rust -#[cfg(not(target_arch = "wasm32"))] -mod parking { - pub type ParkHandle = OnceLock; - pub fn register(handle: &ParkHandle) { handle.set(thread::current()).ok(); } - pub fn unpark(handle: &ParkHandle) { if let Some(t) = handle.get() { t.unpark(); } } - pub fn park_timeout_us(micros: u64) { thread::park_timeout(Duration::from_micros(micros)); } - pub fn yield_now() { thread::yield_now(); } -} -``` - -**Wasm32**: -```rust -#[cfg(target_arch = "wasm32")] -mod parking { - // Use an AtomicI32 as a futex-like signal. Atomics.wait blocks the - // wasm thread, Atomics.notify wakes it — same semantics as park/unpark. - pub struct ParkHandle(AtomicI32); - pub fn register(_: &ParkHandle) {} // no-op, handle is pre-initialized - pub fn unpark(handle: &ParkHandle) { - handle.0.store(1, Ordering::Release); - std::sync::atomic::fence(Ordering::SeqCst); - // Atomics.notify via core::arch::wasm32::memory_atomic_notify - core::arch::wasm32::memory_atomic_notify(&handle.0 as *const _ as *mut i32, 1); - } - pub fn park_timeout_us(micros: u64) { - // Atomics.wait via core::arch::wasm32::memory_atomic_wait32 - core::arch::wasm32::memory_atomic_wait32(ptr, 0, timeout_ns as i64); - } - pub fn yield_now() {} // no-op on wasm -} -``` - -**Scope**: New `parking` sub-module in `runtime.rs` (~30 lines), update `TickContext` to use `ParkHandle` instead of `OnceLock`, update `worker.rs` backoff loop. - -### 3. Thread Spawning — No Change in Core - -Thread spawning (`std::thread::Builder::new().spawn()`) only happens in `Runtime::run()` (line 351). This method will be overridden/wrapped by the browser crate — it won't be called on wasm32. We can gate it: - -```rust -#[cfg(not(target_arch = "wasm32"))] -pub fn run(self) -> Result { ... } -``` - -The wasm browser crate will provide its own `run()` that spawns Web Workers instead. - -### 4. Validate crossbeam Compilation - -Test that `crossbeam-queue` compiles for wasm32 with atomics. If it doesn't, provide a cfg-gated fallback in `src/channel.rs` using `VecDeque` wrapped in `Mutex`. (Likely not needed — crossbeam uses `core::sync::atomic` which works with wasm atomics.) - -### 5. Feature Flag - -Add a `wasm` feature to `Cargo.toml`: -```toml -[features] -wasm = ["web-time", "no_random"] - -[dependencies] -web-time = { version = "0.2", optional = true } -``` - -On wasm32, this feature enables `web-time` and `no_random` together. - -## Verification - -1. `cargo test` passes unchanged on native -2. `cargo build --target wasm32-unknown-unknown --features wasm -Z build-std=std,panic_abort` compiles (may need `+atomics` RUSTFLAGS) -3. No runtime behavior changes on native (confirm with existing test suite) - -## Estimated Scope - -~50-80 lines of new/changed code across 4 files. No new modules. diff --git a/big-feature-phase/notes/feature-stages/02-single-worker-browser.md b/big-feature-phase/notes/feature-stages/02-single-worker-browser.md deleted file mode 100644 index f48e655..0000000 --- a/big-feature-phase/notes/feature-stages/02-single-worker-browser.md +++ /dev/null @@ -1,129 +0,0 @@ -# Stage 2 — Single-Worker Browser Runtime - -**Priority**: P0 -**Depends on**: Stage 1 (platform abstraction) -**Enables**: Stage 3 (multi-worker), Stage 4 (feature parity) - -## Goal - -A working browser runtime on a single dedicated Web Worker with a JS API that supports spawning arbitrary (pre-registered) actor types, sending messages, receiving results, and auto-scheduled ticking. - -## What's Built - -### 1. New Crate: `crates/wasm-browser/` - -Replaces the PoC `crates/wasm/`. Structure: - -``` -crates/wasm-browser/ -├── Cargo.toml -├── src/ -│ ├── lib.rs # wasm-bindgen entry point -│ ├── runtime.rs # BrowserRuntime wrapping swactor::Runtime -│ ├── worker_glue.rs # Web Worker spawn/communication glue -│ └── scheduling.rs # Auto-tick scheduling (setTimeout loop) -├── js/ -│ ├── worker.js # Web Worker bootstrap script -│ └── index.js # Main thread API wrapper (optional) -└── tests/ - └── browser.rs # wasm-pack test suite -``` - -### 2. BrowserRuntime (wasm-bindgen API) - -```rust -#[wasm_bindgen] -pub struct BrowserRuntime { ... } - -#[wasm_bindgen] -impl BrowserRuntime { - #[wasm_bindgen(constructor)] - pub fn new(config: JsValue) -> Self; - - /// Spawn an actor by type name. Returns an opaque handle. - pub fn spawn(&mut self, type_name: &str, init: JsValue) -> JsValue; - - /// Send a message to an actor. - pub fn send(&self, addr: JsValue, msg: JsValue) -> bool; - - /// Drive one tick manually. - pub fn tick(&self); - - /// Start auto-scheduling. Calls tick() in a tight setTimeout(0) loop. - pub fn start(&self); - - /// Stop auto-scheduling. - pub fn stop(&self); - - /// Poll for results from a JS-visible inbox. - pub fn try_recv(&self) -> JsValue; - - /// Runtime stats snapshot. - pub fn stats(&self) -> JsValue; -} -``` - -### 3. Actor Registration - -Since Rust generics can't be dynamically dispatched from JS, actor types are registered at compile time: - -```rust -// In the user's wasm crate that depends on wasm-browser: -register_actors! { - "counter" => Counter, - "relay" => Relay, -} -``` - -This macro generates a factory map that `BrowserRuntime::spawn` indexes by string name. Each entry knows how to deserialize `JsValue` init args into the actor's constructor. - -### 4. Auto-Scheduling - -The runtime self-drives via a `setTimeout(0)` loop: - -```javascript -function tickLoop() { - runtime.tick(); - if (runtime.is_running()) { - setTimeout(tickLoop, 0); - } -} -``` - -This runs as fast as the browser allows (~4ms between ticks in most browsers, faster in Web Workers). The Rust side just calls `tick()` — no async runtime needed. - -### 5. Message Serialization - -JS ↔ Wasm boundary requires serialization. Options: -- **serde-wasm-bindgen**: Serialize Rust types to/from JsValue via serde. Zero-copy for simple types. -- **Manual**: Convert JsValue to bytes, route as `ByteMessage`. - -For Stage 2, use `serde-wasm-bindgen` for typed messages. Actor `Incoming` types must implement `serde::Deserialize`. - -### 6. Single Worker Architecture - -``` -┌─────────────────────┐ postMessage ┌──────────────────────┐ -│ Main Thread │ ◄──────────────────────► │ Web Worker │ -│ │ │ │ -│ JS application │ "spawn", "send", │ BrowserRuntime │ -│ calls API methods │ "tick", "recv" │ swactor::Runtime │ -│ │ │ (1 worker, tick()) │ -└─────────────────────┘ └──────────────────────┘ -``` - -The Web Worker runs the swactor runtime. The main thread sends commands via `postMessage`. This keeps the UI thread free. - -Alternative: run everything on the main thread (simpler, but blocks UI during tick). Support both modes — the user picks. - -## Verification - -1. `wasm-pack build --target web` succeeds -2. `wasm-pack test --headless --chrome` passes -3. Manual test: HTML page spawns actors, sends messages, receives results -4. Auto-scheduling: actors process messages continuously without manual tick calls -5. Performance: measure ticks/sec, compare to native single-threaded - -## Estimated Scope - -~300-500 lines of Rust + ~50 lines of JS glue. diff --git a/big-feature-phase/notes/feature-stages/03-multi-worker-parallelism.md b/big-feature-phase/notes/feature-stages/03-multi-worker-parallelism.md deleted file mode 100644 index 2e1a2a1..0000000 --- a/big-feature-phase/notes/feature-stages/03-multi-worker-parallelism.md +++ /dev/null @@ -1,118 +0,0 @@ -# Stage 3 — Multi-Worker Parallelism - -**Priority**: P1 -**Depends on**: Stage 1 (platform abstraction), Stage 2 (single-worker browser) -**Enables**: Stage 5 (transport) - -## Goal - -Spawn N Web Workers sharing the same swactor runtime via SharedArrayBuffer. Actors distributed across workers for true multi-core parallelism. Worker count configurable, defaults to `navigator.hardwareConcurrency`. - -## Architecture - -``` -┌─────────────┐ -│ Main Thread │ postMessage API -│ (JS app) │◄─────────────────────┐ -└──────┬───────┘ │ - │ spawn workers │ - ▼ │ -┌──────────────┐ SharedArrayBuffer ┌──────────────┐ -│ Web Worker 0 │◄───────────────────►│ Web Worker 1 │ -│ swactor │ (InboxRegistry, │ swactor │ -│ Worker #0 │ AddressMap, │ Worker #1 │ -│ tick loop │ transfer queues, │ tick loop │ -└──────────────┘ atomics) └──────────────┘ - ... - ┌──────────────┐ - │ Web Worker N │ - │ swactor │ - │ Worker #N │ - └──────────────┘ -``` - -## Key Challenge: Web Worker ↔ SharedArrayBuffer - -Web Workers can share `SharedArrayBuffer` instances. The swactor `Runtime` struct contains `Arc`-wrapped shared state (InboxRegistry, AddressMap, etc.). On native, this memory is shared via the process address space. On wasm with SharedArrayBuffer, it's shared via the underlying wasm linear memory. - -### How It Works - -1. **Main thread** creates the `Runtime` (allocates shared structures in wasm linear memory) -2. **Main thread** spawns N Web Workers, each loading the same `.wasm` module with `shared: true` memory -3. Each Web Worker receives a pointer (offset) to the `Runtime` shared state -4. Each Worker runs `worker.run(&tc, &is_running)` — the same tick loop as native -5. Crossbeam queues, atomics, Mutex/RwLock all work because the underlying memory is shared - -### wasm-bindgen + Web Workers - -The `web-sys::Worker` API creates workers. Each worker loads the same wasm module: - -```javascript -// worker.js (loaded by each Web Worker) -import init, { worker_entry } from './pkg/wasm_browser.js'; - -self.onmessage = async (e) => { - const { module, memory, worker_id, runtime_ptr } = e.data; - await init({ module, memory }); // shared memory! - worker_entry(worker_id, runtime_ptr); -}; -``` - -The Rust side: -```rust -#[wasm_bindgen] -pub fn worker_entry(worker_id: usize, runtime_ptr: u32) { - // Reconstruct the shared Runtime reference from the raw pointer - // Run the tick loop for this worker -} -``` - -### Memory Sharing - -With `wasm-threads`, the wasm linear memory is backed by a `SharedArrayBuffer`. All workers see the same memory. `Arc` increments are atomic operations on shared memory. Crossbeam queues use atomic compare-and-swap on shared memory. This is identical to how it works with OS threads. - -**Critical**: The wasm module must be compiled with `--shared-memory` and the `atomics` feature. The `Memory` import must use `shared: true`. - -## Park / Unpark - -The `ParkHandle` from Stage 1 uses `memory_atomic_wait32` / `memory_atomic_notify` — the wasm equivalents of futex. These work across Web Workers sharing the same memory. - -- `park_timeout_us(micros)` → `Atomics.wait(ptr, expected, timeout)` — blocks the Worker thread -- `unpark(handle)` → `Atomics.notify(ptr, 1)` — wakes one waiting Worker - -This gives the same backoff behavior as native: hot spin → yield → sleep with exponential backoff. - -## Worker Spawning - -Replace `std::thread::Builder::new().spawn()` in `Runtime::run()`: - -```rust -#[cfg(target_arch = "wasm32")] -pub fn run(self) -> Result { - let num_workers = self.config.num_threads.max(1); - let rt = Arc::new(self); - - for i in 0..num_workers { - let worker = web_sys::Worker::new("./worker.js")?; - worker.post_message(&JsValue::from(/* module, memory, worker_id, ptr */)); - } - // ... -} -``` - -## Placement - -The existing `Placement` strategy (load-aware round-robin) works unchanged — it reads `WorkerStats` atomics to pick the least-loaded worker. On wasm, these atomics are in SharedArrayBuffer, readable from any worker. - -## Verification - -1. Spawn runtime with `num_threads: 4`, verify 4 Web Workers created -2. Spawn actors, verify they're distributed across workers (check stats per-worker actor count) -3. Cross-worker message delivery works (actor on Worker 0 sends to actor on Worker 1) -4. Backoff/parking works (idle workers sleep, wake on new messages) -5. Throughput scales with worker count (benchmark: N workers vs 1 worker) -6. Shutdown: all workers terminate cleanly when `is_running` set to false - -## Estimated Scope - -~200-300 lines Rust + ~30 lines JS worker bootstrap. Most complexity is in the Web Worker ↔ shared memory plumbing, not the Rust logic (which is the same as native). diff --git a/big-feature-phase/notes/feature-stages/04-feature-parity.md b/big-feature-phase/notes/feature-stages/04-feature-parity.md deleted file mode 100644 index 2e49512..0000000 --- a/big-feature-phase/notes/feature-stages/04-feature-parity.md +++ /dev/null @@ -1,109 +0,0 @@ -# Stage 4 — Feature Parity - -**Priority**: P1 -**Depends on**: Stage 2 (single-worker) or Stage 3 (multi-worker) -**Enables**: Stage 5 (transport), Stage 6 (DX) - -## Goal - -All swactor features that are feasible in a browser environment work and are tested: actor watching, swactor-std extensions (naming, groups, monitoring), stats introspection. - -## Features to Enable - -### 1. Actor Watching / Death Notifications - -**Status**: Already in core (`src/runtime.rs`, `src/worker.rs`) - -Components: -- `ExitReason` enum — normal, panic, stopped -- `ActorExited` message — delivered to watchers -- `on_actor_exit()` default method on `ActorInterface` -- `WatchRegistry` — tracks who watches whom -- Phase 5b in `tick_once` — delivers death notifications - -**Wasm concern**: `WatchRegistry` is behind `Arc>`. With wasm-threads, `Mutex` works. The catch_unwind panic-safety model works identically in wasm. - -**Work needed**: Compile and test. Write wasm-specific tests for: -- Actor dies → watchers notified -- Watcher on different Web Worker receives notification (cross-worker) -- Panic in wasm actor → poisoned, watchers notified - -### 2. swactor-std Extension - -**Status**: Complete in `crates/std/` - -Components: -- `StdExtension` — wraps NameRegistry + MonitorRegistry + GroupRegistry -- `CtxMonitoring` — watch/unwatch actors -- `CtxNaming` — register/resolve actor names -- `CtxGroups` — join/leave groups, broadcast -- `RuntimeNaming` — resolve names from runtime handle -- `RuntimeGroups` — list groups, broadcast from outside -- Supervisor — restart policies - -**Wasm concern**: All use `Arc`, `Mutex`, `HashMap` — standard types that work with wasm-threads. No OS-specific dependencies. - -**Work needed**: -- Add `crates/std/` to wasm build verification -- Test naming: register name → resolve from another actor on different worker -- Test groups: broadcast reaches all group members across workers -- Test supervisor: child dies → supervisor restarts (factory-based) - -### 3. Stats and Introspection - -**Status**: In core (`src/stats.rs`) - -Components: -- `WorkerStats` — per-worker atomic counters (actors, depth, ticks, messages) -- `RuntimeStats` — aggregated snapshot -- `StatsHook` trait — called each tick with stats - -**Wasm concern**: Atomic counters work with wasm-threads. `StatsHook` is called in the tick loop — works. - -**Work needed**: -- Expose `RuntimeStats` to JS via `serde-wasm-bindgen` (JSON-serializable snapshot) -- Optional: periodic stats push to main thread via `postMessage` -- Test: spawn actors across workers, verify stats reflect correct counts - -### 4. Runtime Extensions - -**Status**: In core (`src/extension.rs`) - -The `RuntimeExtension` trait (`on_actor_death`, `cleanup_dead`, `as_any`) is called during phase 7 of tick_once. It uses `Arc` — works with wasm-threads. - -**Work needed**: Verify `StdExtension` as a `RuntimeExtension` compiles and works in wasm. Test the full lifecycle: actor death → extension notified → cleanup runs. - -## JS API Additions - -Extend the `BrowserRuntime` wasm-bindgen API: - -```rust -impl BrowserRuntime { - // Actor watching - pub fn watch(&self, watcher: JsValue, target: JsValue) -> bool; - - // Naming (if StdExtension enabled) - pub fn register_name(&self, name: &str, addr: JsValue) -> bool; - pub fn resolve_name(&self, name: &str) -> JsValue; - - // Groups - pub fn join_group(&self, group: &str, addr: JsValue) -> bool; - pub fn broadcast_group(&self, group: &str, msg: JsValue) -> bool; - - // Stats - pub fn stats(&self) -> JsValue; // JSON snapshot of RuntimeStats -} -``` - -## Verification - -1. All existing native tests for watching/std pass on wasm target -2. Cross-worker watching: actor on Worker 0 watches actor on Worker 1, Worker 1 actor dies → notification arrives -3. Naming works across workers: register on Worker 0, resolve on Worker 1 -4. Group broadcast reaches actors on all workers -5. Stats counters are accurate across workers (compare sum to expected) -6. `cargo test` still passes on native (no regressions) - -## Estimated Scope - -~100-200 lines of new wasm-bindgen API surface + ~200 lines of wasm tests. Core logic should work as-is once it compiles. diff --git a/big-feature-phase/notes/feature-stages/05-transport-foundation.md b/big-feature-phase/notes/feature-stages/05-transport-foundation.md deleted file mode 100644 index 3c3b96e..0000000 --- a/big-feature-phase/notes/feature-stages/05-transport-foundation.md +++ /dev/null @@ -1,107 +0,0 @@ -# Stage 5 — Transport Foundation - -**Priority**: P2 -**Depends on**: Stage 3 (multi-worker), Stage 4 (feature parity) -**Enables**: Browser nodes joining distributed swactor clusters - -## Goal - -Browser nodes connect to native swactor clusters via WebSocket. A browser can spawn actors that communicate with actors on server nodes. Foundation for future STUN/TURN (WebRTC DataChannel) for browser-to-browser direct connections. - -## Architecture - -``` -┌──────────────────┐ WebSocket ┌──────────────────┐ -│ Browser Node │ ◄────────────────────────► │ Server Node │ -│ (wasm runtime) │ │ (native runtime)│ -│ │ swactor wire protocol │ │ -│ Actor A ──────►─┤───── msg for Actor B ─────►├──► Actor B │ -│ │ │ │ -│ Actor C ◄───────┤◄──── msg for Actor C ─────┤───── Actor D │ -└──────────────────┘ └──────────────────┘ -``` - -## Existing Transport Infrastructure - -swactor already has a transport layer (feature-gated under `transport`): - -- `src/transport.rs` — `TransportRouter`, `CodecRegistry`, remote message routing -- `crates/distribution/` — SWIM protocol, gossip, cluster membership -- `crates/distribution/src/driver.rs` — `NodeDriver` bridges `DistributedNode` ↔ TCP -- Wire protocol: Ping/Ack/PingReq with piggyback bytes - -The browser transport needs to implement the same wire protocol over WebSocket instead of raw TCP. - -## What's Built - -### 1. WebSocket Transport Adapter - -A new module in `crates/wasm-browser/` (not in core): - -```rust -pub struct WebSocketTransport { - ws: web_sys::WebSocket, - // ... -} - -impl TransportAdapter for WebSocketTransport { - fn send(&self, dest: SocketAddr, data: &[u8]) -> Result<(), Error>; - fn recv(&self) -> Option<(SocketAddr, Vec)>; -} -``` - -Uses `web-sys::WebSocket` for the browser side. The server side uses a WebSocket server (e.g., `tokio-tungstenite`) that bridges to the existing TCP transport. - -### 2. WebSocket ↔ TCP Bridge (Server Side) - -A thin relay server that accepts WebSocket connections from browsers and translates to/from the TCP wire protocol: - -``` -Browser ──WebSocket──► Bridge Server ──TCP──► swactor-node -``` - -This bridge is a separate binary/service, not part of the runtime. It's a protocol translator. - -### 3. Browser Node Identity - -Browser nodes need: -- A unique node ID (derived from random or assigned by the cluster) -- An address for the cluster to route messages to (the WebSocket endpoint) -- Membership in the SWIM protocol (lightweight — browsers are "client" members that don't participate in failure detection) - -### 4. Cluster Registry Integration - -The existing ClusterRegistry (LWW-Register CRDT in `crates/distribution/src/registry.rs`) should work from browsers: -- `register_name` / `resolve_name` / `registry_events` — all work over the wire -- Piggyback payloads carry registry updates through the WebSocket connection - -## Key Design Decisions - -| Decision | Choice | Rationale | -|----------|--------|-----------| -| Transport protocol | WebSocket (binary frames) | Universal browser support, bidirectional, binary-capable | -| Membership role | Client member (no failure detection) | Browsers are ephemeral; full SWIM overhead not justified | -| Bridge architecture | Separate relay server | Keeps swactor-node unchanged; bridge handles WebSocket↔TCP | -| Wire format | Same as TCP transport | No translation needed beyond framing (WebSocket frames ↔ TCP stream) | - -## STUN/TURN Foundation (Future) - -This stage establishes the transport abstraction. Stage 5 itself is WebSocket only. Future work: - -- **WebRTC DataChannel** — direct browser-to-browser, requires STUN/TURN for NAT traversal -- The `TransportAdapter` trait from this stage will have a WebRTC implementation -- STUN/TURN server infrastructure is out of scope for this feature phase - -## Verification - -1. Browser node connects to server cluster via WebSocket -2. Actor on browser sends message to actor on server → received -3. Actor on server sends message to actor on browser → received -4. Browser appears in cluster membership (visible in dashboard) -5. ClusterRegistry: name registered on server → resolvable from browser -6. Browser disconnects → cluster detects and removes membership -7. Reconnection: browser reconnects → re-joins cluster, actor addresses still valid - -## Estimated Scope - -~500-800 lines for WebSocket transport adapter + bridge server. Builds heavily on existing distribution infrastructure. diff --git a/big-feature-phase/notes/feature-stages/06-developer-experience.md b/big-feature-phase/notes/feature-stages/06-developer-experience.md deleted file mode 100644 index dab3283..0000000 --- a/big-feature-phase/notes/feature-stages/06-developer-experience.md +++ /dev/null @@ -1,102 +0,0 @@ -# Stage 6 — Developer Experience - -**Priority**: P3 -**Depends on**: Stage 2 (single-worker browser), Stage 4 (feature parity) -**Enables**: Adoption, ecosystem growth - -## Goal - -Make it easy for Rust developers to build browser applications with swactor. TypeScript type safety, build tooling, and debugging support. - -## Features - -### 1. TypeScript Type Generation - -Derive TypeScript interfaces from Rust actor message types. When an actor defines: - -```rust -#[derive(Serialize, Deserialize)] -pub struct ChatMessage { - pub from: String, - pub text: String, -} -``` - -Generate: -```typescript -export interface ChatMessage { - from: string; - text: string; -} -``` - -**Approach**: Use `ts-rs` crate or a custom proc macro that emits `.d.ts` files during `wasm-pack build`. This gives TypeScript consumers compile-time type checking for messages. - -### 2. Build Tooling - -A `swactor-build` CLI or build script that wraps: -```bash -RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+mutable-globals' \ - cargo +nightly build --target wasm32-unknown-unknown \ - -Z build-std=std,panic_abort \ - --release -wasm-bindgen --target web --out-dir pkg/ ... -``` - -Into: -```bash -swactor-build --target browser -``` - -Features: -- Detects nightly toolchain, installs if missing -- Sets correct RUSTFLAGS for wasm-threads -- Runs wasm-bindgen with correct target -- Copies worker.js bootstrap into output -- Generates example HTML with correct COOP/COEP headers - -### 3. Example Project Template - -A `cargo generate` template or example project: - -``` -my-swactor-app/ -├── Cargo.toml -├── src/ -│ └── lib.rs # Define actors, register them -├── web/ -│ ├── index.html # With COOP/COEP headers -│ ├── main.js # Import wasm, create runtime, interact -│ └── worker.js # Web Worker bootstrap (auto-generated) -└── tests/ - └── browser.rs # wasm-pack tests -``` - -### 4. Browser Dev Tools Integration - -Expose runtime internals for debugging: - -- **Actor Inspector**: List all actors, their types, mailbox depths, message counts -- **Message Tracer**: Log messages between actors (opt-in, performance impact) -- **Performance Monitor**: Ticks/sec, messages/sec, worker utilization - -Implementation: A `console`-based logger that uses `web-sys::console` to output structured data. Optionally integrates with browser DevTools via custom formatters or a small React/Preact inspector panel. - -### 5. Documentation - -- Getting started guide -- Architecture overview for browser runtime -- Migration guide from native → browser (what works, what doesn't) -- API reference (generated from Rust doc comments) -- Example: Chat application with multiple browser tabs - -## Verification - -1. TypeScript types match Rust types (compile TS project against generated `.d.ts`) -2. Build tool produces working wasm output from example project -3. Template project builds and runs out of the box -4. Dev tools show actor state in browser console - -## Estimated Scope - -Variable — this stage is a collection of independent DX improvements. Each can be implemented and shipped independently. Total: ~500-1000 lines across Rust, JS, and documentation. diff --git a/big-feature-phase/notes/history.md b/big-feature-phase/notes/history.md deleted file mode 100644 index abc909b..0000000 --- a/big-feature-phase/notes/history.md +++ /dev/null @@ -1,7 +0,0 @@ -# Cycle History (append-only) - -## Cycle 0 — Research (complete) -Investigated Lunatic, wasmCloud, Actix-wasm attempts. Analyzed core swactor platform deps: 4 blockers (thread spawn, park/unpark, yield, Instant). User confirmed: performance-first, SharedArrayBuffer+wasm-threads, Rust-only actors, all feasible features, future STUN/TURN. Produced constraints.md, research_synthesis.md, 6 feature-stage docs. Next: Stage 1 platform abstraction. - -## Stage 1 — Platform Abstraction (complete) -Added `wasm` feature + `web-time` dep. Replaced `std::time::Instant` → `crate::Instant` (cfg-gated re-export). Gated `Runtime::run()` and `RuntimeHandle` for non-wasm. Key finding: only `thread::spawn` needed gating — park/unpark/yield/Mutex/RwLock/atomics/crossbeam all work on wasm32 with atomics. Cleaned unused Mutex import in worker.rs. Updated crates/wasm/ to use `wasm` feature. All native tests pass, wasm32 compilation succeeds. Files changed: Cargo.toml, src/lib.rs, src/runtime.rs, src/worker.rs, crates/wasm/Cargo.toml. diff --git a/big-feature-phase/notes/research_synthesis.md b/big-feature-phase/notes/research_synthesis.md deleted file mode 100644 index aaa5da6..0000000 --- a/big-feature-phase/notes/research_synthesis.md +++ /dev/null @@ -1,87 +0,0 @@ -# Research Synthesis — In-Browser Swactor Runtime - -## Ecosystem Landscape - -No established Rust actor framework runs natively in browsers. The closest projects: - -- **Lunatic** — Erlang-inspired Wasm actor runtime using wasmtime (server-side, not browser). Uses preemptive scheduling and work-stealing. Not applicable to browser constraints. -- **wasmCloud** — CNCF distributed actor platform. Single-threaded actors, NATS-backed lattice. Cloud/edge focus, no browser target. -- **Actix** — Tokio-dependent, network stack doesn't compile for wasm32. Community attempts to port failed due to `net2`/tokio dependencies. - -**Implication**: swactor would be the first Rust actor runtime with true multi-threaded browser execution via wasm-threads. This is a differentiated position. - -## Existing Work in This Codebase - -| Component | Status | Notes | -|-----------|--------|-------| -| `crates/wasm/` | Basic PoC | Hardcoded Counter/Relay actors, manual tick, u32-only messages | -| `no_random` feature | Working | Deterministic address generation without `getrandom` | -| `tick()` method | Working | Single-threaded tick for manual driving | -| Actor watching | In core | `ExitReason`, `ActorExited`, `on_actor_exit`, `WatchRegistry` | -| swactor-std | Complete | StdExtension, naming, groups, monitoring, supervisor | - -## Platform Dependencies Analysis - -### Works as-is with wasm-threads -- `crossbeam-queue` (ArrayQueue, SegQueue) — uses `core::sync::atomic` -- `std::sync::{Mutex, RwLock}` — stdlib uses futex on wasm with atomics -- `std::sync::atomic::*` — maps to wasm atomic instructions -- `Arc` — works with atomics -- `std::sync::OnceLock` — works with atomics - -### Requires platform abstraction (4 items) -1. `std::thread::spawn` → Web Worker via `web-sys::Worker` -2. `thread::park_timeout` / `Thread::unpark` → `Atomics.wait` / `Atomics.notify` -3. `thread::yield_now` → no-op (or `Atomics.wait(0)` as hint) -4. `std::time::Instant` → `web_time::Instant` (drop-in crate) - -## Priority Ranking - -### P0 — Must Have (enables everything else) - -1. **Platform abstraction layer** — cfg-gated replacements for thread spawn, park/unpark, yield, Instant. Core swactor compiles for wasm32 with atomics. -2. **Single-worker browser runtime** — Prove the runtime works in a browser. One Web Worker, auto-scheduled tick loop, generic JS API for spawn/send/recv. -3. **Multi-worker parallelism** — N Web Workers sharing runtime state via SharedArrayBuffer. Full utilization of browser CPU cores. - -### P1 — Should Have (full actor system) - -4. **Actor watching in browser** — Death notifications, exit reasons. Already in core, just needs to compile and pass wasm tests. -5. **swactor-std in browser** — Naming, groups, monitoring extensions. Compile and test for wasm32. -6. **Stats and introspection** — Runtime stats accessible from JS. Worker info, actor counts, message throughput. - -### P2 — Important (distributed peer) - -7. **WebSocket transport** — Adapter implementing swactor's transport traits over WebSocket. Browser node joins a distributed cluster. -8. **Browser-to-browser transport foundation** — WebRTC DataChannel scaffolding for future STUN/TURN. - -### P3 — Nice to Have (developer experience) - -9. **TypeScript type generation** — Derive TS interfaces from Rust actor message types. -10. **Build tooling** — wasm-pack wrapper script, example project template, CI configuration. -11. **Browser dev tools** — Actor inspector, message flow visualization, performance profiling. - -### P4 — Future (out of scope for this feature phase) - -12. **STUN/TURN integration** — Full NAT traversal for peer-to-peer browser connections. -13. **Hot code reload** — Swap actor implementations without restarting the runtime. -14. **Wasm component model** — Migrate from wasm-bindgen to component model when stabilized. - -## Key Design Decisions - -| Decision | Choice | Rationale | -|----------|--------|-----------| -| Threading model | SharedArrayBuffer + wasm-threads | swactor's shared-memory architecture (Arc, crossbeam queues, atomics) maps directly. postMessage isolation would require a rewrite. | -| Scheduling | `setTimeout(0)` tight loop | `requestAnimationFrame` caps at 60Hz. setTimeout(0) gives ~4ms resolution, sufficient for actor ticks. For rendering-coupled actors, RAF can be opt-in. | -| Actor definition | Rust only | Keeps the type system intact. JS actors would require dynamic dispatch and lose compile-time guarantees. | -| Platform abstraction approach | cfg-gated type aliases + inline functions | Minimal invasion. No trait-based HAL, no new modules. Just swap `std::time::Instant` → `web_time::Instant` etc. | -| Browser crate location | New `crates/wasm-browser/` | Clean separation from the existing PoC. The old `crates/wasm/` can be deprecated or kept as a minimal example. | - -## Risk Assessment - -| Risk | Likelihood | Impact | Mitigation | -|------|-----------|--------|------------| -| crossbeam-queue doesn't compile for wasm32+atomics | Low | High | Test early in Stage 1. Fallback: thin wrapper over `VecDeque` behind cfg. | -| COOP/COEP headers break third-party integrations | Medium | Medium | Document clearly. This is the standard trade-off for SharedArrayBuffer apps. | -| Nightly Rust + build-std is unstable | Medium | Medium | Pin to a known-good nightly. Revisit when wasm-threads stabilizes. | -| Web Worker spawn latency | Low | Low | Spawn workers eagerly at runtime init, not per-actor. | -| Browser compatibility gaps | Low | Medium | Target Chrome/Firefox/Safari latest. All support SharedArrayBuffer since 2021. | diff --git a/big-feature-phase/notes/state.md b/big-feature-phase/notes/state.md deleted file mode 100644 index df07bba..0000000 --- a/big-feature-phase/notes/state.md +++ /dev/null @@ -1,47 +0,0 @@ -## Current Task -Stage 1 — Platform Abstraction Layer -Step: Complete -Attempt: 1 of 3 - -## Key Files (read these first on resume) -- `big-feature-phase/TASK.md` — workflow rules -- `big-feature-phase/notes/constraints.md` — guardrails -- `big-feature-phase/notes/feature-stages/02-single-worker-browser.md` — Stage 2 spec -- `Cargo.toml` — `wasm` feature flag, `web-time` dep (lines 23, 30) -- `src/lib.rs` — platform-aware `Instant` re-export (lines 20-24) -- `src/runtime.rs` — cfg-gated `run()` (line 340) and `RuntimeHandle` (line 73) -- `crates/wasm/Cargo.toml` — now uses `features = ["wasm"]` -- `docs/development_history/in-browser/PLATFORM_ABSTRACTION.md` — what was done - -## Last Action & Result -Completed Stage 1 (Platform Abstraction Layer): -- Added `web-time` dep + `wasm` feature (`no_random` + `web-time`) -- Replaced `std::time::Instant` → `crate::Instant` in runtime.rs, worker.rs -- cfg-gated `Runtime::run()` and `RuntimeHandle` for `not(target_arch = "wasm32")` -- Removed unused `Mutex` import from worker.rs -- Updated `crates/wasm/` to use `wasm` feature -- Key finding: most std::sync/thread primitives work on wasm32 with atomics; only `thread::spawn` needed gating -- All native tests pass, wasm32 compilation succeeds - -## Next Action -Begin Stage 2 (Single-Worker Browser Runtime) — `notes/feature-stages/02-single-worker-browser.md`: -1. Create `crates/wasm-browser/` crate structure -2. Implement `BrowserRuntime` wasm-bindgen API (spawn, send, tick, try_recv, stats) -3. Actor registration macro/pattern for JS-accessible spawning -4. Auto-scheduling via setTimeout(0) loop -5. Message serialization across JS↔Wasm boundary -6. Tests (wasm-pack test or Node.js) - -## Completed This Session -- [x] Cycle 0 research artifacts (constraints.md, research_synthesis.md, 6 stage docs) -- [x] Stage 1: `web-time` dep + `wasm` feature flag in Cargo.toml -- [x] Stage 1: Platform-aware `Instant` re-export in src/lib.rs -- [x] Stage 1: cfg-gated `Runtime::run()` and `RuntimeHandle` in src/runtime.rs -- [x] Stage 1: Updated crates/wasm/ to use `wasm` feature -- [x] Stage 1: Validated wasm32 compilation and native tests -- [x] Stage 1: Development history doc - -## Open Questions / Blockers -- Stage 2: Need to decide on message serialization (serde-wasm-bindgen vs raw bytes) -- Stage 2: Actor registration pattern — macro vs manual factory map -- Stage 3: Web Worker thread state initialization needs investigation (does std::thread::current() work in a Web Worker context?)