| 1 | package control |
| 2 | |
| 3 | import "reasonix/internal/event" |
| 4 | |
| 5 | // PublishedRuntimeStateReader is the observation boundary for UI projections. |
| 6 | // It never refreshes owners or waits for a controller, session, or tool lock. |
| 7 | // RuntimeStateReader remains available to callers requiring a fresh sample. |
| 8 | type PublishedRuntimeStateReader interface { |
| 9 | PublishedRuntimeStateSnapshot() event.RuntimeStateSnapshot |
| 10 | } |
| 11 | |
| 12 | // commitSnapshot runs under the producer's sampling lock. Both stored values |
| 13 | // own their slices and nested observations; future samples cannot mutate a |
| 14 | // snapshot already visible to a lock-free reader. |
| 15 | func (r *controllerRuntimeState) commitSnapshot(next event.RuntimeStateSnapshot) { |
| 16 | r.snapshot = cloneRuntimeState(next) |
| 17 | committed := cloneRuntimeState(next) |
| 18 | r.published.Store(&committed) |
| 19 | } |
| 20 | |
| 21 | // PublishedRuntimeStateSnapshot returns the last committed immutable state, |
| 22 | // including the initial state before asynchronous notifications begin. Reads |
| 23 | // must remain available while a subsequent producer is waiting on an owner. |
| 24 | func (c *Controller) PublishedRuntimeStateSnapshot() event.RuntimeStateSnapshot { |
| 25 | if c != nil { |
| 26 | if state := c.runtimeState.published.Load(); state != nil { |
| 27 | return cloneRuntimeState(*state) |
| 28 | } |
| 29 | } |
| 30 | return event.RuntimeStateSnapshot{Todos: []event.Todo{}, Interactions: []event.PendingInteraction{}} |
| 31 | } |
| 32 |