diff --git a/Content/Luprex/lxUtilityFunctionsLibrary.uasset b/Content/Luprex/lxUtilityFunctionsLibrary.uasset index 6c434154..11cd6249 100644 --- a/Content/Luprex/lxUtilityFunctionsLibrary.uasset +++ b/Content/Luprex/lxUtilityFunctionsLibrary.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f189858227a966a70f08cf12a1e56dd569eadd85872470600e3d3cd0184a07b -size 83893 +oid sha256:71511653c686e942baa5b873dc2b29a2c1f997c3b44c9d7352e7737440aeb1b5 +size 83208 diff --git a/Content/Tangibles/tangiblecharacter.uasset b/Content/Tangibles/tangiblecharacter.uasset index aaca0c2d..537b88ef 100644 --- a/Content/Tangibles/tangiblecharacter.uasset +++ b/Content/Tangibles/tangiblecharacter.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77c1e7955db95535f55af2cafb864aec14c517962416776e340c4fbc2e538a60 -size 364338 +oid sha256:28be199cf61d9a66b2e0ba224a291ee906eff202435f39290abc0e83a0f8d774 +size 365403 diff --git a/EnginePatches/BuildConfigurationLinux.xml b/EnginePatches/BuildConfigurationLinux.xml new file mode 100644 index 00000000..e69de29b diff --git a/EnginePatches/LogMacros.cpp b/EnginePatches/LogMacros.cpp new file mode 100644 index 00000000..dae6bcde --- /dev/null +++ b/EnginePatches/LogMacros.cpp @@ -0,0 +1,122 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "Logging/LogMacros.h" +#include "CoreGlobals.h" +#include "HAL/Platform.h" +#include "Misc/ScopeLock.h" +#include "Misc/OutputDeviceRedirector.h" +#include "Misc/FeedbackContext.h" +#include "Misc/VarargsHelper.h" +#include "Stats/Stats.h" +#include "ProfilingDebugging/CsvProfiler.h" + +namespace UBreakPoint { + volatile int OnLogError_V; + FORCENOINLINE static void OnLogError() { + OnLogError_V = 0; + } +} + +void StaticFailDebugV(const TCHAR* Error, const ANSICHAR* Expression, const ANSICHAR* File, int32 Line, bool bIsEnsure, void* ProgramCounter, const TCHAR* DescriptionFormat, va_list DescriptionArgs); + +CSV_DEFINE_CATEGORY(FMsgLogf, true); + +void FMsg::LogfImpl(const ANSICHAR* File, int32 Line, const FLogCategoryName& Category, ELogVerbosity::Type Verbosity, const TCHAR* Fmt, ...) +{ +#if !NO_LOGGING + if (LIKELY(Verbosity != ELogVerbosity::Fatal)) + { + // SetColour is routed to GWarn just like the other verbosities and handled in the + // device that does the actual printing. + FOutputDevice* LogOverride = nullptr; + switch (Verbosity) + { + case ELogVerbosity::Error: + case ELogVerbosity::Warning: + case ELogVerbosity::Display: + case ELogVerbosity::SetColor: + LogOverride = GWarn; + break; + default: + break; + } + GROWABLE_LOGF(LogOverride ? LogOverride->Log(Category, Verbosity, Buffer) + : GLog->RedirectLog(Category, Verbosity, Buffer)) + if (Verbosity == ELogVerbosity::Error) { + UBreakPoint::OnLogError(); + } + } + else + { + va_list Args; + va_start(Args, Fmt); + StaticFailDebugV(TEXT("Fatal error:"), "", File, Line, /*bIsEnsure*/ false, PLATFORM_RETURN_ADDRESS(), Fmt, Args); + va_end(Args); + va_start(Args, Fmt); + FDebug::AssertFailedV("", File, Line, Fmt, Args); + va_end(Args); + } +#endif +} + +void FMsg::LogV(const ANSICHAR* File, int32 Line, const FLogCategoryName& Category, ELogVerbosity::Type Verbosity, const TCHAR* Fmt, va_list Args) +{ +#if !NO_LOGGING + LLM_SCOPE_BYNAME("EngineMisc/FMsgLogf") + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMsgLogf); + CSV_CUSTOM_STAT(FMsgLogf, FMsgLogfCount, 1, ECsvCustomStatOp::Accumulate); + + if (LIKELY(Verbosity != ELogVerbosity::Fatal)) + { + TStringBuilder<512> Buffer; + Buffer.AppendV(Fmt, Args); + const TCHAR* Message = *Buffer; + FOutputDevice* OutputDevice = nullptr; + switch (Verbosity) + { + case ELogVerbosity::Error: + case ELogVerbosity::Warning: + case ELogVerbosity::Display: + case ELogVerbosity::SetColor: + OutputDevice = GWarn; + break; + default: + break; + } + + // Logging is always done in the open as we want logs even with transactionalized code. + AutoRTFM::Open([OutputDevice, Message, Verbosity, Category] + { + (OutputDevice ? OutputDevice : GLog)->Serialize(Message, Verbosity, Category); + }); + if (Verbosity == ELogVerbosity::Error) { + UBreakPoint::OnLogError(); + } + } + else + { + StaticFailDebugV(TEXT("Fatal error:"), "", File, Line, /*bIsEnsure*/ false, PLATFORM_RETURN_ADDRESS(), Fmt, Args); + } +#endif +} + +void FMsg::Logf_InternalImpl(const ANSICHAR* File, int32 Line, const FLogCategoryName& Category, ELogVerbosity::Type Verbosity, const TCHAR* Fmt, ...) +{ +#if !NO_LOGGING + va_list Args; + va_start(Args, Fmt); + LogV(File, Line, Category, Verbosity, Fmt, Args); + va_end(Args); +#endif +} + +/** Sends a formatted message to a remote tool. */ +void VARARGS FMsg::SendNotificationStringfImpl( const TCHAR *Fmt, ... ) +{ + GROWABLE_LOGF(SendNotificationString(Buffer)); +} + +void FMsg::SendNotificationString( const TCHAR* Message ) +{ + FPlatformMisc::LowLevelOutputDebugString(Message); +} diff --git a/README.md b/README.md index 69b3d6b7..34bd3c5b 100644 --- a/README.md +++ b/README.md @@ -4,29 +4,33 @@ LINUX INSTRUCTIONS: Install important Software - install visual studio code. Usually: apt-get install code - install dotnet6. Usually: apt-get install dotnet6 - -Git Clone the Unreal Engine source code: + +Git Clone the UnrealEngine repository: - The repository is at https://github.com/EpicGames/UnrealEngine.git - Cloning the repository requires creating an account and a password and some other fiddly credential management stuff I don't remember how to do. - Clone it into your home directory, $HOME/UnrealEngine - -Compile the Unreal Engine, version 5.3.1 - - Change directory to $HOME/UnrealEngine - - git checkout 5.3.1-release - - Run Setup.sh - - Run GenerateProjectFiles.sh - - make - -Clone the Integration Repository + - Check out the correct version: git checkout 5.3.1-release + +Git Clone the integration Repository - The repository is at https://gnaut.com/jyelon/integration.git - Clone it into your home directory, $HOME/integration - + +Apply patches: + - Change directory to $HOME/integration + - Patch the integration repository: python3 patch-integration.py + - Patch the UnrealEngine repository: python3 patch-unrealengine.py + +Compile the Unreal Engine + - Change directory to $HOME/UnrealEngine + - Run Setup.sh + - Run GenerateProjectFiles.sh + - make + Compile Integration - Change directory to $HOME/integration - - Create Makefile and other useful project files: python3 make-makefiles.py - - make - + - make + Launch Integration in the Debugger - Change directory to $HOME/integration - Start the IDE: code Integration.code-workspace @@ -34,13 +38,13 @@ Launch Integration in the Debugger - Install the recommended extensions (only have to do this once) - Click 'Run/Start Debugging' - Is is preconfigured to launch and debug the unreal editor with the integration code. - + To edit and recompile, from inside the IDE: - Edit some source files - Click 'Terminal/Run Task...' - If you edited the code in 'Source': Click 'IntegrationEditor Linux DebugGame Build' - If you edited the code in 'Luprex': Click 'build luprex' - - You can optionally recompile from the command line by typing 'make' again. + - Alternately, you can recompile from the command line by typing 'make' again. - After recompiling in the IDE, you can go straight back to debugging. diff --git a/Source/Integration/Integration.Build.cs b/Source/Integration/Integration.Build.cs index b8725aae..80dd2759 100644 --- a/Source/Integration/Integration.Build.cs +++ b/Source/Integration/Integration.Build.cs @@ -8,9 +8,23 @@ public class Integration : ModuleRules { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "Sockets", "Networking", "EnhancedInput" }); + PublicDependencyModuleNames.AddRange(new string[] { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "Sockets", + "Networking", + "EnhancedInput", + }); - PrivateDependencyModuleNames.AddRange(new string[] { }); + PrivateDependencyModuleNames.AddRange(new string[] { + "KismetCompiler", + "UnrealEd", + "Kismet", + "KismetWidgets", + "BlueprintGraph", + }); // Uncomment if you are using Slate UI // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); diff --git a/Source/Integration/K2Node_RaiseError.cpp b/Source/Integration/K2Node_RaiseError.cpp new file mode 100644 index 00000000..8c798b97 --- /dev/null +++ b/Source/Integration/K2Node_RaiseError.cpp @@ -0,0 +1,587 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + + +#include "K2Node_RaiseError.h" + +#include "BlueprintActionDatabaseRegistrar.h" +#include "BlueprintNodeSpawner.h" +#include "Containers/EnumAsByte.h" +#include "Containers/UnrealString.h" +#include "EdGraph/EdGraph.h" +#include "EdGraph/EdGraphSchema.h" +#include "EdGraphSchema_K2.h" +#include "EdGraphSchema_K2_Actions.h" +#include "EditorCategoryUtils.h" +#include "Engine/Blueprint.h" +#include "HAL/PlatformCrt.h" +#include "Internationalization/Internationalization.h" +#include "K2Node_CallFunction.h" +#include "K2Node_MakeArray.h" +#include "K2Node_MakeStruct.h" +#include "Kismet/KismetMathLibrary.h" +#include "Kismet/KismetSystemLibrary.h" +#include "Kismet/KismetTextLibrary.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "Kismet2/CompilerResultsLog.h" +#include "KismetCompiler.h" +#include "Math/Vector2D.h" +#include "Misc/AssertionMacros.h" +#include "Misc/CString.h" +#include "ScopedTransaction.h" +#include "Templates/Casts.h" +#include "Templates/SubclassOf.h" +#include "UObject/Class.h" +#include "UObject/ObjectPtr.h" +#include "UObject/Package.h" +#include "UObject/UnrealNames.h" +#include "UObject/UnrealType.h" +#include "UObject/WeakObjectPtr.h" +#include "UObject/WeakObjectPtrTemplates.h" + +#define LOCTEXT_NAMESPACE "K2Node_RaiseError" + +///////////////////////////////////////////////////// +// UK2Node_RaiseError + +struct FRaiseErrorNodeHelper +{ + static const FName FormatPinName; + + static const FName GetFormatPinName() + { + return FormatPinName; + } +}; + +const FName FRaiseErrorNodeHelper::FormatPinName(TEXT("Format")); + +UK2Node_RaiseError::UK2Node_RaiseError(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , CachedFormatPin(NULL) +{ + NodeTooltip = LOCTEXT("NodeTooltip", "Builds a formatted string using available format argument values.\n \u2022 Use {} to denote format arguments.\n \u2022 Argument types may be Byte, Integer, Float, Text, String, Name, Boolean, Object or ETextGender."); +} + +void UK2Node_RaiseError::AllocateDefaultPins() +{ + Super::AllocateDefaultPins(); + + CachedFormatPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Text, FRaiseErrorNodeHelper::GetFormatPinName()); + CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Text, TEXT("Result")); + + for (const FName& PinName : PinNames) + { + CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, PinName); + } +} + +void UK2Node_RaiseError::SynchronizeArgumentPinType(UEdGraphPin* Pin) +{ + const UEdGraphPin* FormatPin = GetFormatPin(); + if (Pin != FormatPin && Pin->Direction == EGPD_Input) + { + const UEdGraphSchema_K2* K2Schema = Cast(GetSchema()); + + bool bPinTypeChanged = false; + if (Pin->LinkedTo.Num() == 0) + { + static const FEdGraphPinType WildcardPinType = FEdGraphPinType(UEdGraphSchema_K2::PC_Wildcard, NAME_None, nullptr, EPinContainerType::None, false, FEdGraphTerminalType()); + + // Ensure wildcard + if (Pin->PinType != WildcardPinType) + { + Pin->PinType = WildcardPinType; + bPinTypeChanged = true; + } + } + else + { + UEdGraphPin* ArgumentSourcePin = Pin->LinkedTo[0]; + + // Take the type of the connected pin + if (Pin->PinType != ArgumentSourcePin->PinType) + { + Pin->PinType = ArgumentSourcePin->PinType; + bPinTypeChanged = true; + } + } + + if (bPinTypeChanged) + { + // Let the graph know to refresh + GetGraph()->NotifyNodeChanged(this); + + UBlueprint* Blueprint = GetBlueprint(); + if (!Blueprint->bBeingCompiled) + { + FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); + } + } + } +} + +FText UK2Node_RaiseError::GetNodeTitle(ENodeTitleType::Type TitleType) const +{ + return LOCTEXT("RaiseError_Title", "Raise Error"); +} + +FText UK2Node_RaiseError::GetPinDisplayName(const UEdGraphPin* Pin) const +{ + return FText::FromName(Pin->PinName); +} + +void UK2Node_RaiseError::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) +{ + const FName PropertyName = (PropertyChangedEvent.Property ? PropertyChangedEvent.Property->GetFName() : NAME_None); + if (PropertyName == GET_MEMBER_NAME_CHECKED(UK2Node_RaiseError, PinNames)) + { + ReconstructNode(); + } + Super::PostEditChangeProperty(PropertyChangedEvent); + GetGraph()->NotifyNodeChanged(this); +} + +void UK2Node_RaiseError::PinConnectionListChanged(UEdGraphPin* Pin) +{ + UEdGraphPin* FormatPin = GetFormatPin(); + + Modify(); + + // Clear all pins. + if(Pin == FormatPin && !FormatPin->DefaultTextValue.IsEmpty()) + { + PinNames.Empty(); + GetSchema()->TrySetDefaultText(*FormatPin, FText::GetEmpty()); + + for(auto It = Pins.CreateConstIterator(); It; ++It) + { + UEdGraphPin* CheckPin = *It; + if(CheckPin != FormatPin && CheckPin->Direction == EGPD_Input) + { + CheckPin->Modify(); + CheckPin->MarkAsGarbage(); + Pins.Remove(CheckPin); + --It; + } + } + + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprint()); + } + + // Potentially update an argument pin type + SynchronizeArgumentPinType(Pin); +} + +void UK2Node_RaiseError::PinDefaultValueChanged(UEdGraphPin* Pin) +{ + const UEdGraphPin* FormatPin = GetFormatPin(); + if(Pin == FormatPin && FormatPin->LinkedTo.Num() == 0) + { + TArray< FString > ArgumentParams; + FText::GetFormatPatternParameters(FormatPin->DefaultTextValue, ArgumentParams); + + PinNames.Reset(); + + for (const FString& Param : ArgumentParams) + { + const FName ParamName(*Param); + if (!FindArgumentPin(ParamName)) + { + CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, ParamName); + } + PinNames.Add(ParamName); + } + + for (auto It = Pins.CreateIterator(); It; ++It) + { + UEdGraphPin* CheckPin = *It; + if (CheckPin != FormatPin && CheckPin->Direction == EGPD_Input) + { + const bool bIsValidArgPin = ArgumentParams.ContainsByPredicate([&CheckPin](const FString& InPinName) + { + return InPinName.Equals(CheckPin->PinName.ToString(), ESearchCase::CaseSensitive); + }); + + if(!bIsValidArgPin) + { + CheckPin->MarkAsGarbage(); + It.RemoveCurrent(); + } + } + } + + GetGraph()->NotifyNodeChanged(this); + } +} + +void UK2Node_RaiseError::PinTypeChanged(UEdGraphPin* Pin) +{ + // Potentially update an argument pin type + SynchronizeArgumentPinType(Pin); + + Super::PinTypeChanged(Pin); +} + +FText UK2Node_RaiseError::GetTooltipText() const +{ + return NodeTooltip; +} + +UEdGraphPin* FindOutputStructPinChecked(UEdGraphNode* Node) +{ + check(NULL != Node); + UEdGraphPin* OutputPin = NULL; + for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) + { + UEdGraphPin* Pin = Node->Pins[PinIndex]; + if (Pin && (EGPD_Output == Pin->Direction)) + { + OutputPin = Pin; + break; + } + } + check(NULL != OutputPin); + return OutputPin; +} + +void UK2Node_RaiseError::PostReconstructNode() +{ + Super::PostReconstructNode(); + + UEdGraph* OuterGraph = GetGraph(); + if (!IsTemplate() && OuterGraph && OuterGraph->Schema) { + for (UEdGraphPin* CurrentPin : Pins) + { + // Potentially update an argument pin type + SynchronizeArgumentPinType(CurrentPin); + } + } +} + +void UK2Node_RaiseError::ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) +{ + Super::ExpandNode(CompilerContext, SourceGraph); + + /** + At the end of this, the UK2Node_RaiseError will not be a part of the Blueprint, it merely handles connecting + the other nodes into the Blueprint. + */ + + const UEdGraphSchema_K2* Schema = CompilerContext.GetSchema(); + + // Create a "Make Array" node to compile the list of arguments into an array for the Format function being called + UK2Node_MakeArray* MakeArrayNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); + MakeArrayNode->AllocateDefaultPins(); + CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeArrayNode, this); + + UEdGraphPin* ArrayOut = MakeArrayNode->GetOutputPin(); + + // This is the node that does all the Format work. + UK2Node_CallFunction* CallFormatFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); + CallFormatFunction->SetFromFunction(UKismetTextLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Format))); + CallFormatFunction->AllocateDefaultPins(); + CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallFormatFunction, this); + + // Connect the output of the "Make Array" pin to the function's "InArgs" pin + ArrayOut->MakeLinkTo(CallFormatFunction->FindPinChecked(TEXT("InArgs"))); + + // This will set the "Make Array" node's type, only works if one pin is connected. + MakeArrayNode->PinConnectionListChanged(ArrayOut); + + // For each argument, we will need to add in a "Make Struct" node. + for(int32 ArgIdx = 0; ArgIdx < PinNames.Num(); ++ArgIdx) + { + UEdGraphPin* ArgumentPin = FindArgumentPin(PinNames[ArgIdx]); + + static UScriptStruct* FormatArgumentDataStruct = FindObjectChecked(FindObjectChecked(nullptr, TEXT("/Script/Engine")), TEXT("FormatArgumentData")); + + // Spawn a "Make Struct" node to create the struct needed for formatting the text. + UK2Node_MakeStruct* MakeFormatArgumentDataStruct = CompilerContext.SpawnIntermediateNode(this, SourceGraph); + MakeFormatArgumentDataStruct->StructType = FormatArgumentDataStruct; + MakeFormatArgumentDataStruct->AllocateDefaultPins(); + MakeFormatArgumentDataStruct->bMadeAfterOverridePinRemoval = true; + CompilerContext.MessageLog.NotifyIntermediateObjectCreation(MakeFormatArgumentDataStruct, this); + + // Set the struct's "ArgumentName" pin literal to be the argument pin's name. + MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentName)), ArgumentPin->PinName.ToString()); + + UEdGraphPin* ArgumentTypePin = MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValueType)); + + // Move the connection of the argument pin to the correct argument value pin, and also set the correct argument type based on the pin that was hooked up. + if (ArgumentPin->LinkedTo.Num() > 0) + { + const FName& ArgumentPinCategory = ArgumentPin->PinType.PinCategory; + + // Adds an implicit conversion node to this argument based on its function and pin name + auto AddConversionNode = [&](const FName FuncName, const TCHAR* PinName) + { + // Set the default value if there was something passed in, or default to "Text" + MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Text")); + + // Spawn conversion node based on the given function name + UK2Node_CallFunction* ToTextFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); + ToTextFunction->SetFromFunction(UKismetTextLibrary::StaticClass()->FindFunctionByName(FuncName)); + ToTextFunction->AllocateDefaultPins(); + CompilerContext.MessageLog.NotifyIntermediateObjectCreation(ToTextFunction, this); + + CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *ToTextFunction->FindPinChecked(PinName)); + + ToTextFunction->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue)->MakeLinkTo(MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValue))); + }; + + if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Int) + { + MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Int")); + // Need a manual cast from int -> int64 + UK2Node_CallFunction* CallFloatToDoubleFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); + CallFloatToDoubleFunction->SetFromFunction(UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetMathLibrary, Conv_IntToInt64))); + CallFloatToDoubleFunction->AllocateDefaultPins(); + CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallFloatToDoubleFunction, this); + + // Move the byte output pin to the input pin of the conversion node + CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *CallFloatToDoubleFunction->FindPinChecked(TEXT("InInt"))); + + // Connect the int output pin to the argument value + CallFloatToDoubleFunction->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue)->MakeLinkTo(MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValueInt))); + } + else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Real) + { + if (ArgumentPin->PinType.PinSubCategory == UEdGraphSchema_K2::PC_Float) + { + MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Float")); + CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValueFloat))); + } + else if (ArgumentPin->PinType.PinSubCategory == UEdGraphSchema_K2::PC_Double) + { + MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Double")); + CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValueDouble))); + } + else + { + check(false); + } + } + else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Int64) + { + MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Int64")); + CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValueInt))); + } + else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Text) + { + MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Text")); + CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValue))); + } + else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Byte && !ArgumentPin->PinType.PinSubCategoryObject.IsValid()) + { + MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Int")); + + // Need a manual cast from byte -> int + UK2Node_CallFunction* CallByteToIntFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph); + CallByteToIntFunction->SetFromFunction(UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetMathLibrary, Conv_ByteToInt64))); + CallByteToIntFunction->AllocateDefaultPins(); + CompilerContext.MessageLog.NotifyIntermediateObjectCreation(CallByteToIntFunction, this); + + // Move the byte output pin to the input pin of the conversion node + CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *CallByteToIntFunction->FindPinChecked(TEXT("InByte"))); + + // Connect the int output pin to the argument value + CallByteToIntFunction->FindPinChecked(UEdGraphSchema_K2::PN_ReturnValue)->MakeLinkTo(MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValueInt))); + } + else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Byte || ArgumentPinCategory == UEdGraphSchema_K2::PC_Enum) + { + static UEnum* TextGenderEnum = FindObjectChecked(nullptr, TEXT("/Script/Engine.ETextGender"), /*ExactClass*/true); + if (ArgumentPin->PinType.PinSubCategoryObject == TextGenderEnum) + { + MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Gender")); + CompilerContext.MovePinLinksToIntermediate(*ArgumentPin, *MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValueGender))); + } + } + else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Boolean) + { + AddConversionNode(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_BoolToText), TEXT("InBool")); + } + else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Name) + { + AddConversionNode(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_NameToText), TEXT("InName")); + } + else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_String) + { + AddConversionNode(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_StringToText), TEXT("InString")); + } + else if (ArgumentPinCategory == UEdGraphSchema_K2::PC_Object) + { + AddConversionNode(GET_MEMBER_NAME_CHECKED(UKismetTextLibrary, Conv_ObjectToText), TEXT("InObj")); + } + else + { + // Unexpected pin type! + CompilerContext.MessageLog.Error(*FText::Format(LOCTEXT("Error_UnexpectedPinType", "Pin '{0}' has an unexpected type: {1}"), FText::FromName(PinNames[ArgIdx]), FText::FromName(ArgumentPinCategory)).ToString()); + } + } + else + { + // No connected pin - just default to an empty text + MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultValue(*ArgumentTypePin, TEXT("Text")); + MakeFormatArgumentDataStruct->GetSchema()->TrySetDefaultText(*MakeFormatArgumentDataStruct->FindPinChecked(GET_MEMBER_NAME_STRING_CHECKED(FFormatArgumentData, ArgumentValue)), FText::GetEmpty()); + } + + // The "Make Array" node already has one pin available, so don't create one for ArgIdx == 0 + if(ArgIdx > 0) + { + MakeArrayNode->AddInputPin(); + } + + // Find the input pin on the "Make Array" node by index. + const FString PinName = FString::Printf(TEXT("[%d]"), ArgIdx); + UEdGraphPin* InputPin = MakeArrayNode->FindPinChecked(PinName); + + // Find the output for the pin's "Make Struct" node and link it to the corresponding pin on the "Make Array" node. + FindOutputStructPinChecked(MakeFormatArgumentDataStruct)->MakeLinkTo(InputPin); + } + + // Move connection of RaiseError's "Result" pin to the call function's return value pin. + CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(TEXT("Result")), *CallFormatFunction->GetReturnValuePin()); + // Move connection of RaiseError's "Format" pin to the call function's "InPattern" pin + CompilerContext.MovePinLinksToIntermediate(*GetFormatPin(), *CallFormatFunction->FindPinChecked(TEXT("InPattern"))); + + BreakAllNodeLinks(); +} + +UEdGraphPin* UK2Node_RaiseError::FindArgumentPin(const FName InPinName) const +{ + const UEdGraphPin* FormatPin = GetFormatPin(); + for (UEdGraphPin* Pin : Pins) + { + if( Pin != FormatPin && Pin->Direction != EGPD_Output && Pin->PinName.ToString().Equals(InPinName.ToString(), ESearchCase::CaseSensitive) ) + { + return Pin; + } + } + + return nullptr; +} + +UK2Node::ERedirectType UK2Node_RaiseError::DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const +{ + ERedirectType RedirectType = ERedirectType_None; + + // if the pin names do match + if (NewPin->PinName.ToString().Equals(OldPin->PinName.ToString(), ESearchCase::CaseSensitive)) + { + // Make sure we're not dealing with a menu node + UEdGraph* OuterGraph = GetGraph(); + if( OuterGraph && OuterGraph->Schema ) + { + const UEdGraphSchema_K2* K2Schema = Cast(GetSchema()); + if( !K2Schema || K2Schema->IsSelfPin(*NewPin) || K2Schema->ArePinTypesCompatible(OldPin->PinType, NewPin->PinType) ) + { + RedirectType = ERedirectType_Name; + } + else + { + RedirectType = ERedirectType_None; + } + } + } + else + { + // try looking for a redirect if it's a K2 node + if (UK2Node* Node = Cast(NewPin->GetOwningNode())) + { + // if you don't have matching pin, now check if there is any redirect param set + TArray OldPinNames; + GetRedirectPinNames(*OldPin, OldPinNames); + + FName NewPinName; + RedirectType = ShouldRedirectParam(OldPinNames, /*out*/ NewPinName, Node); + + // make sure they match + if ((RedirectType != ERedirectType_None) && (!NewPin->PinName.ToString().Equals(NewPinName.ToString(), ESearchCase::CaseSensitive))) + { + RedirectType = ERedirectType_None; + } + } + } + + return RedirectType; +} + +bool UK2Node_RaiseError::IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const +{ + // Argument input pins may only be connected to Byte, Integer, Float, Text, and ETextGender pins... + const UEdGraphPin* FormatPin = GetFormatPin(); + + // The format pin cannot be connected to anything. It must be a constant string. + if (MyPin == FormatPin) { + OutReason = LOCTEXT("Error_FormatStringMustBeHardwired", "Format string must be a hardwired constant.").ToString(); + return true; + } + + if (MyPin != FormatPin && MyPin->Direction == EGPD_Input) + { + const UEdGraphSchema_K2* K2Schema = Cast(GetSchema()); + const FName& OtherPinCategory = OtherPin->PinType.PinCategory; + + bool bIsValidType = false; + if (OtherPinCategory == UEdGraphSchema_K2::PC_Int || OtherPinCategory == UEdGraphSchema_K2::PC_Real || OtherPinCategory == UEdGraphSchema_K2::PC_Text || + (OtherPinCategory == UEdGraphSchema_K2::PC_Byte && !OtherPin->PinType.PinSubCategoryObject.IsValid()) || + OtherPinCategory == UEdGraphSchema_K2::PC_Boolean || OtherPinCategory == UEdGraphSchema_K2::PC_String || OtherPinCategory == UEdGraphSchema_K2::PC_Name || OtherPinCategory == UEdGraphSchema_K2::PC_Object || + OtherPinCategory == UEdGraphSchema_K2::PC_Wildcard || OtherPinCategory == UEdGraphSchema_K2::PC_Int64) + { + bIsValidType = true; + } + else if (OtherPinCategory == UEdGraphSchema_K2::PC_Byte || OtherPinCategory == UEdGraphSchema_K2::PC_Enum) + { + static UEnum* TextGenderEnum = FindObjectChecked(nullptr, TEXT("/Script/Engine.ETextGender"), /*ExactClass*/true); + if (OtherPin->PinType.PinSubCategoryObject == TextGenderEnum) + { + bIsValidType = true; + } + } + + if (!bIsValidType) + { + OutReason = LOCTEXT("Error_InvalidArgumentType", "Format arguments may only be Byte, Integer, Int64, Float, Double, Text, String, Name, Boolean, Object, Wildcard or ETextGender.").ToString(); + return true; + } + } + + return Super::IsConnectionDisallowed(MyPin, OtherPin, OutReason); +} + +UEdGraphPin* UK2Node_RaiseError::GetFormatPin() const +{ + UK2Node_RaiseError *mthis = const_cast(this); + if (!CachedFormatPin) + { + mthis->CachedFormatPin = FindPinChecked(FRaiseErrorNodeHelper::GetFormatPinName()); + } + return CachedFormatPin; +} + +void UK2Node_RaiseError::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const +{ + // actions get registered under specific object-keys; the idea is that + // actions might have to be updated (or deleted) if their object-key is + // mutated (or removed)... here we use the node's class (so if the node + // type disappears, then the action should go with it) + UClass* ActionKey = GetClass(); + // to keep from needlessly instantiating a UBlueprintNodeSpawner, first + // check to make sure that the registrar is looking for actions of this type + // (could be regenerating actions for a specific asset, and therefore the + // registrar would only accept actions corresponding to that asset) + if (ActionRegistrar.IsOpenForRegistration(ActionKey)) + { + UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass()); + check(NodeSpawner != nullptr); + + ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner); + } +} + +FText UK2Node_RaiseError::GetMenuCategory() const +{ + return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Text); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Source/Integration/K2Node_RaiseError.h b/Source/Integration/K2Node_RaiseError.h new file mode 100644 index 00000000..23d921d7 --- /dev/null +++ b/Source/Integration/K2Node_RaiseError.h @@ -0,0 +1,81 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Containers/Array.h" +#include "CoreMinimal.h" +#include "EdGraph/EdGraphNode.h" +#include "EdGraph/EdGraphPin.h" +#include "HAL/Platform.h" +#include "Internationalization/Text.h" +#include "K2Node.h" +#include "UObject/NameTypes.h" +#include "UObject/ObjectMacros.h" +#include "UObject/UObjectGlobals.h" + +#include "K2Node_RaiseError.generated.h" + +class FBlueprintActionDatabaseRegistrar; +class FString; +class UEdGraph; +class UObject; + +UCLASS(MinimalAPI) +class UK2Node_RaiseError : public UK2Node +{ + GENERATED_UCLASS_BODY() + + //~ Begin UObject Interface + virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; + //~ End UObject Interface + + //~ Begin UEdGraphNode Interface. + virtual void AllocateDefaultPins() override; + virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; + virtual bool ShouldShowNodeProperties() const override { return true; } + virtual void PinConnectionListChanged(UEdGraphPin* Pin) override; + virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override; + virtual void PinTypeChanged(UEdGraphPin* Pin) override; + virtual FText GetTooltipText() const override; + virtual FText GetPinDisplayName(const UEdGraphPin* Pin) const override; + //~ End UEdGraphNode Interface. + + //~ Begin UK2Node Interface. + virtual bool IsNodePure() const override { return true; } + virtual void PostReconstructNode() override; + virtual bool NodeCausesStructuralBlueprintChange() const override { return true; } + virtual void ExpandNode(class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override; + virtual ERedirectType DoPinsMatchForReconstruction(const UEdGraphPin* NewPin, int32 NewPinIndex, const UEdGraphPin* OldPin, int32 OldPinIndex) const override; + virtual bool IsConnectionDisallowed(const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason) const override; + virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override; + virtual FText GetMenuCategory() const override; + virtual int32 GetNodeRefreshPriority() const override { return EBaseNodeRefreshPriority::Low_UsesDependentWildcard; } + //~ End UK2Node Interface. + +private: + /** returns Format pin */ + UEdGraphPin* GetFormatPin() const; + + /** + * Finds an argument pin by name, checking strings in a strict, case sensitive fashion + * + * @param InPinName The pin name to check for + * @return NULL if the pin was not found, otherwise the found pin. + */ + UEdGraphPin* FindArgumentPin(const FName InPinName) const; + + /** Synchronize the type of the given argument pin with the type its connected to, or reset it to a wildcard pin if there's no connection */ + void SynchronizeArgumentPinType(UEdGraphPin* Pin); + +private: + /** When adding arguments to the node, their names are placed here and are generated as pins during construction */ + UPROPERTY() + TArray PinNames; + + /** The "Format" input pin, always available on the node */ + UEdGraphPin* CachedFormatPin; + + /** Tooltip text for this node. */ + FText NodeTooltip; +}; + diff --git a/make-makefiles.py b/patch-integration.py similarity index 83% rename from make-makefiles.py rename to patch-integration.py index 0db9c929..dfcef528 100755 --- a/make-makefiles.py +++ b/patch-integration.py @@ -1,12 +1,26 @@ #!/usr/bin/python3 # -# This script generates these config files: +# This script applies patches to the integration repository. # -# Saved/UnrealBuildTool/BuildConfiguration.xml -# Integration.uproject -# Integration.code-workspace -# Makefile -# Source/Integration/lpx-paths.hpp +# There are certain files that can't be easily checked into git +# because the contents of these files must contain hardwired +# paths, or hardwired machine-specific settings. These include: +# +# integration/Saved/UnrealBuildTool/BuildConfiguration.xml +# integration/Integration.uproject +# integration/Integration.code-workspace +# integration/Makefile +# integration/Source/Integration/lpx-paths.hpp +# +# This python program will generate these files as necessary. +# +# It is safe to run this patch program a second time to repair +# any of these files, if necessary. Doing so will not corrupt +# anything. +# +#------------------------------------------------------------- +# +# Details you may need to know someday: # # The BuildConfiguration.xml file specifies that this project will # use visual studio code (on linux) or visual studio (on windows). @@ -43,6 +57,7 @@ # C++ code to calculate these dynamically, so that they don't need to # be hardwired in. But this will do for now. # +#------------------------------------------------------------- import sys, os, json, glob from pathlib import Path @@ -88,6 +103,10 @@ f""" VisualStudioCode + + true + true + """) @@ -186,11 +205,12 @@ LUPREXBUILDTASK["options"]["cwd"] = f"{INTEGRATION}/luprex" # Convert all launch configurations to lldb. # -LLDBINIT=f"command script import {UNREALENGINE}/Engine/Extras/LLDBDataFormatters/UEDataFormatters_2ByteChars.py" - for config in WORKSPACE["launch"]["configurations"]: config["type"] = "lldb" - config["initCommands"] = [ LLDBINIT ] + config["initCommands"] = [ + f"command script import {UNREALENGINE}/Engine/Extras/LLDBDataFormatters/UEDataFormatters_2ByteChars.py", + f'target stop-hook add --one-liner "p ::UngrabAllInputImpl()"' + ] config["args"] = [ f"{INTEGRATION}/Integration.uproject", f"-userdir=User/{USER}" ] config.pop("visualizerFile", None) config.pop("showDisplayString", None) diff --git a/patch-unrealengine.py b/patch-unrealengine.py new file mode 100755 index 00000000..f195b01f --- /dev/null +++ b/patch-unrealengine.py @@ -0,0 +1,68 @@ +#!/usr/bin/python3 +# +# This script applies patches to the UnrealEngine repository. +# +# We don't keep our own unreal engine repository because that +# would be overkill. Instead, you must check out the standard +# copy of Unreal engine, then we apply a few simple patches. +# The files patched are: +# +# UnrealEngine/Engine/Saved/UnrealBuildTool/BuildConfiguration.xml +# UnrealEngine/Engine/Source/Runtime/Core/Private/Logging/LogMacros.cpp +# +# This python program will generate these files as necessary. +# +# It is safe to run this patch program a second time to repair +# any of these files, if necessary. Doing so will not corrupt +# anything. +# +#-------------------------------------------------------------- + +import sys, os, json, glob +from pathlib import Path + +# +# Some handy utility functions +# + +def readfile(fn): + with open(fn) as f: + return f.read() + +def writefile(fn, str): + with open(fn, "w") as f: + f.write(str) + +# +# These are some directory paths that we will need. +# + +INTEGRATION = os.path.dirname(os.path.abspath(sys.argv[0])) +UNREALENGINE = os.environ["HOME"] + "/UnrealEngine" +if not os.path.isdir(UNREALENGINE): error("No unreal installed in $HOME/UnrealEngine") + +# +# Change to the target directory. +# Remove any existing project files. +# + +os.chdir(UNREALENGINE) +Path("Engine/Saved/UnrealBuildTool").mkdir(parents=True, exist_ok=True) +Path("Engine/Saved/UnrealBuildTool/BuildConfiguration.xml").unlink(missing_ok=True) +Path("Engine/Source/Runtime/Core/Private/Logging/LogMacros.cpp").unlink(missing_ok=True) + +# +# Write BuildConfiguration.xml +# + +BUILDCONFIG=readfile(f"{INTEGRATION}/EnginePatches/BuildConfigurationLinux.xml") +writefile("Engine/Saved/UnrealBuildTool/BuildConfiguration.xml", BUILDCONFIG) + +# +# Write LogMacros.cpp +# + +LOGMACROS=readfile(f"{INTEGRATION}/EnginePatches/LogMacros.cpp") +writefile(f"{UNREALENGINE}/Engine/Source/Runtime/Core/Private/Logging/LogMacros.cpp"), LOGMACROS) + +