[main.c] Complete list functionality

This commit is contained in:
2024-11-25 22:32:00 -05:00
parent 29598f733c
commit 7120a04f92
3 changed files with 393 additions and 38 deletions

207
arena.c Normal file
View File

@ -0,0 +1,207 @@
/* This arena allocator implementation was created by Will Brown (WCBROW01).
* Orginal source can be found at: https://github.com/WCBROW01/arena-allocator
* Licensed under the MIT License (c) 2022-2024 Will Brown */
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdalign.h>
#include <stdbool.h>
#include <string.h>
#include "arena.h"
#define next_multiple(a, b) ((a) + (b) - (a) % (b))
#define MEM_ALIGNMENT alignof(max_align_t)
#define align(n) ((n) % MEM_ALIGNMENT == 0 ? (n) : next_multiple(n, MEM_ALIGNMENT))
// The arena region itself is allocated after the contents of the struct.
struct Arena {
size_t size;
size_t tmp_size;
void *last_block;
void *next_block;
Arena *next_region;
bool dynamic;
};
// Get the start address of the arena
static inline void *Arena_start(Arena *arena) {
return arena + 1;
}
// Allocates a fixed-size arena. Accepts the size of the arena in bytes.
Arena *Arena_new(size_t size) {
Arena *new_arena = malloc(sizeof(Arena) + size);
*new_arena = (Arena) {
.size = size,
.tmp_size = 0,
.last_block = NULL,
.next_block = Arena_start(new_arena),
.next_region = NULL,
.dynamic = false
};
return new_arena;
}
/* Allocates a dynamically-sized arena. Accepts the initial size of the arena in bytes.
* If there is not enough space in the arena for an allocation, a new region will be created. */
Arena *Arena_new_dynamic(size_t size) {
Arena *new_arena = malloc(sizeof(Arena) + size);
*new_arena = (Arena) {
.size = size,
.tmp_size = 0,
.last_block = NULL,
.next_block = Arena_start(new_arena),
.next_region = NULL,
.dynamic = true
};
return new_arena;
}
// Frees the entire arena from memory.
void Arena_delete(Arena *arena) {
if (arena->next_region != NULL) Arena_delete(arena->next_region);
free(arena);
}
#ifdef ENABLE_DIAG
static void print_diagnostic(Arena *arena, size_t size) {
fprintf(stderr, "Diagnostic info:\n");
fprintf(stderr, "Arena size: %zu bytes\n", arena->size);
fprintf(stderr, "Amount currently allocated: %zu bytes\n", arena->next_block - Arena_start(arena));
fprintf(stderr, "New block size: %zu bytes\n", size);
fprintf(stderr, "New size upon success: %zu bytes\n", arena->next_block + align(size) - Arena_start(arena));
}
#endif
static inline void *Arena_init_block(Arena *arena, size_t size) {
size_t blksize = align(size);
arena->tmp_size += blksize;
void *new_block = arena->next_block;
arena->last_block = new_block;
arena->next_block += blksize;
return new_block;
}
// Will return a null pointer if you've tried allocating too much memory.
void *Arena_alloc(Arena *arena, size_t size) {
if (arena->next_block + align(size) > Arena_start(arena) + arena->size) {
if (arena->dynamic) {
if (arena->next_region == NULL) {
// If the size is too large for a region, make a special region for only that block.
size_t region_size = size > arena->size ? size : arena->size;
arena->next_region = Arena_new_dynamic(region_size);
return Arena_init_block(arena->next_region, size);
} else {
return Arena_alloc(arena->next_region, size);
}
} else {
#ifdef ENABLE_DIAG
fprintf(stderr, "Allocation too large. You've attempted to allocate a block of memory past the end of the arena.\n");
print_diagnostic(arena, size);
#endif
return NULL;
}
} else {
return Arena_init_block(arena, size);
}
}
// Identical to Arena_alloc but it zeros your memory
void *Arena_allocz(Arena *arena, size_t size) {
return memset(Arena_alloc(arena, size), 0, size);
}
/* Copy a block of memory into an arena.
* Functionally equivalent to memcpy. */
void *Arena_copy(Arena *arena, const void *src, size_t size) {
void *new_block = Arena_alloc(arena, size);
if (new_block == NULL) {
return NULL;
} else {
memcpy(new_block, src, size);
return new_block;
}
}
/* If the pointer is to the last allocation, it will be resized.
* Otherwise, a new allocation will be created.
* Be careful with this! A null pointer will be returned upon error.
* Using this with memory outside of the arena is undefined behavior. */
void *Arena_realloc(Arena *arena, void *ptr, size_t size) {
if (ptr == arena->last_block) {
if (arena->last_block + align(size) > Arena_start(arena) + arena->size) {
if (arena->dynamic) {
return Arena_copy(arena, ptr, size);
} else {
#ifdef ENABLE_DIAG
fprintf(stderr, "Allocation too large. You've attempted to allocate a block of memory past the end of the arena.\n");
print_diagnostic(arena, size - (arena->next_block - arena->last_block));
#endif
return NULL;
}
} else {
arena->next_block = arena->last_block + align(size);
return ptr;
}
} else {
return Arena_copy(arena, ptr, size);
}
}
/* Marks the beginning of a temporary buffer that can be deallocated at any time.
* The state of the last one is saved in case you have multiple. */
void Arena_tmp_begin(Arena *arena) {
size_t tmp_size = arena->tmp_size;
void *last_block = arena->last_block;
arena->tmp_size = 0;
void *state = Arena_alloc(arena, sizeof(size_t) + sizeof(void*));
if (state == NULL) return;
*(size_t*) state = tmp_size;
*(void**) (state + sizeof(void*)) = last_block;
}
static void tmp_rewind(Arena *arena, bool *complete) {
void *stateloc = arena->next_block - arena->tmp_size;
if (arena->next_region != NULL) {
tmp_rewind(arena->next_region, complete);
arena->next_region = NULL;
}
if (*complete) return;
if (stateloc == Arena_start(arena)) {
Arena_delete(arena);
} else {
arena->tmp_size = *(size_t*) stateloc;
arena->last_block = *(void**) (stateloc + sizeof(size_t));
arena->next_block = stateloc;
*complete = true;
}
}
/* Deallocates the last temporary buffer. If there is none,
* the entire arena will be deallocated. */
void Arena_tmp_rewind(Arena *arena) {
bool complete = false;
if (arena->next_region != NULL) {
tmp_rewind(arena->next_region, &complete);
arena->next_region = NULL;
}
if (!complete) {
void *stateloc = arena->next_block - arena->tmp_size;
if (stateloc != Arena_start(arena)) {
arena->tmp_size = *(size_t*) stateloc;
arena->last_block = *(void**) (stateloc + sizeof(size_t));
}
arena->next_block = stateloc;
}
}

52
arena.h Normal file
View File

@ -0,0 +1,52 @@
/* This arena allocator implementation was created by Will Brown (WCBROW01).
* Orginal source can be found at: https://github.com/WCBROW01/arena-allocator
* Licensed under the MIT License (c) 2022-2024 Will Brown */
#ifndef ARENA_H
#define ARENA_H
#ifdef __cplusplus
extern "C" {
#endif
typedef struct Arena Arena;
// Allocates a fixed-size arena. Accepts the size of the arena in bytes.
Arena *Arena_new(size_t size);
/* Allocates a dynamically-sized arena. Accepts the initial size of the arena in bytes.
* If there is not enough space in the arena for an allocation, a new region will be created. */
Arena *Arena_new_dynamic(size_t size);
// Frees the entire arena from memory.
void Arena_delete(Arena *arena);
// Will return a null pointer if you've tried allocating too much memory.
void *Arena_alloc(Arena *arena, size_t size);
// Identical to Arena_alloc but it zeros your memory
void *Arena_allocz(Arena *arena, size_t size);
/* Copy a block of memory into an arena.
* Functionally equivalent to memcpy. */
void *Arena_copy(Arena *arena, const void *src, size_t size);
/* If the pointer is to the last allocation, it will be resized.
* Otherwise, a new allocation will be created.
* Be careful with this! A null pointer will be returned upon error.
* Using this with memory outside of the arena is undefined behavior. */
void *Arena_realloc(Arena *arena, void *ptr, size_t size);
/* Marks the beginning of a temporary buffer that can be deallocated at any time.
* The state of the last one is saved in case you have multiple. */
void Arena_tmp_begin(Arena *arena);
/* Deallocates the last temporary buffer. If there is none,
* the entire arena will be deallocated. */
void Arena_tmp_rewind(Arena *arena);
#ifdef __cplusplus
}
#endif
#endif

172
main.c
View File

@ -1,3 +1,4 @@
#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
@ -19,6 +20,7 @@
#include "config.h"
#include "feed_info.h"
#include "arena.h"
// Function pointer type for commands
typedef void (*command_func)(struct discord *, const struct discord_interaction *);
@ -263,48 +265,142 @@ static void bot_command_remove(struct discord *client, const struct discord_inte
discord_create_interaction_response(client, event->id, event->token, &res, NULL);
}
#define LIST_PAGE_SIZE 5
// The arena everything gets allocated to will be returned in the arena pointer
static struct discord_interaction_callback_data *list_data_create(u64snowflake channel_id, int page_number, Arena **arena) {
assert(arena && "No arena provided"); // this is programmer error
// clamp page number
page_number = page_number < 1 ? 1 : page_number;
*arena = Arena_new(8192); // this should be more than enough
struct discord_interaction_callback_data *data = Arena_allocz(*arena, sizeof(*data));
int64_t count;
zblock_feed_info_err error = zblock_feed_info_count_channel(database_conn, channel_id, &count);
if (error) {
char *msg = Arena_alloc(*arena, sizeof(DISCORD_MAX_MESSAGE_LEN));
snprintf(msg, DISCORD_MAX_MESSAGE_LEN, "Error creating list: %s", zblock_feed_info_strerror(error));
data->content = msg;
return data;
}
int last_page_number = count ? count % LIST_PAGE_SIZE ? count / LIST_PAGE_SIZE + 1 : count / LIST_PAGE_SIZE : 1;
zblock_feed_info feeds[LIST_PAGE_SIZE];
int num_retrieved;
error = zblock_feed_info_retrieve_chunk_channel(database_conn, channel_id, (page_number - 1) * LIST_PAGE_SIZE, LIST_PAGE_SIZE, feeds, &num_retrieved);
if (error) {
char *msg = Arena_alloc(*arena, sizeof(DISCORD_MAX_MESSAGE_LEN));
snprintf(msg, DISCORD_MAX_MESSAGE_LEN, "Error creating list: %s", zblock_feed_info_strerror(error));
data->content = msg;
return data;
}
// create our components starting with the action row
data->components = Arena_alloc(*arena, sizeof(*data->components));
data->components->size = 1;
data->components->array = Arena_allocz(*arena, data->components->size * sizeof(*data->components->array));
struct discord_component *action_row = data->components->array;
action_row->type = DISCORD_COMPONENT_ACTION_ROW;
// create buttons
action_row->components = Arena_alloc(*arena, sizeof(*action_row->components));
action_row->components->size = 2;
action_row->components->array = Arena_allocz(*arena, action_row->components->size * sizeof(*action_row->components->array));
struct discord_component *buttons = action_row->components->array;
// create emojis
struct discord_emoji *back_arrow = Arena_allocz(*arena, sizeof(*back_arrow));
back_arrow->name = "◀️";
struct discord_emoji *next_arrow = Arena_allocz(*arena, sizeof(*next_arrow));
next_arrow->name = "▶️";
// create button ids
int back_id_size = snprintf(NULL, 0, "list_page%d", page_number - 1) + 1;
char *back_id = Arena_alloc(*arena, back_id_size);
snprintf(back_id, back_id_size, "list_page%d", page_number - 1);
int next_id_size = snprintf(NULL, 0, "list_page%d", page_number + 1) + 1;
char *next_id = Arena_alloc(*arena, next_id_size);
snprintf(next_id, next_id_size, "list_page%d", page_number + 1);
// populate buttons
buttons[0] = (struct discord_component) {
.type = DISCORD_COMPONENT_BUTTON,
.disabled = page_number == 1,
.style = DISCORD_BUTTON_SECONDARY,
.custom_id = back_id,
.label = "Back",
.emoji = back_arrow
};
buttons[1] = (struct discord_component) {
.type = DISCORD_COMPONENT_BUTTON,
.disabled = page_number == last_page_number,
.style = DISCORD_BUTTON_SECONDARY,
.custom_id = next_id,
.label = "Next",
.emoji = next_arrow
};
// create embed
data->embeds = Arena_alloc(*arena, sizeof(*data->embeds));
data->embeds->size = 1;
data->embeds->array = Arena_allocz(*arena, data->embeds->size * sizeof(*data->embeds->array));
struct discord_embed *embed = data->embeds->array;
int embed_title_size = snprintf(NULL, 0, "Feed List (Page %d of %d)", page_number, last_page_number) + 1;
char *embed_title = Arena_alloc(*arena, embed_title_size);
snprintf(embed_title, embed_title_size, "Feed List (Page %d of %d)", page_number, last_page_number);
// write the description
char *embed_description;
if (count) {
embed_description = Arena_alloc(*arena, 4096); // the current max size of embed descriptions
int embed_description_size = 0;
for (int i = 0; i < num_retrieved; ++i) {
// in case somebody has maliciously long text in their feed
if (embed_description_size < 4096) {
embed_description_size += snprintf(embed_description + embed_description_size, 4096 - embed_description_size,
"### %d. %s\n" // feed title
"Link: %s\n" // feed url
"Last updated: %s\n", // last_pubDate
(page_number - 1) * LIST_PAGE_SIZE + i + 1, feeds[i].title,
feeds[i].url,
feeds[i].last_pubDate
);
}
}
} else {
embed_description = "There are no feeds in this channel.";
}
*embed = (struct discord_embed) {
.title = embed_title,
.type = "rich",
.description = embed_description
};
return data;
}
static void bot_command_list(struct discord *client, const struct discord_interaction *event) {
Arena *arena;
struct discord_interaction_response res = {
.type = DISCORD_INTERACTION_CHANNEL_MESSAGE_WITH_SOURCE,
.data = &(struct discord_interaction_callback_data) {
.components = CREATE_COMPONENTS({
{
.type = DISCORD_COMPONENT_ACTION_ROW,
.components = CREATE_COMPONENTS({
{
.type = DISCORD_COMPONENT_BUTTON,
.disabled = false,
.style = DISCORD_BUTTON_SECONDARY,
.custom_id = "page_back",
.label = "Back",
.emoji = &(struct discord_emoji) {
.name = "◀️"
}
},
{
.type = DISCORD_COMPONENT_BUTTON,
.disabled = false,
.style = DISCORD_BUTTON_SECONDARY,
.custom_id = "page_next",
.label = "Next",
.emoji = &(struct discord_emoji) {
.name = "▶️"
}
}
})
}
}),
.embeds = CREATE_EMBEDS({
{
.title = "Feed List",
.type = "rich",
.description = "List functionality has not been fully implemented yet."
}
})
}
.data = list_data_create(event->channel_id, 1, &arena)
};
discord_create_interaction_response(client, event->id, event->token, &res, NULL);
Arena_delete(arena);
}
static void list_update(struct discord *client, const struct discord_interaction *event) {
int page_number;
sscanf(event->data->custom_id, "list_page%d", &page_number);
Arena *arena;
struct discord_interaction_response res = {
.type = DISCORD_INTERACTION_UPDATE_MESSAGE,
.data = list_data_create(event->channel_id, page_number, &arena)
};
discord_create_interaction_response(client, event->id, event->token, &res, NULL);
Arena_delete(arena);
}
static void bot_command_help(struct discord *client, const struct discord_interaction *event) {
@ -409,8 +505,8 @@ static void on_interaction(struct discord *client, const struct discord_interact
};
discord_create_interaction_response(client, event->id, event->token, &res, NULL);
} break;
case DISCORD_INTERACTION_MESSAGE_COMPONENT: {
// nothing yet
case DISCORD_INTERACTION_MESSAGE_COMPONENT: { // only the list command is used here so far
list_update(client, event);
} break;
default: // nothing
}