diff --git a/CHANGELOG.md b/CHANGELOG.md index 680d009..620a211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.2.0] — 2026-05-19 + +### Added + +- **`Server.Snapshot()`** — returns latest frame from each connected module. Compositor render tick pattern (Android BufferQueue / Vulkan MAILBOX semantics). ADR-002. +- **`Frame.Sequence`** — monotonic frame counter mapped from wire protocol header, for change detection. +- **Per-module mailbox** — each module's latest frame stored server-side. Intermediate frames silently overwritten (latest-frame-wins). Push-based delivery officially supported. +- **8 new tests** — push, pull, concurrent, backward compatibility, multiple modules, Snapshot. + ## [0.1.0] — 2026-05-17 ### Added diff --git a/README.md b/README.md index 7574183..5811752 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Each module is a separate OS binary that renders into an offscreen buffer using This gives you: - **Process isolation** — a crash in one module never takes down the display +- **Push and pull delivery** — modules push frames whenever data changes, or render on-demand via compositor request. Mailbox semantics: latest frame always wins (Android BufferQueue / Vulkan MAILBOX pattern) - **Hot-pluggable modules** — start, stop, replace, and update individual modules without restarting the compositor - **Cross-language modules** — anything that can write RGBA to a Unix socket can participate, not just Go - **Independent module lifecycles** — each module ships, releases, and updates on its own schedule @@ -73,13 +74,19 @@ func main() { ``` ```go -// Compositor side: listen for module frames, place them onto a gogpu window +// Compositor side: listen for module frames, composite on render tick srv, _ := compose.Listen("/tmp/compose.sock") + +// Option A: event-driven (callback fires on every frame receipt) srv.OnFrame(func(f compose.Frame) { - // 'layout' is your application's slot-assignment helper that - // decides where each module's pixels go on the screen. layout.Place(f.Name, f.Pixels) }) + +// Option B: render-tick (sample latest frame from each module — mailbox semantics) +frames := srv.Snapshot() +for id, frame := range frames { + compositor.Blit(id, frame) +} ``` > Unix socket transport, wire protocol v1, LZ4 compression, pull-based flow control. See the [Roadmap](#roadmap) for current progress. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ba87c77..1e44448 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -96,15 +96,38 @@ client.PublishFrame(compose.Frame{ Pixels: rgba, Width: 400, Height: 120 }) | 56 | PayloadSize | 4B | Compressed payload bytes | | 60 | UncompressedSize | 4B | Original pixel bytes | -## Flow Control +## Frame Delivery (ADR-002) -Pull-based (Wayland frame callback pattern): +Both push and pull delivery coexist. No mode negotiation — inferred from behavior (Chromium pattern). -1. Compositor → Module: `FrameRequest` +### Push (module-driven) + +Module calls `PublishFrame()` whenever data changes. Server stores in per-module mailbox (latest-frame-wins). Compositor samples via `Snapshot()`. + +``` +Module: data changes → PublishFrame() → socket → server mailbox (overwrites previous) +Compositor: render tick → Snapshot() → latest frame from each module +``` + +### Pull (compositor-driven, Wayland pattern) + +1. Compositor → Module: `RequestFrame` 2. Module renders → sends `Frame` -3. Compositor processes → sends next `FrameRequest` +3. Frame stored in mailbox + OnFrame fires 4. Adaptive rate: 3 consecutive misses → halve request rate +### Mailbox semantics (Android BufferQueue / Vulkan MAILBOX) + +Each module has one mailbox slot. When a module pushes faster than the compositor renders, intermediate frames are silently overwritten. The compositor always sees the latest frame. No stale frame accumulation, no FIFO backlog. + +```go +// Compositor render tick: +frames := srv.Snapshot() +for id, frame := range frames { + compositor.Blit(id, frame) +} +``` + ## Connection Lifecycle ```