diff options
| author | Sam Anthony <sam@samanthony.xyz> | 2024-08-21 19:27:14 -0400 |
|---|---|---|
| committer | Sam Anthony <sam@samanthony.xyz> | 2024-08-21 19:27:14 -0400 |
| commit | 4f4d5c40bca118562cce0d8daf569e2206f2ad28 (patch) | |
| tree | bc5ed84678a8ae8ea165e9ea4095ae48bfc2c8a1 | |
| parent | 96df27214d149cc60dc08baae9d32f767fd032be (diff) | |
| download | gui-4f4d5c40bca118562cce0d8daf569e2206f2ad28.zip | |
mux: adapt to new Env interface
| -rw-r--r-- | mux.go | 220 |
1 files changed, 146 insertions, 74 deletions
@@ -1,130 +1,202 @@ package gui import ( + "fmt" "image" "image/draw" "git.samanthony.xyz/share" ) -// Mux can be used to multiplex an Env, let's call it a root Env. Mux implements a way to -// create multiple virtual Envs that all interact with the root Env. They receive the same -// events and their draw functions get redirected to the root Env. +// Mux can be used to multiplex an Env, let's call it the parent Env. Mux implements a way to +// create multiple virtual Envs that all interact with the parent Env. They receive the same +// events and their draw functions get redirected to the parent Env. type Mux struct { - addEventsIn chan<- chan<- Event size share.Val[image.Rectangle] draw chan<- func(draw.Image) image.Rectangle + addChild chan<- muxEnv + removeChild chan<- muxEnv + kill chan<- bool + dead <-chan bool } -// NewMux creates a new Mux that multiplexes the given Env. It returns the Mux along with -// a master Env. The master Env is just like any other Env created by the Mux, except that -// closing the Draw() channel on the master Env closes the whole Mux and all other Envs -// created by the Mux. -func NewMux(env Env) (mux *Mux, master Env) { - addEventsIn := make(chan chan<- Event) +func NewMux(parent Env) Mux { size := share.NewVal[image.Rectangle]() drawChan := make(chan func(draw.Image) image.Rectangle) - mux = &Mux{ - addEventsIn: addEventsIn, - size: size, - draw: drawChan, - } + addChild := make(chan muxEnv) + removeChild := make(chan muxEnv) + kill := make(chan bool) + dead := make(chan bool) - go func() { - var eventsIns []chan<- Event + detachFromParent := make(chan bool) - defer close(env.Draw()) - defer close(addEventsIn) + go func() { + defer func() { + dead <- true + close(dead) + }() + defer func() { + detachFromParent <- true + close(detachFromParent) + }() + defer close(kill) + defer close(removeChild) + defer close(addChild) + defer close(drawChan) defer size.Close() + + var children []muxEnv defer func() { - for _, eventsIn := range eventsIns { - close(eventsIn) + go drain(drawChan) // children may still be sending + for _, child := range children { + child.kill <- true + } + for range children { + <-removeChild } }() for { select { - case d, ok := <-drawChan: - if !ok { // closed by master env - return - } - env.Draw() <- d - case e, ok := <-env.Events(): - if !ok { - return - } + case d := <-drawChan: + parent.Draw() <- d + case e := <-parent.Events(): if resize, ok := e.(Resize); ok { size.Set <- resize.Rectangle } - for _, eventsIn := range eventsIns { - eventsIn <- e + for _, child := range children { + child.eventsIn <- e } - case eventsIn := <-addEventsIn: - eventsIns = append(eventsIns, eventsIn) + case child := <-addChild: + children = append(children, child) + case child := <-removeChild: + var err error + // TODO: faster search + if children, err = remove(child, children); err != nil { + panic(fmt.Sprintf("Mux: failed to remove child Env: %v", err)) + } + case <-kill: + return } } }() - master = mux.makeEnv(true) - return mux, master + mux := Mux{ + size: size, + draw: drawChan, + addChild: addChild, + removeChild: removeChild, + kill: kill, + dead: dead, + } + parent.attach() <- attachMsg{mux, detachFromParent} + return mux } -// MakeEnv creates a new virtual Env that interacts with the root Env of the Mux. Closing -// the Draw() channel of the Env will not close the Mux, or any other Env created by the Mux -// but will delete the Env from the Mux. -func (mux *Mux) MakeEnv() Env { - return mux.makeEnv(false) +func (mux Mux) Kill() chan<- bool { + return mux.kill } -type muxEnv struct { - events <-chan Event - draw chan<- func(draw.Image) image.Rectangle +func (mux Mux) Dead() <-chan bool { + return mux.dead } -func (m *muxEnv) Events() <-chan Event { return m.events } -func (m *muxEnv) Draw() chan<- func(draw.Image) image.Rectangle { return m.draw } +type muxEnv struct { + eventsIn chan<- Event + eventsOut <-chan Event + draw chan<- func(draw.Image) image.Rectangle + attachChan chan<- attachMsg + kill chan<- bool + dead <-chan bool +} -func (mux *Mux) makeEnv(master bool) Env { +func (mux Mux) MakeEnv() Env { eventsOut, eventsIn := MakeEventsChan() drawChan := make(chan func(draw.Image) image.Rectangle) - env := &muxEnv{eventsOut, drawChan} - - mux.addEventsIn <- eventsIn + victimChan := make(chan Killable) + kill := make(chan bool) + dead := make(chan bool) + + attached := newAttachHandler() + + env := muxEnv{ + eventsIn: eventsIn, + eventsOut: eventsOut, + draw: drawChan, + attachChan: attached.attach, + kill: kill, + dead: dead, + } + mux.addChild <- env // make sure to always send a resize event to a new Env eventsIn <- Resize{mux.size.Get()} go func() { - func() { - // When the master Env gets its Draw() channel closed, it closes all the Events() - // channels of all the children Envs, and it also closes the internal draw channel - // of the Mux. Otherwise, closing the Draw() channel of the master Env wouldn't - // close the Env the Mux is muxing. However, some child Envs of the Mux may still - // send some drawing commmands before they realize that their Events() channel got - // closed. - // - // That is perfectly fine if their drawing commands simply get ignored. This down here - // is a little hacky, but (I hope) perfectly fine solution to the problem. - // - // When the internal draw channel of the Mux gets closed, the line marked with ! will - // cause panic. We recover this panic, then we receive, but ignore all furhter draw - // commands, correctly draining the Env until it closes itself. - defer func() { - if recover() != nil { - drain(drawChan) - } - }() - for d := range drawChan { - mux.draw <- d // ! - } + defer func() { + dead <- true + close(dead) }() - if master { - close(mux.draw) + defer close(kill) + defer close(victimChan) + defer close(drawChan) + defer close(eventsIn) + // eventsOut closed automatically by MakeEventsChan() + + defer func() { + mux.removeChild <- env + }() + + defer func() { + attached.kill <- true + <-attached.dead + }() + defer func() { + go drain(drawChan) + }() + + for { + select { + case d := <-drawChan: + mux.draw <- d + case <-kill: + return + } } }() return env } +func (env muxEnv) Events() <-chan Event { + return env.eventsOut +} + +func (env muxEnv) Draw() chan<- func(draw.Image) image.Rectangle { + return env.draw +} + +func (env muxEnv) Kill() chan<- bool { + return env.kill +} + +func (env muxEnv) Dead() <-chan bool { + return env.dead +} + +func (env muxEnv) attach() chan<- attachMsg { + return env.attachChan +} + +// remove removes element e from slice s, returning the modified slice, or error if e is not in s. +func remove[S ~[]E, E comparable](e E, s S) ([]E, error) { + for i := range s { + if s[i] == e { + return append(s[:i], s[i+1:]...), nil + } + } + return s, fmt.Errorf("%v not found in %v", e, s) +} + func drain[T any](c <-chan T) { for range c { } |