diff --git a/Common/Thread/ThreadUtil.cpp b/Common/Thread/ThreadUtil.cpp index 8d60ea6140..3e7a809e8b 100644 --- a/Common/Thread/ThreadUtil.cpp +++ b/Common/Thread/ThreadUtil.cpp @@ -31,7 +31,10 @@ void AttachThreadToJNI() { if (g_attach) { g_attach(); } else { +#if PPSSPP_PLATFORM(ANDROID) + // Not relevant on other platforms. ERROR_LOG(Log::System, "Couldn't attach thread - g_attach not set"); +#endif } } diff --git a/Core/Debugger/DisassemblyManager.h b/Core/Debugger/DisassemblyManager.h index 720d3b6d67..b2e028e070 100644 --- a/Core/Debugger/DisassemblyManager.h +++ b/Core/Debugger/DisassemblyManager.h @@ -43,13 +43,13 @@ struct DisassemblyLineInfo u32 totalSize; }; -enum LineType { LINE_UP, LINE_DOWN, LINE_RIGHT }; +enum DisasmLineType { LINE_UP, LINE_DOWN, LINE_RIGHT }; struct BranchLine { u32 first; u32 second; - LineType type; + DisasmLineType type; int laneIndex; bool operator<(const BranchLine& other) const diff --git a/Core/LuaContext.cpp b/Core/LuaContext.cpp index ba27375fb7..a7803d52b3 100644 --- a/Core/LuaContext.cpp +++ b/Core/LuaContext.cpp @@ -3,22 +3,65 @@ #include "Common/Log.h" #include "Common/StringUtils.h" #include "Core/LuaContext.h" - -std::string g_stringBuf; +#include "Core/MemMap.h" // Sol is expensive to include so we only do it here. #include "ext/sol/sol.hpp" LuaContext g_lua; +static bool IsProbablyExpression(std::string_view input) { + // Heuristic: If it's a single-line statement without assignment or keywords, assume it's an expression. + return !(input.find("=") != std::string_view::npos || + input.find("function") != std::string_view::npos || + input.find("do") != std::string_view::npos || + input.find("end") != std::string_view::npos || + input.find("return") != std::string_view::npos || + input.find("local") != std::string_view::npos); +} + // Custom print function -static void log(const std::string& message) { +static void print(const std::string& message) { + g_lua.Print(message); +} + +// TODO: Should these also echo to the console? +static void debug(const std::string &message) { + DEBUG_LOG(Log::System, "%s", message.c_str()); +} + +static void info(const std::string &message) { INFO_LOG(Log::System, "%s", message.c_str()); - g_stringBuf = message; +} + +static void warn(const std::string &message) { + WARN_LOG(Log::System, "%s", message.c_str()); +} + +static void error(const std::string &message) { + ERROR_LOG(Log::System, "%s", message.c_str()); +} + +// TODO: We should probably disallow or at least discourage raw read/writes and instead +// only support read/writes that refer to the name of a memory region. +static int r32(int address) { + if (Memory::IsValid4AlignedAddress(address)) { + return Memory::Read_U32(address); + } else { + g_lua.Print(LogLineType::Error, StringFromFormat("r32: bad address %08x", address)); + return 0; + } +} + +static void w32(int address, int value) { + if (Memory::IsValid4AlignedAddress(address)) { + Memory::Write_U32(value, address); // NOTE: These are backwards for historical reasons. + } else { + g_lua.Print(LogLineType::Error, StringFromFormat("w32: bad address %08x trying to write %08x", address, value)); + } } void LuaContext::Init() { - _dbg_assert_(lua_ == nullptr); lua_.reset(new sol::state()); lua_->open_libraries(sol::lib::base); @@ -27,25 +70,72 @@ void LuaContext::Init() { lua_->open_libraries(sol::lib::string); lua_->open_libraries(sol::lib::math); - // Not sure if we can safely override print(). So making a new function. - lua_->set("log", &log); + extern const char *PPSSPP_GIT_VERSION; + lua_->set("ver", PPSSPP_GIT_VERSION); + + lua_->set("print", &print); + lua_->set("debug", &debug); + lua_->set("info", &info); + lua_->set("warn", &warn); + lua_->set("error", &error); + + lua_->set("r32", &r32); } void LuaContext::Shutdown() { lua_.reset(); } -void LuaContext::Load(const char *code) { - -} - -void LuaContext::Execute(std::string_view cmd, std::string *output) { - try { - lua_->script(cmd); - *output = g_stringBuf; - g_stringBuf.clear(); - } catch (sol::error e) { - ERROR_LOG(Log::System, "Exception: %s", e.what()); - *output = e.what(); +const char *SolTypeToString(sol::type type) { + switch (type) { + case sol::type::boolean: return "boolean"; + default: return "other"; + } +} + +void LuaContext::Print(LogLineType type, std::string_view text) { + lines_.push_back(LuaLogLine{ type, std::string(text)}); +} + +void LuaContext::ExecuteConsoleCommand(std::string_view cmd) { + // TODO: Also rewrite expressions like: + // print "hello" + // to + // print("hello") ? + try { + std::string command; + if (IsProbablyExpression(cmd)) { + command = "return "; + command += cmd; + } else { + command = cmd; + } + auto result = lua_->script(command); + if (result.valid()) { + for (const sol::stack_proxy &item : result) { + switch (item.get_type()) { + case sol::type::number: + { + int num = item.get(); + lines_.push_back(LuaLogLine{ LogLineType::Integer, StringFromFormat("%08x (%d)", num, num), item.get()}); + break; + } + case sol::type::string: + { + // TODO: Linebreak multi-line strings. + lines_.push_back(LuaLogLine{ LogLineType::String, item.get() }); + break; + } + default: + break; + } + } + } else { + sol::error err = result; + lines_.push_back(LuaLogLine{ LogLineType::Error, std::string(err.what()) }); + } + } catch (sol::error e) { + ERROR_LOG(Log::System, "Lua exception: %s", e.what()); + lines_.push_back(LuaLogLine{ LogLineType::Error, std::string(e.what()) }); } } diff --git a/Core/LuaContext.h b/Core/LuaContext.h index eaf59e80ed..13b7c5ceb8 100644 --- a/Core/LuaContext.h +++ b/Core/LuaContext.h @@ -8,20 +8,44 @@ struct lua_State; +enum class LogLineType { + Cmd, + String, + Integer, + Error, + External, + Url, +}; + +// A bit richer than regular log lines, so we can display them in color, and allow various UI tricks. +// All have a string, but some may also have a number or other value. +struct LuaLogLine { + LogLineType type; + std::string line; + int number; +}; + class LuaContext { public: void Init(); void Shutdown(); - void Load(const char *code); + + const std::vector GetLines() const { + return lines_; + } + void Clear() { lines_.clear(); } + + void Print(LogLineType type, std::string_view text); + void Print(std::string_view text) { + Print(LogLineType::External, text); + } // For the console. - void Execute(std::string_view cmd, std::string *output); + void ExecuteConsoleCommand(std::string_view cmd); private: std::unique_ptr lua_; - - // Naming it L is a common convention. - lua_State *L = nullptr; + std::vector lines_; }; extern LuaContext g_lua; diff --git a/UI/ImDebugger/ImConsole.cpp b/UI/ImDebugger/ImConsole.cpp index 321dc93492..c2f87f7f67 100644 --- a/UI/ImDebugger/ImConsole.cpp +++ b/UI/ImDebugger/ImConsole.cpp @@ -7,10 +7,11 @@ #include "UI/ImDebugger/ImDebugger.h" #include "UI/ImDebugger/ImConsole.h" #include "Core/LuaContext.h" +#include "Common/StringUtils.h" ImConsole::ImConsole() { - ClearLog(); memset(InputBuf, 0, sizeof(InputBuf)); + HistoryPos = -1; // "CLASSIFY" is here to provide the test case where "C"+[tab] completes to "CL" and display multiple matches. @@ -19,14 +20,11 @@ ImConsole::ImConsole() { Commands.push_back("CLEAR"); AutoScroll = true; ScrollToBottom = false; - AddLog("Welcome to Dear ImGui!"); } ImConsole::~ImConsole() { - ClearLog(); for (int i = 0; i < History.Size; i++) ImGui::MemFree(History[i]); - AddLog("# Enter 'HELP' for help."); } // Portable helpers @@ -35,23 +33,6 @@ static int Strnicmp(const char* s1, const char* s2, int n) { int d = 0; while static char* Strdup(const char* s) { IM_ASSERT(s); size_t len = strlen(s) + 1; void* buf = ImGui::MemAlloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)s, len); } static void Strtrim(char* s) { char* str_end = s + strlen(s); while (str_end > s && str_end[-1] == ' ') str_end--; *str_end = 0; } -void ImConsole::ClearLog() { - for (int i = 0; i < Items.Size; i++) - ImGui::MemFree(Items[i]); - Items.clear(); -} - -void ImConsole::AddLog(const char* fmt, ...) IM_FMTARGS(2) { - // FIXME-OPT - char buf[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buf, IM_ARRAYSIZE(buf), fmt, args); - buf[IM_ARRAYSIZE(buf) - 1] = 0; - va_end(args); - Items.push_back(Strdup(buf)); -} - // In C++11 you'd be better off using lambdas for this sort of forwarding callbacks static int TextEditCallbackStub(ImGuiInputTextCallbackData* data) { ImConsole* console = (ImConsole*)data->UserData; @@ -60,27 +41,18 @@ static int TextEditCallbackStub(ImGuiInputTextCallbackData* data) { void ImConsole::Draw(ImConfig &cfg) { ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver); - if (!ImGui::Begin("Console", &cfg.luaConsoleOpen)) { + if (!ImGui::Begin("Lua Console", &cfg.luaConsoleOpen)) { ImGui::End(); return; } - ImGui::TextWrapped("Lua console. Enter 'HELP' for help."); - - if (ImGui::SmallButton("Add Debug Text")) { - AddLog("%d some text", Items.Size); - } - ImGui::SameLine(); - if (ImGui::SmallButton("Add Debug Error")) { - AddLog("[error] something went wrong"); - } - ImGui::SameLine(); if (ImGui::SmallButton("Clear")) { - ClearLog(); + g_lua.Clear(); } + ImGui::SameLine(); + bool copy_to_clipboard = ImGui::SmallButton("Copy"); - ImGui::Separator(); // Options menu @@ -99,10 +71,10 @@ void ImConsole::Draw(ImConfig &cfg) { // Reserve enough left-over height for 1 separator + 1 input text const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); - if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NavFlattened)) { + if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), ImGuiChildFlags_None, ImGuiWindowFlags_HorizontalScrollbar | ImGuiChildFlags_NavFlattened)) { if (ImGui::BeginPopupContextWindow()) { if (ImGui::Selectable("Clear")) - ClearLog(); + g_lua.Clear(); ImGui::EndPopup(); } @@ -133,22 +105,35 @@ void ImConsole::Draw(ImConfig &cfg) { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing if (copy_to_clipboard) ImGui::LogToClipboard(); - for (const char* item : Items) { - if (!Filter.PassFilter(item)) + for (const auto &item : g_lua.GetLines()) { + if (!Filter.PassFilter(item.line.c_str())) continue; - - // Normally you would store more information in your item than just a string. - // (e.g. make Items[] an array of structure, store color/type etc.) ImVec4 color; - bool has_color = false; - if (strstr(item, "[error]")) { - color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); has_color = true; - } else if (strncmp(item, "# ", 2) == 0) { - color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f); has_color = true; + bool has_color = true; + + switch (item.type) { + case LogLineType::Cmd: color = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); break; + case LogLineType::Error: color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); break; + case LogLineType::External: color = ImVec4(0.8f, 0.8f, 1.0f, 1.0f); break; + case LogLineType::Integer: color = ImVec4(1.0f, 1.0f, 0.8f, 1.0f); break; + case LogLineType::String: color = ImVec4(0.8f, 1.0f, 0.8f, 1.0f); break; + default: + has_color = false; + break; } + if (has_color) ImGui::PushStyleColor(ImGuiCol_Text, color); - ImGui::TextUnformatted(item); + switch (item.type) { + case LogLineType::Url: + if (ImGui::TextLink(item.line.c_str())) { + System_LaunchUrl(LaunchUrlType::BROWSER_URL, item.line.c_str()); + } + break; + default: + ImGui::TextUnformatted(item.line.data(), item.line.data() + item.line.size()); + break; + } if (has_color) ImGui::PopStyleColor(); } @@ -187,8 +172,6 @@ void ImConsole::Draw(ImConfig &cfg) { } void ImConsole::ExecCommand(const char* command_line) { - AddLog("# %s\n", command_line); - // Insert into history. First find match and delete it so it can be pushed to the back. // This isn't trying to be smart or optimal. HistoryPos = -1; @@ -201,23 +184,24 @@ void ImConsole::ExecCommand(const char* command_line) { } History.push_back(Strdup(command_line)); + g_lua.Print(LogLineType::Cmd, std::string(command_line)); + // Process command - if (Stricmp(command_line, "CLEAR") == 0) { - ClearLog(); - } else if (Stricmp(command_line, "HELP") == 0) { - AddLog("Commands:"); + if (Stricmp(command_line, "clear") == 0) { + g_lua.Clear(); + } else if (Stricmp(command_line, "help") == 0) { + g_lua.Print("Available non-Lua commands:"); for (int i = 0; i < Commands.Size; i++) - AddLog("- %s", Commands[i]); - } else if (Stricmp(command_line, "HISTORY") == 0) { + g_lua.Print(StringFromFormat("- %s", Commands[i])); + g_lua.Print("For Lua help:"); + g_lua.Print(LogLineType::Url, "https://www.lua.org/manual/5.3/"); + // TODO: Also print available Lua commands. + } else if (Stricmp(command_line, "history") == 0) { int first = History.Size - 10; for (int i = first > 0 ? first : 0; i < History.Size; i++) - AddLog("%3d: %s\n", i, History[i]); + g_lua.Print(StringFromFormat("%3d: %s", i, History[i])); } else { - // TODO: Anything else, forward to Lua. - // AddLog("Unknown command: '%s'\n", command_line); - std::string response; - g_lua.Execute(command_line, &response); - AddLog("%s", response.c_str()); + g_lua.ExecuteConsoleCommand(command_line); } // On command input, we scroll to bottom even if AutoScroll==false @@ -248,9 +232,11 @@ int ImConsole::TextEditCallback(ImGuiInputTextCallbackData* data) { if (Strnicmp(Commands[i], word_start, (int)(word_end - word_start)) == 0) candidates.push_back(Commands[i]); + // TODO: Add lua globals to candidates! + if (candidates.Size == 0) { - // No match - AddLog("No match for \"%.*s\"!\n", (int)(word_end - word_start), word_start); + // No match. TODO: Match against lua globals. + g_lua.Print(StringFromFormat("No match for \"%.*s\"!", (int)(word_end - word_start), word_start)); } else if (candidates.Size == 1) { // Single match. Delete the beginning of the word and replace it entirely so we've got nice casing. data->DeleteChars((int)(word_start - data->Buf), (int)(word_end - word_start)); @@ -279,9 +265,9 @@ int ImConsole::TextEditCallback(ImGuiInputTextCallbackData* data) { } // List matches - AddLog("Possible matches:\n"); + g_lua.Print("Possible matches:"); for (int i = 0; i < candidates.Size; i++) { - AddLog("- %s\n", candidates[i]); + g_lua.Print(StringFromFormat("- %s", candidates[i])); } } diff --git a/UI/ImDebugger/ImConsole.h b/UI/ImDebugger/ImConsole.h index d731df944b..b6f93fde7d 100644 --- a/UI/ImDebugger/ImConsole.h +++ b/UI/ImDebugger/ImConsole.h @@ -6,25 +6,22 @@ #include "ext/imgui/imgui.h" // Adapted from the ImGui demo. -struct ImConsole { +class ImConsole { +public: + ImConsole(); + ~ImConsole(); + + void Draw(ImConfig &cfg); + void ExecCommand(const char* command_line); + + int TextEditCallback(ImGuiInputTextCallbackData* data); + +private: char InputBuf[256]; - ImVector Items; ImVector Commands; ImVector History; int HistoryPos; // -1: new line, 0..History.Size-1 browsing history. ImGuiTextFilter Filter; bool AutoScroll; bool ScrollToBottom; - - ImConsole(); - ~ImConsole(); - - void ClearLog(); - - void AddLog(const char* fmt, ...) IM_FMTARGS(2); - - void Draw(ImConfig &cfg); - void ExecCommand(const char* command_line); - - int TextEditCallback(ImGuiInputTextCallbackData* data); }; diff --git a/UI/ImDebugger/ImDebugger.cpp b/UI/ImDebugger/ImDebugger.cpp index 61ae0825f6..f47744b192 100644 --- a/UI/ImDebugger/ImDebugger.cpp +++ b/UI/ImDebugger/ImDebugger.cpp @@ -1757,6 +1757,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu ImGui::EndMenu(); } if (ImGui::BeginMenu("Tools")) { + ImGui::MenuItem("Lua Console", nullptr, &cfg_.luaConsoleOpen); ImGui::MenuItem("Debug stats", nullptr, &cfg_.debugStatsOpen); ImGui::MenuItem("Struct viewer", nullptr, &cfg_.structViewerOpen); ImGui::MenuItem("Log channels", nullptr, &cfg_.logConfigOpen); @@ -2320,6 +2321,7 @@ void ImConfig::SyncConfig(IniFile *ini, bool save) { sync.Sync("internalsOpen", &internalsOpen, false); sync.Sync("sasAudioOpen", &sasAudioOpen, false); sync.Sync("logConfigOpen", &logConfigOpen, false); + sync.Sync("luaConsoleOpen", &luaConsoleOpen, false); sync.Sync("utilityModulesOpen", &utilityModulesOpen, false); for (int i = 0; i < 4; i++) { char name[64]; diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 7314e2d639..85b7abf18f 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -40,7 +40,6 @@ #include "Common/File/FileUtil.h" #include "Common/TimeUtil.h" #include "Common/StringUtils.h" -#include "Common/System/System.h" #include "Common/System/OSD.h" #include "Core/System.h" #include "Core/Util/RecentFiles.h"