215 lines
6.2 KiB
C
215 lines
6.2 KiB
C
// broker.c -- the function registry and the uniform call entry point.
|
|
//
|
|
// The registry is an open-addressing hash table (power-of-two slot count, linear
|
|
// probing, FNV-1a hash). A native function is registered once and becomes
|
|
// callable by name from every engine. calogCall is the single dispatch point;
|
|
// the actor layer will later intercept it to route calls whose owning context is
|
|
// not the caller's, but in the core every call is invoked inline.
|
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
#include "calogInternal.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define BROKER_INITIAL_SLOTS 16
|
|
#define BROKER_LOAD_PERCENT 70
|
|
#define BROKER_PERCENT_SCALE 100
|
|
#define FNV_OFFSET_BASIS 1469598103934665603ULL
|
|
#define FNV_PRIME 1099511628211ULL
|
|
|
|
// probeSlot masks the hash with slotCount - 1, which is only a full bit mask
|
|
// when slotCount is a power of two. Both the initial size and the growth factor
|
|
// must preserve that.
|
|
_Static_assert((BROKER_INITIAL_SLOTS & (BROKER_INITIAL_SLOTS - 1)) == 0, "registry slot count must be a power of two");
|
|
_Static_assert((CALOG_GROWTH_FACTOR & (CALOG_GROWTH_FACTOR - 1)) == 0, "registry growth factor must be a power of two");
|
|
|
|
static int32_t brokerGrow(CalogT *broker);
|
|
static uint64_t hashName(const char *name);
|
|
static CalogEntryT *probeSlot(CalogT *broker, const char *name);
|
|
static int32_t registerEntry(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData, bool runInline);
|
|
|
|
|
|
int32_t calogCall(CalogT *broker, const char *name, CalogValueT *args, int32_t argCount, CalogValueT *result) {
|
|
CalogEntryT *entry;
|
|
|
|
calogValueNil(result);
|
|
entry = calogLookup(broker, name);
|
|
if (entry == NULL) {
|
|
return calogFail(result, calogErrNotFoundE, "no such function");
|
|
}
|
|
if (broker->routeHook != NULL) {
|
|
return broker->routeHook(broker, entry, args, argCount, result);
|
|
}
|
|
return entry->fn(args, argCount, result, entry->userData);
|
|
}
|
|
|
|
|
|
CalogT *calogBrokerCreate(void) {
|
|
CalogT *broker;
|
|
|
|
broker = (CalogT *)calloc(1, sizeof(*broker));
|
|
if (broker == NULL) {
|
|
return NULL;
|
|
}
|
|
broker->slots = (CalogEntryT *)calloc(BROKER_INITIAL_SLOTS, sizeof(CalogEntryT));
|
|
if (broker->slots == NULL) {
|
|
free(broker);
|
|
return NULL;
|
|
}
|
|
broker->slotCount = BROKER_INITIAL_SLOTS;
|
|
broker->entryCount = 0;
|
|
return broker;
|
|
}
|
|
|
|
|
|
void calogBrokerDestroy(CalogT *broker) {
|
|
int64_t index;
|
|
|
|
if (broker == NULL) {
|
|
return;
|
|
}
|
|
for (index = 0; index < broker->slotCount; index++) {
|
|
free(broker->slots[index].name);
|
|
}
|
|
free(broker->slots);
|
|
free(broker->engines);
|
|
free(broker);
|
|
}
|
|
|
|
|
|
int32_t calogFail(CalogValueT *result, int32_t status, const char *message) {
|
|
int32_t initStatus;
|
|
|
|
calogValueFree(result);
|
|
initStatus = calogValueString(result, message, (int64_t)strlen(message));
|
|
if (initStatus != calogOkE) {
|
|
calogValueNil(result);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
|
|
void calogForEach(CalogT *broker, void (*visit)(const CalogEntryT *entry, void *ud), void *ud) {
|
|
int64_t index;
|
|
|
|
for (index = 0; index < broker->slotCount; index++) {
|
|
if (broker->slots[index].name != NULL) {
|
|
visit(&broker->slots[index], ud);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static int32_t brokerGrow(CalogT *broker) {
|
|
CalogEntryT *oldSlots;
|
|
CalogEntryT *newSlots;
|
|
int64_t oldCount;
|
|
int64_t newCount;
|
|
int64_t index;
|
|
|
|
oldSlots = broker->slots;
|
|
oldCount = broker->slotCount;
|
|
newCount = oldCount * CALOG_GROWTH_FACTOR;
|
|
newSlots = (CalogEntryT *)calloc((size_t)newCount, sizeof(CalogEntryT));
|
|
if (newSlots == NULL) {
|
|
return calogErrOomE;
|
|
}
|
|
broker->slots = newSlots;
|
|
broker->slotCount = newCount;
|
|
for (index = 0; index < oldCount; index++) {
|
|
CalogEntryT *target;
|
|
if (oldSlots[index].name == NULL) {
|
|
continue;
|
|
}
|
|
target = probeSlot(broker, oldSlots[index].name);
|
|
*target = oldSlots[index];
|
|
}
|
|
free(oldSlots);
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
CalogEntryT *calogLookup(CalogT *broker, const char *name) {
|
|
CalogEntryT *slot;
|
|
|
|
slot = probeSlot(broker, name);
|
|
if (slot->name == NULL) {
|
|
return NULL;
|
|
}
|
|
return slot;
|
|
}
|
|
|
|
|
|
int32_t calogRegister(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData) {
|
|
return registerEntry(broker, name, fn, userData, false);
|
|
}
|
|
|
|
|
|
int32_t calogRegisterInline(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData) {
|
|
return registerEntry(broker, name, fn, userData, true);
|
|
}
|
|
|
|
|
|
static int32_t registerEntry(CalogT *broker, const char *name, CalogNativeFnT fn, void *userData, bool runInline) {
|
|
CalogEntryT *slot;
|
|
char *nameCopy;
|
|
int32_t status;
|
|
|
|
slot = probeSlot(broker, name);
|
|
if (slot->name != NULL) {
|
|
// Re-registration replaces the existing binding in place: allocation free,
|
|
// so it never grows and never fails.
|
|
slot->fn = fn;
|
|
slot->userData = userData;
|
|
slot->runInline = runInline;
|
|
return calogOkE;
|
|
}
|
|
if ((broker->entryCount + 1) * BROKER_PERCENT_SCALE >= broker->slotCount * BROKER_LOAD_PERCENT) {
|
|
status = brokerGrow(broker);
|
|
if (status != calogOkE) {
|
|
return status;
|
|
}
|
|
slot = probeSlot(broker, name);
|
|
}
|
|
nameCopy = strdup(name);
|
|
if (nameCopy == NULL) {
|
|
return calogErrOomE;
|
|
}
|
|
slot->name = nameCopy;
|
|
slot->fn = fn;
|
|
slot->userData = userData;
|
|
slot->runInline = runInline;
|
|
broker->entryCount++;
|
|
return calogOkE;
|
|
}
|
|
|
|
|
|
static uint64_t hashName(const char *name) {
|
|
uint64_t hash;
|
|
size_t index;
|
|
|
|
hash = FNV_OFFSET_BASIS;
|
|
for (index = 0; name[index] != '\0'; index++) {
|
|
hash ^= (uint64_t)(unsigned char)name[index];
|
|
hash *= FNV_PRIME;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
|
|
static CalogEntryT *probeSlot(CalogT *broker, const char *name) {
|
|
uint64_t mask;
|
|
uint64_t index;
|
|
|
|
mask = (uint64_t)broker->slotCount - 1;
|
|
index = hashName(name) & mask;
|
|
while (broker->slots[index].name != NULL) {
|
|
if (strcmp(broker->slots[index].name, name) == 0) {
|
|
return &broker->slots[index];
|
|
}
|
|
index = (index + 1) & mask;
|
|
}
|
|
return &broker->slots[index];
|
|
}
|