From a48a68cfb04b2020737c0adfb0e7667451a22b5c Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Mon, 6 Nov 2023 11:54:08 -0500 Subject: Add camu - Subprojects temporarily omitted Signed-off-by: Andrew Opalach --- src/util/blocking_ring_buffer.c | 199 ++++++++++++++++++++++++++++++++++++++++ src/util/blocking_ring_buffer.h | 42 +++++++++ src/util/color_palette.c | 62 +++++++++++++ src/util/color_palette.h | 35 +++++++ src/util/meson.build | 3 + src/util/queue.h | 43 +++++++++ 6 files changed, 384 insertions(+) create mode 100644 src/util/blocking_ring_buffer.c create mode 100644 src/util/blocking_ring_buffer.h create mode 100644 src/util/color_palette.c create mode 100644 src/util/color_palette.h create mode 100644 src/util/meson.build create mode 100644 src/util/queue.h (limited to 'src/util') diff --git a/src/util/blocking_ring_buffer.c b/src/util/blocking_ring_buffer.c new file mode 100644 index 0000000..2160fec --- /dev/null +++ b/src/util/blocking_ring_buffer.c @@ -0,0 +1,199 @@ +#include + +#include "blocking_ring_buffer.h" + +#define LOCK(n) \ + buffer->req_start = n; \ + aki_cond_wait(&buffer->cond, &buffer->mutex); + +#define SIGNAL() \ + aki_cond_signal(&buffer->cond); + +void camu_ring_buffer_init(struct camu_ring_buffer *buffer, s32 size) +{ + buffer->size = size; + buffer->data = (u8 *)al_malloc(buffer->size); + aki_mutex_init(&buffer->mutex); + aki_cond_init(&buffer->cond); + camu_ring_buffer_reset(buffer); + buffer->reset_unlock = false; + buffer->enabled = true; +} + +// TODO: does locking here actually prevent this from messing something up. +void camu_ring_buffer_reset(struct camu_ring_buffer *buffer) +{ + aki_mutex_lock(&buffer->mutex); + buffer->valid_size = 0; + buffer->start = 0; + buffer->end = 0; + // If get_chunk was waiting it doesn't matter where req_start + // was, there should be room now. + if (aki_cond_is_waiting(&buffer->cond)) { + buffer->reset_unlock = true; + SIGNAL(); + } + buffer->wrap = false; + aki_mutex_unlock(&buffer->mutex); +} + +void camu_ring_buffer_disable(struct camu_ring_buffer *buffer) +{ + aki_mutex_lock(&buffer->mutex); + buffer->enabled = false; + // No state on the buffer will be edited by the time a get_chunk + // returns because of this. So, the next call to get_chunk after + // the buffer is re-enabled should be correct. + if (aki_cond_is_waiting(&buffer->cond)) { + buffer->reset_unlock = true; + SIGNAL(); + } + aki_mutex_unlock(&buffer->mutex); +} + +void camu_ring_buffer_enable(struct camu_ring_buffer *buffer) +{ + aki_mutex_lock(&buffer->mutex); + buffer->enabled = true; + aki_mutex_unlock(&buffer->mutex); +} + +s32 camu_ring_buffer_space(struct camu_ring_buffer *buffer) +{ + s32 available = 0; + aki_mutex_lock(&buffer->mutex); + if (buffer->end < buffer->start) { + available = buffer->start - buffer->end; + } else { + available = buffer->size - buffer->end; + } + aki_mutex_unlock(&buffer->mutex); + return available; +} + +s32 camu_ring_buffer_occupied(struct camu_ring_buffer *buffer) +{ + s32 filled = 0; + aki_mutex_lock(&buffer->mutex); + if (buffer->end < buffer->start) { + filled = (buffer->valid_size - buffer->start) + buffer->end; + } else { + // If the buffer is empty this will equate to 0. + filled = buffer->end - buffer->start; + } + aki_mutex_unlock(&buffer->mutex); + return filled; +} + +u8 *camu_ring_buffer_get_chunk(struct camu_ring_buffer *buffer, s32 n) +{ + aki_mutex_lock(&buffer->mutex); + + if (!buffer->enabled) { + return NULL; + } + + if (buffer->end < buffer->start) { + if (buffer->end + n > buffer->size) { + // Unsatisfiable, need to increase buffer size. + al_assert(n < buffer->end); + // Wait until read to the end of the buffer, wrap around, and read + // past the amount of data requested in this call (n). + buffer->wrap = true; + LOCK(n); + } else if (buffer->end + n >= buffer->start) { + // Wait until we read past our current write position plus the amount + // requested in this call (n). + LOCK(buffer->end + n); + } + } else if (buffer->end + n > buffer->size) { + // valid_size now marks the end of valid data in the buffer, this may be + // less than the actual buffer size. + buffer->valid_size = buffer->end; + if (n >= buffer->start) { + // Unsatisfiable, need to increase buffer size. + al_assert(n < buffer->valid_size); + // Wait until we read past at least the amount of data requested in this call (n). + LOCK(n); + } + // Set end to zero after waiting because it's possible start is also 0 at this point + // and we would end up with start == end while the buffer is full with data. + buffer->end = 0; + } + + // If a reset happened while waiting, the behavior for the structure is to assume + // you didn't want watever data you were waiting to write. + if (buffer->reset_unlock) { + buffer->reset_unlock = false; + return NULL; + } + + // Return a pointer to the start of the writable data and advance end by + // exactly the amount requested (n). + u8 *data = &buffer->data[buffer->end]; + buffer->end += n; + + return data; +} + +s32 camu_ring_buffer_read(struct camu_ring_buffer *buffer, u8 **data, s32 n) +{ + aki_mutex_lock(&buffer->mutex); + + // Set data even if we are going to read 0 bytes to avoid possible issues + // with data being NULL after a call to read. + *data = &buffer->data[buffer->start]; + + // If start == end, the buffer is empty. + if (buffer->start == buffer->end) { + return 0; + } + + if (buffer->end < buffer->start) { + // If we would read past the valid data in the buffer, instead + // read to the end of the valid data. + if (buffer->start + n > buffer->valid_size) { + n = buffer->valid_size - buffer->start; + } + } else if (buffer->start + n > buffer->end) { + // If we would read to or past end, instead read to the end. + n = buffer->end - buffer->start; + } + + buffer->start += n; + + // If we read to the end of the valid data, wrap around to the begining. + //if (buffer->start == buffer->valid_size && buffer->start != buffer->end) { + if (buffer->start == buffer->valid_size) { + buffer->start = 0; + buffer->valid_size = 0; + // We wrapped and aren't specifically waiting for a position after the wrap, so unlock. + if (aki_cond_is_waiting(&buffer->cond) && !buffer->wrap) { + SIGNAL(); + } + buffer->wrap = false; + } else if (aki_cond_is_waiting(&buffer->cond) && (!buffer->wrap && buffer->start > buffer->req_start)) { + // We read past the position we were waiting for, so unlock. + SIGNAL(); + } + + // After SIGNAL(), LOCK() won't proceed until this thread unlocks. + + return n; +} + +// Unlock the mutex, this needs to be called after a call to either get_chunk() or read() but +// only after the pointer has been used. +void camu_ring_buffer_unlock(struct camu_ring_buffer *buffer) +{ + aki_mutex_unlock(&buffer->mutex); +} + +void camu_ring_buffer_free(struct camu_ring_buffer *buffer) +{ + if (buffer->data) { + al_free(buffer->data); + } + aki_mutex_destroy(&buffer->mutex); + aki_cond_destroy(&buffer->cond); +} diff --git a/src/util/blocking_ring_buffer.h b/src/util/blocking_ring_buffer.h new file mode 100644 index 0000000..ff3521e --- /dev/null +++ b/src/util/blocking_ring_buffer.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +// Ring buffer with blocking write and non-blocking read. +// - Write can block and always returns the exact amount requested. +// - Read will return as many bytes as possible, which can be less than requested or 0. + +struct camu_ring_buffer { + u8 *data; + s32 size; + // The position of the last valid byte at the end of the buffer. + // This can be less than the total size of the buffer. + s32 valid_size; + s32 start; + s32 end; + // The last byte in the chunk that we are waiting to be fully available. + s32 req_start; + // True if we need to wait for the start to wrap around + // to the begining before considering signaling. + bool wrap; + struct aki_mutex mutex; + struct aki_cond cond; + // Signal get_chunk to return null and not modifiy the buffer + // if it was blocking and reset was called. + bool reset_unlock; + // If the buffer is disabled, all calls to get_chunk will return + // early and not block. + bool enabled; +}; + +void camu_ring_buffer_init(struct camu_ring_buffer *buffer, s32 size); +void camu_ring_buffer_reset(struct camu_ring_buffer *buffer); +void camu_ring_buffer_disable(struct camu_ring_buffer *buffer); +void camu_ring_buffer_enable(struct camu_ring_buffer *buffer); +s32 camu_ring_buffer_space(struct camu_ring_buffer *buffer); +s32 camu_ring_buffer_occupied(struct camu_ring_buffer *buffer); +u8 *camu_ring_buffer_get_chunk(struct camu_ring_buffer *buffer, s32 n); +s32 camu_ring_buffer_read(struct camu_ring_buffer *buffer, u8 **data, s32 n); +void camu_ring_buffer_unlock(struct camu_ring_buffer *buffer); +void camu_ring_buffer_free(struct camu_ring_buffer *buffer); diff --git a/src/util/color_palette.c b/src/util/color_palette.c new file mode 100644 index 0000000..e55c9e1 --- /dev/null +++ b/src/util/color_palette.c @@ -0,0 +1,62 @@ +#include +#include + +#include "color_palette.h" + +struct camu_color_palette global_color_palette = {0}; + +static char *special_colors[2] = { + "background", + "foreground" +}; + +static char *normal_colors[16] = { + "color0", + "color1", + "color2", + "color3", + "color4", + "color5", + "color6", + "color7", + "color8", + "color9", + "color10", + "color11", + "color12", + "color13", + "color14", + "color15" +}; + +bool camu_color_palette_init(str *path) +{ + struct aki_file file; + if (!aki_file_open(&file, path, false)) { + return false; + } + str s; + aki_file_read_as_str(&file, &s); + aki_file_close(&file); + json_error_t error; + json_t *root = json_loadb(s.data, s.len, 0, &error); + if (!root) return false; + json_t *special = json_object_get(root, "special"); + json_t *colors = json_object_get(root, "colors"); + if (!special || !colors) return false; + json_t *object; + const char *color; + for (u32 i = 0; i < AL_ARRAY_SIZE(special_colors); i++) { + object = json_object_get(special, special_colors[i]); + if (!object) return false; + color = json_string_value(object); + global_color_palette.colors[i] = (u32)al_str_to_long(al_str_w((char *)color, 1, 6), 16); + } + for (u32 i = 0; i < AL_ARRAY_SIZE(normal_colors); i++) { + object = json_object_get(colors, normal_colors[i]); + if (!object) return false; + color = json_string_value(object); + global_color_palette.colors[i + 2] = (u32)al_str_to_long(al_str_w((char *)color, 1, 6), 16); + } + return true; +} diff --git a/src/util/color_palette.h b/src/util/color_palette.h new file mode 100644 index 0000000..a71360c --- /dev/null +++ b/src/util/color_palette.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +enum { + CAMU_COLOR_BACKGROUND = 0, + CAMU_COLOR_FOREGROUND, + CAMU_COLOR0, + CAMU_COLOR1, + CAMU_COLOR2, + CAMU_COLOR3, + CAMU_COLOR4, + CAMU_COLOR5, + CAMU_COLOR6, + CAMU_COLOR7, + CAMU_COLOR8, + CAMU_COLOR9, + CAMU_COLOR10, + CAMU_COLOR11, + CAMU_COLOR12, + CAMU_COLOR13, + CAMU_COLOR14, + CAMU_COLOR15 +}; + +#define CAMU_COLOR_PALETTE_COUNT 18 + +struct camu_color_palette { + u32 colors[CAMU_COLOR_PALETTE_COUNT]; +}; + +extern struct camu_color_palette global_color_palette; + +bool camu_color_palette_init(str *path); diff --git a/src/util/meson.build b/src/util/meson.build new file mode 100644 index 0000000..3cdaf28 --- /dev/null +++ b/src/util/meson.build @@ -0,0 +1,3 @@ +util_src = ['color_palette.c', 'blocking_ring_buffer.c'] +util_deps = [] +util = declare_dependency(sources: util_src, dependencies: util_deps) diff --git a/src/util/queue.h b/src/util/queue.h new file mode 100644 index 0000000..032bb55 --- /dev/null +++ b/src/util/queue.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +#define queue(type) \ + struct { \ + array(type) a; \ + struct aki_mutex mutex; \ + } + +#define camu_queue_size(q, r) \ + do { \ + aki_mutex_lock(&(q).mutex); \ + r = (q).a.size; \ + aki_mutex_unlock(&(q).mutex); \ + } while (0) + +#define camu_queue_init(q) \ + do { \ + al_array_init((q).a); \ + aki_mutex_init(&(q).mutex); \ + } while (0) + +#define camu_queue_push(q, item) \ + do { \ + aki_mutex_lock(&(q).mutex); \ + al_array_push((q).a, item); \ + aki_mutex_unlock(&(q).mutex); \ + } while (0) + +#define camu_queue_pop(q, r) \ + do { \ + aki_mutex_lock(&(q).mutex); \ + al_array_pop_at((q).a, 0, r); \ + aki_mutex_unlock(&(q).mutex); \ + } while (0) + +#define camu_queue_free(q) \ + do { \ + aki_mutex_destroy(&(q).mutex); \ + al_array_free((q).a); \ + } while (0) -- cgit v1.2.3-101-g0448