summaryrefslogtreecommitdiff
path: root/src/til_stream.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/til_stream.c')
-rw-r--r--src/til_stream.c339
1 files changed, 339 insertions, 0 deletions
diff --git a/src/til_stream.c b/src/til_stream.c
new file mode 100644
index 0000000..0f13d1f
--- /dev/null
+++ b/src/til_stream.c
@@ -0,0 +1,339 @@
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "til_stream.h"
+#include "til_tap.h"
+
+/* A stream in libtil is basically a hash table for tracking dynamic
+ * information for modules to create/modify/access. The objects stored in the
+ * table are "stream pipes" and endpoints called taps, making this something
+ * like a miniature in-memory implementation of named pipes, conceptually
+ * anyways (there are no actual file descriptors).
+ */
+
+#if 0
+/* example usage: */
+typedef struct foo_t {
+ struct {
+ til_tap_t position;
+ } taps;
+ struct {
+ v2f_t position;
+ } vars;
+
+ v2f_t *position;
+} foo_t;
+
+foo_t * foo_create_context(void)
+{
+ foo_t *foo = malloc(sizeof(foo_t));
+
+ /* This creates an isolated (pipe-)tap binding our local position variable and pointer
+ * to a name for later "tapping" onto a stream.
+ */
+ foo->taps.position = til_tap_init_v2f(&foo->position, 1, &foo->vars.position, "position");
+}
+
+foo_render(foo_t *foo, til_fb_fragment_t *fragment)
+{
+ if (!til_stream_tap_context(fragment->stream, foo, &foo->taps.position)) {
+ /* got nothing, we're driving position */
+ foo->position->x = cosf(ticks);
+ foo->position->y = sinf(ticks);
+ } /* else { got something, just use foo->position as-is */
+}
+#endif
+
+
+#define TIL_STREAM_BUCKETS_COUNT 256
+
+typedef struct til_stream_pipe_t til_stream_pipe_t;
+
+struct til_stream_pipe_t {
+ til_stream_pipe_t *next;
+ const void *owner;
+ char *parent_path;
+ const til_tap_t *driving_tap;
+ uint32_t hash;
+};
+
+typedef struct til_stream_t {
+ pthread_mutex_t mutex;
+ til_stream_pipe_t *buckets[TIL_STREAM_BUCKETS_COUNT];
+} til_stream_t;
+
+
+til_stream_t * til_stream_new(void)
+{
+ til_stream_t *stream;
+
+ stream = calloc(1, sizeof(til_stream_t));
+ if (!stream)
+ return NULL;
+
+ pthread_mutex_init(&stream->mutex, NULL);
+
+ return stream;
+}
+
+
+til_stream_t * til_stream_free(til_stream_t *stream)
+{
+ if (!stream)
+ return NULL;
+
+ for (int i = 0; i < TIL_STREAM_BUCKETS_COUNT; i++) {
+ for (til_stream_pipe_t *p = stream->buckets[i], *p_next; p != NULL; p = p_next) {
+ p_next = p->next;
+ free(p->parent_path);
+ free(p);
+ }
+ }
+
+ pthread_mutex_destroy(&stream->mutex);
+ free(stream);
+
+ return NULL;
+}
+
+
+/* Taps the key-named type-typed pipe on the supplied stream.
+ * If this is the first use of the tap on this stream, new pipe will be created.
+ * If the tap exists on this stream, and the type matches, existing pipe will be used as-is.
+ * If the key exists on this stream, but the type mismatches, an error is returned.
+ *
+ * -errno is returned on error, 0 when tap is driving, 1 when tap is passenger.
+ *
+ * If stream is NULL it's treated as if the key doesn't exist without a pipe creation.
+ */
+int til_stream_tap(til_stream_t *stream, const void *owner, const char *parent_path, uint32_t parent_hash, const til_tap_t *tap)
+{
+ uint32_t hash, bucket;
+ til_stream_pipe_t *pipe;
+
+ assert(tap);
+
+ if (!stream) {
+ *(tap->ptr) = tap->elems;
+
+ return 0;
+ }
+
+ pthread_mutex_lock(&stream->mutex);
+
+ hash = (tap->name_hash ^ parent_hash);
+ bucket = hash % TIL_STREAM_BUCKETS_COUNT;
+ for (pipe = stream->buckets[bucket]; pipe != NULL; pipe = pipe->next) {
+ if (pipe->hash == hash) {
+ if (pipe->driving_tap == tap) {
+ /* this is our pipe and we're driving */
+ *(tap->ptr) = pipe->driving_tap->elems;
+
+ pthread_mutex_unlock(&stream->mutex);
+ return 0;
+ }
+
+ if (pipe->driving_tap->elems == *(tap->ptr) ||
+ (!strcmp(pipe->driving_tap->name, tap->name) && !strcmp(pipe->parent_path, parent_path))) {
+ /* this looks to be our pipe, but we're not driving */
+ *(tap->ptr) = pipe->driving_tap->elems;
+
+ pthread_mutex_unlock(&stream->mutex);
+ return 1;
+ }
+ }
+ }
+
+ /* matching pipe not found, create new one with tap as driver */
+ pipe = calloc(1, sizeof(til_stream_pipe_t));
+ if (!pipe) {
+ pthread_mutex_unlock(&stream->mutex);
+ return -ENOMEM;
+ }
+
+ pipe->owner = owner;
+ pipe->driving_tap = tap;
+
+ pipe->parent_path = strdup(parent_path);
+ if (!pipe->parent_path) {
+ free(pipe);
+
+ pthread_mutex_unlock(&stream->mutex);
+ return -ENOMEM;
+ }
+
+ pipe->hash = hash;
+ pipe->next = stream->buckets[bucket];
+ stream->buckets[bucket] = pipe;
+
+ pthread_mutex_unlock(&stream->mutex);
+ return 0;
+}
+
+
+/* remove all pipes belonging to owner in stream */
+void til_stream_untap_owner(til_stream_t *stream, const void *owner)
+{
+ for (int i = 0; i < TIL_STREAM_BUCKETS_COUNT; i++) {
+ for (til_stream_pipe_t *p = stream->buckets[i], *p_next, *p_prev; p != NULL; p = p_next) {
+ p_next = p->next;
+
+ if (p->owner == owner) {
+ if (p == stream->buckets[i])
+ stream->buckets[i] = p_next;
+ else
+ p_prev->next = p_next;
+
+ free(p);
+ } else
+ p_prev = p;
+ }
+ }
+}
+
+
+/* We need the higher-order types defined in order to print their contents.
+ * libtil should probably just formally define these smoewhere for modules to
+ * make use of. Until now it's been very deliberate to try leave modules to be
+ * relatively self-contained, even if they often reinvent the wheel as a
+ * result... it's relatively harmless for small functionalities while keeping
+ * the listings easier to grok as a whole esp. for a newcomer who isn't
+ * necessarily comfortable jumping around a sprawling tree of files.
+ */
+typedef struct v2f_t { float x, y; } v2f_t;
+
+typedef struct v3f_t {
+ float x, y, z;
+} v3f_t;
+
+typedef struct v4f_t {
+ float x, y, z, w;
+} v4f_t;
+
+/* XXX: note that while yes, this does acquire stream->mutex to serialize access to the table/pipes,
+ * this mutex does not serialize access to the tapped variables. So if this print is performed
+ * during the threaded rendering phase of things, it will technically be racy. The only strictly
+ * correct place to perform this print race-free is in the rendering loop between submissions to
+ * the rendering threads. Arguably if careful about only printing while serial, there's no need
+ * to acquire the mutex - but it's also an uncontended lock if that's the case so just take the
+ * mutex anyways since we're accessing the underlying structure it protects.
+ */
+void til_stream_fprint(til_stream_t *stream, FILE *out)
+{
+ fprintf(out, "Pipes on stream %p:\n", stream);
+ pthread_mutex_lock(&stream->mutex);
+ for (int i = 0; i < TIL_STREAM_BUCKETS_COUNT; i++) {
+ for (til_stream_pipe_t *p = stream->buckets[i]; p != NULL; p = p->next) {
+ fprintf(out, "%s/%s: ", p->parent_path, p->driving_tap->name);
+
+ for (size_t j = 0; j < p->driving_tap->n_elems; j++) {
+ const char *sep = j ? ", " : "";
+
+ switch (p->driving_tap->type) {
+ case TIL_TAP_TYPE_I8:
+ fprintf(out, "%"PRIi8"%s",
+ *(*((int8_t **)p->driving_tap->ptr)),
+ sep);
+ break;
+
+ case TIL_TAP_TYPE_I16:
+ fprintf(out, "%"PRIi16"%s",
+ *(*((int16_t **)p->driving_tap->ptr)),
+ sep);
+ break;
+
+ case TIL_TAP_TYPE_I32:
+ fprintf(out, "%"PRIi32"%s",
+ *(*((int32_t **)p->driving_tap->ptr)),
+ sep);
+ break;
+
+ case TIL_TAP_TYPE_I64:
+ fprintf(out, "%"PRIi64"%s",
+ *(*((int64_t **)p->driving_tap->ptr)),
+ sep);
+ break;
+
+ case TIL_TAP_TYPE_U8:
+ fprintf(out, "%"PRIu8"%s",
+ *(*((int8_t **)p->driving_tap->ptr)),
+ sep);
+ break;
+
+ case TIL_TAP_TYPE_U16:
+ fprintf(out, "%"PRIu16"%s",
+ *(*((int16_t **)p->driving_tap->ptr)),
+ sep);
+ break;
+
+ case TIL_TAP_TYPE_U32:
+ fprintf(out, "%"PRIu32"%s",
+ *(*((int32_t **)p->driving_tap->ptr)),
+ sep);
+ break;
+
+ case TIL_TAP_TYPE_U64:
+ fprintf(out, "%"PRIu64"%s",
+ *(*((int64_t **)p->driving_tap->ptr)),
+ sep);
+ break;
+
+ case TIL_TAP_TYPE_FLOAT:
+ fprintf(out, "%f%s",
+ *(*((float **)p->driving_tap->ptr)),
+ sep);
+ break;
+
+ case TIL_TAP_TYPE_DOUBLE:
+ fprintf(out, "%f%s",
+ *(*((double **)p->driving_tap->ptr)),
+ sep);
+ break;
+
+ case TIL_TAP_TYPE_V2F:
+ fprintf(out, "{%f,%f}%s",
+ (*((v2f_t **)p->driving_tap->ptr))->x,
+ (*((v2f_t **)p->driving_tap->ptr))->y,
+ sep);
+ break;
+
+ case TIL_TAP_TYPE_V3F:
+ fprintf(out, "{%f,%f,%f}%s",
+ (*((v3f_t **)p->driving_tap->ptr))->x,
+ (*((v3f_t **)p->driving_tap->ptr))->y,
+ (*((v3f_t **)p->driving_tap->ptr))->z,
+ sep);
+ break;
+
+ case TIL_TAP_TYPE_V4F:
+ fprintf(out, "{%f,%f,%f,%f}%s",
+ (*((v4f_t **)p->driving_tap->ptr))->x,
+ (*((v4f_t **)p->driving_tap->ptr))->y,
+ (*((v4f_t **)p->driving_tap->ptr))->z,
+ (*((v4f_t **)p->driving_tap->ptr))->w,
+ sep);
+ break;
+
+ case TIL_TAP_TYPE_M4F:
+ fprintf(out, "M4F TODO%s", sep);
+ break;
+
+ case TIL_TAP_TYPE_VOIDP:
+ fprintf(out, "%p%s", *((void **)p->driving_tap->ptr), sep);
+ break;
+
+ default:
+ assert(0);
+ }
+ fprintf(out, "\n");
+ }
+ }
+ }
+ pthread_mutex_unlock(&stream->mutex);
+ fprintf(out, "\n");
+}
© All Rights Reserved