244 lines
6.1 KiB
C
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;
|
|
} |