Compare commits
5 commits
c58c262ccf
...
ba97c7c574
Author | SHA1 | Date | |
---|---|---|---|
ba97c7c574 | |||
665f8f6151 | |||
58cd6528a3 | |||
b1670f31e8 | |||
c4c767fda9 |
15 changed files with 288 additions and 127 deletions
|
@ -77,7 +77,7 @@ int main() {
|
|||
}
|
||||
|
||||
void errorCatcher(int id, const char* str) {
|
||||
Logger::fatalError(std::format("GLFW Error: [{}] {}", id, str));
|
||||
Logger::fatalErrorf("GLFW Error: [{}] {}", id, str);
|
||||
}
|
||||
|
||||
float lastTime;
|
||||
|
|
|
@ -38,25 +38,6 @@ LuaSignalConnection::~LuaSignalConnection() {
|
|||
luaL_unref(state, LUA_REGISTRYINDEX, thread);
|
||||
}
|
||||
|
||||
#if 0
|
||||
static void stackdump(lua_State* L) {
|
||||
printf("%d\n", lua_gettop(L));
|
||||
fflush(stdout);
|
||||
lua_getfield(L, LUA_GLOBALSINDEX, "tostring");
|
||||
for (int i = lua_gettop(L)-1; i >= 1; i--) {
|
||||
lua_pushvalue(L, -1);
|
||||
lua_pushvalue(L, i);
|
||||
lua_call(L, 1, 1);
|
||||
const char* str = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
printf("%s: %s\n", lua_typename(L, lua_type(L, i)), str);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
printf("\n\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
#endif
|
||||
|
||||
void LuaSignalConnection::Call(std::vector<Variant> args) {
|
||||
lua_State* thread = lua_newthread(state);
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
static std::ofstream logStream;
|
||||
static std::vector<Logger::LogListener> logListeners;
|
||||
static std::vector<Logger::TraceLogListener> traceLogListeners;
|
||||
std::string Logger::currentLogDir = "NULL";
|
||||
|
||||
void Logger::init() {
|
||||
|
@ -28,7 +27,7 @@ void Logger::finish() {
|
|||
logStream.close();
|
||||
}
|
||||
|
||||
void Logger::log(std::string message, Logger::LogLevel logLevel) {
|
||||
void Logger::log(std::string message, Logger::LogLevel logLevel, ScriptSource source) {
|
||||
std::string logLevelStr = logLevel == Logger::LogLevel::INFO ? "INFO" :
|
||||
logLevel == Logger::LogLevel::DEBUG ? "DEBUG" :
|
||||
logLevel == Logger::LogLevel::TRACE ? "TRACE" :
|
||||
|
@ -44,7 +43,7 @@ void Logger::log(std::string message, Logger::LogLevel logLevel) {
|
|||
printf("%s\n", formattedLogLine.c_str());
|
||||
|
||||
for (Logger::LogListener listener : logListeners) {
|
||||
listener(logLevel, message);
|
||||
listener(logLevel, message, source);
|
||||
}
|
||||
|
||||
if (logLevel == Logger::LogLevel::FATAL_ERROR) {
|
||||
|
@ -52,20 +51,6 @@ void Logger::log(std::string message, Logger::LogLevel logLevel) {
|
|||
}
|
||||
}
|
||||
|
||||
void Logger::trace(std::string source, int line, void* userData) {
|
||||
std::string message = "'" + source + "' Line " + std::to_string(line);
|
||||
|
||||
log(message, Logger::LogLevel::TRACE);
|
||||
|
||||
for (Logger::TraceLogListener listener : traceLogListeners) {
|
||||
listener(message, source, line, userData);
|
||||
}
|
||||
}
|
||||
|
||||
void Logger::addLogListener(Logger::LogListener listener) {
|
||||
logListeners.push_back(listener);
|
||||
}
|
||||
|
||||
void Logger::addLogListener(Logger::TraceLogListener listener) {
|
||||
traceLogListeners.push_back(listener);
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
#include <format>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Script;
|
||||
|
||||
namespace Logger {
|
||||
enum class LogLevel {
|
||||
INFO,
|
||||
|
@ -14,34 +16,39 @@ namespace Logger {
|
|||
FATAL_ERROR,
|
||||
};
|
||||
|
||||
typedef std::function<void(LogLevel logLevel, std::string message)> LogListener;
|
||||
typedef std::function<void(std::string message, std::string source, int line, void* userData)> TraceLogListener;
|
||||
struct ScriptSource {
|
||||
std::shared_ptr<Script> script;
|
||||
int line;
|
||||
};
|
||||
|
||||
typedef std::function<void(LogLevel logLevel, std::string message, ScriptSource source)> LogListener;
|
||||
|
||||
extern std::string currentLogDir;
|
||||
|
||||
void init();
|
||||
void finish();
|
||||
void addLogListener(LogListener);
|
||||
void addLogListener(TraceLogListener);
|
||||
|
||||
void log(std::string message, LogLevel logLevel);
|
||||
void log(std::string message, LogLevel logLevel, ScriptSource source = {});
|
||||
inline void info(std::string message) { log(message, LogLevel::INFO); }
|
||||
inline void debug(std::string message) { log(message, LogLevel::DEBUG); }
|
||||
inline void warning(std::string message) { log(message, LogLevel::WARNING); }
|
||||
inline void error(std::string message) { log(message, LogLevel::ERROR); }
|
||||
inline void fatalError(std::string message) { log(message, LogLevel::FATAL_ERROR); }
|
||||
inline void trace(std::string message) { log(message, LogLevel::TRACE); };
|
||||
|
||||
inline void traceStart() { log("Stack start", LogLevel::TRACE); }
|
||||
inline void traceEnd() { log("Stack end", LogLevel::TRACE); }
|
||||
void trace(std::string source, int line, void* userData = nullptr);
|
||||
template <typename ...Args>
|
||||
void scriptLogf(std::string format, LogLevel logLevel, ScriptSource source, Args&&... args) {
|
||||
char message[200];
|
||||
sprintf(message, format.c_str(), args...);
|
||||
log(message, logLevel, source);
|
||||
}
|
||||
|
||||
template <typename ...Args>
|
||||
void logf(std::string format, LogLevel logLevel, Args&&... args) {
|
||||
char message[200];
|
||||
sprintf(message, format.c_str(), args...);
|
||||
log(message, logLevel);
|
||||
scriptLogf(format, logLevel, {}, args...);
|
||||
}
|
||||
|
||||
|
||||
template <typename ...Args> inline void infof(std::string format, Args&&... args) { logf(format, LogLevel::INFO, args...); }
|
||||
template <typename ...Args> inline void debugf(std::string format, Args&&... args) { logf(format, LogLevel::DEBUG, args...); }
|
||||
template <typename ...Args> inline void warningf(std::string format, Args&&... args) { logf(format, LogLevel::WARNING, args...); }
|
||||
|
|
|
@ -12,8 +12,6 @@
|
|||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
int script_wait(lua_State*);
|
||||
int script_delay(lua_State*);
|
||||
int script_errhandler(lua_State*);
|
||||
|
||||
Script::Script(): Instance(&TYPE) {
|
||||
|
@ -33,27 +31,22 @@ void Script::Run() {
|
|||
this->thread = lua_newthread(L);
|
||||
lua_State* Lt = thread;
|
||||
|
||||
lua_pushthread(Lt); // Push thread for later*
|
||||
|
||||
// Initialize script globals
|
||||
lua_getglobal(Lt, "_G");
|
||||
scriptContext->NewEnvironment(Lt); // Pushes envtable, metatable
|
||||
|
||||
// Set script in metatable source
|
||||
InstanceRef(shared_from_this()).PushLuaValue(Lt);
|
||||
lua_setfield(Lt, -2, "source");
|
||||
|
||||
lua_pop(Lt, 1); // Pop metatable
|
||||
|
||||
InstanceRef(shared_from_this()).PushLuaValue(Lt);
|
||||
lua_setfield(Lt, -2, "script");
|
||||
|
||||
InstanceRef(dataModel().value()).PushLuaValue(Lt);
|
||||
lua_setfield(Lt, -2, "game");
|
||||
|
||||
InstanceRef(dataModel().value()->GetService<Workspace>()).PushLuaValue(Lt);
|
||||
lua_setfield(Lt, -2, "workspace");
|
||||
|
||||
lua_pushlightuserdata(Lt, scriptContext.get());
|
||||
lua_pushcclosure(Lt, script_wait, 1);
|
||||
lua_setfield(Lt, -2, "wait");
|
||||
|
||||
lua_pushlightuserdata(Lt, scriptContext.get());
|
||||
lua_pushcclosure(Lt, script_delay, 1);
|
||||
lua_setfield(Lt, -2, "delay");
|
||||
|
||||
lua_pop(Lt, 1); // _G
|
||||
lua_setfenv(Lt, -2); // *Set env of current thread
|
||||
lua_pop(Lt, 1); // Pop thread
|
||||
|
||||
// Push wrapper as thread function
|
||||
lua_getfield(Lt, LUA_REGISTRYINDEX, "LuaPCallWrapper");
|
||||
|
@ -83,42 +76,13 @@ void Script::Stop() {
|
|||
// TODO:
|
||||
}
|
||||
|
||||
int script_wait(lua_State* L) {
|
||||
ScriptContext* scriptContext = (ScriptContext*)lua_touserdata(L, lua_upvalueindex(1));
|
||||
float secs = lua_gettop(L) == 0 ? 0.03 : std::max(luaL_checknumber(L, 1), 0.03);
|
||||
if (lua_gettop(L) > 0) lua_pop(L, 1); // pop secs
|
||||
|
||||
scriptContext->PushThreadSleep(L, secs);
|
||||
|
||||
// Yield
|
||||
return lua_yield(L, 0);
|
||||
}
|
||||
|
||||
int script_delay(lua_State* L) {
|
||||
ScriptContext* scriptContext = (ScriptContext*)lua_touserdata(L, lua_upvalueindex(1));
|
||||
float secs = std::max(luaL_checknumber(L, 1), 0.03);
|
||||
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||
|
||||
lua_State* Lt = lua_newthread(L); // Create a new thread
|
||||
// I think this is memory abuse??
|
||||
// Wouldn't popping the thread in this case make it eligible for garbage collection?
|
||||
lua_pop(L, 1); // pop the newly created thread so that xmove moves func instead of it into itself
|
||||
lua_xmove(L, Lt, 1); // move func
|
||||
lua_pop(L, 1); // pop secs
|
||||
|
||||
// Schedule next run
|
||||
scriptContext->PushThreadSleep(Lt, secs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int script_errhandler(lua_State* L) {
|
||||
std::string errorMessage = lua_tostring(L, -1);
|
||||
Logger::error(errorMessage);
|
||||
|
||||
// Traceback
|
||||
|
||||
Logger::traceStart();
|
||||
Logger::trace("Stack start");
|
||||
|
||||
lua_Debug dbg;
|
||||
int stack = 1;
|
||||
|
@ -128,10 +92,10 @@ int script_errhandler(lua_State* L) {
|
|||
if (strcmp(dbg.what, "C") == 0 || strcmp(dbg.source, "=PCALL_WRAPPER") == 0)
|
||||
continue;
|
||||
|
||||
Logger::trace(dbg.source, dbg.currentline);
|
||||
Logger::scriptLogf("'%s', Line %d", Logger::LogLevel::TRACE, {}, dbg.source, dbg.currentline);
|
||||
}
|
||||
|
||||
Logger::traceEnd();
|
||||
Logger::trace("Stack end");
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
#include "scriptcontext.h"
|
||||
#include "datatypes/cframe.h"
|
||||
#include "datatypes/color3.h"
|
||||
#include "datatypes/ref.h"
|
||||
#include "datatypes/vector.h"
|
||||
#include "logger.h"
|
||||
#include "objects/datamodel.h"
|
||||
#include "objects/service/workspace.h"
|
||||
#include "timeutil.h"
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
|
@ -10,15 +13,18 @@
|
|||
|
||||
const char* WRAPPER_SRC = "local func, errhandler = ... return function(...) local args = {...} xpcall(function() func(unpack(args)) end, errhandler) end";
|
||||
|
||||
int g_wait(lua_State*);
|
||||
int g_delay(lua_State*);
|
||||
static int g_print(lua_State*);
|
||||
static int g_require(lua_State*);
|
||||
static const struct luaL_Reg luaglobals [] = {
|
||||
static const luaL_Reg luaglobals [] = {
|
||||
{"print", g_print},
|
||||
{"require", g_require},
|
||||
{NULL, NULL} /* end of array */
|
||||
};
|
||||
|
||||
std::string unsafe_globals[] = {
|
||||
// Todo implement our own "safe" setfenv/getfenv
|
||||
"loadfile", "loadstring", "load", "dofile", "getfenv", "setfenv"
|
||||
};
|
||||
|
||||
|
@ -50,6 +56,25 @@ void ScriptContext::InitService() {
|
|||
Color3::PushLuaLibrary(state);
|
||||
Instance::PushLuaLibrary(state);
|
||||
|
||||
// Add other globals
|
||||
lua_getglobal(state, "_G");
|
||||
|
||||
InstanceRef(dataModel().value()).PushLuaValue(state);
|
||||
lua_setfield(state, -2, "game");
|
||||
|
||||
InstanceRef(dataModel().value()->GetService<Workspace>()).PushLuaValue(state);
|
||||
lua_setfield(state, -2, "workspace");
|
||||
|
||||
lua_pushlightuserdata(state, this);
|
||||
lua_pushcclosure(state, g_wait, 1);
|
||||
lua_setfield(state, -2, "wait");
|
||||
|
||||
lua_pushlightuserdata(state, this);
|
||||
lua_pushcclosure(state, g_delay, 1);
|
||||
lua_setfield(state, -2, "delay");
|
||||
|
||||
lua_pop(state, 1); // _G
|
||||
|
||||
// Add wrapper function
|
||||
luaL_loadbuffer(state, WRAPPER_SRC, strlen(WRAPPER_SRC), "=PCALL_WRAPPER");
|
||||
lua_setfield(state, LUA_REGISTRYINDEX, "LuaPCallWrapper");
|
||||
|
@ -137,6 +162,27 @@ void ScriptContext::RunSleepingThreads() {
|
|||
schedTime = tu_clock_micros() - startTime;
|
||||
}
|
||||
|
||||
void ScriptContext::NewEnvironment(lua_State* L) {
|
||||
lua_newtable(L); // Env table
|
||||
lua_newtable(L); // Metatable
|
||||
|
||||
// Push __index
|
||||
lua_pushvalue(L, LUA_GLOBALSINDEX);
|
||||
lua_setfield(L, -2, "__index");
|
||||
|
||||
// Push __metatable
|
||||
lua_pushstring(L, "metatable is locked");
|
||||
lua_setfield(L, -2, "__metatable");
|
||||
|
||||
// Copy metatable and set the env table's metatable
|
||||
lua_pushvalue(L, -1);
|
||||
lua_setmetatable(L, -3);
|
||||
|
||||
// Remainder on stack:
|
||||
// 1. Env table
|
||||
// 2. Metatable
|
||||
}
|
||||
|
||||
// https://www.lua.org/source/5.1/lbaselib.c.html
|
||||
static int g_print(lua_State* L) {
|
||||
std::string buf;
|
||||
|
@ -167,4 +213,33 @@ static int g_require(lua_State* L) {
|
|||
if (nargs < 1) return luaL_error(L, "expected argument module");
|
||||
|
||||
return luaL_error(L, "require is not yet implemented");
|
||||
}
|
||||
|
||||
int g_wait(lua_State* L) {
|
||||
ScriptContext* scriptContext = (ScriptContext*)lua_touserdata(L, lua_upvalueindex(1));
|
||||
float secs = lua_gettop(L) == 0 ? 0.03 : std::max(luaL_checknumber(L, 1), 0.03);
|
||||
if (lua_gettop(L) > 0) lua_pop(L, 1); // pop secs
|
||||
|
||||
scriptContext->PushThreadSleep(L, secs);
|
||||
|
||||
// Yield
|
||||
return lua_yield(L, 0);
|
||||
}
|
||||
|
||||
int g_delay(lua_State* L) {
|
||||
ScriptContext* scriptContext = (ScriptContext*)lua_touserdata(L, lua_upvalueindex(1));
|
||||
float secs = std::max(luaL_checknumber(L, 1), 0.03);
|
||||
luaL_checktype(L, 2, LUA_TFUNCTION);
|
||||
|
||||
lua_State* Lt = lua_newthread(L); // Create a new thread
|
||||
// I think this is memory abuse??
|
||||
// Wouldn't popping the thread in this case make it eligible for garbage collection?
|
||||
lua_pop(L, 1); // pop the newly created thread so that xmove moves func instead of it into itself
|
||||
lua_xmove(L, Lt, 1); // move func
|
||||
lua_pop(L, 1); // pop secs
|
||||
|
||||
// Schedule next run
|
||||
scriptContext->PushThreadSleep(Lt, secs);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -32,5 +32,8 @@ public:
|
|||
void PushThreadSleep(lua_State* thread, float delay);
|
||||
void RunSleepingThreads();
|
||||
|
||||
// Generates an environment with a metatable and pushes it both the env table and metatable in order onto the stack
|
||||
void NewEnvironment(lua_State* state);
|
||||
|
||||
static inline std::shared_ptr<Instance> Create() { return std::make_shared<ScriptContext>(); };
|
||||
};
|
|
@ -38,6 +38,8 @@ set(PROJECT_SOURCES
|
|||
panes/outputtextview.cpp
|
||||
script/scriptdocument.h
|
||||
script/scriptdocument.cpp
|
||||
script/commandedit.h
|
||||
script/commandedit.cpp
|
||||
aboutdialog.ui
|
||||
aboutdialog.h
|
||||
aboutdialog.cpp
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "mainwindow.h"
|
||||
#include "./ui_mainwindow.h"
|
||||
#include "script/commandedit.h"
|
||||
#include "common.h"
|
||||
#include "aboutdialog.h"
|
||||
#include "logger.h"
|
||||
|
@ -14,6 +15,7 @@
|
|||
#include <qevent.h>
|
||||
#include <qglobal.h>
|
||||
#include <qkeysequence.h>
|
||||
#include <qlabel.h>
|
||||
#include <qmessagebox.h>
|
||||
#include <qmimedata.h>
|
||||
#include <qnamespace.h>
|
||||
|
@ -117,6 +119,8 @@ MainWindow::MainWindow(QWidget *parent)
|
|||
undoManager.SetUndoStateListener([&]() {
|
||||
updateToolbars();
|
||||
});
|
||||
|
||||
setUpCommandBar();
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent* evt) {
|
||||
|
@ -142,6 +146,14 @@ void MainWindow::closeEvent(QCloseEvent* evt) {
|
|||
#endif
|
||||
}
|
||||
|
||||
void MainWindow::setUpCommandBar() {
|
||||
CommandEdit* commandEdit;
|
||||
QToolBar* commandBar = ui->commandBar;
|
||||
commandBar->layout()->setSpacing(5);
|
||||
commandBar->addWidget(new QLabel(tr("Command ")));
|
||||
commandBar->addWidget(commandEdit = new CommandEdit());
|
||||
}
|
||||
|
||||
void MainWindow::connectActionHandlers() {
|
||||
connect(ui->actionToolSelect, &QAction::triggered, this, [&]() { selectedTool = TOOL_SELECT; updateToolbars(); });
|
||||
connect(ui->actionToolMove, &QAction::triggered, this, [&](bool state) { selectedTool = state ? TOOL_MOVE : TOOL_SELECT; updateToolbars(); });
|
||||
|
|
|
@ -69,11 +69,11 @@ public:
|
|||
private:
|
||||
PlaceDocument* placeDocument;
|
||||
|
||||
void setUpCommandBar();
|
||||
void connectActionHandlers();
|
||||
void updateToolbars();
|
||||
void closeEvent(QCloseEvent* evt) override;
|
||||
ScriptDocument* findScriptWindow(std::shared_ptr<Script>);
|
||||
|
||||
void connectActionHandlers();
|
||||
|
||||
std::optional<std::string> openFileDialog(QString filter, QString defaultExtension, QFileDialog::AcceptMode acceptMode, QString title = "");
|
||||
};
|
||||
|
|
|
@ -172,7 +172,7 @@
|
|||
</widget>
|
||||
<widget class="QToolBar" name="editTools">
|
||||
<property name="windowTitle">
|
||||
<string>toolBar_3</string>
|
||||
<string>Edit Tools</string>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
|
@ -237,7 +237,7 @@
|
|||
</widget>
|
||||
<widget class="QToolBar" name="toolBar">
|
||||
<property name="windowTitle">
|
||||
<string>toolBar</string>
|
||||
<string>Sound Controls</string>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>TopToolBarArea</enum>
|
||||
|
@ -247,6 +247,17 @@
|
|||
</attribute>
|
||||
<addaction name="actionToggleEditSounds"/>
|
||||
</widget>
|
||||
<widget class="QToolBar" name="commandBar">
|
||||
<property name="windowTitle">
|
||||
<string>Command Bar</string>
|
||||
</property>
|
||||
<attribute name="toolBarArea">
|
||||
<enum>BottomToolBarArea</enum>
|
||||
</attribute>
|
||||
<attribute name="toolBarBreak">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
<action name="actionAddPart">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "outputtextview.h"
|
||||
#include "logger.h"
|
||||
#include "mainwindow.h"
|
||||
#include "objects/script.h"
|
||||
#include "panes/outputtextview.h"
|
||||
|
@ -12,8 +13,7 @@
|
|||
#include <string>
|
||||
|
||||
OutputTextView::OutputTextView(QWidget* parent) : QTextEdit(parent) {
|
||||
Logger::addLogListener(std::bind(&OutputTextView::handleLog, this, std::placeholders::_1, std::placeholders::_2));
|
||||
Logger::addLogListener(std::bind(&OutputTextView::handleLogTrace, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
|
||||
Logger::addLogListener(std::bind(&OutputTextView::handleLog, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
||||
ensureCursorVisible();
|
||||
|
||||
QFont font("");
|
||||
|
@ -36,13 +36,9 @@ OutputTextView::OutputTextView(QWidget* parent) : QTextEdit(parent) {
|
|||
|
||||
OutputTextView::~OutputTextView() = default;
|
||||
|
||||
void OutputTextView::handleLog(Logger::LogLevel logLevel, std::string message) {
|
||||
void OutputTextView::handleLog(Logger::LogLevel logLevel, std::string message, Logger::ScriptSource source) {
|
||||
if (logLevel == Logger::LogLevel::DEBUG) return;
|
||||
|
||||
// Skip if trace, as that is handled by handleLogTrace
|
||||
if (logLevel == Logger::LogLevel::TRACE && !message.starts_with("Stack"))
|
||||
return;
|
||||
|
||||
// https://stackoverflow.com/a/61722734/16255372
|
||||
moveCursor(QTextCursor::MoveOperation::End);
|
||||
QTextCursor cursor = textCursor();
|
||||
|
@ -58,21 +54,10 @@ void OutputTextView::handleLog(Logger::LogLevel logLevel, std::string message) {
|
|||
format.setFontWeight(QFont::Bold);
|
||||
}
|
||||
|
||||
cursor.insertText(message.c_str(), format);
|
||||
cursor.insertText("\n", QTextCharFormat());
|
||||
}
|
||||
|
||||
void OutputTextView::handleLogTrace(std::string message, std::string source, int line, void* userData) {
|
||||
std::weak_ptr<Script>* script = (std::weak_ptr<Script>*)userData;
|
||||
|
||||
// https://stackoverflow.com/a/61722734/16255372
|
||||
QTextCursor cursor = textCursor();
|
||||
QTextCharFormat format = cursor.charFormat();
|
||||
format.setForeground(QColor(0, 127, 255));
|
||||
|
||||
if (userData != nullptr && !script->expired()) {
|
||||
// Add anchor point if source is provided
|
||||
if (source.script != nullptr) {
|
||||
int id = stackTraceScriptsLastId++;
|
||||
stackTraceScripts[id] = *script;
|
||||
stackTraceScripts[id] = source.script;
|
||||
|
||||
format.setAnchor(true);
|
||||
format.setAnchorHref(QString::number(id));
|
||||
|
|
|
@ -14,8 +14,7 @@ private:
|
|||
void mouseReleaseEvent(QMouseEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
|
||||
void handleLog(Logger::LogLevel, std::string);
|
||||
void handleLogTrace(std::string, std::string, int, void*);
|
||||
void handleLog(Logger::LogLevel, std::string, Logger::ScriptSource source);
|
||||
|
||||
std::map<int, std::weak_ptr<Script>> stackTraceScripts;
|
||||
int stackTraceScriptsLastId = 0;
|
||||
|
|
116
editor/script/commandedit.cpp
Normal file
116
editor/script/commandedit.cpp
Normal file
|
@ -0,0 +1,116 @@
|
|||
#include "commandedit.h"
|
||||
#include "common.h"
|
||||
#include "logger.h"
|
||||
#include "lua.h"
|
||||
#include "objects/service/script/scriptcontext.h"
|
||||
#include "luaapis.h" // IWYU pragma: keep
|
||||
#include <qevent.h>
|
||||
#include <qlineedit.h>
|
||||
#include <qnamespace.h>
|
||||
|
||||
int script_errhandler(lua_State*);
|
||||
|
||||
CommandEdit::CommandEdit(QWidget* parent) : QLineEdit(parent) {
|
||||
connect(this, &QLineEdit::returnPressed, this, &CommandEdit::executeCommand);
|
||||
}
|
||||
|
||||
CommandEdit::~CommandEdit() = default;
|
||||
|
||||
void CommandEdit::executeCommand() {
|
||||
std::string command = this->text().toStdString();
|
||||
|
||||
// Output
|
||||
Logger::infof("> %s", command.c_str());
|
||||
|
||||
// Select all so that the user can type over it
|
||||
this->selectAll();
|
||||
|
||||
// Execute via Lua
|
||||
auto context = gDataModel->GetService<ScriptContext>();
|
||||
lua_State* L = context->state;
|
||||
|
||||
int top = lua_gettop(L);
|
||||
lua_State* Lt = lua_newthread(L);
|
||||
|
||||
lua_pushthread(Lt); // Push thread
|
||||
getOrCreateEnvironment(Lt);
|
||||
lua_setfenv(Lt, -2); // Set env of current thread
|
||||
lua_pop(Lt, 1); // Pop thread
|
||||
|
||||
// Push wrapper as thread function
|
||||
lua_getfield(Lt, LUA_REGISTRYINDEX, "LuaPCallWrapper");
|
||||
|
||||
// Load source code and push onto thread as upvalue for wrapper
|
||||
int status = luaL_loadstring(Lt, command.c_str());
|
||||
if (status != LUA_OK) {
|
||||
// Failed to parse/load chunk
|
||||
Logger::error(lua_tostring(Lt, -1));
|
||||
|
||||
lua_settop(L, top);
|
||||
return;
|
||||
}
|
||||
|
||||
// Push our error handler and then generate the wrapped function
|
||||
lua_pushcfunction(Lt, script_errhandler);
|
||||
lua_call(Lt, 2, 1);
|
||||
|
||||
// Resume the thread
|
||||
lua_resume(Lt, 0);
|
||||
|
||||
lua_pop(L, 1); // Pop the thread
|
||||
lua_settop(L, top);
|
||||
|
||||
// Push to history
|
||||
if (commandHistory.size() == 0 || commandHistory.back() != command) {
|
||||
historyIndex = commandHistory.size();
|
||||
commandHistory.push_back(command);
|
||||
}
|
||||
};
|
||||
|
||||
// Gets the command bar environment from the registry, or creates a new one and registers it
|
||||
void CommandEdit::getOrCreateEnvironment(lua_State* L) {
|
||||
auto context = gDataModel->GetService<ScriptContext>();
|
||||
|
||||
// Try to find existing environment
|
||||
lua_getfield(L, LUA_REGISTRYINDEX, "commandBarEnv");
|
||||
if (!lua_isnil(L, -1))
|
||||
return; // Return the found environment
|
||||
lua_pop(L, 1); // Pop nil
|
||||
|
||||
// Initialize script globals
|
||||
context->NewEnvironment(L); // Pushes envtable, metatable
|
||||
|
||||
// Set source in metatable
|
||||
lua_pushstring(L, "commandbar");
|
||||
lua_setfield(L, -2, "source");
|
||||
|
||||
lua_pop(L, 1); // Pop metatable
|
||||
|
||||
// Register it
|
||||
lua_pushvalue(L, -1); // Copy
|
||||
lua_setfield(L, LUA_REGISTRYINDEX, "commandBarEnv");
|
||||
|
||||
// Remainder on stack:
|
||||
// 1. Env table
|
||||
}
|
||||
|
||||
void CommandEdit::keyPressEvent(QKeyEvent* evt) {
|
||||
switch (evt->key()) {
|
||||
case Qt::Key_Up:
|
||||
if (historyIndex > 0)
|
||||
historyIndex--;
|
||||
|
||||
if (commandHistory.size() > 0)
|
||||
this->setText(QString::fromStdString(commandHistory[historyIndex]));
|
||||
return;
|
||||
case Qt::Key_Down:
|
||||
if (historyIndex+1 < (int)commandHistory.size())
|
||||
historyIndex++;
|
||||
|
||||
if (commandHistory.size() > 0)
|
||||
this->setText(QString::fromStdString(commandHistory[historyIndex]));
|
||||
return;
|
||||
}
|
||||
|
||||
return QLineEdit::keyPressEvent(evt);
|
||||
}
|
21
editor/script/commandedit.h
Normal file
21
editor/script/commandedit.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <qlineedit.h>
|
||||
#include <vector>
|
||||
|
||||
struct lua_State;
|
||||
|
||||
class CommandEdit : public QLineEdit {
|
||||
Q_OBJECT
|
||||
|
||||
std::vector<std::string> commandHistory;
|
||||
int historyIndex = 0;
|
||||
|
||||
void executeCommand();
|
||||
void getOrCreateEnvironment(lua_State* L);
|
||||
public:
|
||||
CommandEdit(QWidget* parent = nullptr);
|
||||
~CommandEdit();
|
||||
|
||||
void keyPressEvent(QKeyEvent *) override;
|
||||
};
|
Loading…
Add table
Reference in a new issue