-
Notifications
You must be signed in to change notification settings - Fork 0
/
event-scheduler.red
280 lines (247 loc) · 11 KB
/
event-scheduler.red
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
Red [
title: "Custom event scheduler for Spaces"
purpose: "Provide platform-independent and optimal event processing"
author: @hiiamboris
license: BSD-3
notes: {
Scheduler is an attempt to work around https://github.com/red/red/issues/4881
which freezes some tests (grid-test4-7) and slows down others on GTK.
This implementation is currently replacing do-events, because it needs to
gather all pending events and then start processing right away.
There's no other way I know of of triggering any code "right away" after the last event.
However due to weird behavior of Windows backend, single do-events/no-wait may lock the evaluation.
Happens on menu activation and on resizing. And all events just keep getting queued.
I have yet found no easy workaround for this problem, because
the only code that runs in this situation is event functions and actors.
And they have no knowledge of pending events, nor any way to know if lockup happened.
So I can imagine no logic (other than relying on delay anomalies) that would let me to
temporarily switch events processing from do-events into the event function.
Another use of it is REP #161
On design...
Generally there are 3 groups of events:
- priority events - keys, clicks, change, focus and more
these cannot be skipped and usually require fast UI response
- groupable events - over, wheel, moving, resizing, drawing
these can be skipped if there's another similar event pending but no other events inbetween except 'time
because the order of input is of importance and we can't mix it
wheel is a bit different: as it reports relative change, this change must be summed when grouping
these require less fast UI response
- time event - unordered with the other events, so can always be skipped if another time event is pending
since in Spaces it's the heaviest event that renders everything, it can also be most delayed
In Spaces some of these e.g. moving, resizing, change are unused (not applicable to base face).
'drawing is also not reported to the base at the moment, but even if it was, Spaces do not handle it.
The remaining events - input and time - are prioritized using "delay norms" that are big for 'time and small for input.
An event is only skipped (grouped) in two cases:
- no other events inbetween
- the event inbetween (of different type) has lesser delay to norm value
Since effectively we have only 'time and 'input groups, it's always just a choice whether to delay the timer or fire it.
Delay is measured since time last event of the same type finished processing,
so the bigger the delay gets the more likely this event will be scheduled again,
and on the other hand, if similar event just finished it is likely to get skipped.
So it leads to a fair distribution of events across timeline, removing deadlocks.
}
]
;; requires do-queued-events.red
;; uses events/dispatch and events/copy-event
scheduler: context [
event-types: extract system/view/evt-names 2
accepted-events: make hash! [ ;-- real (not generated) View events that need processing
down up mid-down mid-up alt-down alt-up aux-down aux-up
dbl-click over wheel
key key-down key-up enter
; focus unfocus -- internally generated ;@@ but maybe these will be required too
time
#(true) ;-- used to be able to use path notation (faster)
]
delay-norms: #[ ;-- delay norm per event type, used for prioritization
time 500
; drawing 300 ;-- not reported to the event function
; moving 200 ;-- only concerns windows not hosts
; move 200 ;-- same
; resizing 200 ;-- same
; resize 200 ;-- same
over 100
drag 100
wheel 100
]
default-delay: 50
for-each type event-types [default delay-norms/:type: default-delay]
groupable: make hash! append keys-of delay-norms true ;-- 'true' allows to use path notation which is faster than find
;; event groups determine which events can or cannot be grouped with each other
groups: #[
time time
drawing drawing
]
for-each type exclude event-types [time drawing] [groups/:type: 'normal]
;; similar to system/view/handlers but only for host events
;; goal is to have predictable event order (e.g. hovering may call new 'over event immediately before 'time)
;; if function throws a 'drop word, event is skipped
event-filters: #[]
finish-times: #[] ;-- timestamp of last event of each type processing finish(!)
for-each type event-types [finish-times/:type: now/utc/precise]
shared-queue: make [] 200 ;-- for dispatching events by host
insert-event: group-next-event: take-next-event: process-next-event: process-any-event: none
context [
igroup: 1 ievent: 2 period: 2
limit: 50 ;-- search distance limit
set 'insert-event function [host [object!] event [map!]] [
#assert [host =? event/face]
insert shared-queue host
insert host/queue reduce [
groups/(event/type) event
]
]
set 'take-next-event function [host [object!]] [
event: host/queue/:ievent
quietly host/queue: skip host/queue period
if 100 < index? host/queue [
; remove/part host/queue quietly host/queue: head host/queue
remove/part head host/queue host/queue
quietly host/queue: head host/queue
]
event
]
set 'process-next-event function [host [object!]] [
unless group-next-event host [
event: take-next-event host
#debug events [if event/type <> 'time [#print "about to process (event/type) event for (host/type):(host/size)"]]
#debug focus [if event/type = 'focus [#print "about to process (event/type) event for (host/type):(host/size)"]]
if 'drop <> catch [ ;@@ should be fcatch, but it would be slower :/
foreach [_ filter] event-filters [filter host event] ;-- filters may call out-of-turn events/dispatch themselves
'ok
][
events/dispatch host event
]
finish-times/(event/type): now/utc/precise ;-- mark the end of processing of this event type
]
]
set 'process-any-event function [/extern shared-queue] [ ;-- must return true if processes
unless host: shared-queue/1 [return no]
#assert [
not empty? host/queue "shared and host queues are out of sync"
1000 > length? host/queue "event queue buildup detected"
]
shared-queue: next shared-queue
if 100 < index? shared-queue [
; remove/part shared-queue shared-queue: head shared-queue ;@@ negative part is bugged
remove/part head shared-queue shared-queue
shared-queue: head shared-queue
]
process-next-event host
true
]
set 'group-next-event function [host [object!]] [
unless attempt [window-of host] [ ;-- ignore out-of-tree events (host or window has been destroyed?)
#debug events [#print "ignoring outdated (host/queue/:ievent/type) event for (host/type):(host/size)"]
take-next-event host
return true
]
;; find grouping candidate
rest: skip this: host/queue period
type: this/:ievent/type
unless all [
groupable/:type ;-- this event type cannot be grouped
ahead: find/skip/part rest this/:igroup period limit ;-- no similar event ahead
type = ahead/:ievent/type ;-- similar event of different type blocks grouping
this/away? = ahead/:ievent/away? ;-- cannot group different away states
] [return none]
;; check if grouping would lead us to a more delayed event, otherwise abort
if period <> offset? this rest [ ;-- only if skipping another event
this-delay: difference t-now: now/utc/precise finish-times/:type
this-norm: delay-norms/:type
next-type: rest/:ievent/type
next-delay: difference t-now finish-times/:next-type
next-norm: delay-norms/:next-type
if greater? this-delay / this-norm next-delay / next-norm [return none] ;-- abort if this event is more delayed
]
;; perform grouping
if type = 'wheel [ ;-- the only event that requires summation
ahead/:ievent/picked: ahead/:ievent/picked + this/:ievent/picked
]
take-next-event host
#debug events [if type <> 'time [#print "grouped (type) event for (host/type):(host/size)"]]
true ;-- report success
]
]
tracked: #[ ;@@ remove this if REP #161 gets implemented
flags: []
offset: (0,0) ;-- screen offset!
ctrl?: #(false)
shift?: #(false)
down?: #(false)
mid-down?: #(false)
alt-down?: #(false)
]
track-event: function [
"Stash event flags internally"
event [event! map!]
][
switch event/type [
over wheel down up click dbl-click
alt-down alt-up mid-down mid-up aux-down aux-up
key-down key key-up [ ;-- pointer & key events correctly carry all the flags
foreach [word _] tracked [ ;-- extend can't be used or will include unwanted fields
tracked/:word: event/:word
]
tracked/offset: face-to-screen tracked/offset event/face
]
]
event
]
heal-event: function [ ;@@ need to gather extensive data on which events needs healing
"Ensure correct flags & offset in all events"
event [map!]
][
if event/type = 'time [
extend event tracked ;-- timer carries no own state
event/offset: screen-to-face event/offset event/face
]
event
]
queue-event: function [host event [map!]] [
append shared-queue host
append append host/queue groups/(event/type) event
]
;; sometimes this gets window as 'host', likely when no face is in focus
insert-event-func 'spaces-event-dispatcher func [host event] [
track-event event ;-- keep tracked info up to date, gathering it from ALL faces
all [
host? host ;@@ maybe /content field not /space?
host/space ;-- /space is assigned?
accepted-events/(event/type)
queue-event host heal-event events/copy-event event
]
none ;-- the event can be processed by other handlers
]
#assert [1291100108 = checksum native-mold body-of :do-events 'crc32 "Warning: do-events was likely modified"]
event-loop-depth: 0 ;@@ used to work around #5377
set 'do-events function spec-of native-do-events: :do-events [
either no-wait [
trap/all/catch
[process-any-event]
[print thrown]
native-do-events/no-wait
][
;; 'head' to account for GUI console which enters event loop too:
if window: last head system/view/screens/1/pane [ ;@@ what if windows were reordered?
#assert [window/state]
depth: step 'event-loop-depth
while [all [window/state depth = event-loop-depth]] [ ;-- there's one event loop per window, so leave once it's closed
switch native-do-events/no-wait [ ;-- fetch all pending events ;@@ may deadlock?
#(true) [continue]
#(false) []
#(none) [break]
]
trap/all/catch
[unless process-any-event [wait 1e-3]] ;-- wait is also tainted by single do-events/no-wait
[print thrown]
]
self/event-loop-depth: depth - 1
none ;-- return value must be useful for smth..
]
]
]
;; View is using some functions with compiled version of 'do-events'
;; I have to recreate it to switch to the new scheduler
set 'view func spec-of :view body-of :view
]