Browse Source

Add file watcher to automatically reload script upon change.

tags/v1.1.1
Andrew Belt 5 years ago
parent
commit
0612b0c937
2 changed files with 130 additions and 99 deletions
  1. +13
    -1
      Makefile
  2. +117
    -98
      src/Prototype.cpp

+ 13
- 1
Makefile View File

@@ -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)


+ 117
- 98
src/Prototype.cpp View File

@@ -4,7 +4,9 @@
#include <fstream>
#include <sstream>
#include <mutex>
#include <thread>
#include "ScriptEngine.hpp"
#include <libfswatch/c/libfswatch.h>


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<std::mutex> 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<std::mutex> 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<std::mutex> 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 = &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<LoadScriptItem>("Load script");
loadScriptItem->module = module;
loadScriptItem->rightText = RACK_MOD_CTRL_NAME "+O";
menu->addChild(loadScriptItem);

ReloadScriptItem* reloadScriptItem = createMenuItem<ReloadScriptItem>("Reload/rerun script");
reloadScriptItem->rightText = RACK_MOD_CTRL_NAME "+J";
reloadScriptItem->module = module;
menu->addChild(reloadScriptItem);

SaveScriptItem* saveScriptItem = createMenuItem<SaveScriptItem>("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<Prototype*>(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<Prototype*>(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]);
}
}
};


Loading…
Cancel
Save