diff --git a/src/arena.c b/src/arena.c index 16ebbc11..e9732122 100644 --- a/src/arena.c +++ b/src/arena.c @@ -51,7 +51,7 @@ void arena_release(struct arena *arena) } /* NOTE: Application will exit if arena fails to commit memory */ -void *_arena_push_bytes(struct arena *arena, u64 size, u64 align) +void *arena_push_bytes(struct arena *arena, u64 size, u64 align) { ASSERT(align > 0); ASSERT(!arena->readonly); @@ -101,7 +101,7 @@ void arena_copy_replace(struct arena *dest, struct arena *src) arena_reset(dest); u64 data_size = src->pos; u8 *data_src = src->base; - u8 *data_dest = _arena_push_bytes(dest, data_size, 1); + u8 *data_dest = arena_push_bytes(dest, data_size, 1); MEMCPY(data_dest, data_src, data_size); } diff --git a/src/arena.h b/src/arena.h index 3911761d..b8bf2e53 100644 --- a/src/arena.h +++ b/src/arena.h @@ -3,21 +3,19 @@ #include "memory.h" -#define arena_push(a, type) ((type *)_arena_push_bytes((a), sizeof(type), alignof(type))) -#define arena_push_zero(a, type) ((type *)_arena_push_bytes_zero((a), sizeof(type), alignof(type))) +#define arena_push(a, type) ((type *)arena_push_bytes((a), sizeof(type), alignof(type))) +#define arena_push_zero(a, type) ((type *)arena_push_bytes_zero((a), sizeof(type), alignof(type))) -#define arena_push_array(a, type, n) ((type *)_arena_push_bytes((a), (sizeof(type) * (n)), alignof(type))) -#define arena_push_array_zero(a, type, n) ((type *)_arena_push_bytes_zero((a), (sizeof(type) * (n)), alignof(type))) +#define arena_push_array(a, type, n) ((type *)arena_push_bytes((a), (sizeof(type) * (n)), alignof(type))) +#define arena_push_array_zero(a, type, n) ((type *)arena_push_bytes_zero((a), (sizeof(type) * (n)), alignof(type))) -#define arena_pop(a, type, dest) _arena_pop_struct((a), sizeof(type), dest) -#define arena_pop_array(a, type, n, dest) _arena_pop_struct((a), sizeof(type) * (n), dest) +#define arena_pop(a, type, dest) arena_pop_struct((a), sizeof(type), dest) +#define arena_pop_array(a, type, n, dest) arena_pop_struct((a), sizeof(type) * (n), dest) /* Returns a pointer to where the next allocation would be (at alignment of type). * Equivalent arena_push but without actually allocating anything. */ #define arena_dry_push(a, type) (type *)(_arena_dry_push((a), alignof(type))) -#define arena_align(a, align) (void *)(_arena_align((a), align)) - struct temp_arena { struct arena *arena; u64 start_pos; @@ -29,15 +27,15 @@ struct temp_arena { struct arena arena_alloc(u64 reserve); void arena_release(struct arena *arena); -void *_arena_push_bytes(struct arena *arena, u64 size, u64 align); +void *arena_push_bytes(struct arena *arena, u64 size, u64 align); void arena_copy_replace(struct arena *dest, struct arena *src); void arena_decommit_unused_blocks(struct arena *arena); void arena_set_readonly(struct arena *arena); void arena_set_readwrite(struct arena *arena); -INLINE void *_arena_push_bytes_zero(struct arena *arena, u64 size, u64 align) +INLINE void *arena_push_bytes_zero(struct arena *arena, u64 size, u64 align) { - void *p = _arena_push_bytes(arena, size, align); + void *p = arena_push_bytes(arena, size, align); MEMZERO(p, size); return p; } @@ -51,7 +49,7 @@ INLINE void arena_pop_to(struct arena *arena, u64 pos) arena->pos = pos; } -INLINE void _arena_pop_struct(struct arena *arena, u64 size, void *copy_dest) +INLINE void arena_pop_struct(struct arena *arena, u64 size, void *copy_dest) { ASSERT(arena->pos >= size); ASSERT(!arena->readonly); @@ -64,7 +62,7 @@ INLINE void _arena_pop_struct(struct arena *arena, u64 size, void *copy_dest) arena->pos = new_pos; } -INLINE void *_arena_align(struct arena *arena, u64 align) +INLINE void *arena_align(struct arena *arena, u64 align) { ASSERT(!arena->readonly); diff --git a/src/buddy.c b/src/buddy.c new file mode 100644 index 00000000..85d0ce2a --- /dev/null +++ b/src/buddy.c @@ -0,0 +1,200 @@ +#include "buddy.h" +#include "arena.h" + +/* ========================== * + * Ctx + * ========================== */ + +struct buddy_ctx *buddy_ctx_alloc(u64 reserve) +{ + /* TODO: Determine meta reserve dynamically */ + struct arena meta_arena = arena_alloc(GIGABYTE(64)); + struct buddy_ctx *ctx = arena_push_zero(&meta_arena, struct buddy_ctx); + ctx->meta_arena = meta_arena; + ctx->data_arena = arena_alloc(reserve); + + /* TODO: Minimum block size */ + ctx->levels = arena_push_array_zero(&ctx->meta_arena, struct buddy_level, 64); + for (u64 i = 0; i < 64; ++i) { + struct buddy_level *level = &ctx->levels[i]; + level->ctx = ctx; + level->tier = i; + level->size = (u64)1 << i; + } + + + return ctx; +} + +void buddy_ctx_release(struct buddy_ctx *ctx) +{ + arena_release(&ctx->data_arena); + arena_release(&ctx->meta_arena); +} + +/* ========================== * + * Block + * ========================== */ + +INTERNAL struct buddy_block *buddy_block_alloc_internal(struct buddy_ctx *ctx) +{ + struct buddy_block *block; + if (ctx->first_free_block) { + block = ctx->first_free_block; + ctx->first_free_block = block->next; + } else { + block = arena_push(&ctx->meta_arena, struct buddy_block); + } + MEMZERO_STRUCT(block); + return block; +} + +INTERNAL void buddy_block_release_internal(struct buddy_ctx *ctx, struct buddy_level *level, struct buddy_block *block) +{ + /* Remove from unused list */ + { + struct buddy_block *prev = block->prev; + struct buddy_block *next = block->next; + if (prev) { + prev->next = next; + } else { + level->first_unused_block = next; + } + if (next) { + next->prev = prev; + } + } + + block->next = ctx->first_free_block; + ctx->first_free_block = block; +} + +INTERNAL struct buddy_block *buddy_block_get_unused(struct buddy_ctx *ctx, struct buddy_level *level) +{ + struct buddy_block *block = NULL; + + /* TODO: Tier oob check */ + if (level->first_unused_block) { + block = level->first_unused_block; + level->first_unused_block = block->next; + if (level->first_unused_block) { + level->first_unused_block->prev = NULL; + } + block->used = true; + block->next = NULL; + ASSERT(block->memory); + } else { + if (level->backed) { + struct buddy_level *parent_level = &ctx->levels[level->tier + 1]; + struct buddy_block *parent_block = buddy_block_get_unused(ctx, parent_level); + ASSERT(parent_block->memory); + + /* Create left block from parent block */ + struct buddy_block *left = buddy_block_alloc_internal(ctx); + left->used = true; + left->level = level; + left->parent = parent_block; + left->memory = parent_block->memory; + + /* Create right (unused) block from parent block */ + struct buddy_block *right = buddy_block_alloc_internal(ctx); + right->used = false; + right->level = level; + right->parent = parent_block; + right->memory = left->memory + level->size; + if (level->first_unused_block) { + right->next = level->first_unused_block; + level->first_unused_block->prev = right; + } + level->first_unused_block = right; + + left->sibling = right; + right->sibling = left; + block = left; + ASSERT(block->memory); + } else { +#if 0 + /* Create left block from arena */ + struct buddy_block *left = buddy_block_alloc_internal(ctx); + left->used = true; + left->level = level; + left->memory = arena_push_array(&ctx->data_arena, u8, level->size * 2); + + /* Create right (unused) block from arena */ + struct buddy_block *right = buddy_block_alloc_internal(ctx); + right->used = false; + right->level = level; + right->memory = left->memory + level->size; + if (level->first_unused_block) { + right->next = level->first_unused_block; + level->first_unused_block->prev = right; + } + level->first_unused_block = right; + + left->sibling = right; + right->sibling = left; + block = left; + + level->backed = true; + ASSERT(block->memory); +#else + /* Create block by growing arena */ + block = buddy_block_alloc_internal(ctx); + block->used = true; + block->memory = arena_push_array(&ctx->data_arena, u8, level->size); + block->level = level; + level->backed = true; + ASSERT(block->memory); +#endif + } + } + + ASSERT(block->memory); + return block; +} + +INTERNAL void buddy_block_mark_unused(struct buddy_block *block) +{ + block->used = false; + struct buddy_block *sibling = block->sibling; + struct buddy_level *level = block->level; + if (sibling && !sibling->used) { + /* Merge siblings */ + struct buddy_ctx *ctx = level->ctx; + struct buddy_block *parent = block->parent; + buddy_block_release_internal(ctx, level, block); + buddy_block_release_internal(ctx, level, sibling); + if (parent) { + buddy_block_mark_unused(parent); + } + } else { + if (level->first_unused_block) { + block->next = level->first_unused_block; + level->first_unused_block->prev = block; + } + level->first_unused_block = block; + } +} + +struct buddy_block *buddy_alloc(struct buddy_ctx *ctx, u64 size) +{ + if (size > 0x00FFFFFFFFFFFFFFULL) { + /* TODO: Error */ + } + + u64 desired_block_size = 1; + u64 desired_level_tier = 0; + while (desired_block_size < size && desired_level_tier < 64) { + desired_block_size <<= 1; + ++desired_level_tier; + } + + struct buddy_block *block = buddy_block_get_unused(ctx, &ctx->levels[desired_level_tier]); + + return block; +} + +void buddy_release(struct buddy_block *block) +{ + buddy_block_mark_unused(block); +} diff --git a/src/buddy.h b/src/buddy.h new file mode 100644 index 00000000..6294435f --- /dev/null +++ b/src/buddy.h @@ -0,0 +1,38 @@ +#ifndef BUDDY_H +#define BUDDY_H + +struct buddy_block { + b32 used; + struct buddy_level *level; + struct buddy_block *parent; + struct buddy_block *sibling; + + /* Links to block in level's unused list or in ctx's free list */ + struct buddy_block *prev; + struct buddy_block *next; + + u8 *memory; +}; + +struct buddy_level { + struct buddy_ctx *ctx; + b32 backed; /* Is this level backed by memory in the ctx arena */ + u32 tier; + u64 size; + struct buddy_block *first_unused_block; +}; + +struct buddy_ctx { + struct arena meta_arena; + struct arena data_arena; + struct buddy_level *levels; + struct buddy_block *first_free_block; +}; + +struct buddy_ctx *buddy_ctx_alloc(u64 reserve); +void buddy_ctx_release(struct buddy_ctx *ctx); + +struct buddy_block *buddy_alloc(struct buddy_ctx *ctx, u64 size); +void buddy_release(struct buddy_block *block); + +#endif diff --git a/src/game.c b/src/game.c index e0a899a3..1757e110 100644 --- a/src/game.c +++ b/src/game.c @@ -1327,7 +1327,7 @@ INTERNAL void game_update(void) l.last = &snapshot_event; struct string msg = game_string_from_events(temp.arena, l); - host_queue_write(G.host, HOST_CHANNEL_ID_ALL, msg); + host_queue_write(G.host, HOST_CHANNEL_ID_ALL, msg, 0); arena_temp_end(temp); } diff --git a/src/host.c b/src/host.c index d5639e50..1d7a059d 100644 --- a/src/host.c +++ b/src/host.c @@ -5,6 +5,7 @@ #include "sys.h" #include "util.h" #include "log.h" +#include "buddy.h" //#define HOST_NETWORK_ADDRESS_STRING(str) //#define HOST_NETWORK_ADDRESS_ALL_LOCAL_INTERFACES(port) @@ -104,13 +105,9 @@ struct host_recv_buffer { }; struct host_msg_assembler { - struct host *host; struct host_channel *channel; b32 is_reliable; - /* TODO: Remove this (testing) */ - struct arena testarena; - /* Free list */ struct host_msg_assembler *next_free; @@ -131,8 +128,9 @@ struct host_msg_assembler { i64 touched_ns; - u8 *chunks_received_bitmap; - u8 *data; + struct buddy_block *buddy_block; + u8 *chunk_bitmap; + u8 *chunk_data; }; struct host_msg_assembler_lookup_bucket { @@ -173,6 +171,11 @@ struct host *host_alloc(u16 listen_port) host->cmd_arena = arena_alloc(GIGABYTE(64)); host->queued_event_arena = arena_alloc(GIGABYTE(64)); host->channel_arena = arena_alloc(GIGABYTE(64)); + host->recv_buffer_read = arena_push_zero(&host->arena, struct host_recv_buffer); + host->recv_buffer_write = arena_push_zero(&host->arena, struct host_recv_buffer); + host->recv_buffer_read->arena = arena_alloc(GIGABYTE(64)); + host->recv_buffer_write->arena = arena_alloc(GIGABYTE(64)); + host->buddy = buddy_ctx_alloc(GIGABYTE(64)); host->channels = arena_dry_push(&host->channel_arena, struct host_channel); @@ -182,12 +185,6 @@ struct host *host_alloc(u16 listen_port) host->num_msg_assembler_lookup_buckets = NUM_MSG_ASSEMBLER_LOOKUP_BUCKETS; host->msg_assembler_lookup_buckets = arena_push_array_zero(&host->arena, struct host_msg_assembler_lookup_bucket, host->num_msg_assembler_lookup_buckets); - - host->recv_buffer_read = arena_push_zero(&host->arena, struct host_recv_buffer); - host->recv_buffer_write = arena_push_zero(&host->arena, struct host_recv_buffer); - host->recv_buffer_read->arena = arena_alloc(GIGABYTE(64)); - host->recv_buffer_write->arena = arena_alloc(GIGABYTE(64)); - host->sock = sock_alloc(listen_port); host->recv_buffer_write_mutex = sys_mutex_alloc(); @@ -205,6 +202,7 @@ void host_release(struct host *host) sock_release(host->sock); + buddy_ctx_release(host->buddy); arena_release(&host->recv_buffer_write->arena); arena_release(&host->recv_buffer_read->arena); arena_release(&host->channel_arena); @@ -390,19 +388,27 @@ INTERNAL struct host_msg_assembler *host_msg_assembler_alloc(struct host_channel ma = arena_push(&host->arena, struct host_msg_assembler); } MEMZERO_STRUCT(ma); - ma->host = channel->host; ma->channel = channel; ma->msg_id = msg_id; ma->num_chunks_total = chunk_count; - /* FIXME: Use buddy allocator or something */ - u64 data_size = chunk_count * PACKET_MSG_CHUNK_MAX_LEN; - u64 bitmap_size = ((chunk_count - 1) / 8) + 1; - ma->testarena = arena_alloc(data_size + bitmap_size); + u64 chunk_bitmap_size = ((chunk_count - 1) / 8) + 1; + if ((chunk_bitmap_size % 16) != 0) { + /* Align chunk bitmap to 16 so msg data is aligned */ + chunk_bitmap_size += 16 - (chunk_bitmap_size % 16); + } + u64 chunk_data_size = chunk_count * PACKET_MSG_CHUNK_MAX_LEN; + + /* Allocate msg data using buddy allocator since the assembler has + * arbitrary lifetime and data needs to stay contiguous for random + * access as packets are received */ + ma->buddy_block = buddy_alloc(host->buddy, chunk_bitmap_size + chunk_data_size); + ma->chunk_bitmap = ma->buddy_block->memory; + MEMZERO(ma->chunk_bitmap, chunk_bitmap_size); + ma->chunk_data = ma->chunk_bitmap + chunk_bitmap_size; + /* FIXME: Ensure chunk_count > 0 */ - ma->chunks_received_bitmap = arena_push_array_zero(&ma->testarena, u8, bitmap_size); - ma->data = arena_push_array(&ma->testarena, u8, data_size); ma->is_reliable = is_reliable; /* Insert into channel list */ @@ -432,11 +438,9 @@ INTERNAL struct host_msg_assembler *host_msg_assembler_alloc(struct host_channel INTERNAL void host_msg_assembler_release(struct host_msg_assembler *ma) { - struct host *host = ma->host; struct host_channel *channel = ma->channel; - - /* FIXME: Data should be in buddy allocator or something */ - arena_release(&ma->testarena); + struct host *host = channel->host; + buddy_release(ma->buddy_block); /* Release from channel list */ { @@ -471,7 +475,6 @@ INTERNAL void host_msg_assembler_release(struct host_msg_assembler *ma) } } - ma->next_free = host->first_free_msg_assembler; host->first_free_msg_assembler = ma; } @@ -513,7 +516,7 @@ INTERNAL void host_msg_assembler_touch(struct host_msg_assembler *ma, i64 now_ns INTERNAL b32 host_msg_assembler_is_chunk_filled(struct host_msg_assembler *ma, u64 chunk_id) { if (chunk_id < ma->num_chunks_total) { - return (ma->chunks_received_bitmap[chunk_id / 8] & (1 << (chunk_id % 8))) != 0; + return (ma->chunk_bitmap[chunk_id / 8] & (1 << (chunk_id % 8))) != 0; } return false; } @@ -521,7 +524,7 @@ INTERNAL b32 host_msg_assembler_is_chunk_filled(struct host_msg_assembler *ma, u INTERNAL void host_msg_assembler_set_chunk_received(struct host_msg_assembler *ma, u64 chunk_id) { if (chunk_id < ma->num_chunks_total) { - ma->chunks_received_bitmap[chunk_id / 8] |= (1 << (chunk_id % 8)); + ma->chunk_bitmap[chunk_id / 8] |= (1 << (chunk_id % 8)); } } @@ -593,12 +596,13 @@ void host_queue_disconnect(struct host *host, struct host_channel_id channel_id) cmd->channel_id = channel_id; } -void host_queue_write(struct host *host, struct host_channel_id channel_id, struct string msg) +void host_queue_write(struct host *host, struct host_channel_id channel_id, struct string msg, u32 flags) { struct host_cmd *cmd = host_cmd_alloc_and_append(host); cmd->kind = HOST_CMD_KIND_WRITE; cmd->channel_id = channel_id; cmd->write_msg = string_copy(&host->cmd_arena, msg); + cmd->write_reliable = flags & HOST_WRITE_FLAG_RELIABLE; } /* ========================== * @@ -728,7 +732,7 @@ void host_update(struct host *host) if (!host_msg_assembler_is_chunk_filled(ma, chunk_id)) { u8 *src = br_seek(&br, data_len); if (src) { - u8 *dst = &ma->data[chunk_id * PACKET_MSG_CHUNK_MAX_LEN]; + u8 *dst = &ma->chunk_data[chunk_id * PACKET_MSG_CHUNK_MAX_LEN]; MEMCPY(dst, src, data_len); if (is_last_chunk) { ma->last_chunk_len = data_len; @@ -743,7 +747,7 @@ void host_update(struct host *host) struct string data = ZI; data.len = ((chunk_count - 1) * PACKET_MSG_CHUNK_MAX_LEN) + ma->last_chunk_len; data.text = arena_push_array(&host->queued_event_arena, u8, data.len); - MEMCPY(data.text, ma->data, data.len); + MEMCPY(data.text, ma->chunk_data, data.len); queued_event->event.kind = HOST_EVENT_KIND_MSG; queued_event->event.msg = data; queued_event->event.channel_id = channel->id; diff --git a/src/host.h b/src/host.h index 9ba1171d..995fd942 100644 --- a/src/host.h +++ b/src/host.h @@ -7,6 +7,7 @@ #define HOST_CHANNEL_ID_NIL (struct host_channel_id) { .gen = 0, .idx = 0 } #define HOST_CHANNEL_ID_ALL (struct host_channel_id) { .gen = U32_MAX, .idx = U32_MAX } +struct buddy_ctx; struct host_packet; struct host_channel_lookup_bucket; struct host_recv_buffer; @@ -28,6 +29,12 @@ enum host_event_kind { HOST_EVENT_KIND_MSG }; +enum host_write_flag { + HOST_WRITE_FLAG_NONE = 0, + + HOST_WRITE_FLAG_RELIABLE = (1 << 0) +}; + struct host_cmd { enum host_cmd_kind kind; struct host_channel_id channel_id; @@ -53,6 +60,8 @@ struct host { struct arena arena; struct sock *sock; + struct buddy_ctx *buddy; /* For storing msg assembler data */ + struct arena cmd_arena; struct host_cmd *first_cmd; struct host_cmd *last_cmd; @@ -108,7 +117,7 @@ void host_queue_connect_to_address(struct host *host, struct sock_address connec void host_queue_disconnect(struct host *host, struct host_channel_id channel_id); -void host_queue_write(struct host *host, struct host_channel_id channel_id, struct string msg); +void host_queue_write(struct host *host, struct host_channel_id channel_id, struct string msg, u32 flags); /* ========================== * * Update diff --git a/src/user.c b/src/user.c index a9be8b37..61999cf3 100644 --- a/src/user.c +++ b/src/user.c @@ -1668,7 +1668,7 @@ INTERNAL void user_update(void) struct temp_arena temp = arena_temp_begin(scratch.arena); struct string cmds_str = game_string_from_cmds(temp.arena, cmd_list); - host_queue_write(G.host, HOST_CHANNEL_ID_ALL, cmds_str); + host_queue_write(G.host, HOST_CHANNEL_ID_ALL, cmds_str, 0); arena_temp_end(temp); }