From 2d1def8dc6d250bc5aa00f74d0dc467d1acd12e4 Mon Sep 17 00:00:00 2001 From: jyelon Date: Tue, 9 Dec 2025 02:42:13 -0500 Subject: [PATCH] Lua Console Overhaul in progress --- Content/Luprex/lxGameMode.uasset | 4 +- Content/Widgets/WB_Console.uasset | 4 +- Source/Integration/ConsoleOutput.cpp | 12 +++--- Source/Integration/ConsoleOutput.h | 15 +++++++- Source/Integration/LuaCall.cpp | 32 +++++++++++++++- Source/Integration/LuaCall.h | 24 +++++++++++- Source/Integration/LuprexGameModeBase.cpp | 45 ++++++----------------- Source/Integration/LuprexGameModeBase.h | 28 +++++++------- luprex/cpp/core/lpxclient.cpp | 2 +- luprex/cpp/core/lpxserver.cpp | 2 +- 10 files changed, 105 insertions(+), 63 deletions(-) diff --git a/Content/Luprex/lxGameMode.uasset b/Content/Luprex/lxGameMode.uasset index d5532c4f..c8fbf134 100644 --- a/Content/Luprex/lxGameMode.uasset +++ b/Content/Luprex/lxGameMode.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1470cae3a445cda9c8f78df8b9af59cccff4c34bd190025dd92a2c820459584c -size 164027 +oid sha256:582c94da32ae045895687411e6b5c62c4e835c95a5817b5493f031df59df0b93 +size 150819 diff --git a/Content/Widgets/WB_Console.uasset b/Content/Widgets/WB_Console.uasset index ad32a2b2..9e08ec34 100644 --- a/Content/Widgets/WB_Console.uasset +++ b/Content/Widgets/WB_Console.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8bab42c29be34eed14d47d31594858d3f12376603ae80ffaf7948ef38b9c6686 -size 70196 +oid sha256:13b0fa7e4c14f8a0f2a9d891be892637a7da5ba255196c0a39339f196da1ca57 +size 169046 diff --git a/Source/Integration/ConsoleOutput.cpp b/Source/Integration/ConsoleOutput.cpp index c0b784e6..5739270f 100644 --- a/Source/Integration/ConsoleOutput.cpp +++ b/Source/Integration/ConsoleOutput.cpp @@ -5,12 +5,12 @@ // // ConsoleOutput // -// Storing the text that goes in the unreal console. +// Storing the text that goes in the command console. // ////////////////////////////////////////////////////////////// -void FlxConsoleOutput::Truncate() { +void UlxConsoleOutput::Truncate() { int lines = 50; int csize = Content.Len(); int total = 0; @@ -25,7 +25,7 @@ void FlxConsoleOutput::Truncate() { } } -bool FlxConsoleOutput::MaybeAppendNewline() { +bool UlxConsoleOutput::MaybeAppendNewline() { int csize = Content.Len(); if ((csize > 0) && (Content[csize - 1] != '\n')) { Content += TEXT("\n"); @@ -36,7 +36,7 @@ bool FlxConsoleOutput::MaybeAppendNewline() { } } -bool FlxConsoleOutput::MaybeAppendText(const FString& text) { +bool UlxConsoleOutput::MaybeAppendText(const FString& text) { if (!text.IsEmpty()) { Content += text; return true; @@ -46,7 +46,7 @@ bool FlxConsoleOutput::MaybeAppendText(const FString& text) { } } -void FlxConsoleOutput::Append(const FString& text) { +void UlxConsoleOutput::Append(const FString& text) { bool modified = MaybeAppendText(text); if (modified) { Dirty = true; @@ -54,7 +54,7 @@ void FlxConsoleOutput::Append(const FString& text) { } } -void FlxConsoleOutput::AppendLine(const FString& text) { +void UlxConsoleOutput::AppendLine(const FString& text) { bool modified = MaybeAppendNewline(); modified |= MaybeAppendText(text); modified |= MaybeAppendNewline(); diff --git a/Source/Integration/ConsoleOutput.h b/Source/Integration/ConsoleOutput.h index 359ba68c..8a7cdf61 100644 --- a/Source/Integration/ConsoleOutput.h +++ b/Source/Integration/ConsoleOutput.h @@ -2,6 +2,9 @@ #include "Containers/UnrealString.h" +#include "ConsoleOutput.generated.h" + + ////////////////////////////////////////////////////////////// // // ConsoleOutput @@ -19,11 +22,16 @@ // ////////////////////////////////////////////////////////////// -class FlxConsoleOutput { +UCLASS(BlueprintType) +class UlxConsoleOutput : public UObject +{ + GENERATED_BODY() + private: FString Content; bool Dirty; +private: // Truncate the console to a reasonable number of // lines. The length is hardwired. void Truncate(); @@ -36,18 +44,23 @@ private: public: // Append a line of text to the console. + UFUNCTION(BlueprintCallable) void Append(const FString& text); // Append a line of text to the console on a line by itself. + UFUNCTION(BlueprintCallable) void AppendLine(const FString& text); // Get the console text as a string. + UFUNCTION(BlueprintCallable) const FString& Get() const { return Content; } // Return if the dirty flag is set. + UFUNCTION(BlueprintCallable) bool IsDirty() const { return Dirty; } // Clear the dirty flag. + UFUNCTION(BlueprintCallable) void ClearDirty() { Dirty = false; } }; diff --git a/Source/Integration/LuaCall.cpp b/Source/Integration/LuaCall.cpp index 2a2d5e79..157f2822 100644 --- a/Source/Integration/LuaCall.cpp +++ b/Source/Integration/LuaCall.cpp @@ -202,6 +202,37 @@ FString UlxLuaCallLibrary::AllFunctionsWithPrefix(const TCHAR *Prefix) // ///////////////////////////////////////////////////////////////// +void UlxLuaCallLibrary::ValidateLua( + ElxLuaSyntaxCheck &Result, FString &ErrorMessage, UObject *context, const FString &Code) +{ + if (Code.StartsWith(TEXT("/"))) + { + ErrorMessage = "SlashCommand"; + Result = ElxLuaSyntaxCheck::SlashCommand; + return; + } + if (Code.TrimStart().IsEmpty()) + { + ErrorMessage = ""; + Result = ElxLuaSyntaxCheck::Whitespace; + return; + } + ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); + ErrorMessage = mode->LuaValidate(Code); + if (ErrorMessage.IsEmpty()) + { + Result = ElxLuaSyntaxCheck::ValidLua; + } + else if (ErrorMessage.Contains(TEXT(""))) + { + Result = ElxLuaSyntaxCheck::TruncatedLua; + } + else + { + Result = ElxLuaSyntaxCheck::InvalidLua; + } +} + void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, const FString &fname) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); @@ -211,7 +242,6 @@ void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, con sb.write_string(fname); } - void UlxLuaCallLibrary::LuaCallInvoke(UObject *context, AActor *place) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); diff --git a/Source/Integration/LuaCall.h b/Source/Integration/LuaCall.h index 734729ba..276d5756 100644 --- a/Source/Integration/LuaCall.h +++ b/Source/Integration/LuaCall.h @@ -9,6 +9,23 @@ class UlxLuaValues; +// Classify lua code syntactically: +// +// SlashCommand: starts with a slash, therefore, not lua +// WhitespaceOnly: the input only contains whitespace +// ValidSyntax: the input is valid lua code +// TruncatedCode: the input is truncated +// InvalidSyntax: invalid lua +// +UENUM(BlueprintType) +enum class ElxLuaSyntaxCheck : uint8 { + SlashCommand, + Whitespace, + ValidLua, + TruncatedLua, + InvalidLua, +}; + ///////////////////////////////////////////////////////////////// // // These are the types that can actually be packed into @@ -118,9 +135,12 @@ public: static FString AllKnownReturnValueTypes() { return AllFunctionsWithPrefix(TEXT("LuaCallReturnValue_")); } public: + // Syntactically validate lua code. Parses the code and + // returns an error message. If the code is error-free, the + // error message is the empty string. // - // Functions that do miscellaneous things. - // + UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", ExpandEnumAsExecs="Result"), Category = "Luprex|Call Lua Function") + static void ValidateLua(ElxLuaSyntaxCheck &Result, FString &ErrorMessage, UObject *context, const FString &Code); UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function") static void LuaCallBegin(UObject *context, const FString &ClassName, const FString &FunctionName); diff --git a/Source/Integration/LuprexGameModeBase.cpp b/Source/Integration/LuprexGameModeBase.cpp index 1c431d3a..fde43b89 100644 --- a/Source/Integration/LuprexGameModeBase.cpp +++ b/Source/Integration/LuprexGameModeBase.cpp @@ -3,7 +3,6 @@ #include "LuprexGameModeBase.h" #include "lpx-drvutil.hpp" #include "lpx-paths.hpp" -#include "ConsoleOutput.h" #include "Tangible.h" #include "TangibleManager.h" #include "LuaCall.h" @@ -124,18 +123,15 @@ void ALuprexGameModeBase::ResetToInitialState() NextRotateCube = 1.0; } - void ALuprexGameModeBase::UpdateConsoleOutput() { // Copy Luprex Stdout into the console. FlxLockedWrapper lockedwrap(LockableWrapper); if (Playing) { - ConsoleOutput.Append(lockedwrap.FetchStdout()); - } - - // If the Console text has changed, update the widget. - if (ConsoleOutput.IsDirty()) { - ConsoleSetOutput(ConsoleOutput.Get()); - ConsoleOutput.ClearDirty(); + FString Text = lockedwrap.FetchStdout(); + if (!Text.IsEmpty()) + { + ConsoleAddOutput(Text); + } } } @@ -227,32 +223,15 @@ UlxLuaValues *ALuprexGameModeBase::LuaCallEnd(AccessKind kind, AActor *place) { } } -void ALuprexGameModeBase::ExecuteDebuggingCommand(FlxLockedWrapper &w, const FString &fs) { - // Nothing here right now. -} - -void ALuprexGameModeBase::ConsoleSendInput(const FString& fs) +FString ALuprexGameModeBase::LuaValidate(const FString &Code) { - if (fs.IsEmpty()) { - return; - } - + FTCHARToUTF8 UCode(*Code); FlxLockedWrapper w(LockableWrapper); - if (w->engine != nullptr) - { - ConsoleOutput.AppendLine(FString("> ") + fs); - // This is a bad way to do this. The problem is that if some - // lua code contains '\\', we'll catch it instead of passing it - // through. There's no simple solution, though. - if (fs[0] == '\\') { - ExecuteDebuggingCommand(w, fs); - } else { - FTCHARToUTF8 utf8fs(fs); - std::string ufs(utf8fs.Get(), utf8fs.Length()); - ufs = ufs + "\n"; - w->play_recv_incoming(w.Get(), 0, ufs.size(), ufs.c_str()); - } - } + uint32_t retpklen; + const char *retpk; + w->play_access(w.Get(), AccessKind::VALIDATE_LUA, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk); + FString Result(retpklen, (const UTF8CHAR*)retpk); + return Result; } void ALuprexGameModeBase::OnWorldPreActorTick(UWorld* InWorld, ELevelTick InLevelTick, float deltaseconds) diff --git a/Source/Integration/LuprexGameModeBase.h b/Source/Integration/LuprexGameModeBase.h index 21a7470e..8379549c 100644 --- a/Source/Integration/LuprexGameModeBase.h +++ b/Source/Integration/LuprexGameModeBase.h @@ -5,7 +5,6 @@ #include "CoreMinimal.h" #include "GameFramework/GameModeBase.h" #include "lpx-enginewrapper.hpp" -#include "ConsoleOutput.h" #include "StringDecoder.h" #include "TangibleManager.h" #include "AssetLookup.h" @@ -59,11 +58,7 @@ public: // Set the entire contents of the console output box. UFUNCTION(BlueprintImplementableEvent, Category = "Luprex|Miscellaneous") - void ConsoleSetOutput(const FString& text); - - // This is called by the GUI whenever the user hits enter. - UFUNCTION(BlueprintCallable, Category = "Luprex|Miscellaneous") - void ConsoleSendInput(const FString& text); + void ConsoleAddOutput(const FString& text); UFUNCTION(BlueprintCallable, Category = "Luprex|Miscellaneous") int64 GetPlayerId(); @@ -94,7 +89,11 @@ public: void LookAtChanged(); - // Assemble a lua call. To call into lua: + // Assemble a lua call. Note that this is the lowest-level interface. + // These functions are wrapped by the functions in UlxLuaCallLibrary, + // and those in turn are wrapped by the K2Node "LuaInvoke" and "LuaProbe". + // + // At this level, the process of calling Lua is: // // * Use LuaCallBegin // * Get the lua call buffer: @@ -102,8 +101,7 @@ public: // - add a function name // - add function parameters // * Use LuaCallEnd. - // * Get the lua call result. - // - parse out any return values + // * Process any return values in the UlxLuaValues array. // FlxStreamBuffer &LuaCallBegin() { LuaCallBuffer.clear(); return LuaCallBuffer; } FlxStreamBuffer &LuaCallGetBuffer() { return LuaCallBuffer; } @@ -112,8 +110,13 @@ public: UlxLuaValues *LuaCallEnd(AccessKind kind, AActor *place); void LuaCallClear() { LuaCallBuffer.clear(); } - // Execute a debugging command, typed on the GUI. - void ExecuteDebuggingCommand(FlxLockedWrapper &w, const FString &fs); + // Validate some lua code. Returns an error message. + // If the lua is well-formed, the error message is the + // empty string. The syntax of the code is checked using + // an otherwise empty lua interpreter, so this is purely + // a syntax check. + // + FString LuaValidate(const FString &Code); // Get the Asset Lookup table. const UlxAssetLookup *GetAssetLookup() const { return AssetLookup; } @@ -162,9 +165,6 @@ public: UPROPERTY(EditAnywhere, Category="Debugging Tools") ElxLogVerbosity BreakToDebuggerLogVerbosity; - // This stores the entire text currently visible in the console. - FlxConsoleOutput ConsoleOutput; - // The Luprex EngineWrapper, with a Mutex to protect it. // To access it, construct a FlxLockedWrapper. FlxLockableWrapper LockableWrapper; diff --git a/luprex/cpp/core/lpxclient.cpp b/luprex/cpp/core/lpxclient.cpp index cda3312b..b2c79265 100644 --- a/luprex/cpp/core/lpxclient.cpp +++ b/luprex/cpp/core/lpxclient.cpp @@ -265,7 +265,7 @@ public: } case AccessKind::VALIDATE_LUA: { LuaVar closure; - LuaDefStack LS(lua_syntax_checker_, closure); + LuaExtStack LS(lua_syntax_checker_, closure); eng::string errmsg = LS.load(closure, datapk, "stdin"); retpk->write_bytes(errmsg); break; diff --git a/luprex/cpp/core/lpxserver.cpp b/luprex/cpp/core/lpxserver.cpp index 2dd6e65a..8103c5d9 100644 --- a/luprex/cpp/core/lpxserver.cpp +++ b/luprex/cpp/core/lpxserver.cpp @@ -189,7 +189,7 @@ public: } case AccessKind::VALIDATE_LUA: { LuaVar closure; - LuaDefStack LS(lua_syntax_checker_, closure); + LuaExtStack LS(lua_syntax_checker_, closure); eng::string errmsg = LS.load(closure, datapk, "stdin"); retpk->write_bytes(errmsg); break;