mirror of
https://github.com/WCBROW01/zblock.git
synced 2025-12-10 19:58:05 -05:00
[main.c] Complete list functionality
This commit is contained in:
207
arena.c
Normal file
207
arena.c
Normal 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
52
arena.h
Normal 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
172
main.c
@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user