summaryrefslogtreecommitdiff
path: root/redo/uitask.go
blob: 36d342ca8beb73759941364451fdf604d48d3008 (plain)
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
// 6 july 2014

package ui

import (
	"runtime"
	"sync"
)

// Go initializes package ui.
// TODO write this bit
func Go() error {
	runtime.LockOSThread()
	if err := uiinit(); err != nil {
		return err
	}
	go uitask(Do)
	uimsgloop()
	return nil
}

// Stop issues a Request for package ui to stop and returns immediately.
// Some time after this request is received, Go() will return without performing any final cleanup.
// Stop is internally issued to Do, so it will not be registered until any event handlers and dialog boxes return.
func Stop() {
	go func() {
		c := make(chan interface{})
		Do <- &Request{
			op:		func() {
				uistop()
				c <- struct{}{}
			},
			resp:		c,
		}
		<-c
	}()
}

// This is the ui main loop.
// It is spawned by Go as a goroutine.
// It can also be called recursively using the recur/unrecur chain.
func uitask(doer Doer) {
	for {
		select {
		case req := <-doer:
			// TODO foreign event
			issue(req)
		case rec := <-recur:		// want to perform event
			c := make(Doer)
			rec <- c
			uitask(c)
		case <-unrecur:		// finished with event
			close(doer)
			return
		}
	}
}

// Send a channel over recur to have uitask() above enter a recursive loop in which the Doer sent back becomes the active request handler.
// Pulse unrecur when finished.
var recur = make(chan chan Doer)
var unrecur = make(chan struct{})

type event struct {
	// All events internally return bool; those that don't will be wrapped around to return a dummy value.
	do		func(c Doer) bool
	lock		sync.Mutex
}

// do should never be nil; TODO should we make setters panic instead?

func newEvent() *event {
	return &event{
		do:	func(c Doer) bool {
			return false
		},
	}
}

func (e *event) set(f func(Doer)) {
	e.lock.Lock()
	defer e.lock.Unlock()

	if f == nil {
		f = func(c Doer) {}
	}
	e.do = func(c Doer) bool {
		f(c)
		return false
	}
}

func (e *event) setbool(f func(Doer) bool) {
	e.lock.Lock()
	defer e.lock.Unlock()

	if f == nil {
		f = func(c Doer) 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 {
	cc := make(chan Doer)
	recur <- cc
	c := <-cc
	result := false
	finished := make(chan struct{})
	go func() {
		e.lock.Lock()
		defer e.lock.Unlock()

		result = e.do(c)
		finished <- struct{}{}
	}()
	<-finished
	close(finished)
	// leave the event handler; leave it only after returning from the OS-side event handler so we must issue it like a normal Request
	issue(&Request{
		op:		func() {
			unrecur <- struct{}{}
		},
		// unfortunately, closing a nil channel causes a panic
		// therefore, we have to make a dummy channel
		// TODO add conditional checks to the request handler instead?
		resp:		make(chan interface{}),
	})
	return result
}

// Common code for performing a Request.
// This should run on the main thread.
// Implementations of issue() should call this.
func perform(req *Request) {
	req.op()
	close(req.resp)
}