William Wilgus | 3e2b50e | 2019-06-27 11:28:34 -0500 | [diff] [blame] | 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; |
William Wilgus | 90118f1 | 2019-07-26 01:30:00 -0500 | [diff] [blame^] | 140 | short freq_input; |
William Wilgus | 3e2b50e | 2019-06-27 11:28:34 -0500 | [diff] [blame] | 141 | short next_input; |
| 142 | short next_event; |
| 143 | /* callbacks */ |
| 144 | struct cb_data *cb[EVENT_CT]; |
| 145 | }; |
| 146 | |
| 147 | static struct event_data ev_data; |
| 148 | static struct mutex rev_mtx SHAREDBSS_ATTR; |
| 149 | |
| 150 | #ifdef DEBUG_EV |
| 151 | static int dbg_hook_calls = 0; |
| 152 | #endif |
| 153 | |
| 154 | static inline bool has_event(int ev_flag) |
| 155 | { |
| 156 | return ((THREAD_STATEMASK & (ev_data.thread_state & ev_flag)) == ev_flag); |
| 157 | } |
| 158 | |
| 159 | static 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 | |
| 165 | static 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 Wilgus | 90118f1 | 2019-07-26 01:30:00 -0500 | [diff] [blame^] | 175 | ev_data->freq_input = EV_INPUT; |
| 176 | ev_data->next_input = EV_INPUT; |
| 177 | ev_data->next_event = EV_TICKS; |
William Wilgus | 3e2b50e | 2019-06-27 11:28:34 -0500 | [diff] [blame] | 178 | /* 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 | |
| 186 | static inline void rev_lock_mtx(void) |
| 187 | { |
| 188 | rb->mutex_lock(&rev_mtx); |
| 189 | } |
| 190 | |
| 191 | static inline void rev_unlock_mtx(void) |
| 192 | { |
| 193 | rb->mutex_unlock(&rev_mtx); |
| 194 | } |
| 195 | |
| 196 | static 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 | |
| 223 | static 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 | |
| 233 | static 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 | |
| 252 | static 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 | |
| 322 | event_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 */ |
| 333 | static 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 Wilgus | 90118f1 | 2019-07-26 01:30:00 -0500 | [diff] [blame^] | 341 | ev_data.next_input = ev_data.freq_input; |
William Wilgus | 3e2b50e | 2019-06-27 11:28:34 -0500 | [diff] [blame] | 342 | } |
| 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 | |
| 357 | static 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 | |
| 376 | static 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 | |
| 385 | static 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 | |
| 394 | static 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 | |
| 425 | static 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 | |
| 434 | static 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 | |
| 459 | static 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 | |
| 471 | static 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 | |
| 496 | static 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 | |
| 526 | static 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 Wilgus | 90118f1 | 2019-07-26 01:30:00 -0500 | [diff] [blame^] | 540 | 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 Wilgus | 3e2b50e | 2019-06-27 11:28:34 -0500 | [diff] [blame] | 542 | 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 | |
| 562 | static 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 | |
| 582 | static 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 | |
| 604 | static 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 | */ |
| 614 | static 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 | |
| 622 | static 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 | |
| 630 | int 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 | } |