Compare commits

...

2 commits

13 changed files with 246 additions and 41 deletions

View file

@ -4,10 +4,12 @@
#include <fstream> #include <fstream>
#include <chrono> #include <chrono>
#include <format> #include <format>
#include <string>
#include <vector> #include <vector>
static std::ofstream logStream; static std::ofstream logStream;
static std::vector<Logger::LogListener> logListeners; static std::vector<Logger::LogListener> logListeners;
static std::vector<Logger::TraceLogListener> traceLogListeners;
std::string Logger::currentLogDir = "NULL"; std::string Logger::currentLogDir = "NULL";
void Logger::init() { void Logger::init() {
@ -29,6 +31,7 @@ void Logger::finish() {
void Logger::log(std::string message, Logger::LogLevel logLevel) { void Logger::log(std::string message, Logger::LogLevel logLevel) {
std::string logLevelStr = logLevel == Logger::LogLevel::INFO ? "INFO" : std::string logLevelStr = logLevel == Logger::LogLevel::INFO ? "INFO" :
logLevel == Logger::LogLevel::DEBUG ? "DEBUG" : logLevel == Logger::LogLevel::DEBUG ? "DEBUG" :
logLevel == Logger::LogLevel::TRACE ? "TRACE" :
logLevel == Logger::LogLevel::WARNING ? "WARN" : logLevel == Logger::LogLevel::WARNING ? "WARN" :
logLevel == Logger::LogLevel::ERROR ? "ERROR" : logLevel == Logger::LogLevel::ERROR ? "ERROR" :
logLevel == Logger::LogLevel::FATAL_ERROR ? "FATAL" : "?"; logLevel == Logger::LogLevel::FATAL_ERROR ? "FATAL" : "?";
@ -49,6 +52,20 @@ 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) { void Logger::addLogListener(Logger::LogListener listener) {
logListeners.push_back(listener); logListeners.push_back(listener);
} }
void Logger::addLogListener(Logger::TraceLogListener listener) {
traceLogListeners.push_back(listener);
}

View file

@ -8,18 +8,21 @@ namespace Logger {
enum class LogLevel { enum class LogLevel {
INFO, INFO,
DEBUG, DEBUG,
TRACE,
WARNING, WARNING,
ERROR, ERROR,
FATAL_ERROR, FATAL_ERROR,
}; };
typedef std::function<void(LogLevel logLeve, std::string message)> LogListener; 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;
extern std::string currentLogDir; extern std::string currentLogDir;
void init(); void init();
void finish(); void finish();
void addLogListener(LogListener); void addLogListener(LogListener);
void addLogListener(TraceLogListener);
void log(std::string message, LogLevel logLevel); void log(std::string message, LogLevel logLevel);
inline void info(std::string message) { log(message, LogLevel::INFO); } inline void info(std::string message) { log(message, LogLevel::INFO); }
@ -28,6 +31,10 @@ namespace Logger {
inline void error(std::string message) { log(message, LogLevel::ERROR); } inline void error(std::string message) { log(message, LogLevel::ERROR); }
inline void fatalError(std::string message) { log(message, LogLevel::FATAL_ERROR); } inline void fatalError(std::string message) { log(message, LogLevel::FATAL_ERROR); }
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> template <typename ...Args>
void logf(std::string format, LogLevel logLevel, Args&&... args) { void logf(std::string format, LogLevel logLevel, Args&&... args) {
char message[200]; char message[200];

View file

@ -430,3 +430,16 @@ std::vector<std::pair<std::string, std::shared_ptr<Instance>>> Instance::GetRefe
return referenceProperties; return referenceProperties;
} }
std::string Instance::GetFullName() {
std::string currentName = name;
std::optional<std::shared_ptr<Instance>> currentParent = GetParent();
while (currentParent.has_value() && !currentParent.value()->IsA("DataModel")) {
currentName = currentParent.value()->name + "." + currentName;
currentParent = currentParent.value()->GetParent();
}
return currentName;
}

View file

@ -113,6 +113,7 @@ public:
// Utility functions // Utility functions
inline void AddChild(std::shared_ptr<Instance> object) { object->SetParent(this->shared_from_this()); } inline void AddChild(std::shared_ptr<Instance> object) { object->SetParent(this->shared_from_this()); }
std::optional<std::shared_ptr<Instance>> FindFirstChild(std::string); std::optional<std::shared_ptr<Instance>> FindFirstChild(std::string);
std::string GetFullName();
// Properties // Properties
result<Data::Variant, MemberNotFound> GetPropertyValue(std::string name); result<Data::Variant, MemberNotFound> GetPropertyValue(std::string name);

View file

@ -22,7 +22,8 @@ Script::Script(): Instance(&TYPE) {
"workspace.Part.TouchEnded:Connect(function(otherPart)\n" "workspace.Part.TouchEnded:Connect(function(otherPart)\n"
" print(\"Touched ended with: \", otherPart.Name)\n" " print(\"Touched ended with: \", otherPart.Name)\n"
"end)\n" "end)\n"
"\n"; "\n"
"error(\"Test\")";
} }
Script::~Script() { Script::~Script() {
@ -57,12 +58,39 @@ void Script::Run() {
lua_pop(Lt, 1); // _G lua_pop(Lt, 1); // _G
// Load source and push onto thread stack as function ptr // Load source and push onto thread stack as function ptr
luaL_loadstring(Lt, source.c_str()); // luaL_loadstring(Lt, source.c_str());
luaL_loadbuffer(Lt, source.c_str(), source.size(), scriptContext->RegisterScriptSource(shared<Script>()).c_str());
int status = lua_resume(Lt, 0); int status = lua_resume(Lt, 0);
if (status > LUA_YIELD) { if (status > LUA_YIELD) {
Logger::error(lua_tostring(Lt, -1)); lua_Debug dbg;
lua_getstack(Lt, 1, &dbg);
lua_getinfo(Lt, "S", &dbg);
std::weak_ptr<Script> source = scriptContext->GetScriptFromSource(dbg.source);
std::string errorMessage = lua_tostring(Lt, -1);
if (!source.expired())
errorMessage = source.lock()->GetFullName() + errorMessage.substr(errorMessage.find(':'));
Logger::error(errorMessage);
lua_pop(Lt, 1); // Pop return value lua_pop(Lt, 1); // Pop return value
Logger::traceStart();
int stack = 1;
while (lua_getstack(Lt, stack++, &dbg)) {
lua_getinfo(Lt, "nlSu", &dbg);
std::weak_ptr<Script> source = scriptContext->GetScriptFromSource(dbg.source);
if (source.expired()) {
Logger::trace(dbg.source, dbg.currentline);
} else {
Logger::trace(source.lock()->GetFullName(), dbg.currentline, &source);
}
}
Logger::traceEnd();
} }
lua_pop(L, 1); // Pop the thread lua_pop(L, 1); // Pop the thread

View file

@ -4,9 +4,8 @@
#include "datatypes/vector.h" #include "datatypes/vector.h"
#include "logger.h" #include "logger.h"
#include "timeutil.h" #include "timeutil.h"
#include <cstdint>
#include <ctime> #include <ctime>
#include <chrono> #include <string>
#include "lua.h" #include "lua.h"
static int g_print(lua_State*); static int g_print(lua_State*);
@ -130,6 +129,23 @@ void ScriptContext::RunSleepingThreads() {
} }
} }
std::string ScriptContext::RegisterScriptSource(std::shared_ptr<Script> script) {
// If it has already been registered, reference it here
for (auto& [id, script2] : scriptSources) {
if (!script2.expired() && script2.lock() == script)
return "=f" + std::to_string(id);
}
int id = lastScriptSourceId++;
scriptSources[id] = script;
return "=f" + std::to_string(id);
}
std::weak_ptr<Script> ScriptContext::GetScriptFromSource(std::string source) {
int id = std::stoi(source.c_str() + 2);
return scriptSources[id];
}
// https://www.lua.org/source/5.1/lbaselib.c.html // https://www.lua.org/source/5.1/lbaselib.c.html
static int g_print(lua_State* L) { static int g_print(lua_State* L) {
std::string buf; std::string buf;

View file

@ -3,6 +3,7 @@
#include "objects/annotation.h" #include "objects/annotation.h"
#include "objects/base/service.h" #include "objects/base/service.h"
#include "lua.h" #include "lua.h"
#include <memory>
#include <vector> #include <vector>
struct SleepingThread { struct SleepingThread {
@ -12,10 +13,14 @@ struct SleepingThread {
bool active = true; bool active = true;
}; };
class Script;
class DEF_INST_SERVICE ScriptContext : public Service { class DEF_INST_SERVICE ScriptContext : public Service {
AUTOGEN_PREAMBLE AUTOGEN_PREAMBLE
std::vector<SleepingThread> sleepingThreads; std::vector<SleepingThread> sleepingThreads;
std::map<int, std::weak_ptr<Script>> scriptSources;
int lastScriptSourceId = 0;
protected: protected:
void InitService() override; void InitService() override;
bool initialized = false; bool initialized = false;
@ -27,6 +32,8 @@ public:
lua_State* state; lua_State* state;
void PushThreadSleep(lua_State* thread, float delay); void PushThreadSleep(lua_State* thread, float delay);
void RunSleepingThreads(); void RunSleepingThreads();
std::string RegisterScriptSource(std::shared_ptr<Script>);
std::weak_ptr<Script> GetScriptFromSource(std::string source);
static inline std::shared_ptr<Instance> Create() { return std::make_shared<ScriptContext>(); }; static inline std::shared_ptr<Instance> Create() { return std::make_shared<ScriptContext>(); };
}; };

View file

@ -24,14 +24,16 @@ set(PROJECT_SOURCES
mainwindow.ui mainwindow.ui
mainglwidget.h mainglwidget.h
mainglwidget.cpp mainglwidget.cpp
placedocument.h
placedocument.cpp
panes/explorerview.h panes/explorerview.h
panes/explorerview.cpp panes/explorerview.cpp
panes/explorermodel.h panes/explorermodel.h
panes/explorermodel.cpp panes/explorermodel.cpp
panes/propertiesview.h panes/propertiesview.h
panes/propertiesview.cpp panes/propertiesview.cpp
placedocument.h panes/outputtextview.h
placedocument.cpp panes/outputtextview.cpp
script/scriptdocument.h script/scriptdocument.h
script/scriptdocument.cpp script/scriptdocument.cpp
${TS_FILES} ${TS_FILES}

View file

@ -5,8 +5,10 @@
#include "objects/datamodel.h" #include "objects/datamodel.h"
#include "placedocument.h" #include "placedocument.h"
#include "script/scriptdocument.h" #include "script/scriptdocument.h"
#include <cstdio>
#include <memory> #include <memory>
#include <qclipboard.h> #include <qclipboard.h>
#include <qevent.h>
#include <qglobal.h> #include <qglobal.h>
#include <qmessagebox.h> #include <qmessagebox.h>
#include <qmimedata.h> #include <qmimedata.h>
@ -15,6 +17,8 @@
#include <qstylehints.h> #include <qstylehints.h>
#include <qmdisubwindow.h> #include <qmdisubwindow.h>
#include <pugixml.hpp> #include <pugixml.hpp>
#include <qtextcursor.h>
#include <qtextedit.h>
#ifdef _NDEBUG #ifdef _NDEBUG
#define NDEBUG #define NDEBUG
@ -79,26 +83,6 @@ MainWindow::MainWindow(QWidget *parent)
this->close(); this->close();
}); });
// Logger
Logger::addLogListener(std::bind(&MainWindow::handleLog, this, std::placeholders::_1, std::placeholders::_2));
QFont font("");
font.setStyleHint(QFont::Monospace);
ui->outputTextView->setFont(font);
ui->outputTextView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->outputTextView, &QWidget::customContextMenuRequested, [&](QPoint point) {
QMenu *menu = ui->outputTextView->createStandardContextMenu(point);
menu->addAction("Clear Output", [&]() {
ui->outputTextView->clear();
});
menu->exec(ui->outputTextView->mapToGlobal(point));
delete menu;
});
connectActionHandlers(); connectActionHandlers();
// Update properties // Update properties
@ -156,17 +140,6 @@ void MainWindow::closeEvent(QCloseEvent* evt) {
#endif #endif
} }
void MainWindow::handleLog(Logger::LogLevel logLevel, std::string message) {
if (logLevel == Logger::LogLevel::DEBUG) return;
if (logLevel == Logger::LogLevel::INFO)
ui->outputTextView->appendHtml(QString("<p>%1</p>").arg(QString::fromStdString(message)));
if (logLevel == Logger::LogLevel::WARNING)
ui->outputTextView->appendHtml(QString("<p style=\"color:rgb(255, 127, 0); font-weight: bold;\">%1</p>").arg(QString::fromStdString(message)));
if (logLevel == Logger::LogLevel::ERROR || logLevel == Logger::LogLevel::FATAL_ERROR)
ui->outputTextView->appendHtml(QString("<p style=\"color:rgb(255, 0, 0); font-weight: bold;\">%1</p>").arg(QString::fromStdString(message)));
}
void MainWindow::connectActionHandlers() { void MainWindow::connectActionHandlers() {
// Explorer View // Explorer View

View file

@ -64,7 +64,6 @@ private:
void updateToolbars(); void updateToolbars();
void closeEvent(QCloseEvent* evt) override; void closeEvent(QCloseEvent* evt) override;
void handleLog(Logger::LogLevel, std::string);
void connectActionHandlers(); void connectActionHandlers();

View file

@ -122,7 +122,7 @@
<widget class="QWidget" name="dockWidgetContents_3"> <widget class="QWidget" name="dockWidgetContents_3">
<layout class="QVBoxLayout" name="verticalLayout_4"> <layout class="QVBoxLayout" name="verticalLayout_4">
<item> <item>
<widget class="QPlainTextEdit" name="outputTextView"> <widget class="OutputTextView" name="outputTextView">
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>
@ -780,6 +780,11 @@
<extends>QTreeView</extends> <extends>QTreeView</extends>
<header>panes/propertiesview.h</header> <header>panes/propertiesview.h</header>
</customwidget> </customwidget>
<customwidget>
<class>OutputTextView</class>
<extends>QTextEdit</extends>
<header>panes/outputtextview.h</header>
</customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections/> <connections/>

View file

@ -0,0 +1,112 @@
#include "outputtextview.h"
#include "mainwindow.h"
#include "objects/script.h"
#include "panes/outputtextview.h"
#include <QEvent>
#include <QTextEdit>
#include <QWidget>
#include <QMouseEvent>
#include <qcursor.h>
#include <qmenu.h>
#include <qnamespace.h>
#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));
ensureCursorVisible();
QFont font("");
font.setStyleHint(QFont::Monospace);
setFont(font);
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &QWidget::customContextMenuRequested, [&](QPoint point) {
QMenu *menu = createStandardContextMenu(point);
menu->addAction("Clear Output", [&]() {
clear();
stackTraceScripts.clear();
});
menu->exec(mapToGlobal(point));
delete menu;
});
}
OutputTextView::~OutputTextView() = default;
void OutputTextView::handleLog(Logger::LogLevel logLevel, std::string message) {
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();
QTextCharFormat format = cursor.charFormat();
if (logLevel == Logger::LogLevel::TRACE) {
format.setForeground(QColor(0, 127, 255));
} else if (logLevel == Logger::LogLevel::WARNING) {
format.setForeground(QColor(255, 127, 0));
format.setFontWeight(QFont::Bold); // https://forum.qt.io/post/505920
} else if (logLevel == Logger::LogLevel::ERROR || logLevel == Logger::LogLevel::FATAL_ERROR) {
format.setForeground(QColor(255, 0, 0));
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()) {
int id = stackTraceScriptsLastId++;
stackTraceScripts[id] = *script;
format.setAnchor(true);
format.setAnchorHref(QString::number(id));
}
cursor.insertText(message.c_str(), format);
cursor.insertText("\n", QTextCharFormat());
}
// https://stackoverflow.com/a/61722734/16255372
void OutputTextView::mousePressEvent(QMouseEvent *e) {
QString anchor = anchorAt(e->pos());
if (anchor == "" || e->modifiers() & Qt::AltModifier) return QTextEdit::mousePressEvent(e);
auto script = stackTraceScripts[anchor.toInt()];
if (script.expired()) return QTextEdit::mousePressEvent(e);
MainWindow* mainWnd = dynamic_cast<MainWindow*>(window());
mainWnd->openScriptDocument(script.lock());
}
void OutputTextView::mouseReleaseEvent(QMouseEvent *e) {
return QTextEdit::mouseReleaseEvent(e);
}
void OutputTextView::mouseMoveEvent(QMouseEvent *e) {
QCursor cur = cursor();
QString anchor = anchorAt(e->pos());
if (anchor == "" || e->modifiers() & Qt::AltModifier) {
viewport()->setCursor(Qt::IBeamCursor);
} else {
viewport()->setCursor(Qt::PointingHandCursor);
}
return QTextEdit::mouseMoveEvent(e);
}

View file

@ -0,0 +1,25 @@
#pragma once
#include "logger.h"
#include <memory>
#include <qobjectdefs.h>
#include <qtextedit.h>
class Script;
class OutputTextView : public QTextEdit {
Q_OBJECT
private:
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void handleLog(Logger::LogLevel, std::string);
void handleLogTrace(std::string, std::string, int, void*);
std::map<int, std::weak_ptr<Script>> stackTraceScripts;
int stackTraceScriptsLastId = 0;
public:
OutputTextView(QWidget* parent = nullptr);
~OutputTextView() override;
};