summaryrefslogtreecommitdiff
path: root/prev/uitask.go
diff options
context:
space:
mode:
authorPietro Gagliardi <[email protected]>2015-12-11 20:37:59 -0500
committerPietro Gagliardi <[email protected]>2015-12-11 20:37:59 -0500
commitf8e3f12ab02b528f2a05a4f713d7af7ea8e44b42 (patch)
tree82dedf4d37f0f6d31e88ebb2ca1ce6499dead261 /prev/uitask.go
parente34c561ed5bedeb180437ec165882b98d70d38c1 (diff)
LET'S GET THIS FINAL REWRITE EVER STARTED
Diffstat (limited to 'prev/uitask.go')
-rw-r--r--prev/uitask.go164
1 files changed, 164 insertions, 0 deletions
diff --git a/prev/uitask.go b/prev/uitask.go
new file mode 100644
index 0000000..0d16f8a
--- /dev/null
+++ b/prev/uitask.go
@@ -0,0 +1,164 @@
+// 6 july 2014
+
+package ui
+
+import (
+ "reflect"
+ "runtime"
+ "sync"
+ "unsafe"
+)
+
+// Go initializes and runs package ui.
+// It returns a non-nil error if initialization fails.
+// Otherwise, it will run the event loop and not return until Stop is called.
+// Due to platform-specific issues, it must be called from the main OS thread; in general, do not call Go() from anywhere except main() (including any goroutines).
+func Go() error {
+ runtime.LockOSThread()
+ if err := uiinit(); err != nil {
+ return err
+ }
+ go uiissueloop()
+ uimsgloop()
+ return nil
+}
+
+// To ensure that Do() and Stop() only do things after Go() has been called, this channel accepts the requests to issue. The issuing is done by uiissueloop() below.
+// Notice that this is a pointer ot a function. See Do() below for details.
+var issuer = make(chan *func())
+
+// Do performs f on the main loop, as if it were an event handler.
+// It waits for f to execute before returning.
+// Do cannot be called within event handlers or within Do itself.
+func Do(f func()) {
+ done := make(chan struct{})
+ defer close(done)
+ // THIS MUST BE A POINTER.
+ // Previously, the pointer was constructed within issue().
+ // This meant that if the Do() was stalled, the garbage collector came in and reused the pointer value too soon!
+ call := func() {
+ f()
+ done <- struct{}{}
+ }
+ issuer <- &call
+ <-done
+}
+
+// Stop informs package ui that it should stop.
+// Stop then returns immediately.
+// Some time after this request is received, Go() will return without performing any final cleanup.
+// Stop will not have an effect until any event handlers return.
+func Stop() {
+ // can't send this directly across issuer
+ go func() {
+ Do(uistop)
+ }()
+}
+
+func uiissueloop() {
+ for f := range issuer {
+ issue(f)
+ }
+}
+
+type event struct {
+ // All events internally return bool; those that don't will be wrapped around to return a dummy value.
+ do func() bool
+ lock sync.Mutex
+}
+
+func newEvent() *event {
+ return &event{
+ do: func() bool {
+ return false
+ },
+ }
+}
+
+func (e *event) set(f func()) {
+ e.lock.Lock()
+ defer e.lock.Unlock()
+
+ if f == nil {
+ f = func() {}
+ }
+ e.do = func() bool {
+ f()
+ return false
+ }
+}
+
+func (e *event) setbool(f func() bool) {
+ e.lock.Lock()
+ defer e.lock.Unlock()
+
+ if f == nil {
+ f = func() bool {
+ return false
+ }
+ }
+ e.do = f
+}
+
+// This is the common code for running an event.
+// It runs on the main thread without a message pump; it provides its own.
+func (e *event) fire() bool {
+ e.lock.Lock()
+ defer e.lock.Unlock()
+
+ return e.do()
+}
+
+// Common code for performing a requested action (ui.Do() or ui.Stop()).
+// This should run on the main thread.
+// Implementations of issue() should call this.
+func perform(fp unsafe.Pointer) {
+ f := (*func())(fp)
+ (*f)()
+}
+
+// ForeignEvent wraps a channel in such a way that it can be used safely with package ui.
+type ForeignEvent struct {
+ c reflect.Value
+ e *event
+ d interface{}
+}
+
+// NewForeignEvent creates a new ForeignEvent with the specified channel.
+// It panics if the argument is not a receivable channel.
+// The returned ForeignEvent assumes ownership of the channel.
+// Each time a value is received on the channel, the returned function is invoked on the main thread.
+func NewForeignEvent(channel interface{}, handler func(data interface{})) *ForeignEvent {
+ c := reflect.ValueOf(channel)
+ t := c.Type()
+ if t.Kind() != reflect.Chan || (t.ChanDir()&reflect.RecvDir) == 0 {
+ panic("non-channel or non-receivable channel passed to NewForeignEvent()")
+ }
+ fe := &ForeignEvent{
+ c: c,
+ e: newEvent(),
+ }
+ fe.e.set(func() {
+ handler(fe.d)
+ })
+ go fe.do()
+ return fe
+}
+
+func (fe *ForeignEvent) do() {
+ for {
+ v, ok := fe.c.Recv()
+ if !ok {
+ break
+ }
+ fe.d = v.Interface()
+ Do(func() {
+ fe.e.fire()
+ })
+ }
+}
+
+// Stop ceases all future invocations of the handler passed to NewForeignEvent() on fe; the values read from the channel are merely discarded.
+func (fe *ForeignEvent) Stop() {
+ fe.e.set(nil)
+}