power_play/src/client.c
2025-02-06 14:32:06 -06:00

145 lines
4.6 KiB
C

#include "client.h"
#include "host.h"
#include "arena.h"
#include "util.h"
#define CHANNEL_LOOKUP_BUCKETS 4096
/* Offset in bytes from start of store struct to start of clients array (assume adjacently allocated) */
/* FIXME: Incorrect since buckets are also allocated */
#define STORE_CLIENTS_OFFSET (sizeof(struct client_store) + (sizeof(struct client_store) % alignof(struct client)))
/* Accessed via client_nil() */
READONLY struct client _g_client_nil = { .valid = false };
READONLY struct client_store _g_client_store_nil = { .valid = false };
/* ========================== *
* Store
* ========================== */
struct client_store *client_store_alloc(void)
{
struct arena arena = arena_alloc(GIGABYTE(64));
struct client_store *store = arena_push_zero(&arena, struct client_store);
store->arena = arena;
store->num_channel_lookup_buckets = CHANNEL_LOOKUP_BUCKETS;
store->channel_lookup_buckets = arena_push_array_zero(&arena, struct channel_lookup_bucket, store->num_channel_lookup_buckets);
store->clients = arena_dry_push(&arena, struct client);
return store;
}
void client_store_release(struct client_store *store)
{
arena_release(&store->arena);
}
struct client_store *client_store_from_client(struct client *client)
{
if (client->valid) {
u64 first_client_addr = (u64)(client - client->handle.idx);
struct client_store *client_store = (struct client_store *)(first_client_addr - STORE_CLIENTS_OFFSET);
ASSERT(client_store->clients == (struct client *)first_client_addr);
return client_store;
} else {
return client_store_nil();
}
}
/* ========================== *
* Client
* ========================== */
INTERNAL u64 hash_from_channel_id(struct host_channel_id channel_id)
{
return hash_fnv64(HASH_FNV64_BASIS, STRING_FROM_STRUCT(&channel_id));
}
struct client *client_from_handle(struct client_store *store, struct client_handle handle)
{
if (handle.gen != 0 && handle.idx < store->clients_reserved) {
struct client *client = &store->clients[handle.idx];
if (client->handle.gen == handle.gen) {
return client;
}
}
return client_nil();
}
struct client *client_from_channel_id(struct client_store *store, struct host_channel_id channel_id)
{
struct client *res = client_nil();
u64 channel_hash = hash_from_channel_id(channel_id);
u64 bucket_index = channel_hash % store->num_channel_lookup_buckets;
struct channel_lookup_bucket *bucket = &store->channel_lookup_buckets[bucket_index];
for (struct client *client = bucket->first; client; client = client->next_hash) {
if (client->channel_hash == channel_hash) {
res = client;
break;
}
}
return res;
}
struct client *client_alloc(struct client_store *store, struct host_channel_id channel_id)
{
struct client *client = NULL;
struct client_handle handle = ZI;
if (store->first_free_client) {
client = store->first_free_client;
store->first_free_client = client->next_free;
handle = client->handle;
++handle.gen;
} else {
client = arena_push(&store->arena, struct client);
handle.gen = 1;
handle.idx = store->clients_reserved;
++store->clients_reserved;
}
*client = _g_client_nil;
client->valid = true;
client->handle = handle;
u64 channel_hash = hash_from_channel_id(channel_id);
client->channel_id = channel_id;
client->channel_hash = channel_hash;
/* Insert into channel lookup */
u64 bucket_index = channel_hash % store->num_channel_lookup_buckets;
struct channel_lookup_bucket *bucket = &store->channel_lookup_buckets[bucket_index];
if (bucket->last) {
bucket->last->next_hash = client;
client->prev_hash = bucket->last;
} else {
bucket->first = client;
}
bucket->last = client;
return client;
}
void client_release(struct client *client)
{
struct client_store *store = client_store_from_client(client);
client->valid = false;
++client->handle.gen;
client->next_free = store->first_free_client;
store->first_free_client = client;
/* Remove from channel lookup */
u64 bucket_index = client->channel_hash % store->num_channel_lookup_buckets;
struct channel_lookup_bucket *bucket = &store->channel_lookup_buckets[bucket_index];
struct client *prev = client->prev_hash;
struct client *next = client->next_hash;
if (prev) {
prev->next_hash = next;
} else {
bucket->first = next;
}
if (next) {
next->prev_hash = prev;
} else {
bucket->last = prev;
}
}