summaryrefslogtreecommitdiff
path: root/apps/plugins/lua/rocklib_events.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/lua/rocklib_events.c')
-rw-r--r--apps/plugins/lua/rocklib_events.c632
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
82enum 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
98enum {
99 ACTEVENT = 0,
100 BUTEVENT,
101 CUSTOMEVENT,
102 PLAYBKEVENT,
103 TIMEREVENT,
104 EVENT_CT
105};
106
107static 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
116static 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
125struct cb_data {
126 int cb_ref;
127 unsigned long id;
128 void *data;
129};
130
131struct 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
146static struct event_data ev_data;
147static struct mutex rev_mtx SHAREDBSS_ATTR;
148
149#ifdef DEBUG_EV
150static int dbg_hook_calls = 0;
151#endif
152
153static inline bool has_event(int ev_flag)
154{
155 return ((THREAD_STATEMASK & (ev_data.thread_state & ev_flag)) == ev_flag);
156}
157
158static 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
164static 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
184static inline void rev_lock_mtx(void)
185{
186 rb->mutex_lock(&rev_mtx);
187}
188
189static inline void rev_unlock_mtx(void)
190{
191 rb->mutex_unlock(&rev_mtx);
192}
193
194static 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
221static 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
231static 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
250static 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
320event_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 */
331static 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
355static 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
374static 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
383static 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
392static 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
423static 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
432static 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
457static 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
469static 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
494static 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
524static 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
558static 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
578static 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
600static 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*/
610static 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
618static 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
626int 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}