aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--README.md14
-rw-r--r--core/backend.c31
-rw-r--r--core/backend.h2
-rw-r--r--core/config.c71
-rw-r--r--core/config.h5
-rw-r--r--core/core.c258
-rw-r--r--core/core.h43
-rw-r--r--core/plugin.c40
-rw-r--r--core/plugin.h2
-rw-r--r--core/routing.c198
-rw-r--r--core/routing.h9
-rw-r--r--midimonster.c437
-rw-r--r--midimonster.h9
14 files changed, 631 insertions, 492 deletions
diff --git a/Makefile b/Makefile
index de9b24e..e78a745 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
.PHONY: all clean run sanitize backends windows full backends-full install
-OBJS = core/config.o core/backend.o core/plugin.o
+OBJS = core/core.o core/config.o core/backend.o core/plugin.o core/routing.o
PREFIX ?= /usr
PLUGIN_INSTALL = $(PREFIX)/lib/midimonster
@@ -38,7 +38,7 @@ ifdef DEFAULT_CFG
midimonster: CFLAGS += -DDEFAULT_CFG=\"$(DEFAULT_CFG)\"
endif
ifdef PLUGINS
-midimonster: CFLAGS += -DPLUGINS=\"$(PLUGINS)\"
+core/core.o: CFLAGS += -DPLUGINS=\"$(PLUGINS)\"
PLUGIN_INSTALL = $(PLUGINS)
endif
diff --git a/README.md b/README.md
index f2c9518..638624e 100644
--- a/README.md
+++ b/README.md
@@ -300,12 +300,16 @@ for detailed information).
## Development
-The architecture is split into the `midimonster` core, handling mapping
-and resource management, and the backends, which are shared objects loaded
-at start time, which provide a protocol mapping to instances / channels.
+The architecture is split into the `core`, handling mapping and resource management,
+the `frontends` which handle how the core is invoked and presented (ie. command line or graphical interface),
+and the `backends`, which are shared objects loaded at start time providing a protocol mapping to instances / channels.
-The API and structures are more-or-less documented in [midimonster.h](midimonster.h),
-more detailed documentation may follow.
+There is a general [developer information document](DEVELOPMENT.md) that outlines basic guidelines for
+contribution. The [MIDIMonster knowledge base](https://kb.midimonster.net/) has a section on development,
+containing additional helpful information and tutorials.
+
+The backend API, lifecycle and structures are documented in [midimonster.h](midimonster.h), the
+frontend API and lifecycle in [core/core.h](core/core.h).
To build with `clang` sanitizers and even more warnings enabled, run `make sanitize`.
This is useful to check for common errors and oversights.
diff --git a/core/backend.c b/core/backend.c
index 83121bd..b7c2d9e 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"
@@ -54,7 +55,7 @@ int backends_handle(size_t nfds, managed_fd* fds){
DBGPF("Notifying backend %s of %" PRIsize_t " waiting FDs", registry.backends[u].name, n);
rv |= registry.backends[u].process(n, fds);
if(rv){
- fprintf(stderr, "Backend %s failed to handle input\n", registry.backends[u].name);
+ LOGPF("Backend %s failed to handle input", registry.backends[u].name);
}
}
}
@@ -99,7 +100,7 @@ MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create){
DBGPF("\tBucket %" PRIsize_t " entry %" PRIsize_t " inst %" PRIu64 " ident %" PRIu64, bucket, u, (uint64_t) channels.entry[bucket][u]->instance, channels.entry[bucket][u]->ident);
if(channels.entry[bucket][u]->instance == inst
&& channels.entry[bucket][u]->ident == ident){
- DBGPF("Requested channel %" PRIu64 " on instance %s already exists, reusing (bucket %" PRIsize_t ", %" PRIsize_t " search steps)\n", ident, inst->name, bucket, u);
+ DBGPF("Requested channel %" PRIu64 " on instance %s already exists, reusing (bucket %" PRIsize_t ", %" PRIsize_t " search steps)", ident, inst->name, bucket, u);
return channels.entry[bucket][u];
}
}
@@ -112,14 +113,14 @@ MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create){
DBGPF("Creating previously unknown channel %" PRIu64 " on instance %s, bucket %" PRIsize_t, ident, inst->name, bucket);
channels.entry[bucket] = realloc(channels.entry[bucket], (channels.n[bucket] + 1) * sizeof(channel*));
if(!channels.entry[bucket]){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
channels.n[bucket] = 0;
return NULL;
}
channels.entry[bucket][channels.n[bucket]] = calloc(1, sizeof(channel));
if(!channels.entry[bucket][channels.n[bucket]]){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
return NULL;
}
@@ -160,7 +161,7 @@ MM_API void mm_channel_update(channel* chan, uint64_t ident){
//add to new bucket
channels.entry[new_bucket] = realloc(channels.entry[new_bucket], (channels.n[new_bucket] + 1) * sizeof(channel*));
if(!channels.entry[new_bucket]){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
channels.n[new_bucket] = 0;
return;
}
@@ -183,14 +184,14 @@ instance* mm_instance(backend* b){
//extend
registry.instances[u] = realloc(registry.instances[u], (n + 2) * sizeof(instance*));
if(!registry.instances[u]){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
return NULL;
}
//sentinel
registry.instances[u][n + 1] = NULL;
registry.instances[u][n] = calloc(1, sizeof(instance));
if(!registry.instances[u][n]){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
}
registry.instances[u][n]->backend = b;
return registry.instances[u][n];
@@ -237,7 +238,7 @@ MM_API int mm_backend_instances(char* name, size_t* ninst, instance*** inst){
*inst = calloc(i, sizeof(instance*));
if(!*inst){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
return 1;
}
@@ -303,7 +304,7 @@ MM_API int mm_backend_register(backend b){
registry.backends = realloc(registry.backends, (registry.n + 1) * sizeof(backend));
registry.instances = realloc(registry.instances, (registry.n + 1) * sizeof(instance**));
if(!registry.backends || !registry.instances){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
registry.n = 0;
return 1;
}
@@ -311,7 +312,7 @@ MM_API int mm_backend_register(backend b){
registry.instances[registry.n] = NULL;
registry.n++;
- fprintf(stderr, "Registered backend %s\n", b.name);
+ LOGPF("Registered backend %s", b.name);
return 0;
}
return 1;
@@ -330,14 +331,14 @@ int backends_start(){
//fetch list of instances
if(mm_backend_instances(registry.backends[u].name, &n, &inst)){
- fprintf(stderr, "Failed to fetch instance list for initialization of backend %s\n", registry.backends[u].name);
+ LOGPF("Failed to fetch instance list for initialization of backend %s", registry.backends[u].name);
return 1;
}
//start the backend
current = registry.backends[u].start(n, inst);
if(current){
- fprintf(stderr, "Failed to start backend %s\n", registry.backends[u].name);
+ LOGPF("Failed to start backend %s", registry.backends[u].name);
}
//clean up
@@ -377,7 +378,7 @@ int backends_stop(){
for(u = 0; u < registry.n; u++){
//fetch list of instances
if(mm_backend_instances(registry.backends[u].name, &n, &inst)){
- fprintf(stderr, "Failed to fetch instance list for shutdown of backend %s\n", registry.backends[u].name);
+ LOGPF("Failed to fetch instance list for shutdown of backend %s", registry.backends[u].name);
inst = NULL;
n = 0;
}
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.c b/core/config.c
index c1c3124..b950b25 100644
--- a/core/config.c
+++ b/core/config.c
@@ -3,7 +3,10 @@
#include <unistd.h>
#include <errno.h>
#ifndef _WIN32
-#include <limits.h>
+ #include <limits.h>
+ #define MM_API __attribute__((visibility ("default")))
+#else
+ #define MM_API __attribute__((dllexport))
#endif
#define BACKEND_NAME "core/cfg"
@@ -45,7 +48,7 @@ static ssize_t getline(char** line, size_t* alloc, FILE* stream){
*alloc = GETLINE_BUFFER;
*line = calloc(GETLINE_BUFFER, sizeof(char));
if(!*line){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
return -1;
}
}
@@ -60,7 +63,7 @@ static ssize_t getline(char** line, size_t* alloc, FILE* stream){
*alloc += GETLINE_BUFFER;
*line = realloc(*line, (*alloc) * sizeof(char));
if(!*line){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
return -1;
}
}
@@ -175,13 +178,13 @@ static int config_glob_scan(instance* inst, channel_spec* spec){
for(glob_start = strchr(glob_start, '{'); glob_start; glob_start = strchr(glob_start, '{')){
glob_end = strchr(glob_start, '}');
if(!glob_end){
- fprintf(stderr, "Failed to parse channel spec, unterminated glob: %s\n", spec->spec);
+ LOGPF("Failed to parse channel spec, unterminated glob: %s", spec->spec);
return 1;
}
spec->glob = realloc(spec->glob, (spec->globs + 1) * sizeof(channel_glob));
if(!spec->glob){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
return 1;
}
@@ -205,7 +208,7 @@ static int config_glob_scan(instance* inst, channel_spec* spec){
}
if(!spec->internal){
//TODO try to parse globs externally
- fprintf(stderr, "Failed to parse glob %" PRIsize_t " in %s internally\n", u + 1, spec->spec);
+ LOGPF("Failed to parse glob %" PRIsize_t " in %s internally", u + 1, spec->spec);
return 1;
}
@@ -322,7 +325,7 @@ static int config_map(char* to_raw, char* from_raw){
if(!from || !to){
free(from);
free(to);
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
return 1;
}
@@ -334,7 +337,7 @@ static int config_map(char* to_raw, char* from_raw){
}
if(!spec_from.spec[0] || !spec_to.spec[0]){
- fprintf(stderr, "Mapping does not contain a proper instance specification\n");
+ LOG("Mapping does not contain a proper instance specification");
goto done;
}
@@ -348,7 +351,7 @@ static int config_map(char* to_raw, char* from_raw){
instance_from = instance_match(from);
if(!instance_to || !instance_from){
- fprintf(stderr, "No such instance %s\n", instance_from ? to : from);
+ LOGPF("No such instance %s", instance_from ? to : from);
goto done;
}
@@ -361,7 +364,7 @@ static int config_map(char* to_raw, char* from_raw){
if((spec_to.channels != spec_from.channels && spec_from.channels != 1 && spec_to.channels != 1)
|| spec_to.channels == 0
|| spec_from.channels == 0){
- fprintf(stderr, "Multi-channel specification size mismatch: %s.%s (%" PRIsize_t " channels) - %s.%s (%" PRIsize_t " channels)\n",
+ LOGPF("Multi-channel specification size mismatch: %s.%s (%" PRIsize_t " channels) - %s.%s (%" PRIsize_t " channels)",
instance_from->name,
spec_from.spec,
spec_from.channels,
@@ -410,7 +413,7 @@ static int config_line(char* line){
current_backend = backend_match(line + 9);
if(!current_backend){
- fprintf(stderr, "Cannot configure unknown backend %s\n", line + 9);
+ LOGPF("Cannot configure unknown backend %s", line + 9);
return 1;
}
@@ -419,7 +422,7 @@ static int config_line(char* line){
if(!overrides[u].handled && overrides[u].type == override_backend
&& !strcmp(overrides[u].target, current_backend->name)){
if(current_backend->conf(overrides[u].option, overrides[u].value)){
- fprintf(stderr, "Configuration override for %s failed for backend %s\n",
+ LOGPF("Configuration override for %s failed for backend %s",
overrides[u].option, current_backend->name);
return 1;
}
@@ -447,7 +450,7 @@ static int config_line(char* line){
for(separator = line; *separator && *separator != ' '; separator++){
}
if(!*separator){
- fprintf(stderr, "No instance name specified for backend %s\n", line);
+ LOGPF("No instance name specified for backend %s", line);
return 1;
}
*separator = 0;
@@ -455,18 +458,18 @@ static int config_line(char* line){
current_backend = backend_match(line);
if(!current_backend){
- fprintf(stderr, "No such backend %s\n", line);
+ LOGPF("No such backend %s", line);
return 1;
}
if(instance_match(separator)){
- fprintf(stderr, "Duplicate instance name %s\n", separator);
+ LOGPF("Duplicate instance name %s", separator);
return 1;
}
//validate instance name
if(strchr(separator, ' ') || strchr(separator, '.')){
- fprintf(stderr, "Invalid instance name %s\n", separator);
+ LOGPF("Invalid instance name %s", separator);
return 1;
}
@@ -476,20 +479,20 @@ static int config_line(char* line){
}
if(current_backend->create(current_instance)){
- fprintf(stderr, "Failed to create %s instance %s\n", line, separator);
+ LOGPF("Failed to create %s instance %s", line, separator);
return 1;
}
current_instance->name = strdup(separator);
current_instance->backend = current_backend;
- fprintf(stderr, "Created %s instance %s\n", line, separator);
+ LOGPF("Created %s instance %s", line, separator);
//apply overrides
for(u = 0; u < noverrides; u++){
if(!overrides[u].handled && overrides[u].type == override_instance
&& !strcmp(overrides[u].target, current_instance->name)){
if(current_backend->conf_instance(current_instance, overrides[u].option, overrides[u].value)){
- fprintf(stderr, "Configuration override for %s failed for instance %s\n",
+ LOGPF("Configuration override for %s failed for instance %s",
overrides[u].option, current_instance->name);
return 1;
}
@@ -514,7 +517,7 @@ static int config_line(char* line){
break;
case 0:
default:
- fprintf(stderr, "Not a channel mapping: %s\n", line);
+ LOGPF("Not a channel mapping: %s", line);
return 1;
}
@@ -529,13 +532,13 @@ static int config_line(char* line){
if(mapping_type == map_ltr || mapping_type == map_bidir){
if(config_map(separator, line)){
- fprintf(stderr, "Failed to map channel %s to %s\n", line, separator);
+ LOGPF("Failed to map channel %s to %s", line, separator);
return 1;
}
}
if(mapping_type == map_rtl || mapping_type == map_bidir){
if(config_map(line, separator)){
- fprintf(stderr, "Failed to map channel %s to %s\n", separator, line);
+ LOGPF("Failed to map channel %s to %s", separator, line);
return 1;
}
}
@@ -545,7 +548,7 @@ static int config_line(char* line){
//find separator
separator = strchr(line, '=');
if(!separator){
- fprintf(stderr, "Not an assignment (currently expecting %s configuration): %s\n", line, (parser_state == backend_cfg) ? "backend" : "instance");
+ LOGPF("Not an assignment (currently expecting %s configuration): %s", line, (parser_state == backend_cfg) ? "backend" : "instance");
return 1;
}
@@ -555,11 +558,11 @@ static int config_line(char* line){
separator = config_trim_line(separator);
if(parser_state == backend_cfg && current_backend->conf(line, separator)){
- fprintf(stderr, "Failed to configure backend %s\n", current_backend->name);
+ LOGPF("Failed to configure backend %s", current_backend->name);
return 1;
}
else if(parser_state == instance_cfg && current_backend->conf_instance(current_instance, line, separator)){
- fprintf(stderr, "Failed to configure instance %s\n", current_instance->name);
+ LOGPF("Failed to configure instance %s", current_instance->name);
return 1;
}
}
@@ -583,7 +586,7 @@ int config_read(char* cfg_filepath){
#endif
if(!source_dir){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
return 1;
}
@@ -594,12 +597,12 @@ int config_read(char* cfg_filepath){
source_file++;
if(!getcwd(original_dir, sizeof(original_dir))){
- fprintf(stderr, "Failed to read current working directory: %s\n", strerror(errno));
+ LOGPF("Failed to read current working directory: %s", strerror(errno));
goto bail;
}
if(chdir(source_dir)){
- fprintf(stderr, "Failed to change to configuration file directory %s: %s\n", source_dir, strerror(errno));
+ LOGPF("Failed to change to configuration file directory %s: %s", source_dir, strerror(errno));
goto bail;
}
}
@@ -607,11 +610,11 @@ int config_read(char* cfg_filepath){
source_file = source_dir;
}
- fprintf(stderr, "Reading configuration file %s\n", cfg_filepath);
+ LOGPF("Reading configuration file %s", cfg_filepath);
source = fopen(source_file, "r");
if(!source){
- fprintf(stderr, "Failed to open %s for reading\n", cfg_filepath);
+ LOGPF("Failed to open %s for reading", cfg_filepath);
goto bail;
}
@@ -644,7 +647,7 @@ int config_add_override(override_type type, char* data_raw){
char* data = strdup(data_raw);
if(!data){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
goto bail;
}
@@ -652,7 +655,7 @@ int config_add_override(override_type type, char* data_raw){
char* value = strchr(data, '=');
if(!option || !value){
- fprintf(stderr, "Override %s is not a valid assignment\n", data_raw);
+ LOGPF("Override %s is not a valid assignment", data_raw);
goto bail;
}
@@ -672,14 +675,14 @@ int config_add_override(override_type type, char* data_raw){
};
if(!new.target || !new.option || !new.value){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
goto bail;
}
overrides = realloc(overrides, (noverrides + 1) * sizeof(config_override));
if(!overrides){
noverrides = 0;
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
goto bail;
}
overrides[noverrides] = new;
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..804ec4e
--- /dev/null
+++ b/core/core.c
@@ -0,0 +1,258 @@
+#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
+ #include <fcntl.h>
+ #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)){
+ LOGPF("Failed to update global timestamp, time-based processing for some backends may be impaired: %s", 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){
+ LOGPF("Unknown backend %s registered for managed fd", 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){
+ LOG("Failed to allocate memory");
+ return 1;
+ }
+
+ signaled_fds = realloc(signaled_fds, (fds + 1) * sizeof(managed_fd));
+ if(!signaled_fds){
+ LOG("Failed to allocate memory");
+ 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
+ LOGPF("select failed: %s", 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);
+ LOGPF("select failed: %s", 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", n);
+ 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.c b/core/plugin.c
index e7d8eba..cd85059 100644
--- a/core/plugin.c
+++ b/core/plugin.c
@@ -7,14 +7,18 @@
#include <dirent.h>
#include "portability.h"
#ifdef _WIN32
-#define dlclose FreeLibrary
-#define dlsym GetProcAddress
-#define dlerror() "Failed"
-#define dlopen(lib,ig) LoadLibrary(lib)
+ #define dlclose FreeLibrary
+ #define dlsym GetProcAddress
+ #define dlerror() "Failed"
+ #define dlopen(lib,ig) LoadLibrary(lib)
+ #define MM_API __attribute__((dllexport))
#else
-#include <dlfcn.h>
+ #include <dlfcn.h>
+ #define MM_API __attribute__((visibility ("default")))
#endif
+#define BACKEND_NAME "core/pl"
+#include "midimonster.h"
#include "plugin.h"
static size_t plugins = 0;
@@ -31,13 +35,13 @@ static int plugin_attach(char* path, char* file){
#endif
if(!path || !file || !strlen(path)){
- fprintf(stderr, "Invalid plugin loader path\n");
+ LOG("Invalid plugin loader path");
return 1;
}
lib = calloc(strlen(path) + strlen(file) + 2, sizeof(char));
if(!lib){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
return 1;
}
snprintf(lib, strlen(path) + strlen(file) + 2, "%s%s%s",
@@ -51,10 +55,10 @@ static int plugin_attach(char* path, char* file){
char* error = NULL;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &error, 0, NULL);
- fprintf(stderr, "Failed to load plugin %s, check that all supporting libraries are present: %s\n", lib, error);
+ LOGPF("Failed to load plugin %s, check that all supporting libraries are present: %s", lib, error);
LocalFree(error);
#else
- fprintf(stderr, "Failed to load plugin %s: %s\n", lib, dlerror());
+ LOGPF("Failed to load plugin %s: %s", lib, dlerror());
#endif
free(lib);
return 0;
@@ -63,7 +67,7 @@ static int plugin_attach(char* path, char* file){
init = (plugin_init) dlsym(handle, "init");
if(init){
if(init()){
- fprintf(stderr, "Plugin %s failed to initialize\n", lib);
+ LOGPF("Plugin %s failed to initialize", lib);
dlclose(handle);
free(lib);
return 1;
@@ -78,7 +82,7 @@ static int plugin_attach(char* path, char* file){
plugin_handle = realloc(plugin_handle, (plugins + 1) * sizeof(void*));
if(!plugin_handle){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
dlclose(handle);
return 1;
}
@@ -95,7 +99,7 @@ int plugins_load(char* path){
#ifdef _WIN32
char* search_expression = calloc(strlen(path) + strlen("*.dll") + 1, sizeof(char));
if(!search_expression){
- fprintf(stderr, "Failed to allocate memory\n");
+ LOG("Failed to allocate memory");
return -1;
}
snprintf(search_expression, strlen(path) + strlen("*.dll"), "%s*.dll", path);
@@ -107,7 +111,7 @@ int plugins_load(char* path){
LPVOID lpMsgBuf = NULL;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &lpMsgBuf, 0, NULL);
- fprintf(stderr, "Failed to search for backend plugin files in %s: %s\n", path, lpMsgBuf);
+ LOGPF("Failed to search for backend plugin files in %s: %s", path, lpMsgBuf);
LocalFree(lpMsgBuf);
return -1;
}
@@ -128,7 +132,7 @@ load_done:
struct stat file_stat;
DIR* directory = opendir(path);
if(!directory){
- fprintf(stderr, "Failed to open plugin search path %s: %s\n", path, strerror(errno));
+ LOGPF("Failed to open plugin search path %s: %s", path, strerror(errno));
return 1;
}
@@ -138,7 +142,7 @@ load_done:
}
if(fstatat(dirfd(directory), entry->d_name, &file_stat, 0) < 0){
- fprintf(stderr, "Failed to stat %s: %s\n", entry->d_name, strerror(errno));
+ LOGPF("Failed to stat %s: %s", entry->d_name, strerror(errno));
continue;
}
@@ -154,7 +158,7 @@ load_done:
load_done:
if(closedir(directory) < 0){
- fprintf(stderr, "Failed to close plugin directory %s: %s\n", path, strerror(errno));
+ LOGPF("Failed to close plugin directory %s: %s", path, strerror(errno));
return -1;
}
return rv;
@@ -171,12 +175,12 @@ int plugins_close(){
if(!FreeLibrary(plugin_handle[u])){
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR) &error, 0, NULL);
- fprintf(stderr, "Failed to unload plugin: %s\n", error);
+ LOGPF("Failed to unload plugin: %s", error);
LocalFree(error);
}
#else
if(dlclose(plugin_handle[u])){
- fprintf(stderr, "Failed to unload plugin: %s\n", dlerror());
+ LOGPF("Failed to unload plugin: %s", dlerror());
}
#endif
}
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..284569a
--- /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;
+ LOG("Failed to allocate memory");
+ 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){
+ LOG("Failed to allocate memory");
+ 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){
+ LOG("Failed to allocate memory");
+ 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)){
+ LOG("Backends failed to handle output");
+ 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);
+
diff --git a/midimonster.c b/midimonster.c
index b73eeff..d00f116 100644
--- a/midimonster.c
+++ b/midimonster.c
@@ -1,247 +1,31 @@
#include <string.h>
#include <signal.h>
-#include <unistd.h>
-#include <errno.h>
-#include <time.h>
+#include <stdarg.h>
#ifndef _WIN32
- #include <sys/select.h>
#define MM_API __attribute__((visibility("default")))
#else
#define MM_API __attribute__((dllexport))
#endif
-#define BACKEND_NAME "core"
-#define MM_SWAP_LIMIT 20
+
+#define BACKEND_NAME "cli"
#include "midimonster.h"
+#include "core/core.h"
#include "core/config.h"
-#include "core/backend.h"
-#include "core/plugin.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 fds = 0;
-static managed_fd* fd = NULL;
-static volatile sig_atomic_t fd_set_dirty = 1;
-static uint64_t global_timestamp = 0;
volatile static sig_atomic_t shutdown_requested = 0;
-static void signal_handler(int signum){
- shutdown_requested = 1;
-}
-
-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;
-}
-
-MM_API uint64_t mm_timestamp(){
- return global_timestamp;
-}
-
-static void update_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
-}
-
-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;
-}
-
-static 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;
- }
-}
-
-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;
- }
- fds++;
- }
-
- //store new fd
- fd[u].fd = new_fd;
- fd[u].backend = b;
- fd[u].impl = impl;
- fd_set_dirty = 1;
- return 0;
-}
-
-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;
+MM_API int log_printf(int level, char* module, char* fmt, ...){
+ int rv = 0;
+ va_list args;
+ va_start(args, fmt);
+ fprintf(stderr, "%s%s\t", level ? "debug/" : "", module);
+ rv = vfprintf(stderr, fmt, args);
+ va_end(args);
+ return rv;
}
-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;
+static void signal_handler(int signum){
+ shutdown_requested = 1;
}
static void version(){
@@ -255,36 +39,9 @@ static int usage(char* fn){
return EXIT_FAILURE;
}
-static fd_set fds_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;
-}
static int platform_initialize(){
#ifdef _WIN32
- WSADATA wsa;
- WORD version = MAKEWORD(2, 2);
- if(WSAStartup(version, &wsa)){
- return 1;
- }
-
unsigned error_mode = SetErrorMode(0);
SetErrorMode(error_mode | SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
#endif
@@ -341,131 +98,9 @@ static int args_parse(int argc, char** argv, char** cfg_file){
return 0;
}
-static int core_process(size_t nfds, managed_fd* signaled_fds){
- event_collection* secondary = NULL;
- size_t u, swaps = 0;
-
- //run backend processing, collect events
- DBGPF("%" PRIsize_t " backend FDs signaled", nfds);
- if(backends_handle(nfds, signaled_fds)){
- return 1;
- }
-
- //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;
-}
-
-static int core_loop(){
- fd_set all_fds, read_fds;
- managed_fd* signaled_fds = NULL;
- struct timeval tv;
- int error, maxfd = -1;
- size_t n, u;
- #ifdef _WIN32
- char* error_message = NULL;
- #else
- struct timespec ts;
- #endif
-
- FD_ZERO(&all_fds);
-
- //process events
- while(!shutdown_requested){
- //rebuild fd set if necessary
- if(fd_set_dirty || !signaled_fds){
- all_fds = fds_collect(&maxfd);
- signaled_fds = realloc(signaled_fds, fds * sizeof(managed_fd));
- if(!signaled_fds){
- fprintf(stderr, "Failed to allocate memory\n");
- return 1;
- }
- 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(maxfd >= 0){
- error = select(maxfd + 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
- free(signaled_fds);
- 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
- update_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++;
- }
- }
-
- //fetch and process events
- if(core_process(n, signaled_fds)){
- free(signaled_fds);
- return 1;
- }
- }
-
- free(signaled_fds);
- return 0;
-}
-
int main(int argc, char** argv){
int rv = EXIT_FAILURE;
char* cfg_file = DEFAULT_CFG;
- size_t u, n = 0, max = 0;
//parse commandline arguments
if(args_parse(argc, argv, &cfg_file)){
@@ -479,57 +114,33 @@ int main(int argc, char** argv){
}
//initialize backends
- if(plugins_load(PLUGINS)){
- fprintf(stderr, "Failed to initialize a backend\n");
+ if(core_initialize()){
goto bail;
}
//read config
if(config_read(cfg_file)){
fprintf(stderr, "Failed to parse master configuration file %s\n", cfg_file);
- backends_stop();
- routing_cleanup();
- fds_free();
- plugins_close();
- config_free();
+ core_shutdown();
return (usage(argv[0]) | platform_shutdown());
}
- //load an initial timestamp
- update_timestamp();
-
- //start backends
- if(backends_start()){
+ //start core
+ if(core_start()){
goto bail;
}
signal(SIGINT, signal_handler);
- //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);
-
- if(!fds){
- fprintf(stderr, "No descriptors registered for multiplexing\n");
- }
-
//run the core loop
- if(!core_loop()){
- rv = EXIT_SUCCESS;
+ while(!shutdown_requested){
+ if(core_iteration()){
+ goto bail;
+ }
}
+ rv = EXIT_SUCCESS;
bail:
- //free all data
- backends_stop();
- routing_cleanup();
- fds_free();
- plugins_close();
- config_free();
- platform_shutdown();
-
+ core_shutdown();
return rv;
}
diff --git a/midimonster.h b/midimonster.h
index 64aa1e5..15ce34a 100644
--- a/midimonster.h
+++ b/midimonster.h
@@ -40,16 +40,19 @@
/* Clamp a value to a range */
#define clamp(val,max,min) (((val) > (max)) ? (max) : (((val) < (min)) ? (min) : (val)))
+/* Log function prototype - do not use directly. Use the LOG/LOGPF/DBGPF macros below instead */
+MM_API __attribute__((format(printf, 3, 4))) int log_printf(int level, char* module, char* fmt, ...);
+
/* Debug messages only compile in when DEBUG is set */
#ifdef DEBUG
- #define DBGPF(format, ...) fprintf(stderr, "debug/%s\t" format "\n", (BACKEND_NAME), __VA_ARGS__)
+ #define DBGPF(format, ...) log_printf(1, (BACKEND_NAME), format "\n", __VA_ARGS__)
#else
#define DBGPF(format, ...)
#endif
/* Log messages should be routed through these macros to ensure interoperability with different core implementations */
-#define LOGPF(format, ...) fprintf(stderr, "%s\t" format "\n", (BACKEND_NAME), __VA_ARGS__)
-#define LOG(message) fprintf(stderr, "%s\t%s\n", (BACKEND_NAME), (message))
+#define LOGPF(format, ...) log_printf(0, (BACKEND_NAME), format "\n", __VA_ARGS__)
+#define LOG(message) log_printf(0, (BACKEND_NAME), message "\n")
/* Stop compilation if the build system reports an error */
#ifdef BUILD_ERROR