diff options
Diffstat (limited to 'apps/plugins/lua/rocklib_events.c')
-rw-r--r-- | apps/plugins/lua/rocklib_events.c | 632 |
1 files changed, 632 insertions, 0 deletions
diff --git a/apps/plugins/lua/rocklib_events.c b/apps/plugins/lua/rocklib_events.c new file mode 100644 index 0000000000..9e363edbdd --- /dev/null +++ b/apps/plugins/lua/rocklib_events.c | |||
@@ -0,0 +1,632 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2019 William Wilgus | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | |||
22 | /* lua events from rockbox ***************************************************** | ||
23 | * This library allows events to be subscribed / recieved within a lua script | ||
24 | * most events in rb are synchronous so flags are set and later checked by a | ||
25 | * secondary thread to make them (semi?) asynchronous. | ||
26 | * | ||
27 | * There are a few caveats to be aware of: | ||
28 | * FIRST, The main lua state is halted till the lua callback(s) are finished | ||
29 | * Yielding will not return control to your script from within a callback | ||
30 | * Also, subsequent callbacks may be delayed by the code in your lua callback | ||
31 | * SECOND, You must store the value returned from the event_register function | ||
32 | * you might get away with it for a bit but gc will destroy your callback | ||
33 | * eventually if you do not store the event | ||
34 | * THIRD, You only get one cb per event type | ||
35 | * ["action", "button", "custom", "playback", "timer"] | ||
36 | * (Re-registration of an event overwrites the previous one) | ||
37 | * | ||
38 | * Usage: | ||
39 | * possible events =["action", "button", "custom", "playback", "timer"] | ||
40 | * | ||
41 | * local ev = rockev.register("event", cb_function, [timeout / flags]) | ||
42 | * cb_function([id] [, data]) ... end | ||
43 | * | ||
44 | * | ||
45 | * rockev.trigger("event", [true/false], [id]) | ||
46 | * sets an event to triggered, | ||
47 | * NOTE!, CUSTOM_EVENT must be unset manually | ||
48 | * id is only passed to callback by custom and playback events | ||
49 | * | ||
50 | * rockev.suspend(["event"/nil][true/false]) passing nil affects all events | ||
51 | * stops event from executing, any but the last event before | ||
52 | * re-enabling will be lost. Passing false, unregistering or re-registering | ||
53 | * an event will clear the suspend | ||
54 | * | ||
55 | * rockev.unregister(evX) | ||
56 | * Use unregister(evX) to remove an event | ||
57 | * Unregistering is not necessary before script end, it will be | ||
58 | * cleaned up on script exit | ||
59 | * | ||
60 | ******************************************************************************* | ||
61 | * * | ||
62 | */ | ||
63 | |||
64 | #define LUA_LIB | ||
65 | |||
66 | #define _ROCKCONF_H_ /* We don't need strcmp() etc. wrappers */ | ||
67 | #include "lua.h" | ||
68 | #include "lauxlib.h" | ||
69 | #include "plugin.h" | ||
70 | #include "rocklib_events.h" | ||
71 | |||
72 | #define EVENT_METATABLE "event metatable" | ||
73 | |||
74 | #define EVENT_THREAD LUA_ROCKEVENTSNAME ".thread" | ||
75 | |||
76 | #define LUA_SUCCESS 0 | ||
77 | #define EV_TIMER_FREQ (TIMER_FREQ / HZ) | ||
78 | #define EV_TICKS (HZ / 5) | ||
79 | #define EV_INPUT (HZ / 4) | ||
80 | //#define DEBUG_EV | ||
81 | |||
82 | enum e_thread_state_flags{ | ||
83 | THREAD_QUIT = 0x0, | ||
84 | THREAD_YIELD = 0x1, | ||
85 | THREAD_TIMEREVENT = 0x2, | ||
86 | THREAD_PLAYBKEVENT = 0x4, | ||
87 | THREAD_ACTEVENT = 0x8, | ||
88 | THREAD_BUTEVENT = 0x10, | ||
89 | THREAD_CUSTOMEVENT = 0x20, | ||
90 | //THREAD_AVAILEVENT = 0x40, | ||
91 | //THREAD_AVAILEVENT = 0x80, | ||
92 | /* thread state holds 3 status items using masks and bitshifts */ | ||
93 | THREAD_STATEMASK = 0x00FF, | ||
94 | THREAD_SUSPENDMASK = 0xFF00, | ||
95 | THREAD_INPUTMASK = 0xFF0000, | ||
96 | }; | ||
97 | |||
98 | enum { | ||
99 | ACTEVENT = 0, | ||
100 | BUTEVENT, | ||
101 | CUSTOMEVENT, | ||
102 | PLAYBKEVENT, | ||
103 | TIMEREVENT, | ||
104 | EVENT_CT | ||
105 | }; | ||
106 | |||
107 | static const unsigned char thread_ev_states[EVENT_CT] = | ||
108 | { | ||
109 | [ACTEVENT] = THREAD_ACTEVENT, | ||
110 | [BUTEVENT] = THREAD_BUTEVENT, | ||
111 | [CUSTOMEVENT] = THREAD_CUSTOMEVENT, | ||
112 | [PLAYBKEVENT] = THREAD_PLAYBKEVENT, | ||
113 | [TIMEREVENT] = THREAD_TIMEREVENT, | ||
114 | }; | ||
115 | |||
116 | static const char *const ev_map[EVENT_CT] = | ||
117 | { | ||
118 | [ACTEVENT] = "action", | ||
119 | [BUTEVENT] = "button", | ||
120 | [CUSTOMEVENT] = "custom", | ||
121 | [PLAYBKEVENT] = "playback", | ||
122 | [TIMEREVENT] = "timer", | ||
123 | }; | ||
124 | |||
125 | struct cb_data { | ||
126 | int cb_ref; | ||
127 | unsigned long id; | ||
128 | void *data; | ||
129 | }; | ||
130 | |||
131 | struct event_data { | ||
132 | /* lua */ | ||
133 | lua_State *L; | ||
134 | lua_State *NEWL; | ||
135 | /* rockbox */ | ||
136 | unsigned int thread_id; | ||
137 | int thread_state; | ||
138 | long *event_stack; | ||
139 | long timer_ticks; | ||
140 | short next_input; | ||
141 | short next_event; | ||
142 | /* callbacks */ | ||
143 | struct cb_data *cb[EVENT_CT]; | ||
144 | }; | ||
145 | |||
146 | static struct event_data ev_data; | ||
147 | static struct mutex rev_mtx SHAREDBSS_ATTR; | ||
148 | |||
149 | #ifdef DEBUG_EV | ||
150 | static int dbg_hook_calls = 0; | ||
151 | #endif | ||
152 | |||
153 | static inline bool has_event(int ev_flag) | ||
154 | { | ||
155 | return ((THREAD_STATEMASK & (ev_data.thread_state & ev_flag)) == ev_flag); | ||
156 | } | ||
157 | |||
158 | static inline bool is_suspend(int ev_flag) | ||
159 | { | ||
160 | ev_flag <<= 8; | ||
161 | return ((THREAD_SUSPENDMASK & (ev_data.thread_state & ev_flag)) == ev_flag); | ||
162 | } | ||
163 | |||
164 | static void init_event_data(lua_State *L, struct event_data *ev_data) | ||
165 | { | ||
166 | /* lua */ | ||
167 | ev_data->L = L; | ||
168 | //ev_data->NEWL = NULL; | ||
169 | /* rockbox */ | ||
170 | ev_data->thread_id = UINT_MAX; | ||
171 | ev_data->thread_state = THREAD_YIELD; | ||
172 | //ev_data->event_stack = NULL; | ||
173 | //ev_data->timer_ticks = 0; | ||
174 | ev_data->next_input = EV_TICKS; | ||
175 | ev_data->next_event = EV_INPUT; | ||
176 | /* callbacks */ | ||
177 | for (int i= 0; i < EVENT_CT; i++) | ||
178 | ev_data->cb[i] = NULL; | ||
179 | } | ||
180 | |||
181 | /* lock and unlock routines allow us to execute the event thread without | ||
182 | * trashing the lua state on error, yield, or sleep in the callback functions */ | ||
183 | |||
184 | static inline void rev_lock_mtx(void) | ||
185 | { | ||
186 | rb->mutex_lock(&rev_mtx); | ||
187 | } | ||
188 | |||
189 | static inline void rev_unlock_mtx(void) | ||
190 | { | ||
191 | rb->mutex_unlock(&rev_mtx); | ||
192 | } | ||
193 | |||
194 | static void lua_interrupt_callback( lua_State *L, lua_Debug *ar) | ||
195 | { | ||
196 | (void) L; | ||
197 | (void) ar; | ||
198 | #ifdef DEBUG_EV | ||
199 | dbg_hook_calls++; | ||
200 | #endif | ||
201 | |||
202 | rb->yield(); | ||
203 | |||
204 | rev_lock_mtx(); | ||
205 | rev_unlock_mtx(); /* must wait till event thread is done to continue */ | ||
206 | |||
207 | #ifdef DEBUG_EV | ||
208 | rb->splashf(0, "spin %d, hooked %d", dbg_hook_calls, (lua_gethookmask(L) != 0)); | ||
209 | unsigned char delay = -1; | ||
210 | /* we can't sleep or yield without affecting count so lets spin in a loop */ | ||
211 | while(delay > 0) {delay--;} | ||
212 | if (lua_gethookmask(L) == 0) | ||
213 | dbg_hook_calls = 0; | ||
214 | #endif | ||
215 | |||
216 | /* if callback error, pass error to the main lua state */ | ||
217 | if (lua_status(ev_data.NEWL) != LUA_SUCCESS) | ||
218 | luaL_error (L, lua_tostring (ev_data.NEWL, -1)); | ||
219 | } | ||
220 | |||
221 | static void lua_interrupt_set(lua_State *L, bool is_enabled) | ||
222 | { | ||
223 | const int hookmask = LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT; | ||
224 | |||
225 | if (is_enabled) | ||
226 | lua_sethook(L, lua_interrupt_callback, hookmask, 1 ); | ||
227 | else | ||
228 | lua_sethook(L, NULL, 0, 0 ); | ||
229 | } | ||
230 | |||
231 | static int lua_rev_callback(lua_State *L, struct cb_data *cbd) | ||
232 | { | ||
233 | int lua_status = LUA_ERRRUN; | ||
234 | |||
235 | if (L != NULL) | ||
236 | { | ||
237 | /* load cb function from lua registry */ | ||
238 | lua_rawgeti(L, LUA_REGISTRYINDEX, cbd->cb_ref); | ||
239 | |||
240 | lua_pushinteger(L, cbd->id); | ||
241 | lua_pushlightuserdata (L, cbd->data); | ||
242 | |||
243 | lua_status = lua_resume(L, 2); | ||
244 | if (lua_status == LUA_YIELD) /* coroutine.yield() disallowed */ | ||
245 | luaL_where(L, 0); /* push error string on stack */ | ||
246 | } | ||
247 | return lua_status; | ||
248 | } | ||
249 | |||
250 | static void event_thread(void) | ||
251 | { | ||
252 | unsigned long action; | ||
253 | int event; | ||
254 | int ev_flag; | ||
255 | |||
256 | while(ev_data.thread_state != THREAD_QUIT && lua_status(ev_data.L) == LUA_SUCCESS) | ||
257 | { | ||
258 | rev_lock_mtx(); | ||
259 | lua_interrupt_set(ev_data.L, true); | ||
260 | |||
261 | for (event = 0; event < EVENT_CT; event++) | ||
262 | { | ||
263 | ev_flag = thread_ev_states[event]; | ||
264 | if (!has_event(ev_flag) || is_suspend(ev_flag)) | ||
265 | continue; /* check next event */ | ||
266 | |||
267 | ev_data.thread_state &= ~(ev_flag); /* event handled */ | ||
268 | |||
269 | switch (event) | ||
270 | { | ||
271 | case ACTEVENT: | ||
272 | action = get_plugin_action(TIMEOUT_NOBLOCK, true); | ||
273 | if (action == ACTION_UNKNOWN) | ||
274 | continue; /* check next event */ | ||
275 | else if (action == ACTION_NONE) | ||
276 | { | ||
277 | /* only send ACTION_NONE once */ | ||
278 | if (ev_data.cb[ACTEVENT]->id == ACTION_NONE || | ||
279 | rb->button_status() != 0) | ||
280 | continue; /* check next event */ | ||
281 | } | ||
282 | ev_data.cb[ACTEVENT]->id = action; | ||
283 | break; | ||
284 | case BUTEVENT: | ||
285 | ev_data.cb[BUTEVENT]->id = rb->button_get(false); | ||
286 | if (ev_data.cb[BUTEVENT]->id == 0) | ||
287 | continue; /* check next event */ | ||
288 | break; | ||
289 | case CUSTOMEVENT: | ||
290 | ev_data.thread_state |= thread_ev_states[CUSTOMEVENT]; // don't reset */ | ||
291 | break; | ||
292 | case PLAYBKEVENT: | ||
293 | break; | ||
294 | case TIMEREVENT: | ||
295 | ev_data.cb[TIMEREVENT]->id = *rb->current_tick + ev_data.timer_ticks; | ||
296 | break; | ||
297 | |||
298 | } | ||
299 | |||
300 | if (lua_rev_callback(ev_data.NEWL, ev_data.cb[event]) != LUA_SUCCESS) | ||
301 | { | ||
302 | rev_unlock_mtx(); | ||
303 | goto event_error; | ||
304 | } | ||
305 | } | ||
306 | rev_unlock_mtx(); /* we are safe to release back to main lua state */ | ||
307 | |||
308 | do | ||
309 | { | ||
310 | #ifdef DEBUG_EV | ||
311 | dbg_hook_calls--; | ||
312 | #endif | ||
313 | lua_interrupt_set(ev_data.L, false); | ||
314 | ev_data.next_event = EV_TICKS; | ||
315 | rb->yield(); | ||
316 | } while(ev_data.thread_state == THREAD_YIELD || is_suspend(THREAD_SUSPENDMASK)); | ||
317 | |||
318 | } | ||
319 | |||
320 | event_error: | ||
321 | |||
322 | /* thread is exiting -- clean up */ | ||
323 | rb->timer_unregister(); | ||
324 | rb->yield(); | ||
325 | rb->thread_exit(); | ||
326 | |||
327 | return; | ||
328 | } | ||
329 | |||
330 | /* timer interrupt callback */ | ||
331 | static void rev_timer_isr(void) | ||
332 | { | ||
333 | ev_data.next_event--; | ||
334 | ev_data.next_input--; | ||
335 | |||
336 | if (ev_data.next_input <=0) | ||
337 | { | ||
338 | ev_data.thread_state |= ((ev_data.thread_state & THREAD_INPUTMASK) >> 16); | ||
339 | ev_data.next_input = EV_INPUT; | ||
340 | } | ||
341 | |||
342 | if (ev_data.cb[TIMEREVENT] != NULL && !is_suspend(TIMEREVENT)) | ||
343 | { | ||
344 | if (TIME_AFTER(*rb->current_tick, ev_data.cb[TIMEREVENT]->id)) | ||
345 | { | ||
346 | ev_data.thread_state |= thread_ev_states[TIMEREVENT]; | ||
347 | ev_data.next_event = 0; | ||
348 | } | ||
349 | } | ||
350 | |||
351 | if (ev_data.next_event <= 0) | ||
352 | lua_interrupt_set(ev_data.L, true); | ||
353 | } | ||
354 | |||
355 | static void create_event_thread_ref(struct event_data *ev_data) | ||
356 | { | ||
357 | lua_State *L = ev_data->L; | ||
358 | |||
359 | lua_createtable(L, 2, 0); | ||
360 | |||
361 | ev_data->event_stack = (long *) lua_newuserdata (L, DEFAULT_STACK_SIZE); | ||
362 | |||
363 | /* attach EVENT_METATABLE to ud so we get notified on garbage collection */ | ||
364 | luaL_getmetatable (L, EVENT_METATABLE); | ||
365 | lua_setmetatable (L, -2); | ||
366 | lua_rawseti(L, -2, 1); | ||
367 | |||
368 | ev_data->NEWL = lua_newthread(L); | ||
369 | lua_rawseti(L, -2, 2); | ||
370 | |||
371 | lua_setfield (L, LUA_REGISTRYINDEX, EVENT_THREAD); /* store references */ | ||
372 | } | ||
373 | |||
374 | static void destroy_event_thread_ref(struct event_data *ev_data) | ||
375 | { | ||
376 | lua_State *L = ev_data->L; | ||
377 | ev_data->event_stack = NULL; | ||
378 | ev_data->NEWL = NULL; | ||
379 | lua_pushnil(L); | ||
380 | lua_setfield (L, LUA_REGISTRYINDEX, EVENT_THREAD); /* free references */ | ||
381 | } | ||
382 | |||
383 | static void exit_event_thread(struct event_data *ev_data) | ||
384 | { | ||
385 | ev_data->thread_state = THREAD_QUIT; | ||
386 | rb->thread_wait(ev_data->thread_id); /* wait for thread to exit */ | ||
387 | |||
388 | ev_data->thread_state = THREAD_YIELD; | ||
389 | ev_data->thread_id = UINT_MAX; | ||
390 | } | ||
391 | |||
392 | static void init_event_thread(bool init, struct event_data *ev_data) | ||
393 | { | ||
394 | if (ev_data->event_stack != NULL) /* make sure we don't double free */ | ||
395 | { | ||
396 | if (!init && ev_data->thread_id != UINT_MAX) | ||
397 | { | ||
398 | exit_event_thread(ev_data); | ||
399 | destroy_event_thread_ref(ev_data); | ||
400 | lua_interrupt_set(ev_data->L, false); | ||
401 | } | ||
402 | return; | ||
403 | } | ||
404 | else if (!init) | ||
405 | return; | ||
406 | |||
407 | create_event_thread_ref(ev_data); | ||
408 | if (ev_data->NEWL == NULL || ev_data->event_stack == NULL) | ||
409 | return; | ||
410 | |||
411 | ev_data->thread_id = rb->create_thread(&event_thread, | ||
412 | ev_data->event_stack, | ||
413 | DEFAULT_STACK_SIZE, | ||
414 | 0, | ||
415 | EVENT_THREAD | ||
416 | IF_PRIO(, PRIORITY_SYSTEM) | ||
417 | IF_COP(, COP)); | ||
418 | |||
419 | /* Timer is used to poll waiting events */ | ||
420 | rb->timer_register(0, NULL, EV_TIMER_FREQ, rev_timer_isr IF_COP(, CPU)); | ||
421 | } | ||
422 | |||
423 | static void playback_event_callback(unsigned short id, void *data) | ||
424 | { | ||
425 | /* playback events are synchronous we need to return ASAP so set a flag */ | ||
426 | ev_data.thread_state |= thread_ev_states[PLAYBKEVENT]; | ||
427 | ev_data.cb[PLAYBKEVENT]->id = id; | ||
428 | ev_data.cb[PLAYBKEVENT]->data = data; | ||
429 | lua_interrupt_set(ev_data.L, true); | ||
430 | } | ||
431 | |||
432 | static void register_playbk_events(int flag_events, | ||
433 | void (*handler)(unsigned short id, void *data)) | ||
434 | { | ||
435 | long unsigned int i = 0; | ||
436 | const unsigned short playback_events[7] = | ||
437 | { /*flags*/ | ||
438 | PLAYBACK_EVENT_START_PLAYBACK, /* 0x1 */ | ||
439 | PLAYBACK_EVENT_TRACK_BUFFER, /* 0x2 */ | ||
440 | PLAYBACK_EVENT_CUR_TRACK_READY, /* 0x4 */ | ||
441 | PLAYBACK_EVENT_TRACK_FINISH, /* 0x8 */ | ||
442 | PLAYBACK_EVENT_TRACK_CHANGE, /* 0x10*/ | ||
443 | PLAYBACK_EVENT_TRACK_SKIP, /* 0x20*/ | ||
444 | PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE /* 0x40*/ | ||
445 | }; | ||
446 | |||
447 | for(; i < ARRAYLEN(playback_events); i++, flag_events >>= 1) | ||
448 | { | ||
449 | if (flag_events == 0) /* remove events */ | ||
450 | rb->remove_event(playback_events[i], handler); | ||
451 | else /* add events */ | ||
452 | if ((flag_events & 0x1) == 0x1) | ||
453 | rb->add_event(playback_events[i], handler); | ||
454 | } | ||
455 | } | ||
456 | |||
457 | static void destroy_event_userdata(lua_State *L, int event) | ||
458 | { | ||
459 | if (ev_data.cb[event] != NULL) | ||
460 | { | ||
461 | int ev_flag = thread_ev_states[event]; | ||
462 | ev_data.thread_state &= ~(ev_flag | (ev_flag << 8) | (ev_flag << 16)); | ||
463 | |||
464 | luaL_unref (L, LUA_REGISTRYINDEX, ev_data.cb[event]->cb_ref); | ||
465 | ev_data.cb[event] = NULL; | ||
466 | } | ||
467 | } | ||
468 | |||
469 | static void create_event_userdata(lua_State *L, int event, int index) | ||
470 | { | ||
471 | /* if function is already registered , unregister it */ | ||
472 | destroy_event_userdata(L, event); | ||
473 | |||
474 | if (!lua_isfunction (L, index)) | ||
475 | { | ||
476 | init_event_thread(false, &ev_data); | ||
477 | luaL_typerror (L, index, "function"); | ||
478 | return; | ||
479 | } | ||
480 | |||
481 | lua_pushvalue (L, index); /* copy passed lua function on top of stack */ | ||
482 | int ref_lua = luaL_ref(L, LUA_REGISTRYINDEX); | ||
483 | |||
484 | ev_data.cb[event] = (struct cb_data *)lua_newuserdata(L, sizeof(struct cb_data)); | ||
485 | |||
486 | ev_data.cb[event]->cb_ref = ref_lua; /* store ref for later call/release */ | ||
487 | |||
488 | /* attach EVENT_METATABLE to ud so we get notified on garbage collection */ | ||
489 | luaL_getmetatable (L, EVENT_METATABLE); | ||
490 | lua_setmetatable (L, -2); | ||
491 | /* cb_data is on top of stack */ | ||
492 | } | ||
493 | |||
494 | static int rockev_gc(lua_State *L) { | ||
495 | bool has_events = false; | ||
496 | void *d = (void *) lua_touserdata (L, 1); | ||
497 | |||
498 | if (d == NULL) | ||
499 | return 0; | ||
500 | else if (d == ev_data.event_stack) /* thread stack is gc'd kill thread */ | ||
501 | init_event_thread(false, &ev_data); | ||
502 | else if (d == ev_data.cb[PLAYBKEVENT]) | ||
503 | register_playbk_events(0, &playback_event_callback); | ||
504 | |||
505 | for( int i= 0; i < EVENT_CT; i++) | ||
506 | { | ||
507 | if (d == ev_data.cb[i]) | ||
508 | destroy_event_userdata(L, i); | ||
509 | else if (ev_data.cb[i] != NULL) | ||
510 | has_events = true; | ||
511 | } | ||
512 | |||
513 | if (!has_events) /* nothing to wait for kill thread */ | ||
514 | init_event_thread(false, &ev_data); | ||
515 | |||
516 | return 0; | ||
517 | } | ||
518 | |||
519 | /****************************************************************************** | ||
520 | * LUA INTERFACE ************************************************************** | ||
521 | ******************************************************************************* | ||
522 | */ | ||
523 | |||
524 | static int rockev_register(lua_State *L) | ||
525 | { | ||
526 | int event = luaL_checkoption(L, 1, NULL, ev_map); | ||
527 | int ev_flag = thread_ev_states[event]; | ||
528 | int playbk_events; | ||
529 | |||
530 | lua_settop (L, 3); /* we need to lock our optional args before...*/ | ||
531 | create_event_userdata(L, event, 2);/* cb_data is on top of stack */ | ||
532 | |||
533 | switch (event) | ||
534 | { | ||
535 | case ACTEVENT: | ||
536 | /* fall through */ | ||
537 | case BUTEVENT: | ||
538 | ev_data.thread_state |= (ev_flag | (ev_flag << 16)); | ||
539 | break; | ||
540 | case CUSTOMEVENT: | ||
541 | break; | ||
542 | case PLAYBKEVENT: | ||
543 | /* see register_playbk_events() for flags */ | ||
544 | playbk_events = luaL_optinteger(L, 3, 0x3F); | ||
545 | register_playbk_events(playbk_events, &playback_event_callback); | ||
546 | break; | ||
547 | case TIMEREVENT: | ||
548 | ev_data.timer_ticks = luaL_checkinteger(L, 3); | ||
549 | ev_data.cb[TIMEREVENT]->id = *rb->current_tick + ev_data.timer_ticks; | ||
550 | break; | ||
551 | } | ||
552 | |||
553 | init_event_thread(true, &ev_data); | ||
554 | |||
555 | return 1; /* returns cb_data */ | ||
556 | } | ||
557 | |||
558 | static int rockev_suspend(lua_State *L) | ||
559 | { | ||
560 | int event; /*Arg 1 is event pass nil to suspend all */ | ||
561 | bool suspend = luaL_optboolean(L, 2, true); | ||
562 | int ev_flag = THREAD_SUSPENDMASK; | ||
563 | |||
564 | if (!lua_isnoneornil(L, 1)) | ||
565 | { | ||
566 | event = luaL_checkoption(L, 1, NULL, ev_map); | ||
567 | ev_flag = thread_ev_states[event] << 8; | ||
568 | } | ||
569 | |||
570 | if (suspend) | ||
571 | ev_data.thread_state |= ev_flag; | ||
572 | else | ||
573 | ev_data.thread_state &= ~(ev_flag); | ||
574 | |||
575 | return 0; | ||
576 | } | ||
577 | |||
578 | static int rockev_trigger(lua_State *L) | ||
579 | { | ||
580 | int event = luaL_checkoption(L, 1, NULL, ev_map); | ||
581 | bool enable = luaL_optboolean(L, 2, true); | ||
582 | |||
583 | int ev_flag; | ||
584 | |||
585 | /* protect from invalid events */ | ||
586 | if (ev_data.cb[event] != NULL) | ||
587 | { | ||
588 | ev_flag = thread_ev_states[event]; | ||
589 | /* allow user to pass an id to some of the callback functions */ | ||
590 | ev_data.cb[event]->id = luaL_optinteger(L, 3, ev_data.cb[event]->id); | ||
591 | |||
592 | if (enable) | ||
593 | ev_data.thread_state |= ev_flag; | ||
594 | else | ||
595 | ev_data.thread_state &= ~(ev_flag); | ||
596 | } | ||
597 | return 0; | ||
598 | } | ||
599 | |||
600 | static int rockev_unregister(lua_State *L) | ||
601 | { | ||
602 | luaL_checkudata (L, 1, EVENT_METATABLE); | ||
603 | rockev_gc(L); | ||
604 | lua_pushnil(L); | ||
605 | return 1; | ||
606 | } | ||
607 | /* | ||
608 | ** Creates events metatable. | ||
609 | */ | ||
610 | static int event_create_meta (lua_State *L) { | ||
611 | luaL_newmetatable (L, EVENT_METATABLE); | ||
612 | /* set __gc field so we can clean-up our objects */ | ||
613 | lua_pushcfunction (L, rockev_gc); | ||
614 | lua_setfield (L, -2, "__gc"); | ||
615 | return 1; | ||
616 | } | ||
617 | |||
618 | static const struct luaL_reg evlib[] = { | ||
619 | {"register", rockev_register}, | ||
620 | {"suspend", rockev_suspend}, | ||
621 | {"trigger", rockev_trigger}, | ||
622 | {"unregister", rockev_unregister}, | ||
623 | {NULL, NULL} | ||
624 | }; | ||
625 | |||
626 | int luaopen_rockevents (lua_State *L) { | ||
627 | rb->mutex_init(&rev_mtx); | ||
628 | init_event_data(L, &ev_data); | ||
629 | event_create_meta (L); | ||
630 | luaL_register (L, LUA_ROCKEVENTSNAME, evlib); | ||
631 | return 1; | ||
632 | } | ||