aboutsummaryrefslogtreecommitdiffstats
path: root/mux.go
blob: 6de3583e50aed47512ff612355ea1fe37998d378 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
package gui

import (
	"fmt"
	"image"
	"image/draw"

	"git.samanthony.xyz/share"
)

// 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 {
	size        share.Val[image.Rectangle]
	draw        chan<- func(draw.Image) image.Rectangle
	addChild    chan<- muxEnv
	removeChild chan<- muxEnv
	kill        chan<- bool
	dead        <-chan bool
	detachChan  <-chan bool
}

func NewMux(parent Env) Mux {
	size := share.NewVal[image.Rectangle]()
	drawChan := make(chan func(draw.Image) image.Rectangle)
	addChild := make(chan muxEnv)
	removeChild := make(chan muxEnv)
	kill := make(chan bool)
	dead := make(chan bool)

	detachFromParent := make(chan bool)

	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() {
			go drain(drawChan) // children may still be sending
			for _, child := range children {
				child.kill <- true
			}
			for range children {
				<-removeChild
			}
		}()

		for {
			select {
			case d := <-drawChan:
				parent.Draw() <- d
			case e := <-parent.Events():
				if resize, ok := e.(Resize); ok {
					size.Set <- resize.Rectangle
				}
				for _, child := range children {
					child.events.Enqueue <- e
				}
			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
			}
		}
	}()

	mux := Mux{
		size:        size,
		draw:        drawChan,
		addChild:    addChild,
		removeChild: removeChild,
		kill:        kill,
		dead:        dead,
		detachChan:  detachFromParent,
	}
	parent.attach() <- mux
	return mux
}

func (mux Mux) Kill() chan<- bool {
	return mux.kill
}

func (mux Mux) Dead() <-chan bool {
	return mux.dead
}

func (mux Mux) detach() <-chan bool {
	return mux.detachChan
}

type muxEnv struct {
	events        share.Queue[Event]
	draw          chan<- func(draw.Image) image.Rectangle
	attachChan    chan<- victim
	kill          chan<- bool
	dead          <-chan bool
	detachFromMux <-chan bool
}

func (mux Mux) MakeEnv() Env {
	events := share.NewQueue[Event]()
	drawChan := make(chan func(draw.Image) image.Rectangle)
	child := newKiller()
	kill := make(chan bool)
	dead := make(chan bool)
	detachFromMux := make(chan bool)

	env := muxEnv{
		events:        events,
		draw:          drawChan,
		attachChan:    child.attach(),
		kill:          kill,
		dead:          dead,
		detachFromMux: detachFromMux,
	}
	mux.addChild <- env
	// make sure to always send a resize event to a new Env
	events.Enqueue <- Resize{mux.size.Get()}

	go func() {
		defer func() {
			dead <- true
			close(dead)
		}()
		defer close(kill)
		defer close(drawChan)
		defer close(events.Enqueue)

		defer func() {
			mux.removeChild <- env
		}()

		defer func() {
			child.Kill() <- true
			<-child.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.events.Dequeue
}

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<- victim {
	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 {
	}
}