aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSam Anthony <sam@samanthony.xyz>2024-08-21 19:25:17 -0400
committerSam Anthony <sam@samanthony.xyz>2024-08-21 19:25:55 -0400
commit96df27214d149cc60dc08baae9d32f767fd032be (patch)
treebdea4f92ffab720625ecbc77cd0f9d434a5dd271
parent5e81e13d2bd9de0827d1575a582224ce3b54819f (diff)
downloadgui-96df27214d149cc60dc08baae9d32f767fd032be.zip
explicit and graceful Env shutdown with attach/kill
-rw-r--r--env.go9
-rw-r--r--kill.go75
2 files changed, 78 insertions, 6 deletions
diff --git a/env.go b/env.go
index 2515417..cf7f96c 100644
--- a/env.go
+++ b/env.go
@@ -8,8 +8,6 @@ import (
// Env is the most important thing in this package. It is an interactive graphical
// environment, such as a window.
//
-// It has two channels: Events() and Draw().
-//
// The Events() channel produces events, like mouse and keyboard presses, while the
// Draw() channel receives drawing functions. A drawing function draws onto the
// supplied draw.Image, which is the drawing area of the Env and returns a rectangle
@@ -23,11 +21,10 @@ import (
// The Draw() channel may be synchronous.
//
// Drawing functions sent to the Draw() channel are not guaranteed to be executed.
-//
-// Closing the Draw() channel results in closing the Env. The Env will subsequently
-// close the Events() channel. On the other hand, when the Events() channel gets closed
-// the user of the Env should subsequently close the Draw() channel.
type Env interface {
Events() <-chan Event
Draw() chan<- func(draw.Image) image.Rectangle
+ Killable
+
+ killer
}
diff --git a/kill.go b/kill.go
new file mode 100644
index 0000000..8f91035
--- /dev/null
+++ b/kill.go
@@ -0,0 +1,75 @@
+package gui
+
+// A Killable object can be told to shut down by sending a signal via the Kill() channel.
+// As its last action, the object posts a signal to Dead() and closes both channels, indicating that it has finished shutting down.
+type Killable interface {
+ Kill() chan<- bool
+ Dead() <-chan bool
+}
+
+// A killer can kill the object that is attached to it.
+// The victim can attach itself to the killer by sending an attachMsg via the provided attach() channel.
+// The attachMsg contains the victim itself and a `detach' channel.
+// The victim can detatch itself from the killer by signalling over the `detach' channel.
+//
+// Only one victim can be attached to the killer at a time.
+// Further messages sent on the attach() channel will block until the current victim is detached.
+type killer interface {
+ attach() chan<- attachMsg
+}
+
+// attachMsg is sent to a killer to attach the victim.
+type attachMsg struct {
+ victim Killable
+ detach <-chan bool
+}
+
+// attachHandler implements killer. It allows victims to attach themselves via the attach channel.
+// There can only be one attached victim at a time.
+// If attachHandler is killed while a victim is attached, it kills the victim.
+// When killed, the victim must detach itself before dying.
+type attachHandler struct {
+ attach chan<- attachMsg
+ kill chan<- bool
+ dead <-chan bool
+}
+
+func newAttachHandler() attachHandler {
+ attach := make(chan attachMsg)
+ kill := make(chan bool)
+ dead := make(chan bool)
+
+ go func() {
+ defer func() {
+ dead <- true
+ close(dead)
+ }()
+ defer close(kill)
+ defer close(attach)
+
+ for {
+ var attached attachMsg
+
+ select {
+ case attached = <-attach:
+ case <-kill:
+ return
+ }
+
+ Attached:
+ for {
+ select {
+ case <-attached.detach:
+ break Attached
+ case <-kill:
+ attached.victim.Kill() <- true
+ <-attached.detach
+ <-attached.victim.Dead()
+ return
+ }
+ }
+ }
+ }()
+
+ return attachHandler{attach, kill, dead}
+}