aboutsummaryrefslogtreecommitdiff
path: root/src/event.c
diff options
context:
space:
mode:
authorLudvig Gunne Lindström <ludviggunnelindstrom@gmail.com>2026-07-17 11:42:57 +0200
committerLudvig Gunne Lindström <ludviggunnelindstrom@gmail.com>2026-11-08 14:05:19 +0100
commitfd27bacdd9c97f696938b12d2a437da4e514c26f (patch)
tree2d676bb280b35420a2c105676b0e66fe5b91958d /src/event.c
downloadmelisma-archive.tar.gz
Diffstat (limited to 'src/event.c')
-rw-r--r--src/event.c374
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;
+ }
+}