mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
More work on the lua console
This commit is contained in:
parent
2d69d78e71
commit
acad90a041
8 changed files with 207 additions and 106 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<int>();
|
||||
lines_.push_back(LuaLogLine{ LogLineType::Integer, StringFromFormat("%08x (%d)", num, num), item.get<int>()});
|
||||
break;
|
||||
}
|
||||
case sol::type::string:
|
||||
{
|
||||
// TODO: Linebreak multi-line strings.
|
||||
lines_.push_back(LuaLogLine{ LogLineType::String, item.get<std::string>() });
|
||||
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()) });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<LuaLogLine> 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<sol::state> lua_;
|
||||
|
||||
// Naming it L is a common convention.
|
||||
lua_State *L = nullptr;
|
||||
std::vector<LuaLogLine> lines_;
|
||||
};
|
||||
|
||||
extern LuaContext g_lua;
|
||||
|
|
|
@ -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]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<char*> Items;
|
||||
ImVector<const char*> Commands;
|
||||
ImVector<char*> 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);
|
||||
};
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Reference in a new issue