/*
* 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 .
*/
#include
#include
#include
#include
#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;
}
}