Files
dusk/src/dusk/event/event.c
T
2026-04-21 08:39:50 -05:00

244 lines
6.1 KiB
C

/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "event.h"
#include "assert/assert.h"
#include "util/memory.h"
void eventInit(
event_t *event,
eventlistener_t *array,
const uint16_t maxListeners
) {
assertNotNull(event, "Event cannot be NULL");
assertNotNull(array, "Listener array cannot be NULL");
assertTrue(maxListeners > 0, "Max listeners must be greater than 0");
memoryZero(event, sizeof(event_t));
event->listenerArray = array;
event->maxListeners = maxListeners;
}
eventsub_t eventSubscribeUser(
event_t *event,
const eventtype_t type,
const eventuserdata_t user
) {
assertNotNull(event, "Event cannot be NULL");
assertTrue(
event->listenerCount < event->maxListeners,
"Maximum number of listeners reached"
);
if(type == EVENT_TYPE_C) {
assertNotNull(
user.c.callback,
"C event listener callback cannot be NULL"
);
} else if(type == EVENT_TYPE_SCRIPT) {
assertNotNull(
user.script.context,
"Script event listener context cannot be NULL"
);
assertTrue(
user.script.luaFunctionRef != LUA_NOREF,
"Script event listener function reference is invalid"
);
} else {
assertUnreachable("Unknown event listener type");
}
// Gen a new ID
eventsub_t id = event->nextId++;
// Did we wrap?
assertTrue(event->nextId != 0, "Event subscription ID overflow");
// Append listener
eventlistener_t *listener = &event->listenerArray[event->listenerCount++];
memoryZero(listener, sizeof(eventlistener_t));
listener->user = user;
listener->id = id;
listener->type = type;
return id;
}
eventsub_t eventSubscribe(
event_t *event,
const eventcallback_t callback,
const void *user
) {
return eventSubscribeUser(
event,
EVENT_TYPE_C,
(eventuserdata_t){ .c = { .callback = callback, .user = (void *)user } }
);
}
eventsub_t eventSubscribeScriptContext(
event_t *event,
scriptcontext_t *context,
const int functionIndex
) {
assertNotNull(context, "Script context cannot be NULL");
assertTrue(
lua_isfunction(context->luaState, functionIndex),
"Expected function at given index"
);
// Create a reference to the function
lua_pushvalue(context->luaState, functionIndex);
int funcRef = luaL_ref(context->luaState, LUA_REGISTRYINDEX);
eventscript_t scriptUser = {
.context = context,
.luaFunctionRef = funcRef
};
// Note to the context that it is now a part of this event
bool_t alreadySubbed = false;
uint8_t i;
i = 0;
do {
if(context->subscribedEvents[i] != event) {
i++;
continue;
}
if(context->subscribedEvents[i] == NULL) break;
alreadySubbed = true;
break;
} while(i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS);
if(!alreadySubbed) {
i = 0;
do {
if(context->subscribedEvents[i] != NULL) {
i++;
continue;
}
context->subscribedEvents[i] = event;
break;
} while(i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS);
assertTrue(
i < SCRIPT_CONTEXT_MAX_EVENT_SUBSCRIPTIONS,
"Script context has reached maximum event subscriptions"
);
}
return eventSubscribeUser(
event,
EVENT_TYPE_SCRIPT,
(eventuserdata_t){ .script = scriptUser }
);
}
void eventUnsubscribe(event_t *event, const eventsub_t id) {
assertNotNull(event, "Event cannot be NULL");
assertFalse(event->isInvoking, "Cannot unsubscribe while invoking event");
if(event->listenerCount == 0) return;
uint16_t index = 0;
do {
if(event->listenerArray[index].id != id) {
index++;
continue;
}
// Release Lua registry reference before the slot is overwritten
if(event->listenerArray[index].type == EVENT_TYPE_SCRIPT) {
scriptcontext_t *ctx = event->listenerArray[index].user.script.context;
if(ctx != NULL && ctx->luaState != NULL) {
luaL_unref(
ctx->luaState,
LUA_REGISTRYINDEX,
event->listenerArray[index].user.script.luaFunctionRef
);
}
}
// Swap with last and shrink
event->listenerArray[index] = event->listenerArray[--event->listenerCount];
return;
} while(index < event->listenerCount);
}
void eventUnsubscribeScriptContext(event_t *event, const scriptcontext_t *ctx) {
assertNotNull(event, "Event cannot be NULL");
assertNotNull(ctx, "Script context cannot be NULL");
if(event->listenerCount == 0) return;
uint16_t i = 0;
do {
eventlistener_t *listener = &event->listenerArray[i];
if(
listener->type != EVENT_TYPE_SCRIPT ||
listener->user.script.context != ctx
) {
i++;
continue;
}
// This listener belongs to the context and will need to go away. This will
// in turn decrement the listener count so we don't increment i here.
eventUnsubscribe(event, listener->id);
} while(i < event->listenerCount);
}
void eventInvoke(
event_t *event,
const void *eventParams,
const char_t *metatableName
) {
assertNotNull(event, "Event cannot be NULL");
if(event->listenerCount == 0) return;
event->isInvoking = true;
uint16_t i = 0;
eventdata_t data ={
.event = event,
.eventParams = eventParams,
};
do {
eventlistener_t *listener = &event->listenerArray[i];
if(listener->type == EVENT_TYPE_C) {
listener->user.c.callback(&data, listener->user.c);
} else if(listener->type == EVENT_TYPE_SCRIPT) {
lua_State *L = listener->user.script.context->luaState;
assertNotNull(L, "Lua state in event listener cannot be NULL");
lua_rawgeti(L, LUA_REGISTRYINDEX, listener->user.script.luaFunctionRef);
int numArgs = 0;
if(eventParams != NULL) {
lua_pushlightuserdata(L, (void *)eventParams);
numArgs = 1;
}
if(lua_pcall(L, numArgs, 0, 0) != LUA_OK) {
const char_t *strErr = lua_tostring(L, -1);
lua_pop(L, 1);
printf("Error invoking Lua event listener: %s\n", strErr);
}
} else {
assertUnreachable("Unknown event listener type");
}
i++;
} while(i < event->listenerCount);
event->isInvoking = false;
}