diff options
| author | Pietro Gagliardi <[email protected]> | 2015-12-11 20:37:59 -0500 |
|---|---|---|
| committer | Pietro Gagliardi <[email protected]> | 2015-12-11 20:37:59 -0500 |
| commit | f8e3f12ab02b528f2a05a4f713d7af7ea8e44b42 (patch) | |
| tree | 82dedf4d37f0f6d31e88ebb2ca1ce6499dead261 /prev/uitask.go | |
| parent | e34c561ed5bedeb180437ec165882b98d70d38c1 (diff) | |
LET'S GET THIS FINAL REWRITE EVER STARTED
Diffstat (limited to 'prev/uitask.go')
| -rw-r--r-- | prev/uitask.go | 164 |
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) +} |
