Lua Console Overhaul in progress

This commit is contained in:
2025-12-09 02:42:13 -05:00
parent a242244f9c
commit 2d1def8dc6
10 changed files with 105 additions and 63 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -5,12 +5,12 @@
// //
// ConsoleOutput // 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 lines = 50;
int csize = Content.Len(); int csize = Content.Len();
int total = 0; int total = 0;
@@ -25,7 +25,7 @@ void FlxConsoleOutput::Truncate() {
} }
} }
bool FlxConsoleOutput::MaybeAppendNewline() { bool UlxConsoleOutput::MaybeAppendNewline() {
int csize = Content.Len(); int csize = Content.Len();
if ((csize > 0) && (Content[csize - 1] != '\n')) { if ((csize > 0) && (Content[csize - 1] != '\n')) {
Content += TEXT("\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()) { if (!text.IsEmpty()) {
Content += text; Content += text;
return true; 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); bool modified = MaybeAppendText(text);
if (modified) { if (modified) {
Dirty = true; 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(); bool modified = MaybeAppendNewline();
modified |= MaybeAppendText(text); modified |= MaybeAppendText(text);
modified |= MaybeAppendNewline(); modified |= MaybeAppendNewline();

View File

@@ -2,6 +2,9 @@
#include "Containers/UnrealString.h" #include "Containers/UnrealString.h"
#include "ConsoleOutput.generated.h"
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
// //
// ConsoleOutput // ConsoleOutput
@@ -19,11 +22,16 @@
// //
////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////
class FlxConsoleOutput { UCLASS(BlueprintType)
class UlxConsoleOutput : public UObject
{
GENERATED_BODY()
private: private:
FString Content; FString Content;
bool Dirty; bool Dirty;
private:
// Truncate the console to a reasonable number of // Truncate the console to a reasonable number of
// lines. The length is hardwired. // lines. The length is hardwired.
void Truncate(); void Truncate();
@@ -36,18 +44,23 @@ private:
public: public:
// Append a line of text to the console. // Append a line of text to the console.
UFUNCTION(BlueprintCallable)
void Append(const FString& text); void Append(const FString& text);
// Append a line of text to the console on a line by itself. // Append a line of text to the console on a line by itself.
UFUNCTION(BlueprintCallable)
void AppendLine(const FString& text); void AppendLine(const FString& text);
// Get the console text as a string. // Get the console text as a string.
UFUNCTION(BlueprintCallable)
const FString& Get() const { return Content; } const FString& Get() const { return Content; }
// Return if the dirty flag is set. // Return if the dirty flag is set.
UFUNCTION(BlueprintCallable)
bool IsDirty() const { return Dirty; } bool IsDirty() const { return Dirty; }
// Clear the dirty flag. // Clear the dirty flag.
UFUNCTION(BlueprintCallable)
void ClearDirty() { Dirty = false; } void ClearDirty() { Dirty = false; }
}; };

View File

@@ -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("<eof>")))
{
Result = ElxLuaSyntaxCheck::TruncatedLua;
}
else
{
Result = ElxLuaSyntaxCheck::InvalidLua;
}
}
void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, const FString &fname) void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, const FString &fname)
{ {
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);
@@ -211,7 +242,6 @@ void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, con
sb.write_string(fname); sb.write_string(fname);
} }
void UlxLuaCallLibrary::LuaCallInvoke(UObject *context, AActor *place) void UlxLuaCallLibrary::LuaCallInvoke(UObject *context, AActor *place)
{ {
ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context);

View File

@@ -9,6 +9,23 @@
class UlxLuaValues; 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 // These are the types that can actually be packed into
@@ -118,9 +135,12 @@ public:
static FString AllKnownReturnValueTypes() { return AllFunctionsWithPrefix(TEXT("LuaCallReturnValue_")); } static FString AllKnownReturnValueTypes() { return AllFunctionsWithPrefix(TEXT("LuaCallReturnValue_")); }
public: 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") UFUNCTION(BlueprintCallable, meta = (WorldContext = "context", BlueprintInternalUseOnly = "true"), Category = "Luprex|Call Lua Function")
static void LuaCallBegin(UObject *context, const FString &ClassName, const FString &FunctionName); static void LuaCallBegin(UObject *context, const FString &ClassName, const FString &FunctionName);

View File

@@ -3,7 +3,6 @@
#include "LuprexGameModeBase.h" #include "LuprexGameModeBase.h"
#include "lpx-drvutil.hpp" #include "lpx-drvutil.hpp"
#include "lpx-paths.hpp" #include "lpx-paths.hpp"
#include "ConsoleOutput.h"
#include "Tangible.h" #include "Tangible.h"
#include "TangibleManager.h" #include "TangibleManager.h"
#include "LuaCall.h" #include "LuaCall.h"
@@ -124,18 +123,15 @@ void ALuprexGameModeBase::ResetToInitialState()
NextRotateCube = 1.0; NextRotateCube = 1.0;
} }
void ALuprexGameModeBase::UpdateConsoleOutput() { void ALuprexGameModeBase::UpdateConsoleOutput() {
// Copy Luprex Stdout into the console. // Copy Luprex Stdout into the console.
FlxLockedWrapper lockedwrap(LockableWrapper); FlxLockedWrapper lockedwrap(LockableWrapper);
if (Playing) { if (Playing) {
ConsoleOutput.Append(lockedwrap.FetchStdout()); FString Text = lockedwrap.FetchStdout();
if (!Text.IsEmpty())
{
ConsoleAddOutput(Text);
} }
// If the Console text has changed, update the widget.
if (ConsoleOutput.IsDirty()) {
ConsoleSetOutput(ConsoleOutput.Get());
ConsoleOutput.ClearDirty();
} }
} }
@@ -227,32 +223,15 @@ UlxLuaValues *ALuprexGameModeBase::LuaCallEnd(AccessKind kind, AActor *place) {
} }
} }
void ALuprexGameModeBase::ExecuteDebuggingCommand(FlxLockedWrapper &w, const FString &fs) { FString ALuprexGameModeBase::LuaValidate(const FString &Code)
// Nothing here right now.
}
void ALuprexGameModeBase::ConsoleSendInput(const FString& fs)
{ {
if (fs.IsEmpty()) { FTCHARToUTF8 UCode(*Code);
return;
}
FlxLockedWrapper w(LockableWrapper); FlxLockedWrapper w(LockableWrapper);
if (w->engine != nullptr) uint32_t retpklen;
{ const char *retpk;
ConsoleOutput.AppendLine(FString("> ") + fs); w->play_access(w.Get(), AccessKind::VALIDATE_LUA, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk);
// This is a bad way to do this. The problem is that if some FString Result(retpklen, (const UTF8CHAR*)retpk);
// lua code contains '\\', we'll catch it instead of passing it return Result;
// 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());
}
}
} }
void ALuprexGameModeBase::OnWorldPreActorTick(UWorld* InWorld, ELevelTick InLevelTick, float deltaseconds) void ALuprexGameModeBase::OnWorldPreActorTick(UWorld* InWorld, ELevelTick InLevelTick, float deltaseconds)

View File

@@ -5,7 +5,6 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h" #include "GameFramework/GameModeBase.h"
#include "lpx-enginewrapper.hpp" #include "lpx-enginewrapper.hpp"
#include "ConsoleOutput.h"
#include "StringDecoder.h" #include "StringDecoder.h"
#include "TangibleManager.h" #include "TangibleManager.h"
#include "AssetLookup.h" #include "AssetLookup.h"
@@ -59,11 +58,7 @@ public:
// Set the entire contents of the console output box. // Set the entire contents of the console output box.
UFUNCTION(BlueprintImplementableEvent, Category = "Luprex|Miscellaneous") UFUNCTION(BlueprintImplementableEvent, Category = "Luprex|Miscellaneous")
void ConsoleSetOutput(const FString& text); void ConsoleAddOutput(const FString& text);
// This is called by the GUI whenever the user hits enter.
UFUNCTION(BlueprintCallable, Category = "Luprex|Miscellaneous")
void ConsoleSendInput(const FString& text);
UFUNCTION(BlueprintCallable, Category = "Luprex|Miscellaneous") UFUNCTION(BlueprintCallable, Category = "Luprex|Miscellaneous")
int64 GetPlayerId(); int64 GetPlayerId();
@@ -94,7 +89,11 @@ public:
void LookAtChanged(); 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 // * Use LuaCallBegin
// * Get the lua call buffer: // * Get the lua call buffer:
@@ -102,8 +101,7 @@ public:
// - add a function name // - add a function name
// - add function parameters // - add function parameters
// * Use LuaCallEnd. // * Use LuaCallEnd.
// * Get the lua call result. // * Process any return values in the UlxLuaValues array.
// - parse out any return values
// //
FlxStreamBuffer &LuaCallBegin() { LuaCallBuffer.clear(); return LuaCallBuffer; } FlxStreamBuffer &LuaCallBegin() { LuaCallBuffer.clear(); return LuaCallBuffer; }
FlxStreamBuffer &LuaCallGetBuffer() { return LuaCallBuffer; } FlxStreamBuffer &LuaCallGetBuffer() { return LuaCallBuffer; }
@@ -112,8 +110,13 @@ public:
UlxLuaValues *LuaCallEnd(AccessKind kind, AActor *place); UlxLuaValues *LuaCallEnd(AccessKind kind, AActor *place);
void LuaCallClear() { LuaCallBuffer.clear(); } void LuaCallClear() { LuaCallBuffer.clear(); }
// Execute a debugging command, typed on the GUI. // Validate some lua code. Returns an error message.
void ExecuteDebuggingCommand(FlxLockedWrapper &w, const FString &fs); // 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. // Get the Asset Lookup table.
const UlxAssetLookup *GetAssetLookup() const { return AssetLookup; } const UlxAssetLookup *GetAssetLookup() const { return AssetLookup; }
@@ -162,9 +165,6 @@ public:
UPROPERTY(EditAnywhere, Category="Debugging Tools") UPROPERTY(EditAnywhere, Category="Debugging Tools")
ElxLogVerbosity BreakToDebuggerLogVerbosity; ElxLogVerbosity BreakToDebuggerLogVerbosity;
// This stores the entire text currently visible in the console.
FlxConsoleOutput ConsoleOutput;
// The Luprex EngineWrapper, with a Mutex to protect it. // The Luprex EngineWrapper, with a Mutex to protect it.
// To access it, construct a FlxLockedWrapper. // To access it, construct a FlxLockedWrapper.
FlxLockableWrapper LockableWrapper; FlxLockableWrapper LockableWrapper;

View File

@@ -265,7 +265,7 @@ public:
} }
case AccessKind::VALIDATE_LUA: { case AccessKind::VALIDATE_LUA: {
LuaVar closure; LuaVar closure;
LuaDefStack LS(lua_syntax_checker_, closure); LuaExtStack LS(lua_syntax_checker_, closure);
eng::string errmsg = LS.load(closure, datapk, "stdin"); eng::string errmsg = LS.load(closure, datapk, "stdin");
retpk->write_bytes(errmsg); retpk->write_bytes(errmsg);
break; break;

View File

@@ -189,7 +189,7 @@ public:
} }
case AccessKind::VALIDATE_LUA: { case AccessKind::VALIDATE_LUA: {
LuaVar closure; LuaVar closure;
LuaDefStack LS(lua_syntax_checker_, closure); LuaExtStack LS(lua_syntax_checker_, closure);
eng::string errmsg = LS.load(closure, datapk, "stdin"); eng::string errmsg = LS.load(closure, datapk, "stdin");
retpk->write_bytes(errmsg); retpk->write_bytes(errmsg);
break; break;