#include "LuaCall.h" #include "LockedWrapper.h" #include "Tangible.h" #include "StreamBuffer.h" #include "EdGraphSchema_K2.h" static bool NotInitialized(const FlxStreamBuffer &sb) { if (sb.empty()) { UE_LOG(LogBlueprint, Error, TEXT("Must use LuaCallBegin before other LuaCall steps")); return true; } return false; } static constexpr uint64_t ParseNameAsToken(std::string_view str) { uint64_t result = 0; uint64_t maxint = uint64_t(-1); // Leading zeros are not allowed. if ((!str.empty()) && (str[0]=='0')) return 0; for (int i = 0; i < int(str.size()); i++) { char c = str[i]; uint64_t digit = 0; if ((c >= '0') && (c <= '9')) { digit = uint64_t(c - '0'); } else if ((c >= 'a') && (c <= 'z')) { digit = uint64_t(c - 'a' + 10); } else if ((c >= 'A') && (c <= 'Z')) { digit = uint64_t(c - 'A' + 10); } else { return maxint; } // Multiply existing number by 36, then add the digit. // We have two checks to prevent integer overflow. if (result > (maxint / 36)) return 0; result *= 36; if (digit > (maxint - result)) return 0; result += digit; } return result; } ///////////////////////////////////////////////////////////////// // // The lua function prototype parser. // ///////////////////////////////////////////////////////////////// bool FlxParsedProto::IsLiteral(const TCHAR *text) { return ((NextToken < Tokens.Num()) && (Tokens[NextToken] == text)); } bool FlxParsedProto::IsIdent() { return ((NextToken < Tokens.Num()) && (FChar::IsAlpha(Tokens[NextToken][0]))); } void FlxParsedProto::Empty() { ErrorMessage = TEXT(""); Tokens.Empty(); NextToken = 0; ClassName = TEXT(""); FunctionName = TEXT(""); Arguments.Empty(); ReturnValues.Empty(); ExtraReturnValues = false; } void FlxParsedProto::Syntax() { FString Message; if (Tokens.Num() == 0) { Message = TEXT("Function prototype cannot be blank"); } for (int i = 0; i < Tokens.Num(); i++) { if (i == NextToken) { Message.Append(TEXT(" ? ")); } else { if ((i > 0) && (FChar::IsAlpha(Tokens[i][0])) && (FChar::IsAlpha(Tokens[i-1][0]))) { Message.Append(TEXT(" ")); } } Message.Append(Tokens[i]); } Empty(); ErrorMessage = Message; } bool FlxParsedProto::Tokenize(const FString &str) { int offset = 0; while (offset < str.Len()) { TCHAR c = str[offset]; if (FChar::IsWhitespace(c)) { offset++; } else if (FChar::IsAlpha(c)) { int lo = offset; while ((offset < str.Len()) && FChar::IsAlnum(str[offset])) offset++; Tokens.Add(str.Mid(lo, offset-lo)); } else if (str.Mid(offset, 3) == TEXT("...")) { Tokens.Add(str.Mid(offset, 3)); offset += 3; } else if (FChar::IsPunct(c)) { Tokens.Add(str.Mid(offset, 1)); offset += 1; } else { Empty(); ErrorMessage = FString::Printf(TEXT("%s ? %s"), *str.Mid(0, offset), *str.Mid(offset)); return false; } } NextToken = 0; return true; } void FlxParsedProto::ParseReturnValues() { while (true) { if (IsLiteral(TEXT("..."))) { ExtraReturnValues = true; NextToken++; break; } else if (IsIdent()) { FString Type = Tokens[NextToken++]; if (!IsIdent()) return Syntax(); FString Name = Tokens[NextToken++]; ReturnValues.Add(Pin(Type, Name)); if (!IsLiteral(TEXT(","))) break; NextToken++; } else { return Syntax(); } } } void FlxParsedProto::Parse() { if (!IsLiteral(TEXT("*")) && !IsIdent()) return Syntax(); ClassName = Tokens[NextToken++]; if (!IsLiteral(TEXT("."))) return Syntax(); NextToken++; if (!IsIdent()) return Syntax(); FunctionName = Tokens[NextToken++]; if (!IsLiteral(TEXT("("))) return Syntax(); NextToken++; if (IsIdent()) { while (true) { if (!IsIdent()) return Syntax(); FString Type = Tokens[NextToken++]; if (!IsIdent()) return Syntax(); FString Name = Tokens[NextToken++]; Arguments.Add(Pin(Type, Name)); if (!IsLiteral(TEXT(","))) break; NextToken++; } } if (!IsLiteral(TEXT(")"))) return Syntax(); NextToken++; if (IsLiteral(TEXT(":"))) { NextToken++; ParseReturnValues(); } } FlxParsedProto FlxParsedProto::ParsePrototype(const FString &str) { FlxParsedProto Result; if (!Result.Tokenize(str)) return Result; Result.Parse(); if (Result.NextToken != Result.Tokens.Num()) Result.Syntax(); return Result; } FlxParsedProto FlxParsedProto::ParseReturnValuesOnly(const FString &str) { FlxParsedProto Result; if (!Result.Tokenize(str)) return Result; Result.ParseReturnValues(); if (Result.NextToken != Result.Tokens.Num()) Result.Syntax(); return Result; } ///////////////////////////////////////////////////////////////// // // General functions of the Lua Call Library. // ///////////////////////////////////////////////////////////////// UFunction *UlxLuaCallLibrary::GetArgumentPacker(const FString &Type) { FName PackerName(FString::Printf(TEXT("LuaCallArgument_%s"), *Type)); return UlxLuaCallLibrary::StaticClass()->FindFunctionByName(PackerName); } UFunction *UlxLuaCallLibrary::GetReturnValueUnpacker(const FString &Type) { FName PackerName(FString::Printf(TEXT("Read%s"), *Type)); return UlxLuaValues::StaticClass()->FindFunctionByName(PackerName); } FString UlxLuaCallLibrary::AllFunctionsWithPrefix(const TCHAR *Prefix) { FString Result; for ( TFieldIterator FIT ( UlxLuaCallLibrary::StaticClass(), EFieldIteratorFlags::ExcludeSuper); FIT; ++FIT) { UFunction* Function = *FIT; FString Name = Function->GetName(); if (Name.RemoveFromStart(Prefix)) { if (!Result.IsEmpty()) Result += TEXT(", "); Result += Name; } } return Result; } void UlxLuaCallLibrary::InvokeLuaExpr(UObject *context, const FString &Code) { FlxLockedWrapper w; 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) { FlxStreamBuffer &sb = UlxEngineWrapper::GetLuaCallBuffer(); sb.clear(); sb.write_string(cname); sb.write_string(fname); } void UlxLuaCallLibrary::LuaCallInvoke(UObject *context, AActor *place) { FlxStreamBuffer &sb = UlxEngineWrapper::GetLuaCallBuffer(); if (NotInitialized(sb)) return; int64 place_id = ResolvePlaceId(place); if (place_id < 0) { sb.clear(); return; } FlxLockedWrapper w; w.InvokeLuaFunction(sb.view(), place_id); sb.clear(); } bool UlxLuaCallLibrary::LuaCallProbe(UObject *context, AActor *place, UlxLuaValues *&ReturnArray) { FlxStreamBuffer &sb = UlxEngineWrapper::GetLuaCallBuffer(); if (NotInitialized(sb)) return false; int64 place_id = ResolvePlaceId(place); if (place_id < 0) { sb.clear(); ReturnArray = NewObject(); return false; } ReturnArray = NewObject(); FlxLockedWrapper w; 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; return false; } ElxSuccessOrWrongType Status; FString ErrorMessage; ReturnArray->ReadString(Status, ErrorMessage); if (Status != ElxSuccessOrWrongType::Success) { UE_LOG(LogLuprexIntegration, Error, TEXT("lua probe should always return an error message (possibly empty) as the first parameter")); ReturnArray = nullptr; return false; } if (!ErrorMessage.IsEmpty()) { UE_LOG(LogLuprex, Error, TEXT("%s"), *ErrorMessage); ReturnArray = nullptr; return false; } ReturnArray->DiscardBeforeCursor(); return true; } ///////////////////////////////////////////////////////////////// // // Argument Packing functions // ///////////////////////////////////////////////////////////////// void UlxLuaCallLibrary::LuaCallArgument_string(UObject *context, const FString &pstring) { FlxStreamBuffer &sb = UlxEngineWrapper::GetLuaCallBuffer(); if (NotInitialized(sb)) return; sb.write_lua_value_type(LuaValueType::STRING); sb.write_string(pstring); } void UlxLuaCallLibrary::LuaCallArgument_name(UObject *context, const FName &pname) { FTCHARToUTF8 utf8str(pname.ToString()); std::string_view namestr(utf8str.Get(), utf8str.Length()); uint64_t tokvalue = ParseNameAsToken(namestr); if ((tokvalue == 0) && !namestr.empty()) { UE_LOG(LogBlueprint, Error, TEXT("Names passed to lua must be short, and must contain only lowercase and digits")); } FlxStreamBuffer &sb = UlxEngineWrapper::GetLuaCallBuffer(); if (NotInitialized(sb)) return; sb.write_lua_value_type(LuaValueType::TOKEN); sb.write_string(namestr); } void UlxLuaCallLibrary::LuaCallArgument_float(UObject *context, double pfloat) { FlxStreamBuffer &sb = UlxEngineWrapper::GetLuaCallBuffer(); if (NotInitialized(sb)) return; sb.write_lua_value_type(LuaValueType::NUMBER); sb.write_double(pfloat); } void UlxLuaCallLibrary::LuaCallArgument_int(UObject *context, int value) { FlxStreamBuffer &sb = UlxEngineWrapper::GetLuaCallBuffer(); if (NotInitialized(sb)) return; sb.write_lua_value_type(LuaValueType::NUMBER); sb.write_double(value); } void UlxLuaCallLibrary::LuaCallArgument_vector(UObject *context, const FVector &pvector) { FlxStreamBuffer &sb = UlxEngineWrapper::GetLuaCallBuffer(); if (NotInitialized(sb)) return; sb.write_lua_value_type(LuaValueType::VECTOR); sb.write_fvector(pvector); } void UlxLuaCallLibrary::LuaCallArgument_vector2d(UObject *context, const FVector2D &pvector) { FlxStreamBuffer &sb = UlxEngineWrapper::GetLuaCallBuffer(); if (NotInitialized(sb)) return; sb.write_lua_value_type(LuaValueType::VECTOR); sb.write_double(pvector.X); sb.write_double(pvector.Y); sb.write_double(0.0); } void UlxLuaCallLibrary::LuaCallArgument_boolean(UObject *context, bool pbool) { FlxStreamBuffer &sb = UlxEngineWrapper::GetLuaCallBuffer(); if (NotInitialized(sb)) return; sb.write_lua_value_type(LuaValueType::BOOLEAN); sb.write_bool(pbool); } ///////////////////////////////////////////////////////////////// // // Returning the rest of the lua return values as an array. // ///////////////////////////////////////////////////////////////// void UlxLuaValues::Empty() { Serialized.clear(); Types.Empty(); Data.Empty(); Cursor = 0; } bool UlxLuaValues::Initialize(std::string_view data) { Empty(); Serialized = data; const char *SerializedChar = &Serialized[0]; FlxStreamBuffer Decoder(Serialized); while (!Decoder.empty()) { LuaValueType Tag = Decoder.read_lua_value_type(); int64 Pos = Decoder.total_reads(); ElxLuaValueType Type; switch (Tag) { case LuaValueType::BOOLEAN: Type=ElxLuaValueType::Boolean; Decoder.read_bool(); break; case LuaValueType::NUMBER: Type=ElxLuaValueType::Float; Decoder.read_double(); break; case LuaValueType::STRING: Type=ElxLuaValueType::String; Decoder.read_string_view(); break; case LuaValueType::TOKEN: Type=ElxLuaValueType::Name; Decoder.read_string_view(); break; case LuaValueType::VECTOR: Type=ElxLuaValueType::Vector; Decoder.read_fvector(); break; default: { Empty(); return false; } } int64 Pos2 = Decoder.total_reads(); Types.Add(Type); Data.Add(std::string_view(SerializedChar + Pos, Pos2 - Pos)); } return true; } FString UlxLuaValues::DebugString() const { TStringBuilder<2048> Output; Output << TEXT("{ "); for (int i = 0; i < Types.Num(); i++) { if (i > 0) Output << TEXT(" "); if (i == Cursor) Output << TEXT("^ "); ElxLuaValueType Type = Types[i]; FlxStreamBuffer Decoder(Data[i]); switch (Type) { case ElxLuaValueType::Boolean: Output << Decoder.read_bool(); break; case ElxLuaValueType::Float: Output << FString::Printf(TEXT("%lf"), Decoder.read_double()); break; case ElxLuaValueType::String: Output << TEXT("\"") << Decoder.read_fstring() << TEXT("\""); break; case ElxLuaValueType::Name: Output << Decoder.read_fname(); break; case ElxLuaValueType::Vector: { FVector Vec = Decoder.read_fvector(); Output << FString::Printf(TEXT("(%lf, %lf, %lf)"), Vec.X, Vec.Y, Vec.Z); break; } } } Output << TEXT(" }"); return Output.ToString(); } ElxSuccessOrWrongType UlxLuaValues::CheckType(ElxLuaValueType Type, ElxLuaValueType Desired) { if (Type != Desired) { return ElxSuccessOrWrongType::WrongType; } return ElxSuccessOrWrongType::Success; } ElxLuaValueType UlxLuaValues::NextType() const { if (Cursor < 0) return ElxLuaValueType::End; if (Cursor >= Types.Num()) return ElxLuaValueType::End; return Types[Cursor]; } void UlxLuaValues::DiscardBeforeCursor() { if (Cursor > 0) { Types.RemoveAt(0, Cursor); Data.RemoveAt(0, Cursor); SavedCursor = FMath::Max(0, SavedCursor - Cursor); Cursor = 0; } } void UlxLuaValues::ReadString(ElxSuccessOrWrongType &Status, FString &Result) { Status = CheckType(NextType(), ElxLuaValueType::String); if (Status == ElxSuccessOrWrongType::WrongType) { Cursor = SavedCursor; Result.Empty(); return; } Result = FlxStreamBuffer(Data[Cursor++]).read_fstring(); } void UlxLuaValues::ReadName(ElxSuccessOrWrongType &Status, FName &Result) { Status = CheckType(NextType(), ElxLuaValueType::Name); if (Status == ElxSuccessOrWrongType::WrongType) { Cursor = SavedCursor; Result = FName(); return; } Result = FlxStreamBuffer(Data[Cursor++]).read_fname(); } void UlxLuaValues::ReadFloat(ElxSuccessOrWrongType &Status, double &Result) { Status = CheckType(NextType(), ElxLuaValueType::Float); if (Status == ElxSuccessOrWrongType::WrongType) { Cursor = SavedCursor; Result = 0.0; return; } Result = FlxStreamBuffer(Data[Cursor++]).read_double(); } void UlxLuaValues::ReadInt(ElxSuccessOrWrongType &Status, int &Result) { Status = CheckType(NextType(), ElxLuaValueType::Float); if (Status == ElxSuccessOrWrongType::WrongType) { Cursor = SavedCursor; Result = 0; return; } double dvalue = FlxStreamBuffer(Data[Cursor++]).read_double(); Result = int(dvalue); if (double(Result) != dvalue) { Status = ElxSuccessOrWrongType::WrongType; Cursor = SavedCursor; Result = 0; return; } } void UlxLuaValues::ReadVector(ElxSuccessOrWrongType &Status, FVector &Result) { Status = CheckType(NextType(), ElxLuaValueType::Vector); if (Status == ElxSuccessOrWrongType::WrongType) { Cursor = SavedCursor; Result = FVector(); return; } Result = FlxStreamBuffer(Data[Cursor++]).read_fvector(); } void UlxLuaValues::ReadVector2D(ElxSuccessOrWrongType &Status, FVector2D &Result) { Status = CheckType(NextType(), ElxLuaValueType::Vector); if (Status == ElxSuccessOrWrongType::WrongType) { Cursor = SavedCursor; Result = FVector2D(); return; } FVector VValue = FlxStreamBuffer(Data[Cursor++]).read_fvector(); Result = FVector2D(VValue.X, VValue.Y); } void UlxLuaValues::ReadBoolean(ElxSuccessOrWrongType &Status, bool &Result) { Status = CheckType(NextType(), ElxLuaValueType::Boolean); if (Status == ElxSuccessOrWrongType::WrongType) { Cursor = SavedCursor; Result = false; return; } Result = FlxStreamBuffer(Data[Cursor++]).read_bool(); } FFormatArgumentData UlxLuaValues::FormatArgumentDataLuaValues(const UlxLuaValues *AutoConvertedValue, const FString &Name) { FFormatArgumentData Result; Result.ArgumentValueType = EFormatArgumentType::Text; Result.ArgumentName = Name; Result.ArgumentValue = FText::FromString(AutoConvertedValue->DebugString()); return Result; }