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

View File

@@ -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();

View File

@@ -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; }
};

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)
{
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);

View File

@@ -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);

View File

@@ -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)

View File

@@ -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;