From 8823da5c0d6a6e95e9d9343de59920dedba5c424 Mon Sep 17 00:00:00 2001 From: Ewoud Date: Sun, 8 Dec 2024 11:21:07 +0100 Subject: [PATCH] ELS latest, add pub sub, add presets, StarString, cpuTime pio.ini - ESPLiveScript v3.1 SysModInstances - dash vars: use publish SysModModel - Coord3D constructors - add publish and subscribe - add VarFunction and VarEvent(s)PS (for pubsub) - add presets JSON - triggerEvent: add publish - constructor: read presets.json - UI show eventsVar and eventsPS (devMode) - doWriteModel: save presets SysModNetwork - add presets (WIP) SysModUI - add VCR (WIP) SysModule - add StarString - add cpuTime - addPresets function (preset, assign preset , clear preset SysModules - show cpuTime using cycleCount (WIP) UserModLive - sync/preKill/postKill as static class Vars (sync <- show(M)) - add syncWithSync (using waitingOnLiveScript), to be used by other modules to sync in - cleanup show->sync - remove resetStat - rename fps1/2 to fpsCycles and fpsSync (to be removed) - add error (WIP) - remove show and resetStat as external funs --- misc/misc.txt | 2 +- misc/model.json | 4 +- platformio.ini | 7 +- src/Sys/SysModInstances.h | 5 +- src/Sys/SysModModel.cpp | 56 ++++++++-- src/Sys/SysModModel.h | 37 ++++++- src/Sys/SysModNetwork.cpp | 2 + src/Sys/SysModUI.h | 4 + src/SysModule.cpp | 208 ++++++++++++++++++++++++++++++++++++++ src/SysModule.h | 54 ++++++++++ src/SysModules.cpp | 25 +++++ src/User/UserModLive.cpp | 188 ++++++++++++++++------------------ src/User/UserModLive.h | 10 +- 13 files changed, 476 insertions(+), 126 deletions(-) create mode 100644 src/SysModule.cpp diff --git a/misc/misc.txt b/misc/misc.txt index 5121bf3..8ce4de4 100644 --- a/misc/misc.txt +++ b/misc/misc.txt @@ -1,7 +1,7 @@ Release steps - update dates in files - check all lib_deps on right version -- clean and build to check latest versions (before github actions fail on this) +- clean and build to check latest versions (before github actions fail on this) - (remove .pio and rebuild) WebHook to MM discord https://discord.com/api/webhooks/1229821142479536179/UeO3ryPqUyHABYTAuGtHZK6t7yghM0ZETN0LUYpg32KBleGhm-zvaYDzkyYjiqaVqt0T/github diff --git a/misc/model.json b/misc/model.json index 59c6e8d..0e2ed9e 100644 --- a/misc/model.json +++ b/misc/model.json @@ -951,7 +951,7 @@ "fun": 74 }, { - "id": "fps1", + "id": "fpsCycles", "type": "text", "pid": "LiveScripts", "ro": true, @@ -961,7 +961,7 @@ "value": "0 /s" }, { - "id": "fps2", + "id": "fpsFrame", "type": "text", "pid": "LiveScripts", "ro": true, diff --git a/platformio.ini b/platformio.ini index 6c05bb6..c478893 100644 --- a/platformio.ini +++ b/platformio.ini @@ -79,13 +79,13 @@ build_flags = -D STARBASE_USERMOD_LIVE -D EXTPRINTF=ppf ;redirect Live Script prints to StarBase print lib_deps = - https://github.com/ewowi/ESPLiveScript.git#main ;1.2.0. ewowi repo adds some proposed PR's and makes sure we don't have unexpected updates + https://github.com/ewowi/ESPLiveScript.git#d62bf25 ; v3.1 ;ewowi repo adds some proposed PR's and makes sure we don't have unexpected updates [STARBASE] build_flags = -D APP=StarBase -D PIOENV=$PIOENV - -D VERSION=24112412 ; Date and time (GMT!), update at every commit!! + -D VERSION=24120809 ; Date and time (GMT!), update at every commit!! -D LFS_THREADSAFE ; enables use of semaphores in LittleFS driver -D STARBASE_DEVMODE -mtext-section-literals ;otherwise [UserModLive::setup()]+0xa17): dangerous relocation: l32r: literal target out of range (try using text-section-literals) @@ -156,7 +156,8 @@ lib_deps = ; https://github.com/platformio/platform-espressif32/issues/1360 ; https://community.platformio.org/t/support-esp32-wrover-module/17717 -; note: flasghing to new board goes wrong, try first without ICVD then with and without etc until it works (witchcraft) +; note: flashing to new board goes wrong, try first without ICVD then with and without etc until it works (witchcraft) +; Guru Meditation Error: Core 0 panic'ed (Cache disabled but cached memory region accessed). [env:esp-wrover-kit] board = esp-wrover-kit ; esp-wrover-kit ;https://github.com/platformio/platform-espressif32/blob/develop/boards/esp-wrover-kit.json ; recommended to pin to a platform version, see https://github.com/platformio/platform-espressif32/releases diff --git a/src/Sys/SysModInstances.h b/src/Sys/SysModInstances.h index b3b5835..87469de 100644 --- a/src/Sys/SysModInstances.h +++ b/src/Sys/SysModInstances.h @@ -250,7 +250,10 @@ class SysModInstances:public SysModule { return true; case onUI: // call onUI of the base variable for the new variable - mdl->varEvents[variable.var["fun"]](insVariable, rowNr, onUI); + if (variable.var["fun"].as() != UINT8_MAX) + mdl->varEvents[variable.var["fun"]](insVariable, rowNr, onUI); + else + insVariable.publish(onUI, rowNr); //is insVariable subscribed ??? return true; case onChange: { //do not set this initially!!! diff --git a/src/Sys/SysModModel.cpp b/src/Sys/SysModModel.cpp index 0af3f29..2f55f2d 100644 --- a/src/Sys/SysModModel.cpp +++ b/src/Sys/SysModModel.cpp @@ -97,7 +97,7 @@ { if (rowNr != UINT8_MAX) { if (childVariable.order() < 0) { //if not updated - valArray[rowNr] = (char*)0; // set element in valArray to 0 + valArray[rowNr] = (char*)0; // set element in valArray to 0 (is content deleted from memory?) ppf("varPostDetails %s.%s[%d] <- null\n", id(), childVariable.id(), rowNr); // setValue(var, -99, rowNr); //set value -99 @@ -247,10 +247,13 @@ bool result = false; //call varEvent if exists - if (!var["fun"].isNull()) {//isNull needed here! + if (!var["fun"].isNull()) { //isNull needed here! size_t funNr = var["fun"]; if (funNr < mdl->varEvents.size()) { + // ppf("voor v1 call %s.%s[%d] %d %d %d\n", pid(), id(), rowNr, funNr, eventType, mdl->varEvents.size()); result = mdl->varEvents[funNr](*this, rowNr, eventType); + + //all ppf here: if (result && !readOnly()) { //send rowNr = 0 if no rowNr //only print vars with a value and not onSetValue as that changes a lot due to instances clients etc (tbd) //don't print if onSetValue or oldValue is null @@ -269,10 +272,13 @@ } } //varEvent exists } - else + else if (funNr == UINT8_MAX) + result = publish(eventType, rowNr); + else ppf("dev triggerEvent function nr %s.%s outside bounds %d >= %d\n", pid(), id(), funNr, mdl->varEvents.size()); } //varEvent exists + //delete pointers after calling var.onDelete as var.onDelete might need the values if (eventType == onAdd || eventType == onDelete) { @@ -486,7 +492,7 @@ //sets the default values, by varEvent if exists, otherwise manually (by returning true) if (doSetValue) { bool onSetValueExists = false; - if (!var["fun"].isNull()) { + if (!var["fun"].isNull()) { // && var["fun"] != UINT8_MAX onSetValueExists = triggerEvent(onSetValue, mdl->setValueRowNr); } if (!onSetValueExists) { //setValue provided (if not null) @@ -516,6 +522,7 @@ SysModModel::SysModModel() :SysModule("Model") { model = new JsonDocument(&allocator); + presets = new JsonDocument(&allocator); JsonArray root = model->to(); //create @@ -526,6 +533,9 @@ SysModModel::SysModModel() :SysModule("Model") { } else { root = model->to(); //re create the model as it is corrupted by readFromFile } + + files->readObjectFromFile("/presets.json", presets); //do not create if not exists + } void SysModModel::setup() { @@ -565,6 +575,16 @@ void SysModModel::setup() { default: return false; }}); + Variable currentVar; + currentVar = ui->initText(parentVar, "eventsVar", nullptr, 16, true); + currentVar.subscribe(onLoop1s, [this](EventArguments) { + variable.setValueF("%d x %d = %d", varEvents.size(), sizeof(VarEvent), varEvents.size() * sizeof(VarEvent)); + }); + currentVar = ui->initText(parentVar, "eventsPS", nullptr, 16, true); + currentVar.subscribe(onLoop1s, [this](EventArguments) { + variable.setValueF("%d x %d = %d (%d + %d + %d)", varEventsPS.size(), sizeof(VarEventPS), varEventsPS.size() * sizeof(VarEventPS), sizeof(Variable), sizeof(VarFunction), sizeof(uint8_t)); + }); + #endif //STARBASE_DEVMODE } @@ -593,6 +613,10 @@ void SysModModel::loop20ms() { // print->printJson("Write model", *model); //this shows the model before exclusion + if (!presets->isNull()) + files->writeObjectToFile("/presets.json", presets); + + doWriteModel = false; } } @@ -707,8 +731,8 @@ Variable SysModModel::initVar(Variable parent, const char * id, const char * typ // if (itr!=ucFunctions.end()) //found // var["varEvent"] = distance(ucFunctions.begin(), itr); //assign found function // else { //not found - mdl->varEvents.push_back(varEvent); //add new function - var["fun"] = mdl->varEvents.size()-1; + varEvents.push_back(varEvent); //add new function + var["fun"] = varEvents.size()-1; // } if (varEvent(variable, UINT8_MAX, onLoop)) { //test run if it supports loop @@ -729,6 +753,26 @@ Variable SysModModel::initVar(Variable parent, const char * id, const char * typ return variable; } +void Variable::subscribe(uint8_t eventType, const VarFunction &varFunction) { + ppf("subscribe %d %s.%s\n", eventType, pid(), id()); + mdl->varEventsPS.push_back({*this, eventType, varFunction}); //add new function + var["fun"] = UINT8_MAX; //to trigger response from ui +} + +bool Variable::publish(uint8_t eventType, uint8_t rowNr) { + bool found = false; + for (VarEventPS &varEventPS: mdl->varEventsPS) { + if (eventType == varEventPS.eventType && strncmp(pid(), varEventPS.variable.pid(), 32) == 0 && strncmp(id(), varEventPS.variable.id(), 32) == 0) { + if (strcmp(id(), "effect") == 0 && eventType!= onLoop1s) + ppf("publish %s.%s[%d] %d=%d %s.%s\n", pid(), id(), rowNr, eventType, varEventPS.eventType , varEventPS.variable.pid(), varEventPS.variable.id()); + varEventPS.varFunction(*this, rowNr, eventType); + found = true; + } + } + return found; +} + + JsonObject SysModModel::walkThroughModel(std::function fun, JsonObject parentVar) { for (JsonObject var : parentVar.isNull()?model->as(): parentVar["n"]) { // ppf(" %s", var["id"].as()); diff --git a/src/Sys/SysModModel.h b/src/Sys/SysModModel.h index ca2cef0..4cc22be 100644 --- a/src/Sys/SysModModel.h +++ b/src/Sys/SysModModel.h @@ -33,6 +33,15 @@ struct Coord3D { // this->z = y; // } + Coord3D() { + } + + Coord3D(int x, int y, int z) { + this->x = x; + this->y = y; + this->z = z; + } + //comparisons bool operator!=(Coord3D rhs) { // ppf("Coord3D compare%d %d %d %d %d %d\n", x, y, z, rhs.x, rhs.y, rhs.z); @@ -202,6 +211,16 @@ enum eventTypes f_count }; +class Variable; //forward + +typedef std::function FindFun; + +#define EventArguments Variable variable, uint8_t rowNr, uint8_t eventType +// #define EventArguments2 Variable variable, uint8_t rowNr + +// https://stackoverflow.com/questions/59111610/how-do-you-declare-a-lambda-function-using-typedef-and-then-use-it-by-passing-to +typedef std::function VarEvent; +typedef std::function VarFunction; // void: no return class Variable { public: @@ -216,6 +235,7 @@ class Variable { //core methods const char *pid() const {return var["pid"];} const char *id() const {return var["id"];} + const char *type() const {return var["type"];} JsonVariant value() const {return var["value"];} JsonVariant value(uint8_t rowNr) const {return var["value"][rowNr];} @@ -235,6 +255,7 @@ class Variable { //recursively remove all value[rowNr] from children of var void removeValuesForRow(uint8_t rowNr); + bool valIsArray() const {return var["value"].is();} JsonArray valArray() const {if (var["value"].is()) return var["value"]; else return JsonArray(); } //if variable is a table, loop through its rows @@ -334,14 +355,18 @@ class Variable { //gives a variable an initital value returns true if setValue must be called bool initValue(int min = 0, int max = 255, int pointer = 0); -}; //class Variable + void subscribe(uint8_t eventType, const VarFunction &varFunction = nullptr); + bool publish(uint8_t eventType, uint8_t rowNr = UINT8_MAX); -typedef std::function FindFun; +}; //class Variable -#define EventArguments Variable variable, uint8_t rowNr, uint8_t eventType +//For Publish and Subscribe events +struct VarEventPS { + Variable variable; //8 bytes: cannot be a pointer as Variable is volatile, the var inside variable is not volatile + uint8_t eventType; //1 byte but rounded to 4 bytes (room for more variables, e.g. rowNr?) + VarFunction varFunction; //function: 16 bytes +}; //total 28 bytes -// https://stackoverflow.com/questions/59111610/how-do-you-declare-a-lambda-function-using-typedef-and-then-use-it-by-passing-to -typedef std::function VarEvent; class SysModModel: public SysModule { @@ -349,6 +374,7 @@ class SysModModel: public SysModule { RAM_Allocator allocator; JsonDocument *model = nullptr; + JsonDocument *presets = nullptr; bool doWriteModel = false; @@ -357,6 +383,7 @@ class SysModModel: public SysModule { int varCounter = 1; //start with 1 so it can be negative, see var["o"] std::vector varEvents; + std::vector varEventsPS; SysModModel(); void setup() override; diff --git a/src/Sys/SysModNetwork.cpp b/src/Sys/SysModNetwork.cpp index d29c344..22175d1 100644 --- a/src/Sys/SysModNetwork.cpp +++ b/src/Sys/SysModNetwork.cpp @@ -201,6 +201,8 @@ void SysModNetwork::setup() { default: return false; }}); + addPresets(parentVar.var); + } void SysModNetwork::loop1s() { diff --git a/src/Sys/SysModUI.h b/src/Sys/SysModUI.h index 4a276c6..582ffc0 100644 --- a/src/Sys/SysModUI.h +++ b/src/Sys/SysModUI.h @@ -167,6 +167,10 @@ class SysModUI: public SysModule { return initVarAndValue(parent, id, "url", value, 0, 0, readOnly, varEvent); } + Variable initVCR(Variable parent, const char * id, bool readOnly = false, const VarEvent &varEvent = nullptr) { + return initVarAndValue(parent, id, "vcr", false, 0, 0, readOnly, varEvent); + } + //initVarAndValue using basic value template Variable initVarAndValue(Variable parent, const char * id, const char * type, Type value, int min = 0, int max = 255, bool readOnly = true, const VarEvent &varEvent = nullptr) { diff --git a/src/SysModule.cpp b/src/SysModule.cpp new file mode 100644 index 0000000..68d9287 --- /dev/null +++ b/src/SysModule.cpp @@ -0,0 +1,208 @@ +/* + @title StarBase + @file SysModule.cpp + @date 20241105 + @repo https://github.com/ewowi/StarBase, submit changes to this file as PRs to ewowi/StarBase + @Authors https://github.com/ewowi/StarBase/commits/main + @Copyright © 2024 Github StarBase Commit Authors + @license GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 + @license For non GPL-v3 usage, commercial licenses must be purchased. Contact moonmodules@icloud.com +*/ + +#include "SysModule.h" + +#include "Sys/SysModUI.h" + +void SysModule::addPresets(JsonObject parentVar) { + Variable parentVariable = Variable(parentVar); + Variable currentVar = ui->initSelect(parentVariable, "preset", (uint8_t)0); + + currentVar.subscribe(onUI, [this](Variable variable, uint8_t rowNr, uint8_t eventType) { + ppf("publish preset.onUI %s.%s [%d]\n", variable.pid(), variable.id(), rowNr); + JsonArray options = variable.setOptions(); + + JsonArray modulePresets = mdl->presets->as()[name]; + + for (int i=0; i()); //this causes crashes in asyncwebserver !!! + buf = modulePresets[i]["name"].as(); + ppf("preset.onUI %02d %s (%d)\n", i, buf.getString(), buf.length()); + } + options.add(buf.getString()); //copy! + } + + // print->printJson(" options", options); + + }); + + currentVar.subscribe(onChange, [this](Variable variable, uint8_t rowNr, uint8_t eventType) { + + //on Change: save the old value, retrieve the new value and set all values + //load the complete presets.json, make the changes, save it (using save button... + + uint8_t presetValue = variable.var["value"]; + ppf("publish preset.onchange %s.%s [%d] %d %s\n", variable.pid(), variable.id(), rowNr, presetValue, variable.valueString().c_str()); + + JsonObject allPresets = mdl->presets->as(); + + if (!allPresets.isNull()) { + + JsonArray modulePresets = allPresets[name]; + if (!modulePresets.isNull()) { + + for (JsonPair pidPair: modulePresets[presetValue].as()) { + for (JsonPair idPair: pidPair.value().as()) { + ppf("load %s.%s: %s\n", pidPair.key().c_str(), idPair.key().c_str(), idPair.value().as().c_str()); + if (pidPair.key() != "name") { + JsonVariant jv = idPair.value(); + if (jv.is()) { + uint8_t rowNr = 0; + for (JsonVariant element: jv.as()) { + mdl->setValue(pidPair.key().c_str(), idPair.key().c_str(), element, rowNr++); + } + } + else + mdl->setValue(pidPair.key().c_str(), idPair.key().c_str(), jv); + } + } + } + } + } + + }); + + currentVar = ui->initButton(parentVariable, "assignPreset", false); + + currentVar.subscribe(onUI, [this](Variable variable, uint8_t rowNr, uint8_t eventType) { + variable.setLabel("✅"); + }); + + currentVar.subscribe(onChange, [this, &parentVariable](Variable variable, uint8_t rowNr, uint8_t eventType) { + ppf("assignPreset.onUI \n"); + //save this to the first free slot + //give that a name + //select that one + + Variable presetVariable = Variable(name, "preset"); + + uint8_t presetValue = presetVariable.var["value"]; + + JsonObject allPresets = mdl->presets->as(); + if (allPresets.isNull()) allPresets = mdl->presets->to(); //create + + JsonArray modulePresets = allPresets[name]; + if (modulePresets.isNull()) modulePresets = allPresets[name].to(); + + JsonObject m = mdl->findVar("m", name); //find this module (effects) + + // print->printJson("m", m); ppf("\n"); + // print->printJson("pv", parentVariable.var); ppf("\n"); + + uint8_t presetIndex = 0; + if (modulePresets[presetValue].isNull()) { //if slot is 0 use it + presetIndex = presetValue; + } else { + //find the first empty slot, starting from current position, if none, add + //loop over slots + for (auto modulePreset: modulePresets) { + if (presetIndex >= presetValue) { + //if slot empty use it. + if (modulePreset.isNull()) { + break; + } + } + presetIndex++; + } + } + //post: presetIndex contains empty slot or is new entry + + StarString result; + + if (!m["n"].isNull()) { + // print->printJson("walk for", m["n"]); ppf("\n"); + modulePresets[presetIndex].to();//empty + mdl->walkThroughModel([modulePresets, presetIndex, &result](JsonObject parentVar, JsonObject var) { + Variable variable = Variable(var); + if (!variable.readOnly() && strncmp(variable.id(), "preset", 32) != 0 ) { //exclude preset + ppf("save %s.%s: %s\n", variable.pid(), variable.id(), variable.valueString().c_str()); + modulePresets[presetIndex][variable.pid()][variable.id()] = var["value"]; + + if (var["type"] == "text") { + result += variable.valueString().c_str(); //concat + result.catSep(", "); + } else if (var["type"] == "select") { + char option[32]; + if (variable.valIsArray()) + variable.getOption(option, var["value"][0]); //only one for now + else + variable.getOption(option, var["value"]); + ppf("add option %s.%s[0] %s\n", variable.pid(), variable.id(), option); + result += option; //concat + result.catSep(", "); + } + } + return JsonObject(); //don't stop + }, m); //walk using m["n"] + } + + if (result.length() == 0) result.format("Preset %d", presetIndex); //if no text found then default text + + modulePresets[presetIndex]["name"] = result.getString(); //store the text + + presetVariable.publish(onUI); //reload ui for new list of values + + if (presetIndex != presetValue) presetVariable.setValue(presetIndex); //set the new value, if changed + + }); + + currentVar = ui->initButton(parentVariable, "clearPreset", false); //clear preset + + currentVar.subscribe(onUI, [this](Variable variable, uint8_t rowNr, uint8_t eventType) { + variable.setLabel("❌"); + }); + + currentVar.subscribe(onChange, [this](Variable variable, uint8_t rowNr, uint8_t eventType) { + ppf("delete.onUI \n"); + //free this slot + //remove the name + + Variable presetVariable = Variable(name, "preset"); + + uint8_t presetValue = presetVariable.var["value"]; + + JsonObject allPresets = mdl->presets->as(); + + if (!allPresets.isNull()) { + + JsonArray modulePresets = allPresets[name]; + if (!modulePresets.isNull()) { + + if (presetValue < modulePresets.size()) { + + modulePresets[presetValue] = (char*)0; // set element in valArray to 0 (is content deleted from memory?) + + // presetVariable.publish(onUI); //reload ui for new list of values + } + + uint8_t presetIndex = presetValue; + //cleanup + while (modulePresets.size() && modulePresets[modulePresets.size()-1].isNull()) { + modulePresets.remove(modulePresets.size()-1); + presetIndex = modulePresets.size() - 1; + } + + presetVariable.publish(onUI); //reload ui for new list of values + + if (presetIndex != presetValue) presetVariable.setValue(presetIndex); //set the new value, if changed + } + } + + }); + + // ui->initVCR(parentVariable, "vcr", false); //for next release + +} \ No newline at end of file diff --git a/src/SysModule.h b/src/SysModule.h index 505ca00..b68830e 100644 --- a/src/SysModule.h +++ b/src/SysModule.h @@ -36,6 +36,56 @@ struct VectorString { char s[32]; }; +class StarString { + char s[32] = ""; + char sep[3] = ""; + + public: + + //assign + StarString& operator=(const char *rhs) { + strlcpy(s, rhs, sizeof(s)); + return *this; + } + + //concat + StarString& operator+(const char *rhs) { + strlcat(s, rhs, sizeof(s)); + return *this; + } + + //concat + StarString& operator+=(const char *rhs) { + strlcat(s, sep, sizeof(s)); + strlcat(s, rhs, sizeof(s)); + // strlcpy(sep, ", ", sizeof(s)); + return *this; + } + + void catSep(const char* s) { + strlcpy(sep, s, sizeof(sep)); + } + + size_t length() { + return strnlen(s, sizeof(s)); + } + + char *format(const char * format, ...) { + va_list args; + va_start(args, format); + + size_t len = vsnprintf(s, sizeof(s), format, args); + + va_end(args); + return s; + } + + char *getString() { + return s; + } + +}; + class SysModule { public: @@ -47,6 +97,8 @@ class SysModule { unsigned long tenSecondMillis = millis() - random(10000); //random within a second // void (SysModule::*loopCached)() = &SysModule::loop; //use virtual cached function for speed??? tested, no difference ... + unsigned long cpuTime = 0; + explicit SysModule(const char * name) { this->name = name; success = true; @@ -65,4 +117,6 @@ class SysModule { virtual void connectedChanged() {onOffChanged();} virtual void enabledChanged() {onOffChanged();} virtual void onOffChanged() {} + + void addPresets(JsonObject parentVar); }; \ No newline at end of file diff --git a/src/SysModules.cpp b/src/SysModules.cpp index 12da579..6bf2936 100644 --- a/src/SysModules.cpp +++ b/src/SysModules.cpp @@ -78,6 +78,29 @@ void SysModules::setup() { return true; default: return false; }}); + + Variable currentVar = ui->initText(tableVar, "cpuTime", nullptr, 32, true); + + currentVar.subscribe(onSetValue, [this](Variable variable, uint8_t rowNr, uint8_t eventType) { + for (size_t rowNr = 0; rowNr < modules.size(); rowNr++) { + StarString buf; + uint16_t lps = modules[rowNr]->cpuTime?ESP.getCpuFreqMHz() * 1000000 / modules[rowNr]->cpuTime:0; //lps + if (lps > 2000) + variable.setValue("~0", rowNr); + else if (lps) { + buf.format("%d ms %d lps", 1000 / lps, lps); + variable.setValue(JsonString(buf.getString()), rowNr); + } + else + variable.setValue("0", rowNr); + + } + }); + + currentVar.subscribe(onLoop1s, [this](Variable variable, uint8_t rowNr, uint8_t eventType) { + variable.triggerEvent(onSetValue); + }); + } void SysModules::loop() { @@ -93,6 +116,7 @@ void SysModules::loop() { // } for (SysModule *module:modules) { if (module->isEnabled && module->success) { + uint32_t cycles = ESP.getCycleCount(); module->loop(); // (module->*module->loopCached)(); //use virtual cached function for speed??? tested, no difference ... if (millis() - module->twentyMsMillis >= 20) { @@ -107,6 +131,7 @@ void SysModules::loop() { module->tenSecondMillis = millis(); module->loop10s(); } + module->cpuTime = (ESP.getCycleCount() - cycles); } } if (newConnection) { diff --git a/src/User/UserModLive.cpp b/src/User/UserModLive.cpp index 2ef0fbc..58573e5 100644 --- a/src/User/UserModLive.cpp +++ b/src/User/UserModLive.cpp @@ -24,90 +24,33 @@ #include "ESPLiveScript.h" //note: contains declarations AND definitions, therefore can only be included once! -long time1; -long time4; -static float _min = 9999; -static float _max = 0; -static uint32_t _nb_stat = 0; -static float _totfps; -static float fps = 0; //integer? -static unsigned long frameCounter = 0; -static uint8_t loopState = 0; //waiting on Live Script -Parser parser = Parser(); - -//external function implementation (tbd: move into class) + static long previousCycleCount; + static uint16_t fps; + static unsigned long frameCounter; //temp, can be removed if syncing tested + static bool waitingOnLiveScript = true; + static bool syncActive = false; -//tbd: move this to LedModFixture as show is Leds related... -static void show() -{ - frameCounter++; - - // SKIPPED: check nargs (must be 3 because arg[0] is self) - long time2 = ESP.getCycleCount(); - - // driver.showPixels(WAIT); // LEDS specific - - //show is done in LedModEffects! - - long time3 = ESP.getCycleCount(); - float k = (float)(time2 - time1) / 240000000; - fps = 1 / k; //StarBase: class variable so it can be shown in UI!!! - float k2 = (float)(time3 - time2) / 240000000; - float fps2 = 1 / k2; - float k3 = (float)(time2 - time4) / 240000000; - float fps3 = 1 / k3; - _nb_stat++; - if (_min > fps && fps > 10 && _nb_stat > 10) - _min = fps; - if (_max < fps && fps < 5000 && _nb_stat > 10) - _max = fps; - if (_nb_stat > 10) - _totfps += fps; - if (_nb_stat%10000 == 0) //every 10 sec. (temp) - //Serial.printf("current show fps:%.2f\tglobal fps:%.2f\tfps animation:%.2f\taverage:%.2f\tmin:%.2f\tmax:%.2f\r\n", fps2, fps3, fps, _totfps / (_nb_stat - 10), _min, _max); - ppf("current show fps:%.2f\tglobal fps:%.2f\tfps animation:%.2f average:%.2f min:%.2f max:%.2f\r\n",fps2, fps3, fps, _totfps / (_nb_stat - 10), _min, _max); - time1 = ESP.getCycleCount(); - time4 = time2; - - // SKIPPED: check that both v1 and v2 are int numbers - // RETURN_VALUE(VALUE_FROM_INT(0), rindex); - delay(1); //to feed the watchdog (also if loopState == 0) - while (loopState != 0) { //not waiting on Live Script - delay(1); //to feed the watchdog - // set to 0 by main loop - } - //do Live Script cycle - loopState = 1; //Live Script produced a frame, main loop will deal with it - // ppf("loopState %d\n", loopState); -} +Parser parser = Parser(); -static void preKill() + void UserModLive::preKill() { ppf("ELS preKill\n"); } -static void postKill() + void UserModLive::postKill() { ppf("ELS postKill\n"); } -static void resetShowStats() -{ - float min = 999; - float max = 0; - _nb_stat = 0; - _totfps = 0; -} - static float _hypot(float x,float y) {return hypot(x,y);} static float _atan2(float x,float y) { return atan2(x,y);} static float _sin(float j) {return sin(j);} static float _cos(float j) {return cos(j);} static float _triangle(float j) {return 1.0 - fabs(fmod(2 * j, 2.0) - 1.0);} static float _time(float j) { - float myVal = sys->now; - myVal = myVal / 65535 / j; // PixelBlaze uses 1000/65535 = .015259. - myVal = fmod(myVal, 1.0); // ewowi: with 0.015 as input, you get fmod(millis/1000,1.0), which has a period of 1 second, sounds right - return myVal; + float myVal = sys->now; + myVal = myVal / 65535 / j; // PixelBlaze uses 1000/65535 = .015259. + myVal = fmod(myVal, 1.0); // ewowi: with 0.015 as input, you get fmod(millis/1000,1.0), which has a period of 1 second, sounds right + return myVal; } void UserModLive::setup() { @@ -148,7 +91,7 @@ static float _time(float j) { addExternalFun("void", "digitalWrite", "(int a1, int a2)", (void *)&digitalWrite); addExternalFun("void", "delay", "(int a1)", (void *)&delay); - exeID = compile(fileName, "void main(){resetStat();setup();while(2>1){loop();sync();}}"); + exeID = compile(fileName, "void main(){setup();while(2>1){loop();sync();}}"); } if (exeID != UINT8_MAX) @@ -165,13 +108,15 @@ static float _time(float j) { default: return false; }}); //script - ui->initText(parentVar, "fps1", nullptr, 10, true, [this](EventArguments) { switch (eventType) { + ui->initText(parentVar, "fpsCycles", nullptr, 10, true, [this](EventArguments) { switch (eventType) { case onLoop1s: - variable.setValueF("%.0f /s", fps, 0); //0 is to force format overload used + variable.setValueF("%d /s", fps, 0); //0 is to force format overload used return true; default: return false; }}); - ui->initText(parentVar, "fps2", nullptr, 10, true, [this](EventArguments) { switch (eventType) { + + //temp + ui->initText(parentVar, "fpsSync", nullptr, 10, true, [this](EventArguments) { switch (eventType) { case onLoop1s: variable.setValueF("%d /s", frameCounter, 0); //0 is to force format overload used frameCounter = 0; @@ -181,6 +126,12 @@ static float _time(float j) { Variable tableVar = ui->initTable(parentVar, "scripts", nullptr, true); + //set the values every second + tableVar.subscribe(onLoop1s, [](Variable variable, uint8_t rowNr, uint8_t eventType) { + for (JsonObject childVar: variable.children()) + Variable(childVar).triggerEvent(onSetValue); + }); + ui->initText(tableVar, "name", nullptr, 32, true, [this](EventArguments) { switch (eventType) { case onSetValue: variable.var["value"].to(); web->addResponse(variable.var, "value", variable.value()); // empty the value @@ -243,9 +194,25 @@ static float _time(float j) { rowNr = 0; for (Executable &exec: scriptRuntime._scExecutables) { exe_info exeInfo = scriptRuntime.getExecutableInfo(exec.name); - char text[30]; - print->fFormat(text, sizeof(text), "%d+%d=%d B", exeInfo.binary_size, exeInfo.data_size, exeInfo.total_size); - variable.setValue(JsonString(text), rowNr++); + StarString text; + text.format("%d+%d=%d B", exeInfo.binary_size, exeInfo.data_size, exeInfo.total_size); + variable.setValue(JsonString(text.getString()), rowNr++); + } + return true; + default: return false; + }}); + + ui->initText(tableVar, "error", nullptr, 32, true, [this](EventArguments) { switch (eventType) { + case onSetValue: + variable.var["value"].to(); web->addResponse(variable.var, "value", variable.value()); // empty the value + rowNr = 0; + for (Executable &exec: scriptRuntime._scExecutables) { + if (exec.error.error) { + StarString text; + const char *error_message = exec.error.error_message.c_str(); + text.format("%d-%d %s (%d)", exec.error.line, exec.error.pos, error_message?error_message:"dev", exec.error.error); + variable.setValue(JsonString(text.getString()), rowNr++); + } } return true; default: return false; @@ -262,7 +229,7 @@ static float _time(float j) { }}); runningPrograms.setPrekill(preKill, postKill); //for clockless driver... - runningPrograms.setFunctionToSync(show); + runningPrograms.setFunctionToSync(sync); } //setup @@ -271,9 +238,6 @@ static float _time(float j) { scScript = ""; //Live Scripts defaults - addExternalFun("void", "show", "()", (void *)&show); //comment if setup/loop model works - // addExternalFun("void", "showM", "()", (void *)&UserModLive::showM); // warning: converting from 'void (UserModLive::*)()' to 'void*' [-Wpmf-conversions] - addExternalFun("void", "resetStat", "()", (void *)&resetShowStats); addExternalFun("float", "atan2", "(float a1, float a2)",(void*)_atan2); addExternalFun("float", "hypot", "(float a1, float a2)",(void*)_hypot); @@ -305,29 +269,46 @@ static float _time(float j) { // ppf("external %s(int arg1, int arg2);\n", name.c_str()); //add to string // } - //testing class functions instead of static - void UserModLive::showM() { - long time2 = ESP.getCycleCount(); - // driver.showPixels(WAIT); - frameCounter++; - - float k = (float)(time2 - time1) / 240000000; //always 240MHz? - fps = 1 / k; - time1 = ESP.getCycleCount(); + void UserModLive::sync() { + frameCounter++; //temp + + fps = ESP.getCpuFreqMHz() * 1000000 / (ESP.getCycleCount() - previousCycleCount); + previousCycleCount = ESP.getCycleCount(); + + // Show fps (fps2): is driver.showpixels() this allows to check that there is no issue with the driver + // The global (fps3): is the result of having driver.showPixel and animation sequentially + // Fps animation (fps): fps for the calculation of one frame + // The global fps equals theorically (fpsshow x fpsanimation) / (fpsshow + fpsanimation) + // So if what you display is the seen fps( animation & driver.showPixel) you should see the global FPS. + // fps is shown as fps1 in the ui, frameCounter is shown as fps2 in the ui. + + delay(1); //to feed the watchdog (also if loopState == 0) + while (!waitingOnLiveScript) delay(1); //to feed the watchdog + //do Live Script cycle + waitingOnLiveScript = false; //Live Script produced a frame, main loop will deal with it } - void UserModLive::loop() { - if (loopState == 2) {// show has been called (in other loop) - loopState = 0; //waiting on Live Script - // ppf("loopState %d\n", loopState); + void UserModLive::syncWithSync() { + + if (syncActive && !waitingOnLiveScript) {// show has been called (in other loop) + waitingOnLiveScript = true; //waiting on Live Script + while (waitingOnLiveScript) delay(1); // so live can continue } - else if (loopState == 1) { - loopState = 2; //other loop can call show (or preview) - // ppf("loopState %d\n", loopState); + } + + void UserModLive::loop1s() { + //check if sync is active (to do: only if background process? check hpwit) + bool scriptsRunning = false; + for (Executable &exec: scriptRuntime._scExecutables) { + if (exec.isRunning()) + scriptsRunning = true; } + syncActive = scriptsRunning; + if (!syncActive) waitingOnLiveScript = true; //reset to default + } - void UserModLive::loop20ms() { + // void UserModLive::loop20ms() { //workaround temporary disabled (replace by run?) // if (strnstr(web->lastFileUpdated, ".sc", sizeof(web->lastFileUpdated)) != nullptr) { // if (strnstr(web->lastFileUpdated, "del:/", sizeof(web->lastFileUpdated)) != nullptr) { @@ -343,12 +324,7 @@ static float _time(float j) { // } // strlcpy(web->lastFileUpdated, "", sizeof(web->lastFileUpdated)); // } - } - - void UserModLive::loop1s() { - for (JsonObject childVar: Variable("LiveScripts", "scripts").children()) - Variable(childVar).triggerEvent(onSetValue); //set the value (WIP) - } + // } void UserModLive::executeTask(uint8_t exeID, const char * function, int val) { @@ -433,6 +409,12 @@ static float _time(float j) { } else { scriptRuntime.killAndFreeRunningProgram(); } + + if (waitingOnLiveScript) { + waitingOnLiveScript = false; + ppf("waitingOnLiveScript killAndDelete %d\n", waitingOnLiveScript); + } + // fix->liveFixtureID = nullptr; //to be sure! todo: nullify exec pointers fix->liveFixtureID and leds.liveEffectID } diff --git a/src/User/UserModLive.h b/src/User/UserModLive.h index 3f52a72..7dfd024 100644 --- a/src/User/UserModLive.h +++ b/src/User/UserModLive.h @@ -34,12 +34,12 @@ class UserModLive: public SysModule { // void addExternalFun(string name, std::function fun); // void addExternalFun(string name, std::function fun); - //testing class functions instead of static - void showM(); + //static because called by Parser + static void sync(); + static void preKill(); + static void postKill(); - void loop() override; - - void loop20ms() override; + void syncWithSync(); void loop1s() override;