From 2eacc12cade3234a6f3092bce2144485c444821f Mon Sep 17 00:00:00 2001 From: jyelon Date: Sat, 14 Feb 2026 03:35:08 -0500 Subject: [PATCH] Move a lot of EngineWrapper interfaces out of LuprexGameModeBase --- Source/Integration/Common.h | 23 ++++++ Source/Integration/LockedWrapper.cpp | 39 ++++++++++ Source/Integration/LockedWrapper.h | 24 ++++++ Source/Integration/LuaCall.cpp | 92 +++++++++++------------ Source/Integration/LuaCall.h | 33 -------- Source/Integration/LuprexGameModeBase.cpp | 52 ------------- Source/Integration/LuprexGameModeBase.h | 39 +++------- Source/Integration/UtilityLibrary.cpp | 9 +++ Source/Integration/UtilityLibrary.h | 7 ++ luprex/lua/login.lua | 2 +- 10 files changed, 155 insertions(+), 165 deletions(-) diff --git a/Source/Integration/Common.h b/Source/Integration/Common.h index 75b37077..3e7bb104 100644 --- a/Source/Integration/Common.h +++ b/Source/Integration/Common.h @@ -95,6 +95,29 @@ enum class ElxSuccessOrWrongType : uint8 { WrongType, }; +//////////////////////////////////////////////////////////// +// +// ElxLuaSyntaxCheck +// +// Classifies console commands syntactically: +// +// SlashCommand: starts with a slash, not lua +// Whitespace: the input only contains whitespace +// ValidLua: the input is valid lua code +// TruncatedLua: the input is truncated +// InvalidLua: invalid lua +// +//////////////////////////////////////////////////////////// + +UENUM(BlueprintType) +enum class ElxLuaSyntaxCheck : uint8 { + SlashCommand, + Whitespace, + ValidLua, + TruncatedLua, + InvalidLua, +}; + //////////////////////////////////////////////////////////// // // Log Categories diff --git a/Source/Integration/LockedWrapper.cpp b/Source/Integration/LockedWrapper.cpp index fea4b258..069cca4d 100644 --- a/Source/Integration/LockedWrapper.cpp +++ b/Source/Integration/LockedWrapper.cpp @@ -83,3 +83,42 @@ StringViewVec FlxLockedWrapper::GetAnimationQueues(IdView ids) { } return result; } + +ElxLuaSyntaxCheck FlxLockedWrapper::ValidateLuaExpr(const FString &Code, FString &ErrorMessage) { + FTCHARToUTF8 UCode(*Code); + uint32_t retpklen; + const char *retpk; + Lockable.Wrapper.play_access(Get(), AccessKind::VALIDATE_LUA_EXPR, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk); + ErrorMessage = FString(retpklen, (const UTF8CHAR*)retpk); + if (ErrorMessage.IsEmpty()) + return ElxLuaSyntaxCheck::ValidLua; + if (ErrorMessage == TEXT("slash command")) + return ElxLuaSyntaxCheck::SlashCommand; + if (ErrorMessage == TEXT("white space")) + return ElxLuaSyntaxCheck::Whitespace; + if (ErrorMessage == TEXT("truncated lua")) + return ElxLuaSyntaxCheck::TruncatedLua; + return ElxLuaSyntaxCheck::InvalidLua; +} + +void FlxLockedWrapper::ProbeLuaFunction(std::string_view datapk, int64 place_id, TFunction OnResult) { + if (place_id == 0) place_id = GetActor(); + uint32_t retpklen; + const char *retpk; + Lockable.Wrapper.play_access(Get(), AccessKind::PROBE_LUA_CALL, place_id, datapk.size(), datapk.data(), &retpklen, &retpk); + OnResult(std::string_view(retpk, retpklen)); +} + +void FlxLockedWrapper::InvokeLuaFunction(std::string_view datapk, int64 place_id) { + if (place_id == 0) place_id = GetActor(); + uint32_t retpklen; + const char *retpk; + Lockable.Wrapper.play_access(Get(), AccessKind::INVOKE_LUA_CALL, place_id, datapk.size(), datapk.data(), &retpklen, &retpk); +} + +void FlxLockedWrapper::InvokeLuaExpr(const FString &Code) { + FTCHARToUTF8 UCode(*Code); + uint32_t retpklen; + const char *retpk; + Lockable.Wrapper.play_access(Get(), AccessKind::INVOKE_LUA_EXPR, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk); +} diff --git a/Source/Integration/LockedWrapper.h b/Source/Integration/LockedWrapper.h index 4901eef5..48ea7a3f 100644 --- a/Source/Integration/LockedWrapper.h +++ b/Source/Integration/LockedWrapper.h @@ -119,4 +119,28 @@ public: // next time you call this. // StringViewVec GetAnimationQueues(IdView ids); + + // Call a Lua function. The datapk contains the + // serialized class name, function name, and + // arguments. If place_id is 0, defaults to the + // current actor. The OnResult callback receives + // the raw return data while the lock is held; + // use it to copy the data before it goes away. + // + void ProbeLuaFunction(std::string_view datapk, int64 place_id, TFunction OnResult); + + // Invoke a Lua function (fire-and-forget, no + // return values). + // + void InvokeLuaFunction(std::string_view datapk, int64 place_id); + + // Validate a Lua expression. Returns a syntax + // classification and an error message. The error + // message is empty if the code is valid. + // + ElxLuaSyntaxCheck ValidateLuaExpr(const FString &Code, FString &ErrorMessage); + + // Execute a Lua expression. + // + void InvokeLuaExpr(const FString &Code); }; diff --git a/Source/Integration/LuaCall.cpp b/Source/Integration/LuaCall.cpp index 7742d091..acfd1e59 100644 --- a/Source/Integration/LuaCall.cpp +++ b/Source/Integration/LuaCall.cpp @@ -1,6 +1,7 @@ #include "LuaCall.h" #include "LuprexGameModeBase.h" +#include "Tangible.h" #include "StringDecoder.h" #include "EdGraphSchema_K2.h" @@ -196,51 +197,30 @@ FString UlxLuaCallLibrary::AllFunctionsWithPrefix(const TCHAR *Prefix) return Result; } -///////////////////////////////////////////////////////////////// -// -// General Lua-Callable functions of the Lua Call Library. -// -///////////////////////////////////////////////////////////////// - -void UlxLuaCallLibrary::ValidateLuaExpr( - ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code) -{ - ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); - ErrorMessage = mode->ValidateLuaExpr(Code); - if (ErrorMessage.IsEmpty()) - { - Status = ElxLuaSyntaxCheck::ValidLua; - } - else if (ErrorMessage == TEXT("slash command")) - { - Status = ElxLuaSyntaxCheck::SlashCommand; - } - else if (ErrorMessage == TEXT("white space")) - { - Status = ElxLuaSyntaxCheck::Whitespace; - } - else if (ErrorMessage == TEXT("truncated lua")) - { - Status = ElxLuaSyntaxCheck::TruncatedLua; - } - else - { - Status = ElxLuaSyntaxCheck::InvalidLua; - } -} - void UlxLuaCallLibrary::InvokeLuaExpr(UObject *context, const FString &Code) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); - mode->InvokeLuaExpr(Code); + FlxLockedWrapper w(mode->GetLockableWrapper()); + w.InvokeLuaExpr(Code); } +// Resolve an AActor to a tangible place_id. +// Returns 0 if place is null (meaning "use current +// actor"), or the tangible ID if found. Returns -1 +// if place is non-null but has no tangible. +// +static int64 ResolvePlaceId(AActor *place) +{ + if (place == nullptr) return 0; + UlxTangible *tan = UlxTangible::GetActorTangibleOrLog(place); + return tan ? tan->TangibleId : -1; +} void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, const FString &fname) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); - FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); - mode->LuaCallBegin(); + FlxStreamBuffer &sb = mode->GetLuaCallBuffer(); + sb.clear(); sb.write_string(cname); sb.write_string(fname); } @@ -248,19 +228,33 @@ void UlxLuaCallLibrary::LuaCallBegin(UObject *context, const FString &cname, con void UlxLuaCallLibrary::LuaCallInvoke(UObject *context, AActor *place) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); - FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); + FlxStreamBuffer &sb = mode->GetLuaCallBuffer(); if (NotInitialized(sb)) return; - mode->LuaCallEnd(AccessKind::INVOKE_LUA_CALL, place); + int64 place_id = ResolvePlaceId(place); + if (place_id < 0) { sb.clear(); return; } + FlxLockedWrapper w(mode->GetLockableWrapper()); + w.InvokeLuaFunction(sb.view(), place_id); + sb.clear(); } - bool UlxLuaCallLibrary::LuaCallProbe(UObject *context, AActor *place, UlxLuaValues *&ReturnArray) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); - FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); + FlxStreamBuffer &sb = mode->GetLuaCallBuffer(); if (NotInitialized(sb)) return false; - ReturnArray = mode->LuaCallEnd(AccessKind::PROBE_LUA_CALL, place); - if ((ReturnArray == nullptr) || (ReturnArray->Length() < 1)) + int64 place_id = ResolvePlaceId(place); + if (place_id < 0) { + sb.clear(); + ReturnArray = NewObject(mode); + return false; + } + ReturnArray = NewObject(mode); + FlxLockedWrapper w(mode->GetLockableWrapper()); + w.ProbeLuaFunction(sb.view(), place_id, [&](std::string_view retpk) { + ReturnArray->Initialize(retpk); + }); + sb.clear(); + if (ReturnArray->Length() < 1) { UE_LOG(LogLuprexIntegration, Error, TEXT("corruption in lua_probe")); ReturnArray = nullptr; @@ -292,7 +286,7 @@ bool UlxLuaCallLibrary::LuaCallProbe(UObject *context, AActor *place, UlxLuaValu void UlxLuaCallLibrary::LuaCallArgument_string(UObject *context, const FString &pstring) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); - FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); + FlxStreamBuffer &sb = mode->GetLuaCallBuffer(); if (NotInitialized(sb)) return; sb.write_simple_dynamic_tag(LuaValueType::STRING); sb.write_string(pstring); @@ -307,7 +301,7 @@ void UlxLuaCallLibrary::LuaCallArgument_name(UObject *context, const FName &pnam UE_LOG(LogBlueprint, Error, TEXT("Names passed to lua must be short, and must contain only lowercase and digits")); } ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); - FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); + FlxStreamBuffer &sb = mode->GetLuaCallBuffer(); if (NotInitialized(sb)) return; sb.write_simple_dynamic_tag(LuaValueType::TOKEN); sb.write_string(namestr); @@ -315,7 +309,7 @@ void UlxLuaCallLibrary::LuaCallArgument_name(UObject *context, const FName &pnam void UlxLuaCallLibrary::LuaCallArgument_float(UObject *context, double pfloat) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); - FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); + FlxStreamBuffer &sb = mode->GetLuaCallBuffer(); if (NotInitialized(sb)) return; sb.write_simple_dynamic_tag(LuaValueType::NUMBER); sb.write_double(pfloat); @@ -323,7 +317,7 @@ void UlxLuaCallLibrary::LuaCallArgument_float(UObject *context, double pfloat) { void UlxLuaCallLibrary::LuaCallArgument_int(UObject *context, int value) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); - FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); + FlxStreamBuffer &sb = mode->GetLuaCallBuffer(); if (NotInitialized(sb)) return; sb.write_simple_dynamic_tag(LuaValueType::NUMBER); sb.write_double(value); @@ -331,7 +325,7 @@ void UlxLuaCallLibrary::LuaCallArgument_int(UObject *context, int value) { void UlxLuaCallLibrary::LuaCallArgument_vector(UObject *context, const FVector &pvector) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); - FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); + FlxStreamBuffer &sb = mode->GetLuaCallBuffer(); if (NotInitialized(sb)) return; sb.write_simple_dynamic_tag(LuaValueType::VECTOR); sb.write_fvector(pvector); @@ -339,7 +333,7 @@ void UlxLuaCallLibrary::LuaCallArgument_vector(UObject *context, const FVector & void UlxLuaCallLibrary::LuaCallArgument_vector2d(UObject *context, const FVector2D &pvector) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); - FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); + FlxStreamBuffer &sb = mode->GetLuaCallBuffer(); if (NotInitialized(sb)) return; sb.write_simple_dynamic_tag(LuaValueType::VECTOR); sb.write_double(pvector.X); @@ -349,7 +343,7 @@ void UlxLuaCallLibrary::LuaCallArgument_vector2d(UObject *context, const FVector void UlxLuaCallLibrary::LuaCallArgument_boolean(UObject *context, bool pbool) { ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); - FlxStreamBuffer &sb = mode->LuaCallGetBuffer(); + FlxStreamBuffer &sb = mode->GetLuaCallBuffer(); if (NotInitialized(sb)) return; sb.write_simple_dynamic_tag(LuaValueType::BOOLEAN); sb.write_bool(pbool); diff --git a/Source/Integration/LuaCall.h b/Source/Integration/LuaCall.h index 02e0ef71..0e6c626e 100644 --- a/Source/Integration/LuaCall.h +++ b/Source/Integration/LuaCall.h @@ -23,29 +23,6 @@ class UlxLuaValues; -//////////////////////////////////////////////////////////// -// -// ElxLuaSyntaxCheck -// -// Classifies console commands syntactically: -// -// SlashCommand: starts with a slash, not lua -// Whitespace: the input only contains whitespace -// ValidLua: the input is valid lua code -// TruncatedLua: the input is truncated -// InvalidLua: invalid lua -// -//////////////////////////////////////////////////////////// - -UENUM(BlueprintType) -enum class ElxLuaSyntaxCheck : uint8 { - SlashCommand, - Whitespace, - ValidLua, - TruncatedLua, - InvalidLua, -}; - //////////////////////////////////////////////////////////// // // FlxParsedProto @@ -158,16 +135,6 @@ 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 empty. - // - // TODO: This doesn't belong here. This library - // is for LuaCallNode's internal implementation. - // - UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Call Lua Function") - static void ValidateLuaExpr(ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code); - //////////////////////////////////////////////////////// // // Functions that initiate and complete the lua call. diff --git a/Source/Integration/LuprexGameModeBase.cpp b/Source/Integration/LuprexGameModeBase.cpp index 52efdd3a..d10c5a6e 100644 --- a/Source/Integration/LuprexGameModeBase.cpp +++ b/Source/Integration/LuprexGameModeBase.cpp @@ -187,58 +187,6 @@ void ALuprexGameModeBase::UpdatePossessedTangible() { } } -UlxLuaValues *ALuprexGameModeBase::LuaCallEnd(AccessKind kind, int64 place_id) { - std::string_view datapk = LuaCallBuffer.view(); - FlxLockedWrapper w(LockableWrapper); - if (place_id == 0) place_id = w.GetActor(); - uint32_t retpklen; - const char *retpk; - w->play_access(w.Get(), kind, place_id, datapk.size(), datapk.data(), &retpklen, &retpk); - if (kind == AccessKind::PROBE_LUA_CALL) - { - UlxLuaValues *Result = NewObject(this); - Result->Initialize(std::string_view(retpk, retpklen)); - return Result; - } - else return nullptr; -} - -UlxLuaValues *ALuprexGameModeBase::LuaCallEnd(AccessKind kind) { - return LuaCallEnd(kind, int64(0)); -} - -UlxLuaValues *ALuprexGameModeBase::LuaCallEnd(AccessKind kind, AActor *place) { - if (place == nullptr) { - return LuaCallEnd(kind, int64(0)); - } else { - UlxTangible *tan = UlxTangible::GetActorTangibleOrLog(place); - if (tan == nullptr) { - return NewObject(this); - } else { - return LuaCallEnd(kind, tan->TangibleId); - } - } -} - -FString ALuprexGameModeBase::ValidateLuaExpr(const FString &Code) -{ - FTCHARToUTF8 UCode(*Code); - FlxLockedWrapper w(LockableWrapper); - uint32_t retpklen; - const char *retpk; - w->play_access(w.Get(), AccessKind::VALIDATE_LUA_EXPR, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk); - FString Result(retpklen, (const UTF8CHAR*)retpk); - return Result; -} - -void ALuprexGameModeBase::InvokeLuaExpr(const FString &Code) -{ - FTCHARToUTF8 UCode(*Code); - FlxLockedWrapper w(LockableWrapper); - uint32_t retpklen; - const char *retpk; - w->play_access(w.Get(), AccessKind::INVOKE_LUA_EXPR, 0, UCode.Length(), UCode.Get(), &retpklen, &retpk); -} void ALuprexGameModeBase::OnWorldPreActorTick(UWorld* InWorld, ELevelTick InLevelTick, float deltaseconds) { diff --git a/Source/Integration/LuprexGameModeBase.h b/Source/Integration/LuprexGameModeBase.h index 832bf820..9375cc4f 100644 --- a/Source/Integration/LuprexGameModeBase.h +++ b/Source/Integration/LuprexGameModeBase.h @@ -82,38 +82,12 @@ public: void LookAtChanged(); - // 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". + // The Lua Call Assembly Buffer. Used by + // UlxLuaCallLibrary to build up a call across + // multiple UFUNCTION invocations. // - // At this level, the process of calling Lua is: - // - // * Use LuaCallBegin - // * Get the lua call buffer: - // - add a class name - // - add a function name - // - add function parameters - // * Use LuaCallEnd. - // * Process any return values in the UlxLuaValues array. - // - FlxStreamBuffer &LuaCallBegin() { LuaCallBuffer.clear(); return LuaCallBuffer; } - FlxStreamBuffer &LuaCallGetBuffer() { return LuaCallBuffer; } - UlxLuaValues *LuaCallEnd(AccessKind kind); - UlxLuaValues *LuaCallEnd(AccessKind kind, int64 place_id); - UlxLuaValues *LuaCallEnd(AccessKind kind, AActor *place); - void LuaCallClear() { LuaCallBuffer.clear(); } + FlxStreamBuffer &GetLuaCallBuffer() { return LuaCallBuffer; } - // 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 ValidateLuaExpr(const FString &Code); - - // Invoke some lua code. - // - void InvokeLuaExpr(const FString &Code); // Get the Asset Lookup table. const UlxAssetLookup *GetAssetLookup() const { return AssetLookup; } @@ -164,8 +138,13 @@ public: // The Luprex EngineWrapper, with a Mutex to protect it. // To access it, construct a FlxLockedWrapper. + // FlxLockableWrapper LockableWrapper; + // Get the LockableWrapper. + // + FlxLockableWrapper& GetLockableWrapper() { return LockableWrapper; } + // The Lua Call Assembly Buffer. FlxStreamBuffer LuaCallBuffer; diff --git a/Source/Integration/UtilityLibrary.cpp b/Source/Integration/UtilityLibrary.cpp index e1259889..d2ae3e27 100644 --- a/Source/Integration/UtilityLibrary.cpp +++ b/Source/Integration/UtilityLibrary.cpp @@ -2,6 +2,7 @@ #include "UtilityLibrary.h" +#include "LuprexGameModeBase.h" #include "GameFramework/PlayerController.h" #include "EnhancedInputSubsystems.h" #include "Kismet/KismetSystemLibrary.h" @@ -251,3 +252,11 @@ FVector UlxUtilityLibrary::GetActorForwardVelocity(const AActor *Actor, double S } return Forward * Speed; } + +void UlxUtilityLibrary::ValidateLuaExpr( + ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code) +{ + ALuprexGameModeBase *mode = ALuprexGameModeBase::FromContext(context); + FlxLockedWrapper w(mode->GetLockableWrapper()); + Status = w.ValidateLuaExpr(Code, ErrorMessage); +} diff --git a/Source/Integration/UtilityLibrary.h b/Source/Integration/UtilityLibrary.h index c7db2f29..c06819de 100644 --- a/Source/Integration/UtilityLibrary.h +++ b/Source/Integration/UtilityLibrary.h @@ -165,4 +165,11 @@ public: // UFUNCTION(BlueprintCallable, BlueprintPure, Category = "Luprex|Utility", meta = (DefaultToSelf = "Actor")) static FVector GetActorForwardVelocity(const AActor *Actor, double Speed = 1.0, bool bSnapToXY = false); + + // Syntactically validate lua code. Parses the + // code and returns an error message. If the code + // is error-free, the error message is empty. + // + UFUNCTION(BlueprintCallable, meta = (WorldContext = "context"), Category = "Luprex|Utility") + static void ValidateLuaExpr(ElxLuaSyntaxCheck &Status, FString &ErrorMessage, UObject *context, const FString &Code); }; diff --git a/luprex/lua/login.lua b/luprex/lua/login.lua index 4320c68a..dc0c28c7 100644 --- a/luprex/lua/login.lua +++ b/luprex/lua/login.lua @@ -24,7 +24,7 @@ end function engio.move(action, xyz, facing) -- todo: sanity check the parameters. - -- dprint("engio.move ", action, " ", xyz[1], " ", xyz[2], " ", xyz[3]) + dprint("engio.move ", action, " ", xyz[1], " ", xyz[2], " ", xyz[3]) tangible.animate{tan=actor, anim={action=action, interactive=true, xyz=xyz, facing=facing}} end