aboutsummaryrefslogtreecommitdiffhomepage
path: root/core
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2021-07-01 22:34:24 +0200
committercbdev <cb@cbcdn.com>2021-07-01 22:34:24 +0200
commit3b134cc43965c1c196734be7a162da7cddeeafc8 (patch)
treefa900d172d5c75d48b1a811bde70bfa40dca56bd /core
parent6fab3de48e129e1703f70941f65ae89853e567d5 (diff)
downloadmidimonster-3b134cc43965c1c196734be7a162da7cddeeafc8.tar.gz
midimonster-3b134cc43965c1c196734be7a162da7cddeeafc8.tar.bz2
midimonster-3b134cc43965c1c196734be7a162da7cddeeafc8.zip
Factor out explicit frontend API
Diffstat (limited to 'core')
-rw-r--r--core/backend.c5
-rw-r--r--core/backend.h2
-rw-r--r--core/config.h5
-rw-r--r--core/core.c257
-rw-r--r--core/core.h43
-rw-r--r--core/plugin.h2
-rw-r--r--core/routing.c198
-rw-r--r--core/routing.h9
8 files changed, 517 insertions, 4 deletions
diff --git a/core/backend.c b/core/backend.c
index 83121bd..263fda3 100644
--- a/core/backend.c
+++ b/core/backend.c
@@ -1,9 +1,10 @@
#include <string.h>
#ifndef _WIN32
-#define MM_API __attribute__((visibility ("default")))
+ #define MM_API __attribute__((visibility ("default")))
#else
-#define MM_API __attribute__((dllexport))
+ #define MM_API __attribute__((dllexport))
#endif
+
#define BACKEND_NAME "core/be"
#include "midimonster.h"
#include "backend.h"
diff --git a/core/backend.h b/core/backend.h
index 46c6c3a..1f85424 100644
--- a/core/backend.h
+++ b/core/backend.h
@@ -10,7 +10,7 @@ int backends_start();
int backends_stop();
instance* mm_instance(backend* b);
-/* Backend API */
+/* Public backend API */
MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create);
MM_API void mm_channel_update(channel* chan, uint64_t ident);
MM_API instance* mm_instance_find(char* name, uint64_t ident);
diff --git a/core/config.h b/core/config.h
index b96a866..80e8fff 100644
--- a/core/config.h
+++ b/core/config.h
@@ -49,6 +49,9 @@ typedef struct /*_mm_config_override*/ {
char* value;
} config_override;
+/* Internal API */
+void config_free();
+
+/* Frontent API */
int config_read(char* file);
int config_add_override(override_type type, char* data);
-void config_free();
diff --git a/core/core.c b/core/core.c
new file mode 100644
index 0000000..cd097f8
--- /dev/null
+++ b/core/core.c
@@ -0,0 +1,257 @@
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <errno.h>
+#include <unistd.h>
+#ifndef _WIN32
+ #include <sys/select.h>
+ #define MM_API __attribute__((visibility ("default")))
+#else
+ #define MM_API __attribute__((dllexport))
+#endif
+
+#define BACKEND_NAME "core"
+#include "midimonster.h"
+#include "core.h"
+#include "backend.h"
+#include "routing.h"
+#include "plugin.h"
+#include "config.h"
+
+static size_t fds = 0;
+static int max_fd = -1;
+static managed_fd* fd = NULL;
+static managed_fd* signaled_fds = NULL;
+static volatile sig_atomic_t fd_set_dirty = 1;
+static uint64_t global_timestamp = 0;
+
+static fd_set all_fds;
+
+MM_API uint64_t mm_timestamp(){
+ return global_timestamp;
+}
+
+static void core_timestamp(){
+ #ifdef _WIN32
+ global_timestamp = GetTickCount();
+ #else
+ struct timespec current;
+ if(clock_gettime(CLOCK_MONOTONIC_COARSE, &current)){
+ fprintf(stderr, "Failed to update global timestamp, time-based processing for some backends may be impaired: %s\n", strerror(errno));
+ return;
+ }
+
+ global_timestamp = current.tv_sec * 1000 + current.tv_nsec / 1000000;
+ #endif
+}
+
+static fd_set core_collect(int* max_fd){
+ size_t u = 0;
+ fd_set rv_fds;
+
+ if(max_fd){
+ *max_fd = -1;
+ }
+
+ DBGPF("Building selector set from %" PRIsize_t " FDs registered to core", fds);
+ FD_ZERO(&rv_fds);
+ for(u = 0; u < fds; u++){
+ if(fd[u].fd >= 0){
+ FD_SET(fd[u].fd, &rv_fds);
+ if(max_fd){
+ *max_fd = max(*max_fd, fd[u].fd);
+ }
+ }
+ }
+
+ return rv_fds;
+}
+
+MM_API int mm_manage_fd(int new_fd, char* back, int manage, void* impl){
+ backend* b = backend_match(back);
+ size_t u;
+
+ if(!b){
+ fprintf(stderr, "Unknown backend %s registered for managed fd\n", back);
+ return 1;
+ }
+
+ //find exact match
+ for(u = 0; u < fds; u++){
+ if(fd[u].fd == new_fd && fd[u].backend == b){
+ fd[u].impl = impl;
+ if(!manage){
+ fd[u].fd = -1;
+ fd[u].backend = NULL;
+ fd[u].impl = NULL;
+ fd_set_dirty = 1;
+ }
+ return 0;
+ }
+ }
+
+ if(!manage){
+ return 0;
+ }
+
+ //find free slot
+ for(u = 0; u < fds; u++){
+ if(fd[u].fd < 0){
+ break;
+ }
+ }
+ //if necessary expand
+ if(u == fds){
+ fd = realloc(fd, (fds + 1) * sizeof(managed_fd));
+ if(!fd){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return 1;
+ }
+
+ signaled_fds = realloc(signaled_fds, (fds + 1) * sizeof(managed_fd));
+ if(!signaled_fds){
+ fprintf(stderr, "Failed to allocate memory\n");
+ return 1;
+ }
+ fds++;
+ }
+
+ //store new fd
+ fd[u].fd = new_fd;
+ fd[u].backend = b;
+ fd[u].impl = impl;
+ fd_set_dirty = 1;
+ return 0;
+}
+
+int core_initialize(){
+ FD_ZERO(&all_fds);
+
+ //load initial timestamp
+ core_timestamp();
+
+ #ifdef _WIN32
+ WSADATA wsa;
+ WORD version = MAKEWORD(2, 2);
+ if(WSAStartup(version, &wsa)){
+ return 1;
+ }
+ _fmode = _O_BINARY;
+ #endif
+
+ //attach plugins
+ if(plugins_load(PLUGINS)){
+ LOG("Failed to initialize a backend");
+ return 1;
+ }
+
+ return 0;
+}
+
+int core_start(){
+ if(backends_start()){
+ return 1;
+ }
+
+ routing_stats();
+
+ if(!fds){
+ LOG("No descriptors registered for multiplexing");
+ }
+
+ return 0;
+}
+
+int core_iteration(){
+ fd_set read_fds;
+ struct timeval tv;
+ int error;
+ size_t n, u;
+ #ifdef _WIN32
+ char* error_message = NULL;
+ #else
+ struct timespec ts;
+ #endif
+
+ //rebuild fd set if necessary
+ if(fd_set_dirty || !signaled_fds){
+ all_fds = core_collect(&max_fd);
+ fd_set_dirty = 0;
+ }
+
+ //wait for & translate events
+ read_fds = all_fds;
+ tv = backend_timeout();
+
+ //check whether there are any fds active, windows does not like select() without descriptors
+ if(max_fd >= 0){
+ error = select(max_fd + 1, &read_fds, NULL, NULL, &tv);
+ if(error < 0){
+ #ifndef _WIN32
+ fprintf(stderr, "select failed: %s\n", strerror(errno));
+ #else
+ FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL, WSAGetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &error_message, 0, NULL);
+ fprintf(stderr, "select failed: %s\n", error_message);
+ LocalFree(error_message);
+ error_message = NULL;
+ #endif
+ return 1;
+ }
+ }
+ else{
+ DBGPF("No descriptors, sleeping for %zu msec", tv.tv_sec * 1000 + tv.tv_usec / 1000);
+ #ifdef _WIN32
+ Sleep(tv.tv_sec * 1000 + tv.tv_usec / 1000);
+ #else
+ ts.tv_sec = tv.tv_sec;
+ ts.tv_nsec = tv.tv_usec * 1000;
+ nanosleep(&ts, NULL);
+ #endif
+ }
+
+ //update this iteration's timestamp
+ core_timestamp();
+
+ //find all signaled fds
+ n = 0;
+ for(u = 0; u < fds; u++){
+ if(fd[u].fd >= 0 && FD_ISSET(fd[u].fd, &read_fds)){
+ signaled_fds[n] = fd[u];
+ n++;
+ }
+ }
+
+ //run backend processing to collect events
+ DBGPF("%" PRIsize_t " backend FDs signaled", nfds);
+ if(backends_handle(n, signaled_fds)){
+ return 1;
+ }
+
+ //route generated events
+ return routing_iteration();
+}
+
+static void fds_free(){
+ size_t u;
+ for(u = 0; u < fds; u++){
+ if(fd[u].fd >= 0){
+ close(fd[u].fd);
+ fd[u].fd = -1;
+ }
+ }
+ free(fd);
+ fds = 0;
+ fd = NULL;
+}
+
+void core_shutdown(){
+ backends_stop();
+ routing_cleanup();
+ free(signaled_fds);
+ signaled_fds = NULL;
+ fds_free();
+ plugins_close();
+ config_free();
+ fd_set_dirty = 1;
+}
diff --git a/core/core.h b/core/core.h
new file mode 100644
index 0000000..3237a71
--- /dev/null
+++ b/core/core.h
@@ -0,0 +1,43 @@
+/*
+ * MIDIMonster frontend API
+ *
+ * These APIs expose the core as a linkable module. Frontends will use these calls
+ * as primary interface to interact with the MIDIMonster core.
+ *
+ * The lifecycle is as follows:
+ *
+ * * Initially, only the following API calls are valid:
+ * config_add_override()
+ * core_initialize()
+ * This allows the frontend to configure overrides for any configuration
+ * loaded later (e.g. by parsing command line arguments) before initializing
+ * the core.
+ * * Calling core_initialize() attaches all backend modules to the system and
+ * performs platform specific startup operations. From this point on,
+ * core_shutdown() must be called before terminating the frontend.
+ * All frontend API calls except `core_iteration` are now valid.
+ * The core is now in the configuration stage in which the frontend
+ * will push any configuration files.
+ * * Calling core_start() marks the transition from the configuration phase
+ * to the translation phase. The core will activate any configured backends
+ * and provide them with the information required to connect to their data
+ * sources and sinks. In this stage, only the following API calls are valid:
+ * core_iteration()
+ * core_shutdown()
+ * * The frontend will now repeatedly call core_iteration() to process any incoming
+ * events. This API will block execution until either one or more events have
+ * been registered or an internal timeout expires.
+ * * Calling core_shutdown() releases all memory allocated by the core and any
+ * attached modules or plugins, including all configuration, overrides,
+ * mappings, statistics, etc. The core is now ready to exit or be
+ * reinitialized using core_initialize().
+ */
+
+int core_initialize();
+int core_start();
+int core_iteration();
+void core_shutdown();
+
+/* Public backend API */
+MM_API uint64_t mm_timestamp();
+MM_API int mm_manage_fd(int new_fd, char* back, int manage, void* impl);
diff --git a/core/plugin.h b/core/plugin.h
index 64c557f..cdd544c 100644
--- a/core/plugin.h
+++ b/core/plugin.h
@@ -1,3 +1,5 @@
typedef int (*plugin_init)();
+
+/* Internal API */
int plugins_load(char* dir);
int plugins_close();
diff --git a/core/routing.c b/core/routing.c
new file mode 100644
index 0000000..af93d0f
--- /dev/null
+++ b/core/routing.c
@@ -0,0 +1,198 @@
+#include <string.h>
+#include <signal.h>
+#include <time.h>
+#include <errno.h>
+#include <unistd.h>
+#ifndef _WIN32
+ #include <sys/select.h>
+ #define MM_API __attribute__((visibility ("default")))
+#else
+ #define MM_API __attribute__((dllexport))
+#endif
+
+#define BACKEND_NAME "core/rt"
+#define MM_SWAP_LIMIT 20
+#include "midimonster.h"
+#include "routing.h"
+#include "backend.h"
+
+/* Core-internal structures */
+typedef struct /*_event_collection*/ {
+ size_t alloc;
+ size_t n;
+ channel** channel;
+ channel_value* value;
+} event_collection;
+
+typedef struct /*_mm_channel_mapping*/ {
+ channel* from;
+ size_t destinations;
+ channel** to;
+} channel_mapping;
+
+static struct {
+ //routing_hash is set up for 256 buckets
+ size_t entries[256];
+ channel_mapping* map[256];
+
+ event_collection pool[2];
+ event_collection* events;
+} routing = {
+ .events = routing.pool
+};
+
+static size_t routing_hash(channel* key){
+ uint64_t repr = (uint64_t) key;
+ //return 8bit hash for 256 buckets, not ideal but it works
+ return (repr ^ (repr >> 8) ^ (repr >> 16) ^ (repr >> 24) ^ (repr >> 32)) & 0xFF;
+}
+
+int mm_map_channel(channel* from, channel* to){
+ size_t u, m, bucket = routing_hash(from);
+
+ //find existing source mapping
+ for(u = 0; u < routing.entries[bucket]; u++){
+ if(routing.map[bucket][u].from == from){
+ break;
+ }
+ }
+
+ //create new entry
+ if(u == routing.entries[bucket]){
+ routing.map[bucket] = realloc(routing.map[bucket], (routing.entries[bucket] + 1) * sizeof(channel_mapping));
+ if(!routing.map[bucket]){
+ routing.entries[bucket] = 0;
+ fprintf(stderr, "Failed to allocate memory\n");
+ return 1;
+ }
+
+ memset(routing.map[bucket] + routing.entries[bucket], 0, sizeof(channel_mapping));
+ routing.entries[bucket]++;
+ routing.map[bucket][u].from = from;
+ }
+
+ //check whether the target is already mapped
+ for(m = 0; m < routing.map[bucket][u].destinations; m++){
+ if(routing.map[bucket][u].to[m] == to){
+ return 0;
+ }
+ }
+
+ //add a mapping target
+ routing.map[bucket][u].to = realloc(routing.map[bucket][u].to, (routing.map[bucket][u].destinations + 1) * sizeof(channel*));
+ if(!routing.map[bucket][u].to){
+ fprintf(stderr, "Failed to allocate memory\n");
+ routing.map[bucket][u].destinations = 0;
+ return 1;
+ }
+
+ routing.map[bucket][u].to[routing.map[bucket][u].destinations] = to;
+ routing.map[bucket][u].destinations++;
+ return 0;
+}
+
+MM_API int mm_channel_event(channel* c, channel_value v){
+ size_t u, p, bucket = routing_hash(c);
+
+ //find mapped channels
+ for(u = 0; u < routing.entries[bucket]; u++){
+ if(routing.map[bucket][u].from == c){
+ break;
+ }
+ }
+
+ if(u == routing.entries[bucket]){
+ //target-only channel
+ return 0;
+ }
+
+ //resize event structures to fit additional events
+ if(routing.events->n + routing.map[bucket][u].destinations >= routing.events->alloc){
+ routing.events->channel = realloc(routing.events->channel, (routing.events->alloc + routing.map[bucket][u].destinations) * sizeof(channel*));
+ routing.events->value = realloc(routing.events->value, (routing.events->alloc + routing.map[bucket][u].destinations) * sizeof(channel_value));
+
+ if(!routing.events->channel || !routing.events->value){
+ fprintf(stderr, "Failed to allocate memory\n");
+ routing.events->alloc = 0;
+ routing.events->n = 0;
+ return 1;
+ }
+
+ routing.events->alloc += routing.map[bucket][u].destinations;
+ }
+
+ //enqueue channel events
+ //FIXME this might lead to one channel being mentioned multiple times in an apply call
+ memcpy(routing.events->channel + routing.events->n, routing.map[bucket][u].to, routing.map[bucket][u].destinations * sizeof(channel*));
+ for(p = 0; p < routing.map[bucket][u].destinations; p++){
+ routing.events->value[routing.events->n + p] = v;
+ }
+
+ routing.events->n += routing.map[bucket][u].destinations;
+ return 0;
+}
+
+void routing_stats(){
+ size_t n = 0, u, max = 0;
+
+ //count and report mappings
+ for(u = 0; u < sizeof(routing.map) / sizeof(routing.map[0]); u++){
+ n += routing.entries[u];
+ max = max(max, routing.entries[u]);
+ }
+
+ LOGPF("Routing %" PRIsize_t " sources, largest bucket has %" PRIsize_t " entries",
+ n, max);
+}
+
+int routing_iteration(){
+ event_collection* secondary = NULL;
+ size_t u, swaps = 0;
+
+ //limit number of collector swaps per iteration to prevent complete deadlock
+ while(routing.events->n && swaps < MM_SWAP_LIMIT){
+ //swap primary and secondary event collectors
+ DBGPF("Swapping event collectors, %" PRIsize_t " events in primary", routing.events->n);
+ for(u = 0; u < sizeof(routing.pool) / sizeof(routing.pool[0]); u++){
+ if(routing.events != routing.pool + u){
+ secondary = routing.events;
+ routing.events = routing.pool + u;
+ break;
+ }
+ }
+
+ //push collected events to target backends
+ if(secondary->n && backends_notify(secondary->n, secondary->channel, secondary->value)){
+ fprintf(stderr, "Backends failed to handle output\n");
+ return 1;
+ }
+
+ //reset the event count
+ secondary->n = 0;
+ }
+
+ if(swaps == MM_SWAP_LIMIT){
+ LOG("Iteration swap limit hit, a backend may be configured to route events in an infinite loop");
+ }
+
+ return 0;
+}
+
+void routing_cleanup(){
+ size_t u, n;
+
+ for(u = 0; u < sizeof(routing.map) / sizeof(routing.map[0]); u++){
+ for(n = 0; n < routing.entries[u]; n++){
+ free(routing.map[u][n].to);
+ }
+ free(routing.map[u]);
+ routing.map[u] = NULL;
+ routing.entries[u] = 0;
+ }
+
+ for(u = 0; u < sizeof(routing.pool) / sizeof(routing.pool[0]); u++){
+ free(routing.pool[u].channel);
+ free(routing.pool[u].value);
+ routing.pool[u].alloc = 0;
+ }
+}
diff --git a/core/routing.h b/core/routing.h
new file mode 100644
index 0000000..72dd768
--- /dev/null
+++ b/core/routing.h
@@ -0,0 +1,9 @@
+/* Internal API */
+int mm_map_channel(channel* from, channel* to);
+int routing_iteration();
+void routing_stats();
+void routing_cleanup();
+
+/* Public backend API */
+MM_API int mm_channel_event(channel* c, channel_value v);
+