From 9718e10c7f4151cea895f515c785c14e0021d967 Mon Sep 17 00:00:00 2001 From: cbdev Date: Wed, 18 Mar 2020 22:36:35 +0100 Subject: Implement default channel handlers for Lua/Python --- backends/lua.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index e7ba9f9..498a037 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -308,6 +308,11 @@ static int lua_configure_instance(instance* inst, char* option, char* value){ } return 0; } + else if(!strcmp(option, "default-handler")){ + free(data->default_handler); + data->default_handler = strdup(value); + return 0; + } LOGPF("Unknown instance configuration parameter %s for instance %s", option, inst->name); return 1; @@ -461,7 +466,8 @@ static int lua_start(size_t n, instance** inst){ data = (lua_instance_data*) inst[u]->impl; for(p = 0; p < data->channels; p++){ //exclude reserved names - if(strcmp(data->channel_name[p], "output") + if(!data->default_handler + && strcmp(data->channel_name[p], "output") && strcmp(data->channel_name[p], "input_value") && strcmp(data->channel_name[p], "output_value") && strcmp(data->channel_name[p], "input_channel") @@ -473,6 +479,14 @@ static int lua_start(size_t n, instance** inst){ data->reference[p] = LUA_NOREF; } } + else if(data->default_handler){ + lua_getglobal(data->interpreter, data->default_handler); + data->reference[p] = luaL_ref(data->interpreter, LUA_REGISTRYINDEX); + if(data->reference[p] == LUA_REFNIL){ + data->reference[p] = LUA_NOREF; + LOGPF("Failed to resolve default handler function %s on instance %s", data->default_handler, inst[u]->name); + } + } } } @@ -504,6 +518,7 @@ static int lua_shutdown(size_t n, instance** inst){ free(data->reference); free(data->input); free(data->output); + free(data->default_handler); free(inst[u]->impl); } -- cgit v1.2.3 From 2d66d5cee9bf3ed5779f65d8a99b40ee5181bf30 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 20 Mar 2020 21:50:33 +0100 Subject: Implement Lua threading --- backends/lua.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- backends/lua.h | 7 ++++ backends/lua.md | 30 ++++++++++++--- 3 files changed, 144 insertions(+), 7 deletions(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index 498a037..b66a27a 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -10,6 +10,7 @@ #define LUA_REGISTRY_KEY "_midimonster_lua_instance" #define LUA_REGISTRY_CURRENT_CHANNEL "_midimonster_lua_channel" +#define LUA_REGISTRY_CURRENT_THREAD "_midimonster_lua_thread" static size_t timers = 0; static lua_timer* timer = NULL; @@ -20,6 +21,9 @@ static int timer_fd = -1; static uint64_t last_timestamp; #endif +static size_t threads = 0; +static lua_thread* thread = NULL; + MM_PLUGIN_API int init(){ backend lua = { #ifndef MMBACKEND_LUA_TIMERFD @@ -86,6 +90,12 @@ static int lua_update_timerfd(){ interval = timer[n].interval; } } + + for(n = 0; n < threads; n++){ + if(thread[n].timeout && (!interval || thread[n].timeout < interval)){ + interval = thread[n].timeout; + } + } DBGPF("Recalculating timers, minimum is %" PRIu64, interval); //calculate gcd of all timers if any are active @@ -100,7 +110,8 @@ static int lua_update_timerfd(){ gcd = residual; } //since we round everything, 10 is the lowest interval we get - if(interval == 10){ + if(interval <= 10){ + interval = 10; break; } } @@ -126,6 +137,89 @@ static int lua_update_timerfd(){ return 0; } +static void lua_thread_resume(size_t current_thread){ + //push coroutine reference + lua_pushstring(thread[current_thread].thread, LUA_REGISTRY_CURRENT_THREAD); + lua_pushnumber(thread[current_thread].thread, current_thread); + lua_settable(thread[current_thread].thread, LUA_REGISTRYINDEX); + + //call thread main + DBGPF("Resuming thread %" PRIsize_t " on %s", current_thread, thread[current_thread].instance->name); + if(lua_resume(thread[current_thread].thread, NULL, 0) != LUA_YIELD){ + DBGPF("Thread %" PRIsize_t " on %s terminated", current_thread, thread[current_thread].instance->name); + thread[current_thread].timeout = 0; + } + + //remove coroutine reference + lua_pushstring(thread[current_thread].thread, LUA_REGISTRY_CURRENT_THREAD); + lua_pushnil(thread[current_thread].thread); + lua_settable(thread[current_thread].thread, LUA_REGISTRYINDEX); +} + +static int lua_callback_thread(lua_State* interpreter){ + instance* inst = NULL; + size_t u = threads; + if(lua_gettop(interpreter) != 1){ + LOGPF("Thread function called with %d arguments, expected function", lua_gettop(interpreter)); + return 0; + } + + luaL_checktype(interpreter, 1, LUA_TFUNCTION); + + //get instance pointer from registry + lua_pushstring(interpreter, LUA_REGISTRY_KEY); + lua_gettable(interpreter, LUA_REGISTRYINDEX); + inst = (instance*) lua_touserdata(interpreter, -1); + + //make space for a new thread + thread = realloc(thread, (threads + 1) * sizeof(lua_thread)); + if(!thread){ + threads = 0; + LOG("Failed to allocate memory"); + return 0; + } + threads++; + + thread[u].thread = lua_newthread(interpreter); + thread[u].instance = inst; + thread[u].timeout = 0; + thread[u].reference = luaL_ref(interpreter, LUA_REGISTRYINDEX); + + DBGPF("Registered thread %" PRIsize_t " on %s", threads, inst->name); + + //push thread main + luaL_checktype(interpreter, 1, LUA_TFUNCTION); + lua_pushvalue(interpreter, 1); + lua_xmove(interpreter, thread[u].thread, 1); + + lua_thread_resume(u); + lua_update_timerfd(); + return 0; +} + +static int lua_callback_sleep(lua_State* interpreter){ + uint64_t timeout = 0; + size_t current_thread = threads; + if(lua_gettop(interpreter) != 1){ + LOGPF("Sleep function called with %d arguments, expected number", lua_gettop(interpreter)); + return 0; + } + + timeout = luaL_checkinteger(interpreter, 1); + + lua_pushstring(interpreter, LUA_REGISTRY_CURRENT_THREAD); + lua_gettable(interpreter, LUA_REGISTRYINDEX); + + current_thread = luaL_checkinteger(interpreter, -1); + + if(current_thread < threads){ + DBGPF("Yielding for %" PRIu64 "msec on thread %" PRIsize_t, timeout, current_thread); + thread[current_thread].timeout = timeout; + lua_yield(interpreter, 0); + } + return 0; +} + static int lua_callback_output(lua_State* interpreter){ size_t n = 0; channel_value val; @@ -341,6 +435,8 @@ static int lua_instance(instance* inst){ lua_register(data->interpreter, "output_value", lua_callback_output_value); lua_register(data->interpreter, "input_channel", lua_callback_input_channel); lua_register(data->interpreter, "timestamp", lua_callback_timestamp); + lua_register(data->interpreter, "thread", lua_callback_thread); + lua_register(data->interpreter, "sleep", lua_callback_sleep); //store instance pointer to the lua state lua_pushstring(data->interpreter, LUA_REGISTRY_KEY); @@ -454,6 +550,17 @@ static int lua_handle(size_t num, managed_fd* fds){ } } } + + //check for threads to wake up + for(n = 0; n < threads; n++){ + if(thread[n].timeout && delta >= thread[n].timeout){ + lua_thread_resume(n); + lua_update_timerfd(); + } + else if(thread[n].timeout){ + thread[n].timeout -= delta; + } + } return 0; } @@ -468,6 +575,8 @@ static int lua_start(size_t n, instance** inst){ //exclude reserved names if(!data->default_handler && strcmp(data->channel_name[p], "output") + && strcmp(data->channel_name[p], "thread") + && strcmp(data->channel_name[p], "sleep") && strcmp(data->channel_name[p], "input_value") && strcmp(data->channel_name[p], "output_value") && strcmp(data->channel_name[p], "input_channel") @@ -526,6 +635,9 @@ static int lua_shutdown(size_t n, instance** inst){ free(timer); timer = NULL; timers = 0; + free(thread); + thread = NULL; + threads = 0; #ifdef MMBACKEND_LUA_TIMERFD close(timer_fd); timer_fd = -1; diff --git a/backends/lua.h b/backends/lua.h index 743f978..d8e720a 100644 --- a/backends/lua.h +++ b/backends/lua.h @@ -39,3 +39,10 @@ typedef struct /*_lua_interval_callback*/ { lua_State* interpreter; int reference; } lua_timer; + +typedef struct /*_lua_coroutine*/ { + instance* instance; + lua_State* thread; + int reference; + uint64_t timeout; +} lua_thread; diff --git a/backends/lua.md b/backends/lua.md index 96e53c8..05509b6 100644 --- a/backends/lua.md +++ b/backends/lua.md @@ -19,24 +19,41 @@ The following functions are provided within the Lua interpreter for interaction | `output_value(string)` | `output_value("bar")` | Get the last output value on a channel | | `input_channel()` | `print(input_channel())` | Returns the name of the input channel whose handler function is currently running or `nil` if in an `interval`'ed function (or the initial parse step) | | `timestamp()` | `print(timestamp())` | Returns the core timestamp for this iteration with millisecond resolution. This is not a performance timer, but intended for timeouting, etc | +| `thread(function)` | `thread(run_show)` | Run a function as a Lua thread (see below) | +| `sleep(number)` | `sleep(100)` | Suspend current thread for time specified in milliseconds | Example script: -``` +```lua function bar(value) - output("foo", value / 2) + output("foo", value / 2); end step = 0 function toggle() - output("bar", step * 1.0) + output("bar", step * 1.0); step = (step + 1) % 2; end +function run_show() + while(true) do + sleep(1000); + output("narf", 0); + sleep(1000); + output("narf", 1.0); + end +end + interval(toggle, 1000) +thread(run_show) ``` Input values range between 0.0 and 1.0, output values are clamped to the same range. +Threads are implemented as Lua coroutines, not operating system threads. This means that +cooperative multithreading is required, which can be achieved by calling the `sleep(number)` +function from within a running thread. Calling that function from any other context is +not supported. + #### Global configuration The `lua` backend does not take any global configuration. @@ -61,9 +78,10 @@ lua1.foo > lua2.bar #### Known bugs / problems -Using any of the interface functions (`output`, `interval`, `input_value`, `output_value`, `input_channel`, -`timestamp`) as an input channel name to a Lua instance will not call any handler functions. -Using these names as arguments to the output and value interface functions works as intended. +Using any of the interface functions (`output`, `interval`, etc.) as an input channel name to a +Lua instance will not call any handler functions. Using these names as arguments to the output and +value interface functions works as intended. When using a default handler, the default handler will +be called. Output values will not trigger corresponding input event handlers unless the channel is mapped back in the MIDIMonster configuration. This is intentional. -- cgit v1.2.3 From 03a75ea3b4e72f09843bc8b12f8bac84a2f27709 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 20 Mar 2020 23:35:53 +0100 Subject: Restructure lua channel resolution --- backends/lua.c | 105 +++++++++++++++++++++++++++++------------------------- backends/lua.h | 14 +++++--- backends/python.c | 3 +- 3 files changed, 68 insertions(+), 54 deletions(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index b66a27a..b71ca97 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -245,13 +245,13 @@ static int lua_callback_output(lua_State* interpreter){ //find correct channel & output value for(n = 0; n < data->channels; n++){ - if(!strcmp(channel_name, data->channel_name[n])){ + if(!strcmp(channel_name, data->channel[n].name)){ channel = mm_channel(inst, n, 0); if(!channel){ return 0; } mm_channel_event(channel, val); - data->output[n] = val.normalised; + data->channel[n].out = val.normalised; return 0; } } @@ -357,8 +357,8 @@ static int lua_callback_value(lua_State* interpreter, uint8_t input){ //find correct channel & return value for(n = 0; n < data->channels; n++){ - if(!strcmp(channel_name, data->channel_name[n])){ - lua_pushnumber(interpreter, (input) ? data->input[n] : data->output[n]); + if(!strcmp(channel_name, data->channel[n].name)){ + lua_pushnumber(interpreter, (input) ? data->channel[n].in : data->channel[n].out); return 1; } } @@ -453,26 +453,23 @@ static channel* lua_channel(instance* inst, char* spec, uint8_t flags){ //find matching channel for(u = 0; u < data->channels; u++){ - if(!strcmp(spec, data->channel_name[u])){ + if(!strcmp(spec, data->channel[u].name)){ break; } } //allocate new channel if(u == data->channels){ - data->channel_name = realloc(data->channel_name, (u + 1) * sizeof(char*)); - data->reference = realloc(data->reference, (u + 1) * sizeof(int)); - data->input = realloc(data->input, (u + 1) * sizeof(double)); - data->output = realloc(data->output, (u + 1) * sizeof(double)); - if(!data->channel_name || !data->reference || !data->input || !data->output){ + data->channel = realloc(data->channel, (data->channels + 1) * sizeof(lua_channel_data)); + if(!data->channel){ LOG("Failed to allocate memory"); + data->channels = 0; return NULL; } - data->reference[u] = LUA_NOREF; - data->input[u] = data->output[u] = 0.0; - data->channel_name[u] = strdup(spec); - if(!data->channel_name[u]){ + data->channel[u].in = data->channel[u].out = 0.0; + data->channel[u].name = strdup(spec); + if(!data->channel[u].name){ LOG("Failed to allocate memory"); return NULL; } @@ -483,23 +480,24 @@ static channel* lua_channel(instance* inst, char* spec, uint8_t flags){ } static int lua_set(instance* inst, size_t num, channel** c, channel_value* v){ - size_t n = 0; + size_t n = 0, ident; lua_instance_data* data = (lua_instance_data*) inst->impl; //handle all incoming events for(n = 0; n < num; n++){ - data->input[c[n]->ident] = v[n].normalised; + ident = c[n]->ident; + data->channel[ident].in = v[n].normalised; //call lua channel handlers if present - if(data->reference[c[n]->ident] != LUA_NOREF){ + if(data->channel[ident].reference != LUA_NOREF){ //push the channel name lua_pushstring(data->interpreter, LUA_REGISTRY_CURRENT_CHANNEL); - lua_pushstring(data->interpreter, data->channel_name[c[n]->ident]); + lua_pushstring(data->interpreter, data->channel[ident].name); lua_settable(data->interpreter, LUA_REGISTRYINDEX); - lua_rawgeti(data->interpreter, LUA_REGISTRYINDEX, data->reference[c[n]->ident]); + lua_rawgeti(data->interpreter, LUA_REGISTRYINDEX, data->channel[ident].reference); lua_pushnumber(data->interpreter, v[n].normalised); if(lua_pcall(data->interpreter, 1, 0, 0) != LUA_OK){ - LOGPF("Failed to call handler for %s.%s: %s", inst->name, data->channel_name[c[n]->ident], lua_tostring(data->interpreter, -1)); + LOGPF("Failed to call handler for %s.%s: %s", inst->name, data->channel[ident].name, lua_tostring(data->interpreter, -1)); lua_pop(data->interpreter, 1); } } @@ -564,37 +562,51 @@ static int lua_handle(size_t num, managed_fd* fds){ return 0; } +static int lua_resolve_symbol(lua_State* interpreter, char* symbol){ + int reference = LUA_REFNIL; + + //exclude reserved names + if(!strcmp(symbol, "output") + || !strcmp(symbol, "thread") + || !strcmp(symbol, "sleep") + || !strcmp(symbol, "input_value") + || !strcmp(symbol, "output_value") + || !strcmp(symbol, "input_channel") + || !strcmp(symbol, "timestamp") + || !strcmp(symbol, "interval")){ + return LUA_NOREF; + } + + lua_getglobal(interpreter, symbol); + reference = luaL_ref(interpreter, LUA_REGISTRYINDEX); + if(reference == LUA_REFNIL){ + return LUA_NOREF; + } + return reference; +} + static int lua_start(size_t n, instance** inst){ size_t u, p; lua_instance_data* data = NULL; + int default_handler; //resolve channels to their handler functions for(u = 0; u < n; u++){ data = (lua_instance_data*) inst[u]->impl; - for(p = 0; p < data->channels; p++){ - //exclude reserved names - if(!data->default_handler - && strcmp(data->channel_name[p], "output") - && strcmp(data->channel_name[p], "thread") - && strcmp(data->channel_name[p], "sleep") - && strcmp(data->channel_name[p], "input_value") - && strcmp(data->channel_name[p], "output_value") - && strcmp(data->channel_name[p], "input_channel") - && strcmp(data->channel_name[p], "timestamp") - && strcmp(data->channel_name[p], "interval")){ - lua_getglobal(data->interpreter, data->channel_name[p]); - data->reference[p] = luaL_ref(data->interpreter, LUA_REGISTRYINDEX); - if(data->reference[p] == LUA_REFNIL){ - data->reference[p] = LUA_NOREF; - } + default_handler = LUA_NOREF; + + //try to resolve default handler if given + if(data->default_handler){ + default_handler = lua_resolve_symbol(data->interpreter, data->default_handler); + if(default_handler == LUA_NOREF){ + LOGPF("Failed to resolve default handler %s on %s", data->default_handler, inst[u]->name); } - else if(data->default_handler){ - lua_getglobal(data->interpreter, data->default_handler); - data->reference[p] = luaL_ref(data->interpreter, LUA_REGISTRYINDEX); - if(data->reference[p] == LUA_REFNIL){ - data->reference[p] = LUA_NOREF; - LOGPF("Failed to resolve default handler function %s on instance %s", data->default_handler, inst[u]->name); - } + } + + for(p = 0; p < data->channels; p++){ + data->channel[p].reference = default_handler; + if(!data->default_handler){ + data->channel[p].reference = lua_resolve_symbol(data->interpreter, data->channel[p].name); } } } @@ -621,12 +633,9 @@ static int lua_shutdown(size_t n, instance** inst){ lua_close(data->interpreter); //cleanup channel data for(p = 0; p < data->channels; p++){ - free(data->channel_name[p]); + free(data->channel[p].name); } - free(data->channel_name); - free(data->reference); - free(data->input); - free(data->output); + free(data->channel); free(data->default_handler); free(inst[u]->impl); } diff --git a/backends/lua.h b/backends/lua.h index d8e720a..3c7568b 100644 --- a/backends/lua.h +++ b/backends/lua.h @@ -22,14 +22,18 @@ static int lua_shutdown(size_t n, instance** inst); static uint32_t lua_interval(); #endif +typedef struct /*_lua_channel*/ { + char* name; + int reference; + double in; + double out; +} lua_channel_data; + typedef struct /*_lua_instance_data*/ { size_t channels; - char** channel_name; - int* reference; - double* input; - double* output; - lua_State* interpreter; + lua_channel_data* channel; + lua_State* interpreter; char* default_handler; } lua_instance_data; diff --git a/backends/python.c b/backends/python.c index 735e838..c658f30 100644 --- a/backends/python.c +++ b/backends/python.c @@ -78,7 +78,8 @@ static void python_timer_recalculate(){ } //10msec is absolute lower limit and minimum gcd due to rounding - if(next_interval == 10){ + if(next_interval <= 10){ + next_interval = 10; break; } } -- cgit v1.2.3 From d1baab6ce4b13c562867147ed60906dfe651ae81 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 21 Mar 2020 19:00:28 +0100 Subject: Enable setting channel values at load time for lua --- backends/lua.c | 29 +++++++++++++++++++---------- backends/lua.h | 1 + 2 files changed, 20 insertions(+), 10 deletions(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index b71ca97..7f80cc7 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -17,9 +17,8 @@ static lua_timer* timer = NULL; uint64_t timer_interval = 0; #ifdef MMBACKEND_LUA_TIMERFD static int timer_fd = -1; -#else -static uint64_t last_timestamp; #endif +static uint64_t last_timestamp = 0; static size_t threads = 0; static lua_thread* thread = NULL; @@ -224,7 +223,6 @@ static int lua_callback_output(lua_State* interpreter){ size_t n = 0; channel_value val; const char* channel_name = NULL; - channel* channel = NULL; instance* inst = NULL; lua_instance_data* data = NULL; @@ -243,15 +241,21 @@ static int lua_callback_output(lua_State* interpreter){ channel_name = lua_tostring(interpreter, 1); val.normalised = clamp(luaL_checknumber(interpreter, 2), 1.0, 0.0); + //if not started yet, create any requested channels so scripts may set them at load time + if(!last_timestamp && channel_name){ + lua_channel(inst, (char*) channel_name, mmchannel_output); + } + //find correct channel & output value for(n = 0; n < data->channels; n++){ if(!strcmp(channel_name, data->channel[n].name)){ - channel = mm_channel(inst, n, 0); - if(!channel){ - return 0; - } - mm_channel_event(channel, val); data->channel[n].out = val.normalised; + if(!last_timestamp){ + data->channel[n].mark = 1; + } + else{ + mm_channel_event(mm_channel(inst, n, 0), val); + } return 0; } } @@ -589,6 +593,7 @@ static int lua_start(size_t n, instance** inst){ size_t u, p; lua_instance_data* data = NULL; int default_handler; + channel_value v; //resolve channels to their handler functions for(u = 0; u < n; u++){ @@ -608,6 +613,11 @@ static int lua_start(size_t n, instance** inst){ if(!data->default_handler){ data->channel[p].reference = lua_resolve_symbol(data->interpreter, data->channel[p].name); } + //push initial values + if(data->channel[p].mark){ + v.normalised = data->channel[p].out; + mm_channel_event(mm_channel(inst[u], p, 0), v); + } } } @@ -617,9 +627,8 @@ static int lua_start(size_t n, instance** inst){ if(mm_manage_fd(timer_fd, BACKEND_NAME, 1, NULL)){ return 1; } - #else - last_timestamp = mm_timestamp(); #endif + last_timestamp = mm_timestamp(); return 0; } diff --git a/backends/lua.h b/backends/lua.h index 3c7568b..4583dfe 100644 --- a/backends/lua.h +++ b/backends/lua.h @@ -27,6 +27,7 @@ typedef struct /*_lua_channel*/ { int reference; double in; double out; + uint8_t mark; } lua_channel_data; typedef struct /*_lua_instance_data*/ { -- cgit v1.2.3 From 18db3856a3a3e81f3e2050e3f137e6e15103f9a4 Mon Sep 17 00:00:00 2001 From: cbdev Date: Tue, 24 Mar 2020 23:04:12 +0100 Subject: Simplify backend code according to new guarantees --- backends/artnet.c | 5 ----- backends/evdev.c | 8 -------- backends/jack.c | 52 +++++++++++++++++++++++++--------------------------- backends/lua.c | 3 ++- backends/osc.c | 14 +++++--------- backends/sacn.c | 9 --------- backends/winmidi.c | 5 ----- 7 files changed, 32 insertions(+), 64 deletions(-) (limited to 'backends/lua.c') diff --git a/backends/artnet.c b/backends/artnet.c index caab6e0..c59a79d 100644 --- a/backends/artnet.c +++ b/backends/artnet.c @@ -378,11 +378,6 @@ static int artnet_handle(size_t num, managed_fd* fds){ } } - if(!num){ - //early exit - return 0; - } - for(u = 0; u < num; u++){ do{ bytes_read = recv(fds[u].fd, recv_buf, sizeof(recv_buf), 0); diff --git a/backends/evdev.c b/backends/evdev.c index af5ec74..8a14200 100644 --- a/backends/evdev.c +++ b/backends/evdev.c @@ -357,10 +357,6 @@ static int evdev_handle(size_t num, managed_fd* fds){ int read_status; struct input_event ev; - if(!num){ - return 0; - } - for(fd = 0; fd < num; fd++){ inst = (instance*) fds[fd].impl; if(!inst){ @@ -437,10 +433,6 @@ static int evdev_set(instance* inst, size_t num, channel** c, channel_value* v) int32_t value = 0; uint64_t range = 0; - if(!num){ - return 0; - } - if(!data->output_enabled){ LOGPF("Instance %s not enabled for output (%" PRIsize_t " channel events)", inst->name, num); return 0; diff --git a/backends/jack.c b/backends/jack.c index c862096..d430c60 100644 --- a/backends/jack.c +++ b/backends/jack.c @@ -549,38 +549,36 @@ static int mmjack_handle(size_t num, managed_fd* fds){ ssize_t bytes; uint8_t recv_buf[1024]; - if(num){ - for(u = 0; u < num; u++){ - inst = (instance*) fds[u].impl; - data = (mmjack_instance_data*) inst->impl; - bytes = recv(fds[u].fd, recv_buf, sizeof(recv_buf), 0); - if(bytes < 0){ - LOGPF("Failed to receive on feedback socket for instance %s", inst->name); - return 1; - } + for(u = 0; u < num; u++){ + inst = (instance*) fds[u].impl; + data = (mmjack_instance_data*) inst->impl; + bytes = recv(fds[u].fd, recv_buf, sizeof(recv_buf), 0); + if(bytes < 0){ + LOGPF("Failed to receive on feedback socket for instance %s", inst->name); + return 1; + } - for(p = 0; p < data->ports; p++){ - if(data->port[p].input && data->port[p].mark){ - pthread_mutex_lock(&data->port[p].lock); - switch(data->port[p].type){ - case port_cv: - mmjack_handle_cv(inst, p, data->port + p); - break; - case port_midi: - mmjack_handle_midi(inst, p, data->port + p); - break; - default: - LOGPF("Output handler not implemented for unknown channel type on %s.%s", inst->name, data->port[p].name); - break; - } - - data->port[p].mark = 0; - pthread_mutex_unlock(&data->port[p].lock); + for(p = 0; p < data->ports; p++){ + if(data->port[p].input && data->port[p].mark){ + pthread_mutex_lock(&data->port[p].lock); + switch(data->port[p].type){ + case port_cv: + mmjack_handle_cv(inst, p, data->port + p); + break; + case port_midi: + mmjack_handle_midi(inst, p, data->port + p); + break; + default: + LOGPF("Output handler not implemented for unknown channel type on %s.%s", inst->name, data->port[p].name); + break; } + + data->port[p].mark = 0; + pthread_mutex_unlock(&data->port[p].lock); } } } - + if(config.jack_shutdown){ LOG("Server disconnected"); return 1; diff --git a/backends/lua.c b/backends/lua.c index 7f80cc7..968193e 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -471,7 +471,8 @@ static channel* lua_channel(instance* inst, char* spec, uint8_t flags){ return NULL; } - data->channel[u].in = data->channel[u].out = 0.0; + //initialize new channel + memset(data->channel + u, 0, sizeof(lua_channel_data)); data->channel[u].name = strdup(spec); if(!data->channel[u].name){ LOG("Failed to allocate memory"); diff --git a/backends/osc.c b/backends/osc.c index 754c290..f40f3f5 100644 --- a/backends/osc.c +++ b/backends/osc.c @@ -231,7 +231,7 @@ static int osc_path_validate(char* path, uint8_t allow_patterns){ char pattern_chars[] = "?[]{}*"; size_t u, c; uint8_t square_open = 0, curly_open = 0; - + if(path[0] != '/'){ LOGPF("%s is not a valid OSC path: Missing root /", path); return 1; @@ -331,7 +331,7 @@ static int osc_path_match(char* pattern, char* path){ } if(pattern[match_end + 1] == '-' && pattern[match_end + 2] != ']'){ - if((pattern[match_end] > pattern[match_end + 2] + if((pattern[match_end] > pattern[match_end + 2] && path[u] >= pattern[match_end + 2] && path[u] <= pattern[match_end]) || (pattern[match_end] <= pattern[match_end + 2] @@ -666,7 +666,7 @@ static int osc_output_channel(instance* inst, size_t channel){ memcpy(xmit_buf, data->root, strlen(data->root)); offset += strlen(data->root); } - + memcpy(xmit_buf + offset, data->channel[channel].path, strlen(data->channel[channel].path)); offset += strlen(data->channel[channel].path) + 1; offset = osc_align(offset); @@ -703,16 +703,12 @@ static int osc_output_channel(instance* inst, size_t channel){ static int osc_set(instance* inst, size_t num, channel** c, channel_value* v){ size_t evt = 0, mark = 0; int rv = 0; + osc_instance_data* data = (osc_instance_data*) inst->impl; osc_channel_ident ident = { .label = 0 }; osc_parameter_value current; - if(!num){ - return 0; - } - - osc_instance_data* data = (osc_instance_data*) inst->impl; if(!data->dest_len){ LOGPF("Instance %s does not have a destination, output is disabled (%" PRIsize_t " channels)", inst->name, num); return 0; @@ -747,7 +743,7 @@ static int osc_set(instance* inst, size_t num, channel** c, channel_value* v){ mark = 1; } } - + if(mark){ //output all marked channels for(evt = 0; !rv && evt < num; evt++){ diff --git a/backends/sacn.c b/backends/sacn.c index bd5c75a..bce3887 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -330,10 +330,6 @@ static int sacn_set(instance* inst, size_t num, channel** c, channel_value* v){ uint32_t frame_delta = 0; sacn_instance_data* data = (sacn_instance_data*) inst->impl; - if(!num){ - return 0; - } - if(!data->xmit_prio){ LOGPF("Instance %s not enabled for output (%" PRIsize_t " channel events)", inst->name, num); return 0; @@ -554,11 +550,6 @@ static int sacn_handle(size_t num, managed_fd* fds){ } } - //early exit - if(!num){ - return 0; - } - for(u = 0; u < num; u++){ do{ bytes_read = recv(fds[u].fd, recv_buf, sizeof(recv_buf), 0); diff --git a/backends/winmidi.c b/backends/winmidi.c index d9b3047..753f25c 100644 --- a/backends/winmidi.c +++ b/backends/winmidi.c @@ -185,11 +185,6 @@ static int winmidi_set(instance* inst, size_t num, channel** c, channel_value* v }; size_t u; - //early exit - if(!num){ - return 0; - } - if(!data->device_out){ LOGPF("Instance %s has no output device", inst->name); return 0; -- cgit v1.2.3 From 2a079f72483aa853d68430883b2281f436512c6b Mon Sep 17 00:00:00 2001 From: cbdev Date: Thu, 26 Mar 2020 22:42:38 +0100 Subject: Implement lua cleanup handlers --- TODO | 3 --- backends/lua.c | 74 +++++++++++++++++++++++++++++++++++++-------------------- backends/lua.h | 1 + backends/lua.md | 14 ++++++++--- 4 files changed, 60 insertions(+), 32 deletions(-) (limited to 'backends/lua.c') diff --git a/TODO b/TODO index 900cc1b..ccad973 100644 --- a/TODO +++ b/TODO @@ -1,9 +1,6 @@ keepalive channels per backend? Note source in channel value struct -Optimize core channel search (store backend offset) udp backends may ignore MTU -mm_managed_fd.impl is not freed currently (and is heaped most of the time anyway) -> documentation make event collectors threadsafe to stop marshalling data... collect & check backend API version windows strerror -move all connection establishment to _start to be able to hot-stop/start all backends diff --git a/backends/lua.c b/backends/lua.c index 968193e..7424f65 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -155,8 +155,19 @@ static void lua_thread_resume(size_t current_thread){ lua_settable(thread[current_thread].thread, LUA_REGISTRYINDEX); } -static int lua_callback_thread(lua_State* interpreter){ +static instance* lua_fetch_instance(lua_State* interpreter){ instance* inst = NULL; + + //get instance pointer from registry + lua_pushstring(interpreter, LUA_REGISTRY_KEY); + lua_gettable(interpreter, LUA_REGISTRYINDEX); + inst = (instance*) lua_touserdata(interpreter, -1); + lua_pop(interpreter, 1); + return inst; +} + +static int lua_callback_thread(lua_State* interpreter){ + instance* inst = lua_fetch_instance(interpreter); size_t u = threads; if(lua_gettop(interpreter) != 1){ LOGPF("Thread function called with %d arguments, expected function", lua_gettop(interpreter)); @@ -165,11 +176,6 @@ static int lua_callback_thread(lua_State* interpreter){ luaL_checktype(interpreter, 1, LUA_TFUNCTION); - //get instance pointer from registry - lua_pushstring(interpreter, LUA_REGISTRY_KEY); - lua_gettable(interpreter, LUA_REGISTRYINDEX); - inst = (instance*) lua_touserdata(interpreter, -1); - //make space for a new thread thread = realloc(thread, (threads + 1) * sizeof(lua_thread)); if(!thread){ @@ -223,20 +229,14 @@ static int lua_callback_output(lua_State* interpreter){ size_t n = 0; channel_value val; const char* channel_name = NULL; - instance* inst = NULL; - lua_instance_data* data = NULL; + instance* inst = lua_fetch_instance(interpreter); + lua_instance_data* data = (lua_instance_data*) inst->impl; if(lua_gettop(interpreter) != 2){ LOGPF("Output function called with %d arguments, expected 2 (string, number)", lua_gettop(interpreter)); return 0; } - //get instance pointer from registry - lua_pushstring(interpreter, LUA_REGISTRY_KEY); - lua_gettable(interpreter, LUA_REGISTRYINDEX); - inst = (instance*) lua_touserdata(interpreter, -1); - data = (lua_instance_data*) inst->impl; - //fetch function parameters channel_name = lua_tostring(interpreter, 1); val.normalised = clamp(luaL_checknumber(interpreter, 2), 1.0, 0.0); @@ -264,6 +264,28 @@ static int lua_callback_output(lua_State* interpreter){ return 0; } +static int lua_callback_cleanup_handler(lua_State* interpreter){ + instance* inst = lua_fetch_instance(interpreter); + lua_instance_data* data = (lua_instance_data*) inst->impl; + int current_handler = data->cleanup_handler; + + if(lua_gettop(interpreter) != 1){ + LOGPF("Cleanup handler function called with %d arguments, expected 1 (function)", lua_gettop(interpreter)); + return 0; + } + + luaL_checktype(interpreter, 1, LUA_TFUNCTION); + + data->cleanup_handler = luaL_ref(interpreter, LUA_REGISTRYINDEX); + if(current_handler == LUA_NOREF){ + lua_pushnil(interpreter); + return 1; + } + lua_rawgeti(interpreter, LUA_REGISTRYINDEX, current_handler); + luaL_unref(interpreter, LUA_REGISTRYINDEX, current_handler); + return 1; +} + static int lua_callback_interval(lua_State* interpreter){ size_t n = 0; uint64_t interval = 0; @@ -274,10 +296,6 @@ static int lua_callback_interval(lua_State* interpreter){ return 0; } - //get instance pointer from registry - lua_pushstring(interpreter, LUA_REGISTRY_KEY); - lua_gettable(interpreter, LUA_REGISTRYINDEX); - //fetch and round the interval interval = luaL_checkinteger(interpreter, 2); if(interval % 10 < 5){ @@ -341,21 +359,15 @@ static int lua_callback_interval(lua_State* interpreter){ static int lua_callback_value(lua_State* interpreter, uint8_t input){ size_t n = 0; - instance* inst = NULL; - lua_instance_data* data = NULL; const char* channel_name = NULL; + instance* inst = lua_fetch_instance(interpreter); + lua_instance_data* data = (lua_instance_data*) inst->impl; if(lua_gettop(interpreter) != 1){ LOGPF("get_value function called with %d arguments, expected 1 (string)", lua_gettop(interpreter)); return 0; } - //get instance pointer from registry - lua_pushstring(interpreter, LUA_REGISTRY_KEY); - lua_gettable(interpreter, LUA_REGISTRYINDEX); - inst = (instance*) lua_touserdata(interpreter, -1); - data = (lua_instance_data*) inst->impl; - //fetch argument channel_name = lua_tostring(interpreter, 1); @@ -425,6 +437,7 @@ static int lua_instance(instance* inst){ //load the interpreter data->interpreter = luaL_newstate(); + data->cleanup_handler = LUA_NOREF; if(!data->interpreter){ LOG("Failed to initialize interpreter"); free(data); @@ -441,6 +454,7 @@ static int lua_instance(instance* inst){ lua_register(data->interpreter, "timestamp", lua_callback_timestamp); lua_register(data->interpreter, "thread", lua_callback_thread); lua_register(data->interpreter, "sleep", lua_callback_sleep); + lua_register(data->interpreter, "cleanup_handler", lua_callback_cleanup_handler); //store instance pointer to the lua state lua_pushstring(data->interpreter, LUA_REGISTRY_KEY); @@ -578,6 +592,7 @@ static int lua_resolve_symbol(lua_State* interpreter, char* symbol){ || !strcmp(symbol, "output_value") || !strcmp(symbol, "input_channel") || !strcmp(symbol, "timestamp") + || !strcmp(symbol, "cleanup_handler") || !strcmp(symbol, "interval")){ return LUA_NOREF; } @@ -639,6 +654,13 @@ static int lua_shutdown(size_t n, instance** inst){ for(u = 0; u < n; u++){ data = (lua_instance_data*) inst[u]->impl; + + //call cleanup function if one is registered + if(data->cleanup_handler != LUA_NOREF){ + lua_rawgeti(data->interpreter, LUA_REGISTRYINDEX, data->cleanup_handler); + lua_pcall(data->interpreter, 0, 0, 0); + } + //stop the interpreter lua_close(data->interpreter); //cleanup channel data diff --git a/backends/lua.h b/backends/lua.h index 4583dfe..5587bf9 100644 --- a/backends/lua.h +++ b/backends/lua.h @@ -35,6 +35,7 @@ typedef struct /*_lua_instance_data*/ { lua_channel_data* channel; lua_State* interpreter; + int cleanup_handler; char* default_handler; } lua_instance_data; diff --git a/backends/lua.md b/backends/lua.md index 05509b6..30d7580 100644 --- a/backends/lua.md +++ b/backends/lua.md @@ -13,14 +13,15 @@ The following functions are provided within the Lua interpreter for interaction | Function | Usage example | Description | |-------------------------------|-------------------------------|---------------------------------------| -| `output(string, number)` | `output("foo", 0.75)` | Output a value event to a channel | +| `output(string, number)` | `output("foo", 0.75)` | Output a value event to a channel on this instance | | `interval(function, number)` | `interval(update, 100)` | Register a function to be called periodically. Intervals are milliseconds (rounded to the nearest 10 ms). Calling `interval` on a Lua function multiple times updates the interval. Specifying `0` as interval stops periodic calls to the function | -| `input_value(string)` | `input_value("foo")` | Get the last input value on a channel | -| `output_value(string)` | `output_value("bar")` | Get the last output value on a channel | +| `input_value(string)` | `input_value("foo")` | Get the last input value on a channel on this instance | +| `output_value(string)` | `output_value("bar")` | Get the last output value on a channel on this instance | | `input_channel()` | `print(input_channel())` | Returns the name of the input channel whose handler function is currently running or `nil` if in an `interval`'ed function (or the initial parse step) | | `timestamp()` | `print(timestamp())` | Returns the core timestamp for this iteration with millisecond resolution. This is not a performance timer, but intended for timeouting, etc | | `thread(function)` | `thread(run_show)` | Run a function as a Lua thread (see below) | | `sleep(number)` | `sleep(100)` | Suspend current thread for time specified in milliseconds | +| `cleanup_handler(function)` | | Register a function to be called when the instance is destroyed (on MIDIMonster shutdown). One cleanup handler can be registered per instance. Calling this function when the instance already has a cleanup handler registered replaces the handler, returning the old one. | Example script: ```lua @@ -43,8 +44,12 @@ function run_show() end end +function save_values() +end + interval(toggle, 1000) thread(run_show) +cleanup_handler(save_values) ``` Input values range between 0.0 and 1.0, output values are clamped to the same range. @@ -86,6 +91,9 @@ be called. Output values will not trigger corresponding input event handlers unless the channel is mapped back in the MIDIMonster configuration. This is intentional. +Output events generated from cleanup handlers called during shutdown will not be routed, as the core +routing facility has already shut down at this point. There are no plans to change this behaviour. + To build (and run) the `lua` backend on Windows, a compiled version of the Lua 5.3 library is required. For various reasons (legal, separations of concern, not wanting to ship binary data in the repository), the MIDIMonster project can not provide this file within this repository. -- cgit v1.2.3 From 253125ea28925e5207c375987ac36468327bed66 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 27 Mar 2020 21:40:45 +0100 Subject: Implement python cleanup handlers --- backends/lua.c | 9 +++-- backends/lua.md | 3 +- backends/python.c | 99 ++++++++++++++++++++++++++++++++++++------------------ backends/python.h | 1 + backends/python.md | 16 ++++++--- 5 files changed, 87 insertions(+), 41 deletions(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index 7424f65..127933a 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -274,10 +274,13 @@ static int lua_callback_cleanup_handler(lua_State* interpreter){ return 0; } - luaL_checktype(interpreter, 1, LUA_TFUNCTION); + if(lua_type(interpreter, 1) != LUA_TFUNCTION && lua_type(interpreter, 1) != LUA_TNIL){ + LOG("Cleanup handler function parameter was neither nil nor a function"); + return 0; + } data->cleanup_handler = luaL_ref(interpreter, LUA_REGISTRYINDEX); - if(current_handler == LUA_NOREF){ + if(current_handler == LUA_NOREF || current_handler == LUA_REFNIL){ lua_pushnil(interpreter); return 1; } @@ -656,7 +659,7 @@ static int lua_shutdown(size_t n, instance** inst){ data = (lua_instance_data*) inst[u]->impl; //call cleanup function if one is registered - if(data->cleanup_handler != LUA_NOREF){ + if(data->cleanup_handler != LUA_NOREF && data->cleanup_handler != LUA_REFNIL){ lua_rawgeti(data->interpreter, LUA_REGISTRYINDEX, data->cleanup_handler); lua_pcall(data->interpreter, 0, 0, 0); } diff --git a/backends/lua.md b/backends/lua.md index 30d7580..e59e513 100644 --- a/backends/lua.md +++ b/backends/lua.md @@ -15,13 +15,13 @@ The following functions are provided within the Lua interpreter for interaction |-------------------------------|-------------------------------|---------------------------------------| | `output(string, number)` | `output("foo", 0.75)` | Output a value event to a channel on this instance | | `interval(function, number)` | `interval(update, 100)` | Register a function to be called periodically. Intervals are milliseconds (rounded to the nearest 10 ms). Calling `interval` on a Lua function multiple times updates the interval. Specifying `0` as interval stops periodic calls to the function | +| `cleanup_handler(function)` | `cleanup_handler(shutdown)` | Register a function to be called when the instance is destroyed (on MIDIMonster shutdown). One cleanup handler can be registered per instance. Calling this function when the instance already has a cleanup handler registered replaces the handler, returning the old one. | | `input_value(string)` | `input_value("foo")` | Get the last input value on a channel on this instance | | `output_value(string)` | `output_value("bar")` | Get the last output value on a channel on this instance | | `input_channel()` | `print(input_channel())` | Returns the name of the input channel whose handler function is currently running or `nil` if in an `interval`'ed function (or the initial parse step) | | `timestamp()` | `print(timestamp())` | Returns the core timestamp for this iteration with millisecond resolution. This is not a performance timer, but intended for timeouting, etc | | `thread(function)` | `thread(run_show)` | Run a function as a Lua thread (see below) | | `sleep(number)` | `sleep(100)` | Suspend current thread for time specified in milliseconds | -| `cleanup_handler(function)` | | Register a function to be called when the instance is destroyed (on MIDIMonster shutdown). One cleanup handler can be registered per instance. Calling this function when the instance already has a cleanup handler registered replaces the handler, returning the old one. | Example script: ```lua @@ -45,6 +45,7 @@ function run_show() end function save_values() + -- Store state to a file, for example end interval(toggle, 1000) diff --git a/backends/python.c b/backends/python.c index 9f1d642..4c9248d 100644 --- a/backends/python.c +++ b/backends/python.c @@ -257,6 +257,33 @@ static PyObject* mmpy_interval(PyObject* self, PyObject* args){ return Py_None; } +static PyObject* mmpy_cleanup_handler(PyObject* self, PyObject* args){ + instance* inst = *((instance**) PyModule_GetState(self)); + python_instance_data* data = (python_instance_data*) inst->impl; + PyObject* current_handler = data->cleanup_handler; + + if(!PyArg_ParseTuple(args, "O", &(data->cleanup_handler)) + || (data->cleanup_handler != Py_None && !PyCallable_Check(data->cleanup_handler))){ + data->cleanup_handler = current_handler; + return NULL; + } + + if(data->cleanup_handler == Py_None){ + data->cleanup_handler = NULL; + } + else{ + Py_INCREF(data->cleanup_handler); + } + + if(!current_handler){ + Py_INCREF(Py_None); + return Py_None; + } + + Py_DECREF(current_handler); + return current_handler; +} + static PyObject* mmpy_manage_fd(PyObject* self, PyObject* args){ instance* inst = *((instance**) PyModule_GetState(self)); python_instance_data* data = (python_instance_data*) inst->impl; @@ -264,12 +291,10 @@ static PyObject* mmpy_manage_fd(PyObject* self, PyObject* args){ size_t u = 0, last_free = 0; int fd = -1; - if(!PyArg_ParseTuple(args, "OO", &handler, &sock)){ - return NULL; - } - - if(handler != Py_None && !PyCallable_Check(handler)){ - PyErr_SetString(PyExc_TypeError, "manage() requires either None or a callable"); + if(!PyArg_ParseTuple(args, "OO", &handler, &sock) + || sock == Py_None + || (handler != Py_None && !PyCallable_Check(handler))){ + PyErr_SetString(PyExc_TypeError, "manage() requires either None or a callable and a socket-like object"); return NULL; } @@ -398,13 +423,14 @@ static PyObject* mmpy_init(){ }; static PyMethodDef mmpy_methods[] = { - {"output", mmpy_output, METH_VARARGS, "Output a channel event"}, - {"inputvalue", mmpy_input_value, METH_VARARGS, "Get last input value for a channel"}, - {"outputvalue", mmpy_output_value, METH_VARARGS, "Get the last output value for a channel"}, + {"output", mmpy_output, METH_VARARGS, "Output a channel event on the instance"}, + {"inputvalue", mmpy_input_value, METH_VARARGS, "Get last input value for a channel on the instance"}, + {"outputvalue", mmpy_output_value, METH_VARARGS, "Get the last output value for a channel on the instance"}, {"current", mmpy_current_handler, METH_VARARGS, "Get the name of the currently executing channel handler"}, {"timestamp", mmpy_timestamp, METH_VARARGS, "Get the core timestamp (in milliseconds)"}, {"manage", mmpy_manage_fd, METH_VARARGS, "(Un-)register a socket or file descriptor for notifications"}, {"interval", mmpy_interval, METH_VARARGS, "Register or update an interval handler"}, + {"cleanup_handler", mmpy_cleanup_handler, METH_VARARGS, "Register or update the instances cleanup handler"}, {0} }; @@ -674,29 +700,44 @@ static int python_start(size_t n, instance** inst){ static int python_shutdown(size_t n, instance** inst){ size_t u, p; + PyObject* result = NULL; python_instance_data* data = NULL; - //clean up channels - //this needs to be done before stopping the interpreters, - //because the handler references are refcounted - for(u = 0; u < n; u++){ - data = (python_instance_data*) inst[u]->impl; - for(p = 0; p < data->channels; p++){ - free(data->channel[p].name); - Py_XDECREF(data->channel[p].handler); + //if there are no instances, the python interpreter is not started, so cleanup can be skipped + if(python_main){ + //release interval references + for(p = 0; p < intervals; p++){ + //swap to interpreter + PyEval_RestoreThread(interval[p].interpreter); + Py_XDECREF(interval[p].reference); + PyEval_ReleaseThread(interval[p].interpreter); } - free(data->channel); - free(data->default_handler); - //do not free data here, needed for shutting down interpreters - } - if(python_main){ - //just used to lock the GIL + //lock the GIL for later interpreter release PyEval_RestoreThread(python_main); for(u = 0; u < n; u++){ data = (python_instance_data*) inst[u]->impl; + //swap to interpreter to be safe for releasing the references + PyThreadState_Swap(data->interpreter); + + //run cleanup handler before cleaning up channel data to allow reading channel data + if(data->cleanup_handler){ + result = PyObject_CallFunction(data->cleanup_handler, NULL); + Py_XDECREF(result); + Py_XDECREF(data->cleanup_handler); + } + + //clean up channels + for(p = 0; p < data->channels; p++){ + free(data->channel[p].name); + Py_XDECREF(data->channel[p].handler); + } + free(data->channel); + free(data->default_handler); + Py_XDECREF(data->handler); + //close sockets for(p = 0; p < data->sockets; p++){ close(data->socket[p].fd); //FIXME does python do this on its own? @@ -704,26 +745,18 @@ static int python_shutdown(size_t n, instance** inst){ Py_XDECREF(data->socket[p].handler); } - //release interval references - for(p = 0; p handler); - + //shut down interpreter, GIL is held after this but state is NULL DBGPF("Shutting down interpreter for instance %s", inst[u]->name); - //swap to interpreter and end it, GIL is held after this but state is NULL - PyThreadState_Swap(data->interpreter); PyErr_Clear(); //PyThreadState_Clear(data->interpreter); Py_EndInterpreter(data->interpreter); - free(data); } //shut down main interpreter PyThreadState_Swap(python_main); if(Py_FinalizeEx()){ - LOG("Failed to destroy python interpreters"); + LOG("Failed to shut down python library"); } PyMem_RawFree(program_name); } diff --git a/backends/python.h b/backends/python.h index 020aeac..539389b 100644 --- a/backends/python.h +++ b/backends/python.h @@ -45,4 +45,5 @@ typedef struct /*_python_instance_data*/ { char* default_handler; PyObject* handler; + PyObject* cleanup_handler; } python_instance_data; diff --git a/backends/python.md b/backends/python.md index 6852a79..ab0fb38 100644 --- a/backends/python.md +++ b/backends/python.md @@ -17,13 +17,14 @@ The `midimonster` module provides the following functions: | Function | Usage example | Description | |-------------------------------|---------------------------------------|-----------------------------------------------| -| `output(string, float)` | `midimonster.output("foo", 0.75)` | Output a value event to a channel | -| `inputvalue(string)` | `midimonster.inputvalue("foo")` | Get the last input value on a channel | -| `outputvalue(string)` | `midimonster.outputvalue("bar")` | Get the last output value on a channel | +| `output(string, float)` | `midimonster.output("foo", 0.75)` | Output a value event to a channel on this instance | +| `inputvalue(string)` | `midimonster.inputvalue("foo")` | Get the last input value on a channel of this instance | +| `outputvalue(string)` | `midimonster.outputvalue("bar")` | Get the last output value on a channel of this instance | | `current()` | `print(midimonster.current())` | Returns the name of the input channel whose handler function is currently running or `None` if the interpreter was called from another context | | `timestamp()` | `print(midimonster.timestamp())` | Get the internal core timestamp (in milliseconds) | | `interval(function, long)` | `midimonster.interval(toggle, 100)` | Register a function to be called periodically. Interval is specified in milliseconds (accurate to 10msec). Calling `interval` with the same function again updates the interval. Specifying the interval as `0` cancels the interval | -| `manage(function, socket)` | `midimonster.manage(handler, socket)`| Register a (connected/listening) socket to the MIDIMonster core. Calls `function(socket)` when the socket is ready to read. Calling this method with `None` as the function argument unregisters the socket. A socket may only have one associated handler | +| `manage(function, socket)` | `midimonster.manage(handler, socket)` | Register a (connected/listening) socket to the MIDIMonster core. Calls `function(socket)` when the socket is ready to read. Calling this method with `None` as the function argument unregisters the socket. A socket may only have one associated handler | +| `cleanup_handler(function)` | `midimonster.cleanup_handler(save_all)`| Register a function to be called when the instance is destroyed (on MIDIMonster shutdown). One cleanup handler can be registered per instance. Calling this function when the instance already has a cleanup handler registered replaces the handler, returning the old one. | Example Python module: ```python @@ -48,12 +49,16 @@ def socket_handler(sock): def ping(): print(midimonster.timestamp()) +def save_positions(): + # Store some data to disk + # Register an interval midimonster.interval(ping, 1000) # Create and register a client socket (add error handling as you like) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", 8990)) midimonster.manage(socket_handler, s) +midimonster.cleanup_handler(save_positions) ``` Input values range between 0.0 and 1.0, output values are clamped to the same range. @@ -92,6 +97,9 @@ py1.out1 > py2.module.handler Output values will not trigger corresponding input event handlers unless the channel is mapped back in the MIDIMonster configuration. This is intentional. +Output events generated from cleanup handlers called during shutdown will not be routed, as the core +routing facility has already shut down at this point. There are no plans to change this behaviour. + Importing a Python module named `midimonster` is probably a bad idea and thus unsupported. The MIDIMonster is, at its core, single-threaded. Do not try to use Python's `threading` -- cgit v1.2.3 From 5fdb638454ce90eb565555dfece5030a2ec4a576 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 28 Mar 2020 01:09:58 +0100 Subject: Fix reference counting bug and lua timing --- backends/lua.c | 6 ++---- backends/python.c | 7 +++++-- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index 127933a..e2f3b0e 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -533,7 +533,8 @@ static int lua_set(instance* inst, size_t num, channel** c, channel_value* v){ } static int lua_handle(size_t num, managed_fd* fds){ - uint64_t delta = timer_interval; + uint64_t delta = mm_timestamp() - last_timestamp; + last_timestamp = mm_timestamp(); size_t n; #ifdef MMBACKEND_LUA_TIMERFD @@ -547,9 +548,6 @@ static int lua_handle(size_t num, managed_fd* fds){ LOGPF("Failed to read timer: %s", strerror(errno)); return 1; } - #else - delta = mm_timestamp() - last_timestamp; - last_timestamp = mm_timestamp(); #endif //no timers active diff --git a/backends/python.c b/backends/python.c index 4c9248d..94f8e24 100644 --- a/backends/python.c +++ b/backends/python.c @@ -1,4 +1,5 @@ #define BACKEND_NAME "python" +#define DEBUG #define PY_SSIZE_T_CLEAN #include @@ -269,9 +270,11 @@ static PyObject* mmpy_cleanup_handler(PyObject* self, PyObject* args){ } if(data->cleanup_handler == Py_None){ + DBGPF("Cleanup handler removed on %s (previously %s)", inst->name, current_handler ? "active" : "inactive"); data->cleanup_handler = NULL; } else{ + DBGPF("Cleanup handler installed on %s (previously %s)", inst->name, current_handler ? "active" : "inactive"); Py_INCREF(data->cleanup_handler); } @@ -280,7 +283,7 @@ static PyObject* mmpy_cleanup_handler(PyObject* self, PyObject* args){ return Py_None; } - Py_DECREF(current_handler); + //do not decrease refcount on current_handler here as the reference may be used by python code again return current_handler; } @@ -595,6 +598,7 @@ static int python_handle(size_t num, managed_fd* fds){ //if timer expired, call handler if(interval[u].delta >= interval[u].interval){ interval[u].delta %= interval[u].interval; + DBGPF("Calling interval handler %" PRIsize_t ", last delta %" PRIu64, u, delta); //swap to interpreter PyEval_RestoreThread(interval[u].interpreter); @@ -603,7 +607,6 @@ static int python_handle(size_t num, managed_fd* fds){ Py_XDECREF(result); //release interpreter PyEval_ReleaseThread(interval[u].interpreter); - DBGPF("Calling interval handler %" PRIsize_t, u); } } } -- cgit v1.2.3 From 1891979d878e946941c6f236c3393cae943a4f1d Mon Sep 17 00:00:00 2001 From: cbdev Date: Mon, 30 Mar 2020 03:15:36 +0200 Subject: Fix Coverity CID 355268 --- backends/lua.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index e2f3b0e..d7f2643 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -239,10 +239,15 @@ static int lua_callback_output(lua_State* interpreter){ //fetch function parameters channel_name = lua_tostring(interpreter, 1); + if(!channel_name){ + LOG("Output function called with invalid channel specification"); + return 0; + } + val.normalised = clamp(luaL_checknumber(interpreter, 2), 1.0, 0.0); //if not started yet, create any requested channels so scripts may set them at load time - if(!last_timestamp && channel_name){ + if(!last_timestamp){ lua_channel(inst, (char*) channel_name, mmchannel_output); } @@ -373,6 +378,10 @@ static int lua_callback_value(lua_State* interpreter, uint8_t input){ //fetch argument channel_name = lua_tostring(interpreter, 1); + if(!channel_name){ + LOG("get_value function called with invalid channel specification"); + return 0; + } //find correct channel & return value for(n = 0; n < data->channels; n++){ -- cgit v1.2.3 From ff785c5c5a300d01b404c48335de7f68ad8711a9 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 24 Apr 2020 00:18:36 +0200 Subject: Update lua documentation, keep last value for handler --- backends/lua.c | 3 ++- backends/lua.md | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) (limited to 'backends/lua.c') diff --git a/backends/lua.c b/backends/lua.c index d7f2643..98ce369 100644 --- a/backends/lua.c +++ b/backends/lua.c @@ -517,7 +517,6 @@ static int lua_set(instance* inst, size_t num, channel** c, channel_value* v){ //handle all incoming events for(n = 0; n < num; n++){ ident = c[n]->ident; - data->channel[ident].in = v[n].normalised; //call lua channel handlers if present if(data->channel[ident].reference != LUA_NOREF){ //push the channel name @@ -532,6 +531,8 @@ static int lua_set(instance* inst, size_t num, channel** c, channel_value* v){ lua_pop(data->interpreter, 1); } } + //update the channel input value after the handler call, so we can use both values there + data->channel[ident].in = v[n].normalised; } //clear the channel name diff --git a/backends/lua.md b/backends/lua.md index 0a31dce..4e58ded 100644 --- a/backends/lua.md +++ b/backends/lua.md @@ -3,18 +3,21 @@ The `lua` backend provides a flexible programming environment, allowing users to route, generate and manipulate events using the Lua scripting language. -Every instance has its own interpreter state which can be loaded with custom handler scripts. +Every instance has its own interpreter state which can be loaded with custom scripts. To process incoming channel events, the MIDIMonster calls corresponding Lua functions (if they exist) with the value (as a Lua `number` type) as parameter. Alternatively, a designated default channel handler -may be supplied in the configuration. +which will receive events for all incoming channels may be supplied in the configuration. + +The backend can also call Lua functions repeatedly using a timer, allowing users to implement time-based +functionality (such as evaluating a fixed mathematical function or outputting periodic updates). The following functions are provided within the Lua interpreter for interaction with the MIDIMonster | Function | Usage example | Description | |-------------------------------|-------------------------------|---------------------------------------| | `output(string, number)` | `output("foo", 0.75)` | Output a value event to a channel on this instance | -| `interval(function, number)` | `interval(update, 100)` | Register a function to be called periodically. Intervals are milliseconds (rounded to the nearest 10 ms). Calling `interval` on a Lua function multiple times updates the interval. Specifying `0` as interval stops periodic calls to the function | +| `interval(function, number)` | `interval(update, 100)` | Register a function to be called periodically. Intervals are milliseconds (rounded to the nearest 10 ms). Calling `interval` on a Lua function multiple times updates the interval. Specifying `0` as interval stops periodic calls to the function. Do not call this function from within a Lua thread. | | `cleanup_handler(function)` | `cleanup_handler(shutdown)` | Register a function to be called when the instance is destroyed (on MIDIMonster shutdown). One cleanup handler can be registered per instance. Calling this function when the instance already has a cleanup handler registered replaces the handler, returning the old one. | | `input_value(string)` | `input_value("foo")` | Get the last input value on a channel on this instance | | `output_value(string)` | `output_value("bar")` | Get the last output value on a channel on this instance | @@ -23,18 +26,27 @@ The following functions are provided within the Lua interpreter for interaction | `thread(function)` | `thread(run_show)` | Run a function as a Lua thread (see below) | | `sleep(number)` | `sleep(100)` | Suspend current thread for time specified in milliseconds | +While a channel handler executes, calling `input_value` for that channel returns the previous value. Once +the handler returns, the internal buffer is updated. + Example script: ```lua +-- This function is called when there are incoming events on input channel `bar` +-- It outputs half the input value on the channel `foo` function bar(value) output("foo", value / 2); end +-- This function is registered below to execute every second +-- It toggles output channel `bar` every time it is called by storing the next state in the variable `step` step = 0 function toggle() output("bar", step * 1.0); step = (step + 1) % 2; end +-- This function is registered below to run as a Lua thread +-- It loops infinitely and toggles the output channel `narf` every second function run_show() while(true) do sleep(1000); @@ -44,10 +56,12 @@ function run_show() end end +-- This function is registered below to be called when the MIDIMonster shuts down function save_values() -- Store state to a file, for example end +-- Register the functions interval(toggle, 1000) thread(run_show) cleanup_handler(save_values) -- cgit v1.2.3