blob: 270c4ab39390eaad88c8f6c9cbfc5d5f3a66ff88 [file] [log] [blame]
William Wilgus3e2b50e2019-06-27 11:28:34 -05001/***************************************************************************
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;
William Wilgus90118f12019-07-26 01:30:00 -0500140 short freq_input;
William Wilgus3e2b50e2019-06-27 11:28:34 -0500141 short next_input;
142 short next_event;
143 /* callbacks */
144 struct cb_data *cb[EVENT_CT];
145};
146
147static struct event_data ev_data;
148static struct mutex rev_mtx SHAREDBSS_ATTR;
149
150#ifdef DEBUG_EV
151static int dbg_hook_calls = 0;
152#endif
153
154static inline bool has_event(int ev_flag)
155{
156 return ((THREAD_STATEMASK & (ev_data.thread_state & ev_flag)) == ev_flag);
157}
158
159static inline bool is_suspend(int ev_flag)
160{
161 ev_flag <<= 8;
162 return ((THREAD_SUSPENDMASK & (ev_data.thread_state & ev_flag)) == ev_flag);
163}
164
165static void init_event_data(lua_State *L, struct event_data *ev_data)
166{
167 /* lua */
168 ev_data->L = L;
169 //ev_data->NEWL = NULL;
170 /* rockbox */
171 ev_data->thread_id = UINT_MAX;
172 ev_data->thread_state = THREAD_YIELD;
173 //ev_data->event_stack = NULL;
174 //ev_data->timer_ticks = 0;
William Wilgus90118f12019-07-26 01:30:00 -0500175 ev_data->freq_input = EV_INPUT;
176 ev_data->next_input = EV_INPUT;
177 ev_data->next_event = EV_TICKS;
William Wilgus3e2b50e2019-06-27 11:28:34 -0500178 /* callbacks */
179 for (int i= 0; i < EVENT_CT; i++)
180 ev_data->cb[i] = NULL;
181}
182
183/* lock and unlock routines allow us to execute the event thread without
184 * trashing the lua state on error, yield, or sleep in the callback functions */
185
186static inline void rev_lock_mtx(void)
187{
188 rb->mutex_lock(&rev_mtx);
189}
190
191static inline void rev_unlock_mtx(void)
192{
193 rb->mutex_unlock(&rev_mtx);
194}
195
196static void lua_interrupt_callback( lua_State *L, lua_Debug *ar)
197{
198 (void) L;
199 (void) ar;
200#ifdef DEBUG_EV
201 dbg_hook_calls++;
202#endif
203
204 rb->yield();
205
206 rev_lock_mtx();
207 rev_unlock_mtx(); /* must wait till event thread is done to continue */
208
209#ifdef DEBUG_EV
210 rb->splashf(0, "spin %d, hooked %d", dbg_hook_calls, (lua_gethookmask(L) != 0));
211 unsigned char delay = -1;
212 /* we can't sleep or yield without affecting count so lets spin in a loop */
213 while(delay > 0) {delay--;}
214 if (lua_gethookmask(L) == 0)
215 dbg_hook_calls = 0;
216#endif
217
218 /* if callback error, pass error to the main lua state */
219 if (lua_status(ev_data.NEWL) != LUA_SUCCESS)
220 luaL_error (L, lua_tostring (ev_data.NEWL, -1));
221}
222
223static void lua_interrupt_set(lua_State *L, bool is_enabled)
224{
225 const int hookmask = LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT;
226
227 if (is_enabled)
228 lua_sethook(L, lua_interrupt_callback, hookmask, 1 );
229 else
230 lua_sethook(L, NULL, 0, 0 );
231}
232
233static int lua_rev_callback(lua_State *L, struct cb_data *cbd)
234{
235 int lua_status = LUA_ERRRUN;
236
237 if (L != NULL)
238 {
239 /* load cb function from lua registry */
240 lua_rawgeti(L, LUA_REGISTRYINDEX, cbd->cb_ref);
241
242 lua_pushinteger(L, cbd->id);
243 lua_pushlightuserdata (L, cbd->data);
244
245 lua_status = lua_resume(L, 2);
246 if (lua_status == LUA_YIELD) /* coroutine.yield() disallowed */
247 luaL_where(L, 0); /* push error string on stack */
248 }
249 return lua_status;
250}
251
252static void event_thread(void)
253{
254 unsigned long action;
255 int event;
256 int ev_flag;
257
258 while(ev_data.thread_state != THREAD_QUIT && lua_status(ev_data.L) == LUA_SUCCESS)
259 {
260 rev_lock_mtx();
261 lua_interrupt_set(ev_data.L, true);
262
263 for (event = 0; event < EVENT_CT; event++)
264 {
265 ev_flag = thread_ev_states[event];
266 if (!has_event(ev_flag) || is_suspend(ev_flag))
267 continue; /* check next event */
268
269 ev_data.thread_state &= ~(ev_flag); /* event handled */
270
271 switch (event)
272 {
273 case ACTEVENT:
274 action = get_plugin_action(TIMEOUT_NOBLOCK, true);
275 if (action == ACTION_UNKNOWN)
276 continue; /* check next event */
277 else if (action == ACTION_NONE)
278 {
279 /* only send ACTION_NONE once */
280 if (ev_data.cb[ACTEVENT]->id == ACTION_NONE ||
281 rb->button_status() != 0)
282 continue; /* check next event */
283 }
284 ev_data.cb[ACTEVENT]->id = action;
285 break;
286 case BUTEVENT:
287 ev_data.cb[BUTEVENT]->id = rb->button_get(false);
288 if (ev_data.cb[BUTEVENT]->id == 0)
289 continue; /* check next event */
290 break;
291 case CUSTOMEVENT:
292 ev_data.thread_state |= thread_ev_states[CUSTOMEVENT]; // don't reset */
293 break;
294 case PLAYBKEVENT:
295 break;
296 case TIMEREVENT:
297 ev_data.cb[TIMEREVENT]->id = *rb->current_tick + ev_data.timer_ticks;
298 break;
299
300 }
301
302 if (lua_rev_callback(ev_data.NEWL, ev_data.cb[event]) != LUA_SUCCESS)
303 {
304 rev_unlock_mtx();
305 goto event_error;
306 }
307 }
308 rev_unlock_mtx(); /* we are safe to release back to main lua state */
309
310 do
311 {
312#ifdef DEBUG_EV
313 dbg_hook_calls--;
314#endif
315 lua_interrupt_set(ev_data.L, false);
316 ev_data.next_event = EV_TICKS;
317 rb->yield();
318 } while(ev_data.thread_state == THREAD_YIELD || is_suspend(THREAD_SUSPENDMASK));
319
320 }
321
322event_error:
323
324 /* thread is exiting -- clean up */
325 rb->timer_unregister();
326 rb->yield();
327 rb->thread_exit();
328
329 return;
330}
331
332/* timer interrupt callback */
333static void rev_timer_isr(void)
334{
335 ev_data.next_event--;
336 ev_data.next_input--;
337
338 if (ev_data.next_input <=0)
339 {
340 ev_data.thread_state |= ((ev_data.thread_state & THREAD_INPUTMASK) >> 16);
William Wilgus90118f12019-07-26 01:30:00 -0500341 ev_data.next_input = ev_data.freq_input;
William Wilgus3e2b50e2019-06-27 11:28:34 -0500342 }
343
344 if (ev_data.cb[TIMEREVENT] != NULL && !is_suspend(TIMEREVENT))
345 {
346 if (TIME_AFTER(*rb->current_tick, ev_data.cb[TIMEREVENT]->id))
347 {
348 ev_data.thread_state |= thread_ev_states[TIMEREVENT];
349 ev_data.next_event = 0;
350 }
351 }
352
353 if (ev_data.next_event <= 0)
354 lua_interrupt_set(ev_data.L, true);
355}
356
357static void create_event_thread_ref(struct event_data *ev_data)
358{
359 lua_State *L = ev_data->L;
360
361 lua_createtable(L, 2, 0);
362
363 ev_data->event_stack = (long *) lua_newuserdata (L, DEFAULT_STACK_SIZE);
364
365 /* attach EVENT_METATABLE to ud so we get notified on garbage collection */
366 luaL_getmetatable (L, EVENT_METATABLE);
367 lua_setmetatable (L, -2);
368 lua_rawseti(L, -2, 1);
369
370 ev_data->NEWL = lua_newthread(L);
371 lua_rawseti(L, -2, 2);
372
373 lua_setfield (L, LUA_REGISTRYINDEX, EVENT_THREAD); /* store references */
374}
375
376static void destroy_event_thread_ref(struct event_data *ev_data)
377{
378 lua_State *L = ev_data->L;
379 ev_data->event_stack = NULL;
380 ev_data->NEWL = NULL;
381 lua_pushnil(L);
382 lua_setfield (L, LUA_REGISTRYINDEX, EVENT_THREAD); /* free references */
383}
384
385static void exit_event_thread(struct event_data *ev_data)
386{
387 ev_data->thread_state = THREAD_QUIT;
388 rb->thread_wait(ev_data->thread_id); /* wait for thread to exit */
389
390 ev_data->thread_state = THREAD_YIELD;
391 ev_data->thread_id = UINT_MAX;
392}
393
394static void init_event_thread(bool init, struct event_data *ev_data)
395{
396 if (ev_data->event_stack != NULL) /* make sure we don't double free */
397 {
398 if (!init && ev_data->thread_id != UINT_MAX)
399 {
400 exit_event_thread(ev_data);
401 destroy_event_thread_ref(ev_data);
402 lua_interrupt_set(ev_data->L, false);
403 }
404 return;
405 }
406 else if (!init)
407 return;
408
409 create_event_thread_ref(ev_data);
410 if (ev_data->NEWL == NULL || ev_data->event_stack == NULL)
411 return;
412
413 ev_data->thread_id = rb->create_thread(&event_thread,
414 ev_data->event_stack,
415 DEFAULT_STACK_SIZE,
416 0,
417 EVENT_THREAD
418 IF_PRIO(, PRIORITY_SYSTEM)
419 IF_COP(, COP));
420
421 /* Timer is used to poll waiting events */
422 rb->timer_register(0, NULL, EV_TIMER_FREQ, rev_timer_isr IF_COP(, CPU));
423}
424
425static void playback_event_callback(unsigned short id, void *data)
426{
427 /* playback events are synchronous we need to return ASAP so set a flag */
428 ev_data.thread_state |= thread_ev_states[PLAYBKEVENT];
429 ev_data.cb[PLAYBKEVENT]->id = id;
430 ev_data.cb[PLAYBKEVENT]->data = data;
431 lua_interrupt_set(ev_data.L, true);
432}
433
434static void register_playbk_events(int flag_events,
435 void (*handler)(unsigned short id, void *data))
436{
437 long unsigned int i = 0;
438 const unsigned short playback_events[7] =
439 { /*flags*/
440 PLAYBACK_EVENT_START_PLAYBACK, /* 0x1 */
441 PLAYBACK_EVENT_TRACK_BUFFER, /* 0x2 */
442 PLAYBACK_EVENT_CUR_TRACK_READY, /* 0x4 */
443 PLAYBACK_EVENT_TRACK_FINISH, /* 0x8 */
444 PLAYBACK_EVENT_TRACK_CHANGE, /* 0x10*/
445 PLAYBACK_EVENT_TRACK_SKIP, /* 0x20*/
446 PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE /* 0x40*/
447 };
448
449 for(; i < ARRAYLEN(playback_events); i++, flag_events >>= 1)
450 {
451 if (flag_events == 0) /* remove events */
452 rb->remove_event(playback_events[i], handler);
453 else /* add events */
454 if ((flag_events & 0x1) == 0x1)
455 rb->add_event(playback_events[i], handler);
456 }
457}
458
459static void destroy_event_userdata(lua_State *L, int event)
460{
461 if (ev_data.cb[event] != NULL)
462 {
463 int ev_flag = thread_ev_states[event];
464 ev_data.thread_state &= ~(ev_flag | (ev_flag << 8) | (ev_flag << 16));
465
466 luaL_unref (L, LUA_REGISTRYINDEX, ev_data.cb[event]->cb_ref);
467 ev_data.cb[event] = NULL;
468 }
469}
470
471static void create_event_userdata(lua_State *L, int event, int index)
472{
473 /* if function is already registered , unregister it */
474 destroy_event_userdata(L, event);
475
476 if (!lua_isfunction (L, index))
477 {
478 init_event_thread(false, &ev_data);
479 luaL_typerror (L, index, "function");
480 return;
481 }
482
483 lua_pushvalue (L, index); /* copy passed lua function on top of stack */
484 int ref_lua = luaL_ref(L, LUA_REGISTRYINDEX);
485
486 ev_data.cb[event] = (struct cb_data *)lua_newuserdata(L, sizeof(struct cb_data));
487
488 ev_data.cb[event]->cb_ref = ref_lua; /* store ref for later call/release */
489
490 /* attach EVENT_METATABLE to ud so we get notified on garbage collection */
491 luaL_getmetatable (L, EVENT_METATABLE);
492 lua_setmetatable (L, -2);
493 /* cb_data is on top of stack */
494}
495
496static int rockev_gc(lua_State *L) {
497 bool has_events = false;
498 void *d = (void *) lua_touserdata (L, 1);
499
500 if (d == NULL)
501 return 0;
502 else if (d == ev_data.event_stack) /* thread stack is gc'd kill thread */
503 init_event_thread(false, &ev_data);
504 else if (d == ev_data.cb[PLAYBKEVENT])
505 register_playbk_events(0, &playback_event_callback);
506
507 for( int i= 0; i < EVENT_CT; i++)
508 {
509 if (d == ev_data.cb[i])
510 destroy_event_userdata(L, i);
511 else if (ev_data.cb[i] != NULL)
512 has_events = true;
513 }
514
515 if (!has_events) /* nothing to wait for kill thread */
516 init_event_thread(false, &ev_data);
517
518 return 0;
519}
520
521/******************************************************************************
522 * LUA INTERFACE **************************************************************
523*******************************************************************************
524*/
525
526static int rockev_register(lua_State *L)
527{
528 int event = luaL_checkoption(L, 1, NULL, ev_map);
529 int ev_flag = thread_ev_states[event];
530 int playbk_events;
531
532 lua_settop (L, 3); /* we need to lock our optional args before...*/
533 create_event_userdata(L, event, 2);/* cb_data is on top of stack */
534
535 switch (event)
536 {
537 case ACTEVENT:
538 /* fall through */
539 case BUTEVENT:
William Wilgus90118f12019-07-26 01:30:00 -0500540 ev_data.freq_input = luaL_optinteger(L, 3, EV_INPUT);
541 if (ev_data.freq_input < HZ / 20) ev_data.freq_input = HZ / 20;
William Wilgus3e2b50e2019-06-27 11:28:34 -0500542 ev_data.thread_state |= (ev_flag | (ev_flag << 16));
543 break;
544 case CUSTOMEVENT:
545 break;
546 case PLAYBKEVENT:
547 /* see register_playbk_events() for flags */
548 playbk_events = luaL_optinteger(L, 3, 0x3F);
549 register_playbk_events(playbk_events, &playback_event_callback);
550 break;
551 case TIMEREVENT:
552 ev_data.timer_ticks = luaL_checkinteger(L, 3);
553 ev_data.cb[TIMEREVENT]->id = *rb->current_tick + ev_data.timer_ticks;
554 break;
555 }
556
557 init_event_thread(true, &ev_data);
558
559 return 1; /* returns cb_data */
560}
561
562static int rockev_suspend(lua_State *L)
563{
564 int event; /*Arg 1 is event pass nil to suspend all */
565 bool suspend = luaL_optboolean(L, 2, true);
566 int ev_flag = THREAD_SUSPENDMASK;
567
568 if (!lua_isnoneornil(L, 1))
569 {
570 event = luaL_checkoption(L, 1, NULL, ev_map);
571 ev_flag = thread_ev_states[event] << 8;
572 }
573
574 if (suspend)
575 ev_data.thread_state |= ev_flag;
576 else
577 ev_data.thread_state &= ~(ev_flag);
578
579 return 0;
580}
581
582static int rockev_trigger(lua_State *L)
583{
584 int event = luaL_checkoption(L, 1, NULL, ev_map);
585 bool enable = luaL_optboolean(L, 2, true);
586
587 int ev_flag;
588
589 /* protect from invalid events */
590 if (ev_data.cb[event] != NULL)
591 {
592 ev_flag = thread_ev_states[event];
593 /* allow user to pass an id to some of the callback functions */
594 ev_data.cb[event]->id = luaL_optinteger(L, 3, ev_data.cb[event]->id);
595
596 if (enable)
597 ev_data.thread_state |= ev_flag;
598 else
599 ev_data.thread_state &= ~(ev_flag);
600 }
601 return 0;
602}
603
604static int rockev_unregister(lua_State *L)
605{
606 luaL_checkudata (L, 1, EVENT_METATABLE);
607 rockev_gc(L);
608 lua_pushnil(L);
609 return 1;
610}
611/*
612** Creates events metatable.
613*/
614static int event_create_meta (lua_State *L) {
615 luaL_newmetatable (L, EVENT_METATABLE);
616 /* set __gc field so we can clean-up our objects */
617 lua_pushcfunction (L, rockev_gc);
618 lua_setfield (L, -2, "__gc");
619 return 1;
620}
621
622static const struct luaL_reg evlib[] = {
623 {"register", rockev_register},
624 {"suspend", rockev_suspend},
625 {"trigger", rockev_trigger},
626 {"unregister", rockev_unregister},
627 {NULL, NULL}
628};
629
630int luaopen_rockevents (lua_State *L) {
631 rb->mutex_init(&rev_mtx);
632 init_event_data(L, &ev_data);
633 event_create_meta (L);
634 luaL_register (L, LUA_ROCKEVENTSNAME, evlib);
635 return 1;
636}