diff --git a/Makefile b/Makefile index c677bc0..a9430a3 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ RACK_DIR ?= ../.. -FLAGS += +FLAGS += -Idep/include CFLAGS += CXXFLAGS += @@ -12,6 +12,18 @@ DISTRIBUTABLES += res examples DISTRIBUTABLES += $(wildcard LICENSE*) +# fswatch +fswatch := dep/lib/libfswatch.a +DEPS += $(fswatch) +OBJECTS += $(fswatch) +$(fswatch): + cd dep && $(WGET) "https://github.com/emcrisostomo/fswatch/releases/download/1.14.0/fswatch-1.14.0.tar.gz" + cd dep && $(SHA256) fswatch-1.14.0.tar.gz 44d5707adc0e46d901ba95a5dc35c5cc282bd6f331fcf9dbf9fad4af0ed5b29d + cd dep && $(UNTAR) fswatch-1.14.0.tar.gz + cd dep/fswatch-1.14.0 && $(CONFIGURE) --enable-shared=no + cd dep/fswatch-1.14.0 && $(MAKE) + cd dep/fswatch-1.14.0 && $(MAKE) install + # Duktape duktape := dep/duktape-2.4.0/src/duktape.c DEPS += $(duktape) diff --git a/src/Prototype.cpp b/src/Prototype.cpp index 8a337b1..1d188ad 100644 --- a/src/Prototype.cpp +++ b/src/Prototype.cpp @@ -4,7 +4,9 @@ #include #include #include +#include #include "ScriptEngine.hpp" +#include using namespace rack; @@ -42,6 +44,9 @@ struct Prototype : Module { ScriptEngine::ProcessBlock block; int bufferIndex = 0; + FSW_SESSION* fsw = NULL; + std::thread watchThread; + Prototype() { config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); for (int i = 0; i < NUM_ROWS; i++) @@ -49,22 +54,18 @@ struct Prototype : Module { for (int i = 0; i < NUM_ROWS; i++) configParam(SWITCH_PARAMS + i, 0.f, 1.f, 0.f, string::f("Switch %d", i + 1)); - clearScriptEngine(); + setPath(""); } ~Prototype() { - std::lock_guard lock(scriptMutex); - clearScriptEngine(); + setPath(""); } void onReset() override { - reloadScript(); + setScript(script); } void process(const ProcessArgs& args) override { - if (!scriptEngine) - return; - // Frame divider for reducing sample rate if (++frame < frameDivider) return; @@ -97,7 +98,8 @@ struct Prototype : Module { if (scriptEngine) { if (scriptEngine->process()) { WARN("Script %s process() failed. Stopped script.", path.c_str()); - clearScriptEngine(); + delete scriptEngine; + scriptEngine = NULL; return; } } @@ -122,11 +124,72 @@ struct Prototype : Module { outputs[OUT_OUTPUTS + i].setVoltage(block.outputs[i][bufferIndex]); } - void clearScriptEngine() { + void setPath(std::string path) { + // Cleanup + if (fsw) { + fsw_stop_monitor(fsw); + fsw_destroy_session(fsw); + watchThread.join(); + fsw = NULL; + } + this->path = ""; + setScript(""); + + if (path == "") + return; + + this->path = path; + loadPath(); + + if (this->script == "") + return; + + // Watch file + FSW_STATUS err = fsw_init_library(); + if (err == FSW_OK) { +#ifdef ARCH_LIN + fsw_monitor_type type = inotify_monitor_type; +#endif + fsw = fsw_init_session(type); + fsw_add_path(fsw, this->path.c_str()); + fsw_set_callback(fsw, watchCallback, this); + fsw_set_allow_overflow(fsw, false); + fsw_set_latency(fsw, 0.5); + watchThread = std::thread(watchRun, fsw); + } + } + + void loadPath() { + // Read file + std::ifstream file; + file.exceptions(std::ifstream::failbit | std::ifstream::badbit); + try { + file.open(path); + std::stringstream buffer; + buffer << file.rdbuf(); + std::string script = buffer.str(); + setScript(script); + } + catch (const std::runtime_error& err) { + // Fail silently + } + } + + void setScript(std::string script) { + std::lock_guard lock(scriptMutex); + // Reset script state if (scriptEngine) { delete scriptEngine; scriptEngine = NULL; } + this->script = ""; + this->engineName = ""; + this->message = ""; + // Reset process state + frameDivider = 32; + frame = 0; + block = ScriptEngine::ProcessBlock(); + bufferIndex = 0; // Reset outputs and lights because they might hold old values for (int i = 0; i < NUM_ROWS; i++) outputs[OUT_OUTPUTS + i].setVoltage(0.f); @@ -136,26 +199,11 @@ struct Prototype : Module { for (int i = 0; i < NUM_ROWS; i++) for (int c = 0; c < 3; c++) lights[SWITCH_LIGHTS + i * 3 + c].setBrightness(0.f); - // Reset settings - frameDivider = 32; - frame = 0; - block = ScriptEngine::ProcessBlock(); - bufferIndex = 0; - } - void setScriptString(std::string path, std::string script) { - std::lock_guard lock(scriptMutex); - message = ""; - this->path = ""; - this->script = ""; - this->engineName = ""; - clearScriptEngine(); - // Get ScriptEngine from path extension - if (path == "") { - // Empty path means no script is requested. Fail silently. + if (script == "") return; - } - INFO("Loading script %s", path.c_str()); + + // Create script engine from path extension std::string ext = string::filenameExtension(string::filename(path)); scriptEngine = createScriptEngine(ext); if (!scriptEngine) { @@ -164,33 +212,37 @@ struct Prototype : Module { } scriptEngine->module = this; scriptEngine->block = █ - this->path = path; - this->script = script; - this->engineName = scriptEngine->getEngineName(); - // Read file - std::ifstream file; - file.exceptions(std::ifstream::failbit | std::ifstream::badbit); - try { - file.open(this->path); - std::stringstream buffer; - buffer << file.rdbuf(); - this->script = buffer.str(); - } - catch (const std::runtime_error& err) { - WARN("Script %s not found, using stored script string", this->path.c_str()); - } + // Run script - if (this->script == "") { - message = "Could not load script."; - clearScriptEngine(); + if (scriptEngine->run(path, script)) { + // Error message should have been set by ScriptEngine + delete scriptEngine; + scriptEngine = NULL; return; } - if (scriptEngine->run(this->path, this->script)) { - // Error message should have been set by ScriptEngine - clearScriptEngine(); + this->script = script; + this->engineName = scriptEngine->getEngineName(); + } + + static void watchRun(FSW_SESSION* fsw) { + fsw_start_monitor(fsw); + } + + static void watchCallback(fsw_cevent const* const events, const unsigned int event_num, void* data) { + Prototype* that = (Prototype*) data; + if (event_num < 1) return; + + // Look for flags + for (unsigned i = 0; i < event_num; i++) { + for (unsigned j = 0; j < events[i].flags_num; j++) { + fsw_event_flag flag = events[i].flags[j]; + if (flag == Created || flag == Updated) { + that->loadPath(); + return; + } + } } - INFO("Successfully ran script %s", this->path.c_str()); } json_t* dataToJson() override { @@ -204,11 +256,19 @@ struct Prototype : Module { void dataFromJson(json_t* rootJ) override { json_t* pathJ = json_object_get(rootJ, "path"); - json_t* scriptJ = json_object_get(rootJ, "script"); - if (pathJ && scriptJ) { + if (pathJ) { std::string path = json_string_value(pathJ); - std::string script = std::string(json_string_value(scriptJ), json_string_length(scriptJ)); - setScriptString(path, script); + setPath(path); + } + + // Only get the script string if the script file wasn't found. + if (this->path != "" && this->script == "") { + WARN("Script file %s not found, using script in patch", this->path.c_str()); + json_t* scriptJ = json_object_get(rootJ, "script"); + if (scriptJ) { + std::string script = std::string(json_string_value(scriptJ), json_string_length(scriptJ)); + setScript(script); + } } } @@ -221,11 +281,7 @@ struct Prototype : Module { std::string path = pathC; std::free(pathC); - setScriptString(path, ""); - } - - void reloadScript() { - setScriptString(path, script); + setPath(path); } void saveScriptDialog() { @@ -248,6 +304,7 @@ struct Prototype : Module { std::ofstream f(newPath); f << script; + // Set path directly path = newPath; } }; @@ -339,14 +396,6 @@ struct LoadScriptItem : MenuItem { }; -struct ReloadScriptItem : MenuItem { - Prototype* module; - void onAction(const event::Action& e) override { - module->reloadScript(); - } -}; - - struct SaveScriptItem : MenuItem { Prototype* module; void onAction(const event::Action& e) override { @@ -417,14 +466,8 @@ struct PrototypeWidget : ModuleWidget { LoadScriptItem* loadScriptItem = createMenuItem("Load script"); loadScriptItem->module = module; - loadScriptItem->rightText = RACK_MOD_CTRL_NAME "+O"; menu->addChild(loadScriptItem); - ReloadScriptItem* reloadScriptItem = createMenuItem("Reload/rerun script"); - reloadScriptItem->rightText = RACK_MOD_CTRL_NAME "+J"; - reloadScriptItem->module = module; - menu->addChild(reloadScriptItem); - SaveScriptItem* saveScriptItem = createMenuItem("Save script as"); saveScriptItem->module = module; menu->addChild(saveScriptItem); @@ -432,32 +475,8 @@ struct PrototypeWidget : ModuleWidget { void onPathDrop(const event::PathDrop& e) override { Prototype* module = dynamic_cast(this->module); - if (module && !e.paths.empty()) { - module->setScriptString(e.paths[0], ""); - } - } - - void onHoverKey(const event::HoverKey& e) override { - ModuleWidget::onHoverKey(e); - if (e.isConsumed()) - return; - - Prototype* module = dynamic_cast(this->module); - if (e.action == GLFW_PRESS) { - switch (e.key) { - case GLFW_KEY_O: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - module->loadScriptDialog(); - e.consume(this); - } - } break; - case GLFW_KEY_J: { - if ((e.mods & RACK_MOD_MASK) == RACK_MOD_CTRL) { - module->reloadScript(); - e.consume(this); - } - } break; - } + if (module && e.paths.size() >= 1) { + module->setPath(e.paths[0]); } } };