summaryrefslogtreecommitdiff
path: root/src/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/util')
-rw-r--r--src/util/blocking_ring_buffer.c199
-rw-r--r--src/util/blocking_ring_buffer.h42
-rw-r--r--src/util/color_palette.c62
-rw-r--r--src/util/color_palette.h35
-rw-r--r--src/util/meson.build3
-rw-r--r--src/util/queue.h43
6 files changed, 384 insertions, 0 deletions
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 <al/lib.h>
+
+#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 <al/types.h>
+#include <aki/thread.h>
+
+// 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 <aki/file.h>
+#include <jansson.h>
+
+#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 <al/types.h>
+#include <al/str.h>
+
+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 <al/array.h>
+#include <aki/thread.h>
+
+#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)