calog/src/broker.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];
}