Documentation
¶
Overview ¶
Package hub coordinates a family of visualizer windows: one leader process spawns child windows of its own executable and relays JSON messages between them over the children's stdin and stdout, so every window shows the same state.
The message type belongs to each application; a Hub is generic over it. A Config supplies a routing policy that sorts each inbound message into a Route: RouteState caches and rebroadcasts shared state, RouteBroadcast fans a message out once, and RouteSpawn and RouteCloseNewest open and close windows. Most applications never touch a Hub directly: RunLeader wires up the leader window and RunChild a spawned one, each handing the window a Link to speak through. Delivery is lossy by design through TrySend, so a stalled window never stalls the hub.
Index ¶
Examples ¶
Constants ¶
const DefaultMaxWindows = 16
DefaultMaxWindows is the window cap applied when Config.MaxWindows is zero.
Variables ¶
This section is empty.
Functions ¶
func RunChild ¶
RunChild runs a child window wired to the leader over stdin/stdout: run receives the child's Link and blocks until the window closes. The Link's In channel closes when the leader disappears.
Example ¶
ExampleRunChild is the counterpart run by spawned windows: the Link speaks to the leader over stdin/stdout, and In closes when the leader goes away.
package main
import (
"fmt"
"github.com/danielriddell21/crucible/hub"
)
// Msg is the application's own wire type; the hub is generic over it.
type Msg struct {
Type string `json:"t"`
Yaw float32 `json:"yaw,omitempty"`
}
func main() {
err := hub.RunChild(func(l hub.Link[Msg]) error {
for m := range l.In {
_ = m // apply the shared state to this window
}
return nil
})
if err != nil {
fmt.Println(err)
}
}
Output:
func RunLeader ¶
RunLeader starts a hub for the leader window: run receives the leader's Link and blocks until the window closes (conventionally by calling the application's gui.Run). Child windows spawned along the way are killed on return.
Example ¶
ExampleRunLeader shows the leader side of a multi-window visualizer: the Route policy maps the app's message types onto hub behaviours, and the window loop talks through the Link. Child processes run the same binary with ExampleRunChild's wiring.
package main
import (
"fmt"
"os"
"github.com/danielriddell21/crucible/hub"
)
// Msg is the application's own wire type; the hub is generic over it.
type Msg struct {
Type string `json:"t"`
Yaw float32 `json:"yaw,omitempty"`
}
func main() {
cfg := hub.Config[Msg]{
Self: os.Args[0],
ChildArgs: func(idx int) []string {
return []string{"view", fmt.Sprintf("--child=%d", idx)}
},
Route: func(m Msg) hub.Route {
switch m.Type {
case "state":
return hub.RouteState
case "add":
return hub.RouteSpawn
case "remove":
return hub.RouteCloseNewest
}
return hub.RouteNone
},
Quit: Msg{Type: "quit"},
}
err := hub.RunLeader(cfg, func(l hub.Link[Msg]) error {
// gui.Run(gui.Config{Link: l, ...}) — the window sends its state on
// l.Out and applies messages arriving on l.In.
return nil
})
if err != nil {
fmt.Println(err)
}
}
Output:
Types ¶
type Config ¶
type Config[M any] struct { // Self is the executable to spawn for child windows, conventionally // os.Args[0]. Self string // ChildArgs returns the arguments for the idx-th child window, e.g. // {"view", "--child=1"}. ChildArgs func(idx int) []string // Route classifies inbound messages. Route func(M) Route // Quit is the message sent to a child window to make it close. Quit M // MaxWindows caps the number of simultaneous windows. Zero means the // conventional 16. MaxWindows int }
Config describes how a Hub runs. M is the application's message type, which must marshal to JSON.
type Hub ¶
type Hub[M any] struct { // contains filtered or unexported fields }
Hub relays messages between the leader window and its children. Create one with New, register the leader with Hub.AddParticipant, then start Hub.Run in a goroutine.
func (*Hub[M]) AddParticipant ¶
AddParticipant registers a window that receives messages on out and returns its id. cmd is the child process behind the window, or nil for the in-process leader. A late joiner immediately receives the cached state, if any.
func (*Hub[M]) CloseNewest ¶
func (h *Hub[M]) CloseNewest()
CloseNewest sends the quit message to the most recently spawned child window, if any.
func (*Hub[M]) Inject ¶
Inject feeds a message from participant src into the hub, as if it had arrived from that window's process.
func (*Hub[M]) Run ¶
func (h *Hub[M]) Run()
Run dispatches messages until Shutdown. Run it in its own goroutine.
func (*Hub[M]) Shutdown ¶
func (h *Hub[M]) Shutdown()
Shutdown kills every child window process and stops Run.
func (*Hub[M]) SpawnChild ¶
func (h *Hub[M]) SpawnChild()
SpawnChild starts another child window process and wires it into the hub, unless the window cap is reached.
type Link ¶
type Link[M any] struct { In <-chan M Out chan<- M }
Link is the pair of channels a window uses to talk to the hub: In carries messages from the other windows, Out carries this window's messages to them. In is closed when the leader goes away, so a child window can treat that as its signal to terminate.
type Route ¶
type Route int
Route says what the hub should do with an inbound message.
const ( // RouteNone ignores the message. RouteNone Route = iota // RouteState caches the message as the latest shared state, replays it // to windows that join later, and broadcasts it to every other window. RouteState // RouteBroadcast forwards the message to every other window once. RouteBroadcast // RouteSpawn opens another child window. RouteSpawn // RouteCloseNewest asks the most recently spawned child window to quit. RouteCloseNewest )