diff options
| author | Ludvig Gunne Lindström <ludviggunnelindstrom@gmail.com> | 2026-07-17 11:42:57 +0200 |
|---|---|---|
| committer | Ludvig Gunne Lindström <ludviggunnelindstrom@gmail.com> | 2026-11-08 14:07:53 +0100 |
| commit | 98b3c66ebc79597f5b54fff132eaafbebf581248 (patch) | |
| tree | f999ec466ae3ac635653d1fe0a6a6dd1a2f2c3b0 /src/event.c | |
| download | melisma-main.tar.gz | |
Diffstat (limited to 'src/event.c')
| -rw-r--r-- | src/event.c | 374 |
1 files changed, 374 insertions, 0 deletions
diff --git a/src/event.c b/src/event.c new file mode 100644 index 0000000..2d5a41f --- /dev/null +++ b/src/event.c @@ -0,0 +1,374 @@ +/* + * melisma - extensible and scriptable audio engine + * Copyright (C) 2026 Ludvig Gunne Lindström + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <stdbool.h> + +#include <jack/jack.h> + +#include <lua.h> +#include <lauxlib.h> + +#include "mem.h" +#include "event.h" +#include "util.h" +#include "melisma.h" + +#define WHEEL_SIZE 4096 +#define MAX_EVENTS 2048 +#define MAINTHREAD_EVENT_CAPACITY (1<<17) +#define EVENT_HANDLE_TYPENAME "event handle" + +struct event +{ + ml_event_id id; + unsigned long seq; + unsigned long rounds; + int fn; + int arg; + ml_event_id next_sched; + ml_event_id next_free; + bool cancel; +}; + +extern jack_client_t *client; + +static struct event *pool; +static size_t pool_len; +static size_t pool_cap; +static size_t num_events; +static size_t counter; +static ml_event_id free_head = NO_EVENT; +static ml_event_id wheel[WHEEL_SIZE]; +static size_t cursor; +static int fn_cache_ref; + +static void init_event(ml_event_id id) +{ + struct event *e = &pool[id]; + e->seq = counter++; + e->next_free = NO_EVENT; + e->next_sched = NO_EVENT; + e->cancel = false; +} + +static ml_event_id alloc_event(void) +{ + ml_event_id id; + + if (free_head != NO_EVENT) + { + id = free_head; + free_head = pool[id].next_free; + goto done; + } + + if (pool_len == pool_cap) + { + pool_cap = pool_cap == 0 ? 64 : pool_cap * 2; + pool = srealloc(pool,sizeof(*pool)*pool_cap); + } + + id = pool_len++; + +done: + init_event(id); + num_events++; + return id; +} + +static void free_event(ml_event_id id) +{ + pool[id].seq = (unsigned long) -1; + + if (id == pool_len - 1) + { + pool_len--; + } + else + { + pool[id].next_free = free_head; + free_head = id; + } + + num_events--; +} + +static void schedule(ml_event_id id, unsigned long t) +{ + if (num_events == MAX_EVENTS) + { + fprintf(stderr,"WARNING: event dropped\n"); + return; + } + + unsigned long rounds = t / WHEEL_SIZE; + unsigned long index = (t+cursor) % WHEEL_SIZE; + ml_event_id next = wheel[index]; + ml_event_id prev = NO_EVENT; + + while (next != NO_EVENT && pool[next].rounds <= rounds) + { + prev = next; + next = pool[next].next_sched; + } + + if (prev == NO_EVENT) + { + wheel[index] = id; + } + else + { + pool[prev].next_sched = id; + } + + pool[id].rounds = rounds; + pool[id].next_sched = next; +} + +static bool cancel(long id, long seq) +{ + extern lua_State *state; + struct event *e = &pool[id]; + + if (e->seq == seq) + { + luaL_unref(state,LUA_REGISTRYINDEX,e->arg); + e->arg = LUA_NOREF; + e->cancel = true; + return true; + } + + return false; +} + +ml_event_handle ml_schedule_event(unsigned long t, int fn, int arg) +{ + ml_event_id id = alloc_event(); + + struct event *e = &pool[id]; + e->fn = fn; + e->arg = arg; + + schedule(id,t); + + ml_event_handle hdl = { + .id = id, + .seq = e->seq, + }; + return hdl; +} + +void ml_cancel_event(const ml_event_handle *hdl) +{ + cancel(hdl->id,hdl->seq); +} + +ml_event_id next_event(unsigned long tmax, unsigned long *t) +{ + *t = 0; + + while (true) + { + if (*t == tmax) + { + return NO_EVENT; + } + + if (cursor == WHEEL_SIZE) + { + cursor = 0; + } + + ml_event_id id = wheel[cursor]; + + if (id == NO_EVENT) + { + cursor++; + (*t)++; + continue; + } + + if (pool[id].rounds > 0) + { + while (id != NO_EVENT) + { + pool[id].rounds--; + id = pool[id].next_sched; + } + + cursor++; + (*t)++; + continue; + } + + wheel[cursor] = pool[id].next_sched; + + if (pool[id].cancel) + { + free_event(id); + continue; + } + + return id; + } +} + +void dispatch_event(ml_event_id id) +{ + struct event *e = &pool[id]; + + int fn = e->fn; + int arg = e->arg; + + extern lua_State *state; + + lua_rawgeti(state,LUA_REGISTRYINDEX,fn); + lua_rawgeti(state,LUA_REGISTRYINDEX,arg); + + if (lua_pcall_with_backtrace(state,1,0) != LUA_OK) + { + const char *err = luaL_checkstring(state,-1); + fprintf(stderr,"%s\n",err); + lua_pop(state,1); + } + + luaL_unref(state,LUA_REGISTRYINDEX,pool[id].arg); + + free_event(id); +} + +static int make_fn_ref(lua_State *s, int fn) +{ + int ref; + int top = lua_gettop(s); + + lua_pushvalue(s,fn); + fn = lua_gettop(s); + + lua_rawgeti(s,LUA_REGISTRYINDEX,fn_cache_ref); + int fn_cache = lua_gettop(s); + + lua_pushvalue(s,fn); + lua_gettable(s,fn_cache); + + if (lua_isinteger(s,-1)) + { + ref = lua_tointeger(s,-1); + goto done; + } + + lua_pushvalue(s,fn); + ref = luaL_ref(s,LUA_REGISTRYINDEX); + + lua_pushvalue(s,fn); + lua_pushinteger(s,ref); + lua_settable(s,fn_cache); + +done: + lua_settop(s,top); + return ref; +} + +static int l_schedule(lua_State *s) +{ + float real_t = luaL_checknumber(s,1); + luaL_checktype(s,2,LUA_TFUNCTION); + + int fn = make_fn_ref(s,2); + + lua_pushvalue(s,3); + int arg = luaL_ref(s,LUA_REGISTRYINDEX); + + unsigned long sr = ml_sample_rate(); + unsigned long t = sr * real_t; + + ml_event_id id = alloc_event(); + struct event *e = &pool[id]; + + e->fn = fn; + e->arg = arg; + + schedule(id,t); + + ml_event_handle *h = lua_newuserdata(s,sizeof *h); + luaL_setmetatable(s,EVENT_HANDLE_TYPENAME); + h->id = id; + h->seq = e->seq; + + return 1; +} + +static int l_cancel(lua_State *s) +{ + ml_event_handle *h = luaL_checkudata(s,1,EVENT_HANDLE_TYPENAME); + lua_pushboolean(s,cancel(h->id,h->seq)); + return 1; +} + +static int load_events(lua_State *s) +{ + luaL_newmetatable(s,EVENT_HANDLE_TYPENAME); + + lua_newtable(s); + int tbl = lua_gettop(s); + + lua_pushcfunction(s,l_schedule); + lua_setfield(s,tbl,"schedule"); + + lua_pushcfunction(s,l_cancel); + lua_setfield(s,tbl,"cancel"); + + lua_rawgeti(s,LUA_REGISTRYINDEX,fn_cache_ref); + lua_setfield(s,tbl,"function_cache"); + + lua_settop(s,tbl); + return 1; +} + +static void create_module(lua_State *s) +{ + int top = lua_gettop(s); + + lua_getglobal(s,"package"); + int package = lua_gettop(s); + + lua_getfield(s,package,"preload"); + int preload = lua_gettop(s); + + lua_pushcfunction(s,load_events); + lua_setfield(s,preload,"melisma.event"); + + lua_settop(s,top); +} + +static void create_fn_cache(lua_State *s) +{ + lua_newtable(s); + fn_cache_ref = luaL_ref(s,LUA_REGISTRYINDEX); +} + +void init_events(lua_State *s) +{ + create_fn_cache(s); + create_module(s); + + for (size_t i = 0; i < WHEEL_SIZE; i++) + { + wheel[i] = NO_EVENT; + } +} |
