Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
31 changes: 27 additions & 4 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

```
Expand Down
Loading