new scripting system

pull/22/head
Ryzerth 2020-10-07 14:44:39 +02:00
rodzic ac068036b8
commit 46d5b8a750
16 zmienionych plików z 105440 dodań i 7 usunięć

Wyświetl plik

@ -14,6 +14,9 @@
#include <stb_image.h>
#include <config.h>
#include <core.h>
#include <duktape/duktape.h>
#include <duktape/duk_console.h>
#include <scripting.h>
#include <dsp/block.h>
@ -26,6 +29,7 @@
namespace core {
ConfigManager configManager;
ScriptManager scriptManager;
void setInputSampleRate(double samplerate) {
// NOTE: Zoom controls won't work
@ -50,12 +54,55 @@ static void maximized_callback(GLFWwindow* window, int n) {
}
}
duk_ret_t test_func(duk_context *ctx) {
printf("Hello from C++\n");
return 1;
}
// main
int sdrpp_main() {
#ifdef _WIN32
//FreeConsole();
#endif
// TESTING
// duk_context* ctx = duk_create_heap_default();
// duk_console_init(ctx, DUK_CONSOLE_PROXY_WRAPPER);
// std::ifstream file("test.js", std::ios::in);
// std::stringstream ss;
// ss << file.rdbuf();
// std::string code = ss.str();
// duk_idx_t baseObj = duk_push_object(ctx);
// duk_push_int(ctx, 42);
// duk_put_prop_string(ctx, baseObj, "my_property");
// duk_push_c_function(ctx, test_func, 0);
// duk_put_prop_string(ctx, baseObj, "my_func");
// duk_put_global_string(ctx, "my_object");
// duk_push_object(ctx);
// if (duk_peval_string(ctx, code.c_str()) != 0) {
// printf("Error: %s\n", duk_safe_to_string(ctx, -1));
// return -1;
// }
// duk_pop(ctx);
core::scriptManager.createScript("TestScript 1", "test.js");
core::scriptManager.createScript("TestScript 2", "test.js");
core::scriptManager.createScript("TestScript 3", "test.js");
core::scriptManager.createScript("TestScript 4", "test.js");
// TESTING
spdlog::info("SDR++ v" VERSION_STR);
// Load config

Wyświetl plik

@ -1,9 +1,11 @@
#pragma once
#include <module.h>
#include <config.h>
#include <scripting.h>
namespace core {
SDRPP_EXPORT ConfigManager configManager;
SDRPP_EXPORT ScriptManager scriptManager;
void setInputSampleRate(double samplerate);
};

Plik diff jest za duży Load Diff

Wyświetl plik

@ -0,0 +1,185 @@
/*
* Minimal 'console' binding.
*
* https://github.com/DeveloperToolsWG/console-object/blob/master/api.md
* https://developers.google.com/web/tools/chrome-devtools/debug/console/console-reference
* https://developer.mozilla.org/en/docs/Web/API/console
*/
#include <stdio.h>
#include <stdarg.h>
#include "duktape.h"
#include "duk_console.h"
/* XXX: Add some form of log level filtering. */
/* XXX: Should all output be written via e.g. console.write(formattedMsg)?
* This would make it easier for user code to redirect all console output
* to a custom backend.
*/
/* XXX: Init console object using duk_def_prop() when that call is available. */
static duk_ret_t duk__console_log_helper(duk_context *ctx, const char *error_name) {
duk_uint_t flags = (duk_uint_t) duk_get_current_magic(ctx);
FILE *output = (flags & DUK_CONSOLE_STDOUT_ONLY) ? stdout : stderr;
duk_idx_t n = duk_get_top(ctx);
duk_idx_t i;
duk_get_global_string(ctx, "console");
duk_get_prop_string(ctx, -1, "format");
for (i = 0; i < n; i++) {
if (duk_check_type_mask(ctx, i, DUK_TYPE_MASK_OBJECT)) {
/* Slow path formatting. */
duk_dup(ctx, -1); /* console.format */
duk_dup(ctx, i);
duk_call(ctx, 1);
duk_replace(ctx, i); /* arg[i] = console.format(arg[i]); */
}
}
duk_pop_2(ctx);
duk_push_string(ctx, " ");
duk_insert(ctx, 0);
duk_join(ctx, n);
if (error_name) {
duk_push_error_object(ctx, DUK_ERR_ERROR, "%s", duk_require_string(ctx, -1));
duk_push_string(ctx, "name");
duk_push_string(ctx, error_name);
duk_def_prop(ctx, -3, DUK_DEFPROP_FORCE | DUK_DEFPROP_HAVE_VALUE); /* to get e.g. 'Trace: 1 2 3' */
duk_get_prop_string(ctx, -1, "stack");
}
fprintf(output, "%s\n", duk_to_string(ctx, -1));
if (flags & DUK_CONSOLE_FLUSH) {
fflush(output);
}
return 0;
}
static duk_ret_t duk__console_assert(duk_context *ctx) {
if (duk_to_boolean(ctx, 0)) {
return 0;
}
duk_remove(ctx, 0);
return duk__console_log_helper(ctx, "AssertionError");
}
static duk_ret_t duk__console_log(duk_context *ctx) {
return duk__console_log_helper(ctx, NULL);
}
static duk_ret_t duk__console_trace(duk_context *ctx) {
return duk__console_log_helper(ctx, "Trace");
}
static duk_ret_t duk__console_info(duk_context *ctx) {
return duk__console_log_helper(ctx, NULL);
}
static duk_ret_t duk__console_warn(duk_context *ctx) {
return duk__console_log_helper(ctx, NULL);
}
static duk_ret_t duk__console_error(duk_context *ctx) {
return duk__console_log_helper(ctx, "Error");
}
static duk_ret_t duk__console_dir(duk_context *ctx) {
/* For now, just share the formatting of .log() */
return duk__console_log_helper(ctx, 0);
}
static void duk__console_reg_vararg_func(duk_context *ctx, duk_c_function func, const char *name, duk_uint_t flags) {
duk_push_c_function(ctx, func, DUK_VARARGS);
duk_push_string(ctx, "name");
duk_push_string(ctx, name);
duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | DUK_DEFPROP_FORCE); /* Improve stacktraces by displaying function name */
duk_set_magic(ctx, -1, (duk_int_t) flags);
duk_put_prop_string(ctx, -2, name);
}
void duk_console_init(duk_context *ctx, duk_uint_t flags) {
duk_uint_t flags_orig;
/* If both DUK_CONSOLE_STDOUT_ONLY and DUK_CONSOLE_STDERR_ONLY where specified,
* just turn off DUK_CONSOLE_STDOUT_ONLY and keep DUK_CONSOLE_STDERR_ONLY.
*/
if ((flags & DUK_CONSOLE_STDOUT_ONLY) && (flags & DUK_CONSOLE_STDERR_ONLY)) {
flags &= ~DUK_CONSOLE_STDOUT_ONLY;
}
/* Remember the (possibly corrected) flags we received. */
flags_orig = flags;
duk_push_object(ctx);
/* Custom function to format objects; user can replace.
* For now, try JX-formatting and if that fails, fall back
* to ToString(v).
*/
duk_eval_string(ctx,
"(function (E) {"
"return function format(v){"
"try{"
"return E('jx',v);"
"}catch(e){"
"return String(v);" /* String() allows symbols, ToString() internal algorithm doesn't. */
"}"
"};"
"})(Duktape.enc)");
duk_put_prop_string(ctx, -2, "format");
flags = flags_orig;
if (!(flags & DUK_CONSOLE_STDOUT_ONLY) && !(flags & DUK_CONSOLE_STDERR_ONLY)) {
/* No output indicators were specified; these levels go to stdout. */
flags |= DUK_CONSOLE_STDOUT_ONLY;
}
duk__console_reg_vararg_func(ctx, duk__console_assert, "assert", flags);
duk__console_reg_vararg_func(ctx, duk__console_log, "log", flags);
duk__console_reg_vararg_func(ctx, duk__console_log, "debug", flags); /* alias to console.log */
duk__console_reg_vararg_func(ctx, duk__console_trace, "trace", flags);
duk__console_reg_vararg_func(ctx, duk__console_info, "info", flags);
flags = flags_orig;
if (!(flags & DUK_CONSOLE_STDOUT_ONLY) && !(flags & DUK_CONSOLE_STDERR_ONLY)) {
/* No output indicators were specified; these levels go to stderr. */
flags |= DUK_CONSOLE_STDERR_ONLY;
}
duk__console_reg_vararg_func(ctx, duk__console_warn, "warn", flags);
duk__console_reg_vararg_func(ctx, duk__console_error, "error", flags);
duk__console_reg_vararg_func(ctx, duk__console_error, "exception", flags); /* alias to console.error */
duk__console_reg_vararg_func(ctx, duk__console_dir, "dir", flags);
duk_put_global_string(ctx, "console");
/* Proxy wrapping: ensures any undefined console method calls are
* ignored silently. This was required specifically by the
* DeveloperToolsWG proposal (and was implemented also by Firefox:
* https://bugzilla.mozilla.org/show_bug.cgi?id=629607). This is
* apparently no longer the preferred way of implementing console.
* When Proxy is enabled, whitelist at least .toJSON() to avoid
* confusing JX serialization of the console object.
*/
if (flags & DUK_CONSOLE_PROXY_WRAPPER) {
/* Tolerate failure to initialize Proxy wrapper in case
* Proxy support is disabled.
*/
(void) duk_peval_string_noresult(ctx,
"(function(){"
"var D=function(){};"
"var W={toJSON:true};" /* whitelisted */
"console=new Proxy(console,{"
"get:function(t,k){"
"var v=t[k];"
"return typeof v==='function'||W[k]?v:D;"
"}"
"});"
"})();"
);
}
}

Wyświetl plik

@ -0,0 +1,29 @@
#if !defined(DUK_CONSOLE_H_INCLUDED)
#define DUK_CONSOLE_H_INCLUDED
#include "duktape.h"
#if defined(__cplusplus)
extern "C" {
#endif
/* Use a proxy wrapper to make undefined methods (console.foo()) no-ops. */
#define DUK_CONSOLE_PROXY_WRAPPER (1U << 0)
/* Flush output after every call. */
#define DUK_CONSOLE_FLUSH (1U << 1)
/* Send output to stdout only (default is mixed stdout/stderr). */
#define DUK_CONSOLE_STDOUT_ONLY (1U << 2)
/* Send output to stderr only (default is mixed stdout/stderr). */
#define DUK_CONSOLE_STDERR_ONLY (1U << 3)
/* Initialize the console system */
extern void duk_console_init(duk_context *ctx, duk_uint_t flags);
#if defined(__cplusplus)
}
#endif /* end 'extern "C"' wrapper */
#endif /* DUK_CONSOLE_H_INCLUDED */

Plik diff jest za duży Load Diff

Plik diff jest za duży Load Diff

Wyświetl plik

@ -59,6 +59,7 @@ void windowInit() {
gui::menu.registerEntry("Source", sourecmenu::draw, NULL);
gui::menu.registerEntry("Audio", audiomenu::draw, NULL);
gui::menu.registerEntry("Scripting", scriptingmenu::draw, NULL);
gui::menu.registerEntry("Band Plan", bandplanmenu::draw, NULL);
gui::menu.registerEntry("Display", displaymenu::draw, NULL);
@ -76,6 +77,7 @@ void windowInit() {
sourecmenu::init();
audiomenu::init();
scriptingmenu::init();
bandplanmenu::init();
displaymenu::init();

Wyświetl plik

@ -31,6 +31,7 @@
#include <gui/menus/display.h>
#include <gui/menus/bandplan.h>
#include <gui/menus/audio.h>
#include <gui/menus/scripting.h>
#include <gui/dialogs/credits.h>
#include <signal_path/source.h>

Wyświetl plik

@ -0,0 +1,21 @@
#include <gui/menus/scripting.h>
#include <core.h>
#include <gui/style.h>
namespace scriptingmenu {
void init() {
}
void draw(void* ctx) {
float menuWidth = ImGui::GetContentRegionAvailWidth();
for (const auto& [name, script] : core::scriptManager.scripts) {
bool running = script->running;
if (running) { style::beginDisabled(); }
if (ImGui::Button(name.c_str(), ImVec2(menuWidth, 0))) {
script->run();
}
if (running) { style::endDisabled(); }
}
}
}

Wyświetl plik

@ -0,0 +1,6 @@
#pragma once
namespace scriptingmenu {
void init();
void draw(void* ctx);
}

Wyświetl plik

@ -0,0 +1,75 @@
#include <scripting.h>
#include <duktape/duk_console.h>
#include <version.h>
#include <fstream>
#include <sstream>
ScriptManager::ScriptManager() {
}
ScriptManager::Script* ScriptManager::createScript(std::string name, std::string path) {
ScriptManager::Script* script = new ScriptManager::Script(this, name, path);
scripts[name] = script;
return script;
}
ScriptManager::Script::Script(ScriptManager* man, std::string name, std::string path) {
this->name = name;
manager = man;
std::ifstream file(path, std::ios::in);
std::stringstream ss;
ss << file.rdbuf();
code = ss.str();
}
void ScriptManager::Script::run() {
ctx = ScriptManager::createContext(manager, name);
running = true;
if (worker.joinable()) {
worker.join();
}
worker = std::thread(scriptWorker, this);
}
duk_context* ScriptManager::createContext(ScriptManager* _this, std::string name) {
duk_context* ctx = duk_create_heap_default();
duk_console_init(ctx, DUK_CONSOLE_PROXY_WRAPPER);
duk_push_string(ctx, name.c_str());
duk_put_global_string(ctx, "SCRIPT_NAME");
duk_idx_t sdrppBase = duk_push_object(ctx);
duk_push_string(ctx, VERSION_STR);
duk_put_prop_string(ctx, sdrppBase, "version");
duk_idx_t modObjId = duk_push_object(ctx);
for (const auto& [name, handler] : _this->handlers) {
duk_idx_t objId = duk_push_object(ctx);
handler.handler(handler.ctx, ctx, objId);
duk_put_prop_string(ctx, modObjId, name.c_str());
}
duk_put_prop_string(ctx, sdrppBase, "modules");
duk_put_global_string(ctx, "sdrpp");
return ctx;
}
// TODO: Switch to spdlog
void ScriptManager::Script::scriptWorker(Script* script) {
if (duk_peval_string(script->ctx, script->code.c_str()) != 0) {
printf("Error: %s\n", duk_safe_to_string(script->ctx, -1));
//return;
}
duk_destroy_heap(script->ctx);
script->running = false;
}
void ScriptManager::bindScriptRunHandler(std::string name, ScriptManager::ScriptRunHandler_t handler) {
// TODO: check if it exists and add a "unbind" function
handlers[name] = handler;
}

Wyświetl plik

@ -0,0 +1,48 @@
#pragma once
#include <string>
#include <thread>
#include <vector>
#include <map>
#include <duktape/duktape.h>
class ScriptManager {
public:
ScriptManager();
friend class Script;
class Script {
public:
Script(ScriptManager* man, std::string name, std::string path);
void run();
bool running = false;
private:
static void scriptWorker(Script* _this);
duk_context* ctx;
std::thread worker;
std::string code;
std::string name;
ScriptManager* manager;
};
struct ScriptRunHandler_t {
void (*handler)(void* ctx, duk_context* dukCtx, duk_idx_t objId);
void* ctx;
};
void bindScriptRunHandler(std::string name, ScriptManager::ScriptRunHandler_t handler);
ScriptManager::Script* createScript(std::string name, std::string path);
std::map<std::string, ScriptManager::Script*> scripts;
private:
static duk_context* createContext(ScriptManager* _this, std::string name);
std::map<std::string, ScriptManager::ScriptRunHandler_t> handlers;
};

Wyświetl plik

@ -3,6 +3,7 @@
#include <path.h>
#include <watcher.h>
#include <config.h>
#include <core.h>
#define CONCAT(a, b) ((std::string(a) + b).c_str())
#define DEEMP_LIST "50µS\00075µS\000none\000"
@ -26,6 +27,11 @@ public:
sigPath.start();
sigPath.setDemodulator(SigPath::DEMOD_FM, bandWidth);
gui::menu.registerEntry(name, menuHandler, this);
ScriptManager::ScriptRunHandler_t handler;
handler.ctx = this;
handler.handler = scriptCreateHandler;
core::scriptManager.bindScriptRunHandler(name, handler);
}
~RadioModule() {
@ -117,6 +123,32 @@ private:
ImGui::PopItemWidth();
}
static void scriptCreateHandler(void* ctx, duk_context* dukCtx, duk_idx_t objId) {
duk_push_string(dukCtx, "Hello from modules ;)");
duk_put_prop_string(dukCtx, objId, "test");
duk_push_c_function(dukCtx, duk_setDemodulator, 1);
duk_put_prop_string(dukCtx, objId, "setDemodulator");
duk_push_pointer(dukCtx, ctx);
duk_put_prop_string(dukCtx, objId, DUK_HIDDEN_SYMBOL("radio_ctx"));
}
static duk_ret_t duk_setDemodulator(duk_context* dukCtx) {
const char* str = duk_require_string(dukCtx, -1);
std::string modName = str;
if (modName == "USB") {
_this->demod = 4;
_this->bandWidth = 3000;
_this->bandWidthMin = 1500;
_this->bandWidthMax = 3000;
_this->sigPath.setDemodulator(SigPath::DEMOD_USB, _this->bandWidth);
}
return 0;
}
std::string name;
int demod = 1;
int deemp = 0;

Wyświetl plik

@ -11,6 +11,7 @@
"Radio",
"Recorder",
"Audio",
"Scripting",
"Band Plan",
"Display"
],
@ -20,7 +21,7 @@
"source": "",
"sourceSettings": {},
"windowSize": {
"h": 1053,
"w": 1920
"h": 720,
"w": 1280
}
}

Wyświetl plik

@ -1,7 +1,7 @@
{
"Radio": "./radio/radio.so",
"Recorder": "./recorder/recorder.so",
"Soapy": "./soapy/soapy.so",
"FileSource": "./file_source/file_source.so",
"RTLTCPSource": "./rtl_tcp_source/rtl_tcp_source.so"
"Radio": "./radio/Release/radio.dll",
"Recorder": "./recorder/Release/recorder.dll",
"Soapy": "./soapy/Release/soapy.dll",
"FileSource": "./file_source/Release/file_source.dll",
"RTLTCPSource": "./rtl_tcp_source/Release/rtl_tcp_source.dll"
}