aboutsummaryrefslogtreecommitdiffhomepage
path: root/midimonster.c
diff options
context:
space:
mode:
Diffstat (limited to 'midimonster.c')
-rw-r--r--midimonster.c389
1 files changed, 253 insertions, 136 deletions
diff --git a/midimonster.c b/midimonster.c
index 2ec165b..5109eab 100644
--- a/midimonster.c
+++ b/midimonster.c
@@ -9,11 +9,13 @@
#else
#define MM_API __attribute__((dllexport))
#endif
+#define BACKEND_NAME "core"
#include "midimonster.h"
#include "config.h"
#include "backend.h"
#include "plugin.h"
+/* Core-internal structures */
typedef struct /*_event_collection*/ {
size_t alloc;
size_t n;
@@ -21,25 +23,40 @@ typedef struct /*_event_collection*/ {
channel_value* value;
} event_collection;
-static size_t mappings = 0;
-static channel_mapping* map = NULL;
+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;
-static event_collection event_pool[2] = {
- {0},
- {0}
-};
-static event_collection* primary = event_pool;
-
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;
}
@@ -59,53 +76,66 @@ static void update_timestamp(){
}
int mm_map_channel(channel* from, channel* to){
- size_t u, m;
+ size_t u, m, bucket = routing_hash(from);
+
//find existing source mapping
- for(u = 0; u < mappings; u++){
- if(map[u].from == from){
+ for(u = 0; u < routing.entries[bucket]; u++){
+ if(routing.map[bucket][u].from == from){
break;
}
}
//create new entry
- if(u == mappings){
- map = realloc(map, (mappings + 1) * sizeof(channel_mapping));
- if(!map){
+ 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(map + mappings, 0, sizeof(channel_mapping));
- mappings++;
- map[u].from = from;
+
+ 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 < map[u].destinations; m++){
- if(map[u].to[m] == to){
+ for(m = 0; m < routing.map[bucket][u].destinations; m++){
+ if(routing.map[bucket][u].to[m] == to){
return 0;
}
}
- map[u].to = realloc(map[u].to, (map[u].destinations + 1) * sizeof(channel*));
- if(!map[u].to){
+ //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");
- map[u].destinations = 0;
+ routing.map[bucket][u].destinations = 0;
return 1;
}
- map[u].to[map[u].destinations] = to;
- map[u].destinations++;
+ routing.map[bucket][u].to[routing.map[bucket][u].destinations] = to;
+ routing.map[bucket][u].destinations++;
return 0;
}
-static void map_free(){
- size_t u;
- for(u = 0; u < mappings; u++){
- free(map[u].to);
+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;
}
- free(map);
- mappings = 0;
- map = NULL;
}
MM_API int mm_manage_fd(int new_fd, char* back, int manage, void* impl){
@@ -120,6 +150,7 @@ MM_API int mm_manage_fd(int new_fd, char* back, int manage, void* impl){
//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;
@@ -161,7 +192,6 @@ MM_API int mm_manage_fd(int new_fd, char* back, int manage, void* impl){
static void fds_free(){
size_t u;
for(u = 0; u < fds; u++){
- //TODO free impl
if(fd[u].fd >= 0){
close(fd[u].fd);
fd[u].fd = -1;
@@ -173,56 +203,46 @@ static void fds_free(){
}
MM_API int mm_channel_event(channel* c, channel_value v){
- size_t u, p;
+ size_t u, p, bucket = routing_hash(c);
//find mapped channels
- for(u = 0; u < mappings; u++){
- if(map[u].from == c){
+ for(u = 0; u < routing.entries[bucket]; u++){
+ if(routing.map[bucket][u].from == c){
break;
}
}
- if(u == mappings){
+ if(u == routing.entries[bucket]){
//target-only channel
return 0;
}
//resize event structures to fit additional events
- if(primary->n + map[u].destinations >= primary->alloc){
- primary->channel = realloc(primary->channel, (primary->alloc + map[u].destinations) * sizeof(channel*));
- primary->value = realloc(primary->value, (primary->alloc + map[u].destinations) * sizeof(channel_value));
+ 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(!primary->channel || !primary->value){
+ if(!routing.events->channel || !routing.events->value){
fprintf(stderr, "Failed to allocate memory\n");
- primary->alloc = 0;
- primary->n = 0;
+ routing.events->alloc = 0;
+ routing.events->n = 0;
return 1;
}
- primary->alloc += map[u].destinations;
+ 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
- for(p = 0; p < map[u].destinations; p++){
- primary->channel[primary->n + p] = map[u].to[p];
- primary->value[primary->n + p] = v;
+ 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;
}
- primary->n += map[u].destinations;
+ routing.events->n += routing.map[bucket][u].destinations;
return 0;
}
-static void event_free(){
- size_t u;
-
- for(u = 0; u < sizeof(event_pool) / sizeof(event_collection); u++){
- free(event_pool[u].channel);
- free(event_pool[u].value);
- event_pool[u].alloc = 0;
- }
-}
-
static void version(){
printf("MIDIMonster %s\n", MIDIMONSTER_VERSION);
}
@@ -257,13 +277,27 @@ static fd_set fds_collect(int* max_fd){
}
static int platform_initialize(){
-#ifdef _WIN32
+ #ifdef _WIN32
WSADATA wsa;
WORD version = MAKEWORD(2, 2);
if(WSAStartup(version, &wsa)){
return 1;
}
-#endif
+ #endif
+ return 0;
+}
+
+static int platform_shutdown(){
+ #ifdef _WIN32
+ DWORD processes;
+ if(GetConsoleProcessList(&processes, 1) == 1){
+ fprintf(stderr, "\nMIDIMonster is the last process in this console, please press any key to exit\n");
+ HANDLE input = GetStdHandle(STD_INPUT_HANDLE);
+ SetConsoleMode(input, 0);
+ FlushConsoleInputBuffer(input);
+ WaitForSingleObject(input, INFINITE);
+ }
+ #endif
return 0;
}
@@ -274,22 +308,155 @@ static int args_parse(int argc, char** argv, char** cfg_file){
version();
return 1;
}
+ else if(!strcmp(argv[u], "-i")){
+ if(!argv[u + 1]){
+ fprintf(stderr, "Missing instance override specification\n");
+ return 1;
+ }
+ if(config_add_override(override_instance, argv[u + 1])){
+ return 1;
+ }
+ u++;
+ }
+ else if(!strcmp(argv[u], "-b")){
+ if(!argv[u + 1]){
+ fprintf(stderr, "Missing backend override specification\n");
+ return 1;
+ }
+ if(config_add_override(override_backend, argv[u + 1])){
+ return 1;
+ }
+ u++;
+ }
+ else{
+ //if nothing else matches, it's probably the configuration file
+ *cfg_file = argv[u];
+ }
+ }
- //if nothing else matches, it's probably the configuration file
- *cfg_file = argv[u];
+ return 0;
+}
+
+static int core_process(size_t nfds, managed_fd* signaled_fds){
+ event_collection* secondary = NULL;
+ size_t u;
+
+ //run backend processing, collect events
+ DBGPF("%lu backend FDs signaled\n", nfds);
+ if(backends_handle(nfds, signaled_fds)){
+ return 1;
+ }
+
+ while(routing.events->n){
+ //swap primary and secondary event collectors
+ DBGPF("Swapping event collectors, %lu events in primary\n", 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;
}
return 0;
}
-int main(int argc, char** argv){
+static int core_loop(){
fd_set all_fds, read_fds;
- event_collection* secondary = NULL;
- struct timeval tv;
- size_t u, n;
managed_fd* signaled_fds = NULL;
- int rv = EXIT_FAILURE, error, maxfd = -1;
+ 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)){
@@ -301,7 +468,6 @@ int main(int argc, char** argv){
return EXIT_FAILURE;
}
- FD_ZERO(&all_fds);
//initialize backends
if(plugins_load(PLUGINS)){
fprintf(stderr, "Failed to initialize a backend\n");
@@ -312,14 +478,13 @@ int main(int argc, char** argv){
if(config_read(cfg_file)){
fprintf(stderr, "Failed to read configuration file %s\n", cfg_file);
backends_stop();
- channels_free();
- instances_free();
- map_free();
+ routing_cleanup();
fds_free();
plugins_close();
- return usage(argv[0]);
+ config_free();
+ return (usage(argv[0]) | platform_shutdown());
}
-
+
//load an initial timestamp
update_timestamp();
@@ -330,79 +495,31 @@ int main(int argc, char** argv){
signal(SIGINT, signal_handler);
- //process events
- while(!shutdown_requested){
- //rebuild fd set if necessary
- if(fd_set_dirty){
- all_fds = fds_collect(&maxfd);
- signaled_fds = realloc(signaled_fds, fds * sizeof(managed_fd));
- if(!signaled_fds){
- fprintf(stderr, "Failed to allocate memory\n");
- goto bail;
- }
- fd_set_dirty = 0;
- }
-
- //wait for & translate events
- read_fds = all_fds;
- tv = backend_timeout();
- error = select(maxfd + 1, &read_fds, NULL, NULL, &tv);
- if(error < 0){
- fprintf(stderr, "select failed: %s\n", strerror(errno));
- break;
- }
-
- //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++;
- }
- }
-
- //update this iteration's timestamp
- update_timestamp();
-
- //run backend processing, collect events
- DBGPF("%lu backend FDs signaled\n", n);
- if(backends_handle(n, signaled_fds)){
- goto bail;
- }
-
- while(primary->n){
- //swap primary and secondary event collectors
- DBGPF("Swapping event collectors, %lu events in primary\n", primary->n);
- for(u = 0; u < sizeof(event_pool) / sizeof(event_collection); u++){
- if(primary != event_pool + u){
- secondary = primary;
- primary = event_pool + u;
- break;
- }
- }
+ //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);
- //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");
- goto bail;
- }
+ if(!fds){
+ fprintf(stderr, "No descriptors registered for multiplexing\n");
+ }
- //reset the event count
- secondary->n = 0;
- }
+ //run the core loop
+ if(!core_loop()){
+ rv = EXIT_SUCCESS;
}
- rv = EXIT_SUCCESS;
bail:
//free all data
- free(signaled_fds);
backends_stop();
- channels_free();
- instances_free();
- map_free();
+ routing_cleanup();
fds_free();
- event_free();
plugins_close();
+ config_free();
+ platform_shutdown();
return rv;
}