From d57bbbcdaf42b628fb415dd5a857ef0bf9faa59c Mon Sep 17 00:00:00 2001 From: Ryan Vance Date: Thu, 17 Jan 2019 19:43:28 -0500 Subject: [PATCH] Merging using MainToDevVR #rb integration [CL 4748914 by Ryan Vance in Dev-VR branch] --- .../epicgames/ue4/GameActivity.java.template | 53 +- .../src/com/epicgames/ue4/SplashActivity.java | 31 + Engine/Build/Commit.gitdeps.xml | 1733 +++++++++-------- Engine/Config/BaseEngine.ini | 6 +- Engine/Config/BaseInput.ini | 1 + .../Private/ConcertClientSequencerManager.cpp | 2 +- .../PerforceSourceControlOperations.cpp | 2 +- .../ReferenceViewer/EdGraphNode_Reference.cpp | 6 +- .../EdGraph_ReferenceViewer.cpp | 4 +- .../ReferenceViewer/ReferenceViewerSchema.cpp | 6 +- .../ReferenceViewer/SReferenceNode.cpp | 2 +- .../ReferenceViewer/SReferenceViewer.cpp | 6 +- .../ReferenceViewer/EdGraphNode_Reference.h | 40 +- .../ReferenceViewer/EdGraph_ReferenceViewer.h | 21 +- .../ReferenceViewer/ReferenceViewerSchema.h | 6 +- .../Private/GameplayTagsEditorModule.cpp | 40 +- .../Private/SGameplayTagGraphPin.cpp | 7 +- .../Sequencer/ControlRigSequenceExporter.cpp | 2 +- .../Source/StructBox/Classes/StructBox.h | 58 - .../StructBox/Classes/StructBoxLibrary.h | 83 - .../Source/StructBox/Private/StructBox.cpp | 110 -- .../StructBox/Private/StructBoxLibrary.cpp | 14 - .../StructBox/Private/StructBoxModule.cpp | 12 - .../StructBox/Public/IStructBoxModule.h | 13 - .../Source/StructBox/StructBox.Build.cs | 21 - .../Experimental/StructBox/StructBox.uplugin | 25 - .../NiagaraDataInterfaceHoudiniCSV.cpp | 2 +- .../Plugins/FX/Niagara/Config/BaseNiagara.ini | 11 +- .../Source/Niagara/Classes/NiagaraCollision.h | 4 +- .../Source/Niagara/Classes/NiagaraConstants.h | 131 +- .../NiagaraDataInterfaceCollisionQuery.h | 20 +- .../Classes/NiagaraDataInterfaceCurlNoise.h | 31 +- .../Classes/NiagaraDataInterfaceVectorField.h | 88 +- .../Source/Niagara/Classes/NiagaraDataSet.h | 38 +- .../Source/Niagara/Classes/NiagaraEmitter.h | 28 +- .../Niagara/Classes/NiagaraEmitterInstance.h | 28 +- .../Classes/NiagaraEmitterInstanceBatcher.h | 5 +- .../Classes/NiagaraParameterCollection.h | 2 + .../Source/Niagara/Classes/NiagaraScript.h | 40 +- .../Classes/NiagaraScriptExecutionContext.h | 72 +- .../Source/Niagara/Classes/NiagaraSystem.h | 23 +- .../Niagara/Private/NiagaraCollision.cpp | 76 +- .../Source/Niagara/Private/NiagaraCommon.cpp | 25 +- .../Niagara/Private/NiagaraComponent.cpp | 39 +- .../Niagara/Private/NiagaraConstants.cpp | 34 +- .../Niagara/Private/NiagaraDataInterface.cpp | 2 +- .../NiagaraDataInterfaceCollisionQuery.cpp | 496 ++++- .../Private/NiagaraDataInterfaceCurlNoise.cpp | 631 +++--- .../Private/NiagaraDataInterfaceTexture.cpp | 37 +- .../NiagaraDataInterfaceVectorField.cpp | 1272 +++++------- .../Source/Niagara/Private/NiagaraDataSet.cpp | 106 +- .../Source/Niagara/Private/NiagaraEmitter.cpp | 35 +- .../Private/NiagaraEmitterInstance.cpp | 144 +- .../Private/NiagaraEmitterInstanceBatcher.cpp | 211 +- .../NiagaraLightRendererProperties.cpp | 11 +- .../Source/Niagara/Private/NiagaraModule.cpp | 54 +- .../Niagara/Private/NiagaraParameterStore.cpp | 28 +- .../Niagara/Private/NiagaraRenderer.cpp | 44 +- .../Niagara/Private/NiagaraRendererMeshes.cpp | 4 +- .../Private/NiagaraRendererRibbons.cpp | 38 +- .../Private/NiagaraRendererSprites.cpp | 4 +- .../Source/Niagara/Private/NiagaraScript.cpp | 61 +- .../Private/NiagaraScriptExecutionContext.cpp | 10 +- .../NiagaraSpriteRendererProperties.cpp | 4 +- .../Source/Niagara/Private/NiagaraStats.h | 15 +- .../Source/Niagara/Private/NiagaraSystem.cpp | 65 +- .../Niagara/Private/NiagaraSystemInstance.cpp | 214 +- .../Private/NiagaraSystemSimulation.cpp | 71 +- .../NiagaraUserRedirectionParameterStore.cpp | 113 ++ .../Niagara/Private/NiagaraWorldManager.cpp | 11 +- .../Source/Niagara/Public/NiagaraCommon.h | 18 +- .../Source/Niagara/Public/NiagaraComponent.h | 19 +- .../Public/NiagaraLightRendererProperties.h | 35 +- .../Source/Niagara/Public/NiagaraModule.h | 18 + .../Niagara/Public/NiagaraParameterStore.h | 32 +- .../NiagaraScriptExecutionParameterStore.h | 8 +- .../Niagara/Public/NiagaraSystemInstance.h | 15 +- .../Niagara/Public/NiagaraSystemSimulation.h | 17 +- .../Source/Niagara/Public/NiagaraTypes.h | 18 + .../NiagaraUserRedirectionParameterStore.h | 66 + .../Niagara/Public/NiagaraWorldManager.h | 19 +- .../Private/NiagaraCustomVersion.cpp | 2 +- .../Public/NiagaraDataInterfaceBase.h | 4 +- .../AssetTypeActions_NiagaraEmitter.cpp | 6 + .../NiagaraComponentDetails.cpp | 20 +- .../Private/EdGraphSchema_Niagara.cpp | 160 +- .../NiagaraEditor/Private/NiagaraCompiler.cpp | 25 + .../Private/NiagaraEditorCommon.cpp | 146 +- .../Private/NiagaraEditorModule.cpp | 23 +- .../Private/NiagaraEditorUtilities.cpp | 56 +- .../Private/NiagaraEmitterFactoryNew.cpp | 3 +- .../NiagaraEditor/Private/NiagaraGraph.cpp | 85 + .../Private/NiagaraHlslTranslator.cpp | 428 ++-- .../Private/NiagaraHlslTranslator.h | 14 +- .../NiagaraEditor/Private/NiagaraNode.cpp | 96 + .../Private/NiagaraNodeCustomHlsl.cpp | 4 +- .../Private/NiagaraNodeFunctionCall.cpp | 44 +- .../NiagaraEditor/Private/NiagaraNodeIf.cpp | 28 +- .../NiagaraEditor/Private/NiagaraNodeIf.h | 12 +- .../NiagaraEditor/Private/NiagaraNodeOp.cpp | 149 +- .../Private/NiagaraNodeParameterMapBase.cpp | 3 +- .../Private/NiagaraNodeReroute.cpp | 6 + .../Private/NiagaraNodeReroute.h | 1 + .../Private/NiagaraNodeSimTargetSelector.cpp | 122 ++ .../Private/NiagaraNodeSimTargetSelector.h | 30 + .../Private/NiagaraNodeUsageSelector.cpp | 65 +- .../Private/NiagaraNodeUsageSelector.h | 8 +- .../Private/NiagaraParameterMapHistory.cpp | 13 +- .../Private/NiagaraScriptFactory.cpp | 4 +- .../Private/NiagaraScriptSource.cpp | 9 +- .../Private/NiagaraStackEditorData.cpp | 24 + .../Private/NiagaraSystemEditorData.cpp | 2 +- .../Private/NiagaraSystemFactoryNew.cpp | 2 +- .../Private/Toolkits/NiagaraScriptToolkit.cpp | 2 +- .../Private/Toolkits/NiagaraSystemToolkit.cpp | 2 + .../NiagaraCollectionParameterViewModel.cpp | 2 +- .../NiagaraScriptParameterViewModel.cpp | 2 +- .../ViewModels/NiagaraSystemViewModel.cpp | 2 + .../ViewModels/Stack/NiagaraStackEntry.cpp | 34 +- .../Stack/NiagaraStackFunctionInput.cpp | 143 +- .../Stack/NiagaraStackGraphUtilities.cpp | 13 +- .../Stack/NiagaraStackInputCategory.cpp | 2 +- .../ViewModels/Stack/NiagaraStackItem.cpp | 2 +- .../Stack/NiagaraStackModuleItem.cpp | 297 ++- ...raStackModuleItemLinkedInputCollection.cpp | 92 + .../Stack/NiagaraStackPropertyRow.cpp | 7 +- .../Stack/NiagaraStackScriptItemGroup.cpp | 13 +- .../Stack/NiagaraStackViewModel.cpp | 71 +- .../Widgets/SNiagaraParameterMapView.cpp | 6 + .../Widgets/SNiagaraParameterMapView.h | 3 + .../Widgets/SNiagaraSpreadsheetView.cpp | 21 +- .../Private/Widgets/SNiagaraSpreadsheetView.h | 1 + .../Public/EdGraphSchema_Niagara.h | 3 +- .../Public/NiagaraEditorCommon.h | 19 +- .../Public/NiagaraEditorSettings.h | 6 +- .../NiagaraEditor/Public/NiagaraGraph.h | 16 + .../Source/NiagaraEditor/Public/NiagaraNode.h | 8 + .../Public/NiagaraNodeAssignment.h | 4 +- .../Public/NiagaraNodeCustomHlsl.h | 2 +- .../Public/NiagaraNodeDataSetBase.h | 4 +- .../Public/NiagaraNodeFunctionCall.h | 3 + .../NiagaraEditor/Public/NiagaraNodeInput.h | 4 +- .../NiagaraEditor/Public/NiagaraNodeOp.h | 40 +- .../NiagaraEditor/Public/NiagaraNodeOutput.h | 2 +- .../Public/NiagaraNodeWithDynamicPins.h | 2 +- .../Public/NiagaraParameterMapHistory.h | 2 +- .../Public/NiagaraScriptFactoryNew.h | 2 +- .../Public/NiagaraStackEditorData.h | 26 + .../ViewModels/Stack/NiagaraStackEntry.h | 16 +- .../Stack/NiagaraStackFunctionInput.h | 19 + .../Stack/NiagaraStackGraphUtilities.h | 2 +- .../ViewModels/Stack/NiagaraStackModuleItem.h | 19 + ...garaStackModuleItemLinkedInputCollection.h | 32 + .../Stack/NiagaraStackPropertyRow.h | 2 +- .../ViewModels/Stack/NiagaraStackViewModel.h | 4 + .../Private/SNiagaraStack.cpp | 119 +- .../Private/SNiagaraStack.h | 4 +- .../Stack/SNiagaraStackFunctionInputValue.cpp | 88 +- .../Stack/SNiagaraStackFunctionInputValue.h | 5 + .../Private/Stack/SNiagaraStackModuleItem.cpp | 96 +- .../Private/Stack/SNiagaraStackModuleItem.h | 3 + .../NiagaraShader/Private/NiagaraShader.cpp | 3 +- .../NiagaraShaderCompilationManager.cpp | 8 +- .../Private/NiagaraShaderDerivedDataVersion.h | 2 +- .../NiagaraShader/Private/NiagaraShared.cpp | 5 +- .../Private/NiagaraRibbonVertexFactory.cpp | 8 +- .../Public/NiagaraRibbonVertexFactory.h | 22 +- .../Widgets/SMediaPermutationsSelector.inl | 2 +- .../Source/Party/Private/User/SocialUser.cpp | 19 +- .../Source/Private/OnlineError.cpp | 2 +- .../Source/Private/OnlineSubsystemModule.cpp | 2 + .../Private/OnlineSubsystemFacebookModule.cpp | 6 +- .../Public/OnlineSubsystemUtils.h | 31 +- .../AppleARKit/Private/AppleARKitSystem.cpp | 69 +- .../AndroidPermission.uplugin | 4 - .../GameplayAbilities.uplugin | 2 +- .../Private/AbilitySystemBlueprintLibrary.cpp | 42 + .../AbilitySystemComponent_Abilities.cpp | 3 +- .../Private/GameplayCueManager.cpp | 1 + .../Public/AbilitySystemBlueprintLibrary.h | 8 + .../Public/GameplayEffectTypes.h | 2 +- .../TimeSynth/Classes/TimeSynthComponent.h | 4 +- .../TimeSynth/Private/TimeSynthComponent.cpp | 14 +- Engine/Shaders/Private/Common.ush | 2 +- .../Private/NiagaraEmitterInstanceShader.usf | 200 +- .../Private/NiagaraMeshVertexFactory.ush | 42 +- .../Private/NiagaraRibbonVertexFactory.ush | 16 +- .../Private/NiagaraSpriteVertexFactory.ush | 24 +- .../Private/VolumetricFogLightFunction.usf | 2 +- .../BlueprintCompilerCppBackendBase.cpp | 28 +- ...ntCompilerCppBackendGatherDependencies.cpp | 116 +- .../BlueprintCompilerCppBackendUtils.cpp | 15 +- ...BlueprintCompilerCppBackendValueHelper.cpp | 177 +- ...rintCompilerCppBackendGatherDependencies.h | 14 +- .../Private/BlueprintNativeCodeGenModule.cpp | 82 +- .../Private/NativeCodeGenerationTool.cpp | 10 +- .../Private/ir_vm_gen_bytecode_visitor.cpp | 11 +- .../Classes/K2Node_CallFunction.h | 4 + .../Classes/K2Node_ConstructObjectFromClass.h | 1 + .../Private/CallFunctionHandler.cpp | 13 +- .../Private/EdGraphSchema_K2.cpp | 132 +- .../Private/K2Node_CallFunction.cpp | 445 +++-- .../K2Node_ConstructObjectFromClass.cpp | 5 + .../Private/K2Node_ExecutionSequence.cpp | 4 +- .../Private/K2Node_MacroInstance.cpp | 5 + .../Private/K2Node_MakeContainer.cpp | 3 +- .../Private/K2Node_VariableGet.cpp | 10 +- .../Private/K2Node_VariableSet.cpp | 10 +- .../Public/CallFunctionHandler.h | 2 +- .../Private/BlutilityMenuExtensions.cpp | 3 + .../Private/ContentBrowserUtils.cpp | 34 +- .../Private/InputStructCustomization.cpp | 12 +- .../Private/MathStructProxyCustomizations.cpp | 6 +- .../Private/SoftObjectPathCustomization.h | 2 +- .../EditorStyle/Private/SlateEditorStyle.cpp | 7 +- .../Private/GraphEditorActions.cpp | 14 +- .../SGraphNodeK2CreateDelegate.cpp | 17 +- .../Private/KismetPins/SGraphPinObject.cpp | 16 +- .../GraphEditor/Private/SCommentBubble.cpp | 13 +- .../GraphEditor/Private/SGraphActionMenu.cpp | 46 +- .../GraphEditor/Private/SGraphPanel.cpp | 2 +- .../Private/BlueprintCompilationManager.cpp | 2 +- .../Private/BlueprintDetailsCustomization.cpp | 4 +- .../Editor/Kismet/Private/DiffUtils.cpp | 9 + .../Kismet/Private/FindInBlueprintManager.cpp | 6 +- .../Private/SCSEditorViewportClient.cpp | 14 +- .../Editor/Kismet/Private/SSCSEditor.cpp | 162 +- .../Private/UserDefinedStructureEditor.cpp | 2 +- .../KismetCompiler/Private/KismetCompiler.cpp | 35 +- .../Private/KismetCompilerMisc.cpp | 74 +- .../Public/KismetCompilerMisc.h | 13 +- .../LevelEditor/Private/LevelEditor.cpp | 3 +- .../Private/LevelEditorActions.cpp | 31 +- .../Private/LevelEditorToolBar.cpp | 162 +- .../LevelEditor/Private/LevelEditorToolBar.h | 7 - .../LevelEditor/Public/LevelEditorActions.h | 2 +- .../Private/K2Node_GetSequenceBinding.cpp | 2 +- .../Persona/Private/AnimGraphNodeDetails.cpp | 313 +-- .../Persona/Private/AnimGraphNodeDetails.h | 3 + .../Persona/Private/PersonaMeshDetails.cpp | 21 +- .../Private/PhysicsAssetEditorSharedData.cpp | 2 +- .../PhysicsAssetGraphSchema.cpp | 4 +- .../PhysicsAssetGraphSchema.h | 4 +- .../Private/DetailLayoutBuilderImpl.cpp | 11 + .../Private/DetailLayoutBuilderImpl.h | 1 + .../Private/PropertyHandleImpl.cpp | 4 +- .../Public/DetailLayoutBuilder.h | 6 + .../UMGDetailCustomizations.cpp | 3 + .../Private/ComponentTypeRegistry.cpp | 19 +- .../UnrealEd/Private/CookOnTheFlyServer.cpp | 2 +- .../Private/Factories/SkeletalMeshImport.cpp | 8 +- .../UnrealEd/Private/Fbx/FbxMainExport.cpp | 19 +- .../UnrealEd/Private/Fbx/FbxMainImport.cpp | 6 +- .../Private/Fbx/FbxSkeletalMeshExport.cpp | 19 +- .../Private/Fbx/FbxSkeletalMeshImport.cpp | 4 +- .../Private/Kismet2/BlueprintEditorUtils.cpp | 115 +- .../Private/Kismet2/ComponentEditorUtils.cpp | 5 +- .../UnrealEd/Private/Kismet2/Kismet2.cpp | 9 +- .../Private/Kismet2/KismetDebugUtilities.cpp | 2 +- .../Kismet2/KismetReinstanceUtilities.cpp | 38 +- .../Editor/UnrealEd/Private/LODUtilities.cpp | 23 +- .../Editor/UnrealEd/Private/PlayLevel.cpp | 2 +- .../UnrealEd/Private/SComponentClassCombo.cpp | 16 + .../UnrealEd/Private/ThumbnailHelpers.cpp | 2 +- .../Editor/UnrealEd/Public/CookerSettings.h | 2 +- .../Editor/UnrealEd/Public/FbxImporter.h | 2 +- .../UnrealEd/Public/SComponentClassCombo.h | 2 + .../Private/LevelCollectionModel.cpp | 5 + .../StreamingLevelCollectionModel.cpp | 15 +- .../StreamingLevelCollectionModel.h | 3 + .../DotNETUtilities/CommandLineArguments.cs | 17 +- .../Modes/GenerateProjectFilesMode.cs | 3 +- .../Platform/Android/AndroidToolChain.cs | 36 +- .../Platform/Android/UEDeployAndroid.cs | 16 +- .../Platform/TVOS/UEBuildTVOS.cs | 2 +- .../ProjectFiles/VisualStudio/VCProject.cs | 2 +- .../UnrealBuildTool/System/RulesAssembly.cs | 6 +- .../UnrealGameSync/AutomationServer.cs | 239 +++ .../UnrealGameSync/BuildStep.cs | 15 + .../UnrealGameSync/ConfigFile.cs | 2 +- .../UnrealGameSync/Controls/StatusPanel.cs | 21 +- .../Controls/SyncFilterControl.Designer.cs | 21 +- .../Controls/WorkspaceControl.Designer.cs | 207 +- .../Controls/WorkspaceControl.cs | 1027 +++++++--- .../Controls/WorkspaceControl.resx | 21 +- .../UnrealGameSync/DeploymentSettings.cs | 28 + .../DetectProjectSettingsTask.cs | 250 ++- .../UnrealGameSync/EventMonitor.cs | 137 +- .../UnrealGameSync/FindFoldersToCleanTask.cs | 8 +- .../ApplicationSettingsWindow.Designer.cs | 307 +++ .../Forms/ApplicationSettingsWindow.cs | 213 ++ .../Forms/ApplicationSettingsWindow.resx | 120 ++ .../Forms/AutomatedSyncWindow.Designer.cs | 216 ++ .../Forms/AutomatedSyncWindow.cs | 243 +++ .../Forms/AutomatedSyncWindow.resx | 120 ++ .../Forms/BuildStepWindow.Designer.cs | 8 +- .../Forms/ChangelistWindow.Designer.cs | 8 +- .../Forms/CleanWorkspaceWindow.Designer.cs | 4 +- .../Forms/ConnectWindow.Designer.cs | 16 +- .../Forms/DiagnosticsWindow.Designer.cs | 1 + .../Forms/LeaveCommentWindow.Designer.cs | 8 +- .../Forms/MainWindow.Designer.cs | 1 + .../UnrealGameSync/Forms/MainWindow.cs | 261 ++- .../Forms/NewWorkspaceWindow.Designer.cs | 4 +- .../Forms/NewWorkspaceWindow.cs | 22 +- .../Forms/OpenProjectWindow.Designer.cs | 4 +- .../UnrealGameSync/Forms/OpenProjectWindow.cs | 35 +- .../Forms/PasswordWindow.Designer.cs | 4 +- ...=> PerforceSyncSettingsWindow.Designer.cs} | 16 +- ...indow.cs => PerforceSyncSettingsWindow.cs} | 4 +- ...w.resx => PerforceSyncSettingsWindow.resx} | 0 .../Forms/ProgramsRunningWindow.Designer.cs | 62 +- .../Forms/ScheduleWindow.Designer.cs | 98 +- ...lectProjectFromWorkspaceWindow.Designer.cs | 19 +- .../Forms/SelectProjectFromWorkspaceWindow.cs | 26 +- .../Forms/SelectStreamWindow.Designer.cs | 4 +- .../Forms/SelectWorkspaceWindow.Designer.cs | 4 +- .../Forms/SyncFilter.Designer.cs | 4 +- .../UnrealGameSync/Forms/SyncFilter.cs | 20 +- .../UnrealGameSync/OutputAdapters.cs | 10 + .../UnrealGameSync/UnrealGameSync/Perforce.cs | 47 +- .../UnrealGameSync/PerforceModalTask.cs | 24 +- .../UnrealGameSync/PerforceMonitor.cs | 114 +- .../UnrealGameSync/UnrealGameSync/Program.cs | 15 +- .../ProgramApplicationContext.cs | 55 +- .../UnrealGameSync/Properties/AssemblyInfo.cs | 4 +- .../UnrealGameSync/UnrealGameSync.csproj | 37 +- .../UnrealGameSync/UpdateMonitor.cs | 28 +- .../UnrealGameSync/UserSettings.cs | 81 + .../UnrealGameSync/UnrealGameSync/Utility.cs | 135 ++ .../UnrealGameSync/Workspace.cs | 151 +- .../UnrealHeaderTool/Private/HeaderParser.cpp | 41 +- .../Runtime/AIModule/Classes/AIController.h | 15 +- .../EnvQueryInstanceBlueprintWrapper.h | 4 +- .../Perception/AIPerceptionComponent.h | 12 +- .../Runtime/AIModule/Private/AIController.cpp | 12 +- .../EnvQueryInstanceBlueprintWrapper.cpp | 4 +- .../Navigation/PathFollowingComponent.cpp | 3 +- .../Perception/AIPerceptionComponent.cpp | 20 +- .../IOSAdvertising/Private/IOSAdvertising.cpp | 4 +- .../Classes/AndroidRuntimeSettings.h | 4 + .../Public/AnimNodes/AnimNode_ApplyAdditive.h | 6 +- .../AnimNodes/AnimNode_BlendBoneByChannel.h | 4 +- .../BoneControllers/AnimNode_AnimDynamics.h | 40 +- .../Apple/MetalRHI/Private/MetalUAV.cpp | 37 +- .../MetalRHI/Private/MetalVertexBuffer.cpp | 39 +- .../Apple/MetalRHI/Public/MetalResources.h | 4 + .../Private/Android/AndroidInputInterface.cpp | 18 + .../Private/IOS/IOSInputInterface.cpp | 34 +- .../Private/Windows/XInputInterface.cpp | 16 +- .../Private/Windows/XInputInterface.h | 11 +- .../Public/Android/AndroidInputInterface.h | 4 +- .../Private/AudioMixerBlueprintLibrary.cpp | 113 ++ .../AudioMixer/Private/AudioMixerDevice.cpp | 68 + .../AudioMixer/Private/AudioMixerSubmix.cpp | 82 + .../AudioMixer/Private/DSP/AudioFFT.cpp | 55 +- .../Private/DSP/SpectrumAnalyzer.cpp | 112 +- .../Public/AudioMixerBlueprintLibrary.h | 68 +- .../AudioMixer/Public/AudioMixerDevice.h | 6 + .../AudioMixer/Public/AudioMixerSubmix.h | 23 + .../Runtime/AudioMixer/Public/DSP/AudioFFT.h | 64 +- .../Runtime/AudioMixer/Public/DSP/Dsp.h | 23 + .../AudioMixer/Public/DSP/SpectrumAnalyzer.h | 105 +- .../Core/Private/Logging/LogMacros.cpp | 6 +- .../Core/Private/Misc/ConfigCacheIni.cpp | 35 +- .../Core/Private/Misc/DefaultValueHelper.cpp | 83 +- .../Core/Public/Math/TransformCalculus2D.h | 2 + .../Runtime/Core/Public/Math/UnrealMathNeon.h | 2 +- .../Core/Public/Serialization/Archive.h | 2 +- .../Public/UObject/FrameworkObjectVersion.h | 3 + .../Private/Blueprint/BlueprintSupport.cpp | 16 +- .../CoreUObject/Private/Misc/PackageName.cpp | 4 +- .../Private/Misc/RedirectCollector.cpp | 4 +- .../CoreUObject/Private/UObject/CoreNet.cpp | 25 +- .../Private/UObject/LinkerLoad.cpp | 12 + .../Private/UObject/PropertySoftObjectPtr.cpp | 8 + .../Private/UObject/SavePackage.cpp | 10 +- .../Public/Misc/AssetRegistryInterface.h | 2 +- .../Public/Misc/RedirectCollector.h | 4 +- .../CoreUObject/Public/UObject/CoreNet.h | 2 + .../Public/UObject/SoftObjectPath.h | 12 +- .../CoreUObject/Public/UObject/UnrealType.h | 1 + .../D3D12RHI/Private/D3D12Allocation.cpp | 2 +- .../D3D12RHI/Private/D3D12Allocation.h | 2 + .../Components/SkeletalMeshComponent.h | 2 +- .../WindDirectionalSourceComponent.h | 6 +- .../Engine/Classes/Curves/IndexedCurve.h | 3 + .../Engine/Classes/Curves/IntegralCurve.h | 3 + .../Runtime/Engine/Classes/Curves/NameCurve.h | 3 + .../Runtime/Engine/Classes/Curves/RichCurve.h | 3 + .../Engine/Classes/Curves/SimpleCurve.h | 3 + .../Engine/Classes/Curves/StringCurve.h | 3 + .../Engine/Classes/EdGraph/EdGraphSchema.h | 11 +- .../Runtime/Engine/Classes/Engine/Blueprint.h | 3 - .../Engine/Classes/Engine/BlueprintCore.h | 15 +- .../Classes/Engine/ContentEncryptionConfig.h | 2 +- .../Engine/Classes/Engine/StreamableManager.h | 24 +- .../Classes/Engine/TextureRenderTarget.h | 3 + .../Classes/Engine/ViewportSplitScreen.h | 4 + .../Engine/Classes/GameFramework/Controller.h | 28 +- .../GameFramework/ForceFeedbackEffect.h | 38 +- .../Classes/GameFramework/PlayerController.h | 39 +- .../Classes/Kismet/KismetArrayLibrary.h | 40 + .../Classes/Kismet/KismetSystemLibrary.h | 21 +- .../Classes/VectorField/VectorFieldStatic.h | 21 + .../Source/Runtime/Engine/Private/Actor.cpp | 11 +- .../Engine/Private/ActorConstruction.cpp | 4 +- .../Engine/Private/Animation/AnimSequence.cpp | 10 +- .../Runtime/Engine/Private/AssetManager.cpp | 2 +- .../Runtime/Engine/Private/AudioVolume.cpp | 56 +- .../Runtime/Engine/Private/Blueprint.cpp | 83 +- .../Runtime/Engine/Private/Controller.cpp | 24 +- .../Engine/Private/DataReplication.cpp | 2 +- .../Runtime/Engine/Private/DemoNetDriver.cpp | 2 +- .../Engine/Private/EdGraph/EdGraphSchema.cpp | 20 +- .../GameFramework/ForceFeedbackEffect.cpp | 4 +- .../GameFramework/SpringArmComponent.cpp | 13 +- .../Runtime/Engine/Private/GameMode.cpp | 7 +- .../Engine/Private/GameViewportClient.cpp | 36 + .../Private/InheritableComponentHandler.cpp | 38 +- .../Engine/Private/KismetArrayLibrary.cpp | 32 + .../Engine/Private/KismetSystemLibrary.cpp | 14 +- .../Engine/Private/LevelScriptBlueprint.cpp | 7 +- .../Runtime/Engine/Private/LevelStreaming.cpp | 34 +- .../Private/Materials/MaterialExpressions.cpp | 5 +- .../Private/Particles/ParticleComponents.cpp | 14 + .../Particles/ParticleSystemRender.cpp | 8 + .../Engine/Private/Particles/WorldPSCPool.cpp | 11 +- .../Engine/Private/PlayerController.cpp | 137 +- .../Private/SimpleConstructionScript.cpp | 13 +- .../Engine/Private/StaticMeshActor.cpp | 37 +- .../Engine/Private/StreamableManager.cpp | 86 +- .../Engine/Private/TextureRenderTarget.cpp | 1 + .../Engine/Private/TextureRenderTarget2D.cpp | 5 + .../Runtime/Engine/Private/VectorField.cpp | 111 +- .../Runtime/Engine/Private/VoiceConfig.cpp | 14 +- .../Source/Runtime/Engine/Private/World.cpp | 67 +- .../Runtime/Engine/Public/AudioDevice.h | 21 + .../Source/Runtime/Engine/Public/EngineLogs.h | 1 + .../Runtime/Engine/Public/Net/VoiceConfig.h | 11 - .../Engine/Public/ParticleEmitterInstances.h | 4 + .../Runtime/Engine/Public/ParticleHelper.h | 7 + .../Experimental/PhysInterface_Chaos.h | 3 + .../EngineSettings/Classes/GameMapsSettings.h | 24 +- .../ChaosSolvers/Private/PBDRigidsSolver.cpp | 6 +- .../Classes/GameplayTagContainer.h | 11 +- .../Classes/GameplayTagsManager.h | 33 +- .../Classes/GameplayTagsSettings.h | 14 +- .../Private/GameplayTagContainer.cpp | 6 +- .../Private/GameplayTagsManager.cpp | 536 +++-- .../Private/GameplayTagsSettings.cpp | 1 + .../InputCore/Classes/InputCoreTypes.h | 2 + .../InputCore/Private/InputCoreTypes.cpp | 5 + .../JsonUtilities/Public/JsonDomBuilder.h | 76 +- .../Launch/Private/LaunchEngineLoop.cpp | 41 + .../LevelSequence/Private/LevelSequence.cpp | 7 +- .../Private/LevelSequenceActor.cpp | 8 +- .../Private/LevelSequenceBindingReference.cpp | 60 +- .../Private/LevelSequencePlayer.cpp | 43 +- .../LevelSequence/Public/LevelSequence.h | 2 + .../Public/LevelSequenceBindingReference.h | 14 +- .../Public/LevelSequencePlayer.h | 11 +- .../Private/Assets/MediaSoundComponent.cpp | 14 +- .../MediaAssets/Public/MediaSoundComponent.h | 31 +- .../Channels/MovieSceneObjectPathChannel.cpp | 33 +- .../MovieSceneEvaluationTemplateInstance.cpp | 2 +- .../Private/NavMesh/NavMeshPath.cpp | 22 +- .../Private/NavigationSystem.cpp | 7 +- .../Public/PropertyTypeCompatibility.h | 8 +- Engine/Source/Runtime/RHI/Public/DynamicRHI.h | 8 +- .../RenderCore/Public/RendererInterface.h | 33 +- .../Private/DeferredShadingRenderer.cpp | 6 +- .../Runtime/Renderer/Private/RendererModule.h | 3 + .../Renderer/Private/SceneRendering.cpp | 8 + .../Public/GlobalDistanceFieldParameters.h | 9 +- .../Slate/Private/Widgets/Input/SButton.cpp | 6 + .../Private/Widgets/Input/SMenuAnchor.cpp | 2 +- .../LayerManager/STooltipPresenter.cpp | 9 +- .../Slate/Private/Widgets/Layout/SBorder.cpp | 38 +- .../Widgets/Layout/SWidgetSwitcher.cpp | 2 +- .../Widgets/Notifications/SProgressBar.cpp | 16 +- .../Private/Widgets/Views/SHeaderRow.cpp | 4 +- .../Slate/Public/Widgets/Input/SButton.h | 1 + .../Slate/Public/Widgets/Layout/SBorder.h | 8 + .../Private/Layout/FlowDirection.cpp | 43 + .../Private/Widgets/Images/SImage.cpp | 34 +- .../SlateCore/Private/Widgets/SBoxPanel.cpp | 18 +- .../Private/Widgets/SCompoundWidget.cpp | 2 +- .../SlateCore/Private/Widgets/SOverlay.cpp | 4 +- .../SlateCore/Private/Widgets/SWidget.cpp | 12 +- .../SlateCore/Private/Widgets/SWindow.cpp | 10 +- .../SlateCore/Public/Layout/Children.h | 118 ++ .../SlateCore/Public/Layout/FlowDirection.h | 51 + .../SlateCore/Public/Layout/Geometry.h | 5 + .../SlateCore/Public/Layout/LayoutUtils.h | 85 +- .../Public/Widgets/DeclarativeSyntaxSupport.h | 4 + .../SlateCore/Public/Widgets/Images/SImage.h | 41 +- .../SlateCore/Public/Widgets/SWidget.h | 67 +- .../Private/SlateRHIRenderer.cpp | 2 +- .../Runtime/UMG/Private/Components/Border.cpp | 3 +- .../Runtime/UMG/Private/Components/Image.cpp | 4 +- .../Runtime/UMG/Private/Components/Widget.cpp | 2 + .../Runtime/UMG/Public/Components/Border.h | 6 +- .../Runtime/UMG/Public/Components/Image.h | 4 + .../Runtime/UMG/Public/Components/Widget.h | 4 + .../Runtime/VectorVM/Private/VectorVM.cpp | 76 +- .../Source/Runtime/VectorVM/Public/VectorVM.h | 4 +- 507 files changed, 15393 insertions(+), 6335 deletions(-) rename Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/{Private => Public}/ReferenceViewer/EdGraphNode_Reference.h (53%) rename Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/{Private => Public}/ReferenceViewer/EdGraph_ReferenceViewer.h (90%) rename Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/{Private => Public}/ReferenceViewer/ReferenceViewerSchema.h (90%) delete mode 100644 Engine/Plugins/Experimental/StructBox/Source/StructBox/Classes/StructBox.h delete mode 100644 Engine/Plugins/Experimental/StructBox/Source/StructBox/Classes/StructBoxLibrary.h delete mode 100644 Engine/Plugins/Experimental/StructBox/Source/StructBox/Private/StructBox.cpp delete mode 100644 Engine/Plugins/Experimental/StructBox/Source/StructBox/Private/StructBoxLibrary.cpp delete mode 100644 Engine/Plugins/Experimental/StructBox/Source/StructBox/Private/StructBoxModule.cpp delete mode 100644 Engine/Plugins/Experimental/StructBox/Source/StructBox/Public/IStructBoxModule.h delete mode 100644 Engine/Plugins/Experimental/StructBox/Source/StructBox/StructBox.Build.cs delete mode 100644 Engine/Plugins/Experimental/StructBox/StructBox.uplugin create mode 100644 Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraUserRedirectionParameterStore.cpp create mode 100644 Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraUserRedirectionParameterStore.h create mode 100644 Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeSimTargetSelector.cpp create mode 100644 Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeSimTargetSelector.h create mode 100644 Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItemLinkedInputCollection.cpp create mode 100644 Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackModuleItemLinkedInputCollection.h create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/AutomationServer.cs create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DeploymentSettings.cs create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.Designer.cs create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.cs create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.resx create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.Designer.cs create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.cs create mode 100644 Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.resx rename Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/{PerforceSettingsWindow.Designer.cs => PerforceSyncSettingsWindow.Designer.cs} (94%) rename Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/{PerforceSettingsWindow.cs => PerforceSyncSettingsWindow.cs} (94%) rename Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/{PerforceSettingsWindow.resx => PerforceSyncSettingsWindow.resx} (100%) create mode 100644 Engine/Source/Runtime/SlateCore/Private/Layout/FlowDirection.cpp create mode 100644 Engine/Source/Runtime/SlateCore/Public/Layout/FlowDirection.h diff --git a/Engine/Build/Android/Java/src/com/epicgames/ue4/GameActivity.java.template b/Engine/Build/Android/Java/src/com/epicgames/ue4/GameActivity.java.template index aa900aa45496..58a156c8b8fa 100644 --- a/Engine/Build/Android/Java/src/com/epicgames/ue4/GameActivity.java.template +++ b/Engine/Build/Android/Java/src/com/epicgames/ue4/GameActivity.java.template @@ -121,6 +121,7 @@ import android.webkit.CookieSyncManager; import android.media.AudioManager; import android.util.DisplayMetrics; +import android.view.DisplayCutout; import android.view.InputDevice; import android.view.KeyEvent; import android.view.Gravity; @@ -133,6 +134,7 @@ import android.view.ViewGroup.MarginLayoutParams; import android.view.Surface; import android.view.SurfaceHolder; import android.view.SurfaceView; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.Window; import android.widget.LinearLayout; @@ -412,6 +414,8 @@ public class GameActivity extends NativeActivity implements SurfaceHolder.Callba private String ForceExitUpdateButtonText = ""; private String ForceExitLink = ""; + private boolean SplashScreenLaunch = false; + private boolean UseDisplayCutout = false; private boolean ShouldHideUI = false; /** Whether this application is for distribution */ @@ -1948,8 +1952,10 @@ public class GameActivity extends NativeActivity implements SurfaceHolder.Callba if (_extrasBundle != null) { ShouldHideUI = _extrasBundle.getString("ShouldHideUI") != null; + UseDisplayCutout = _extrasBundle.getString("UseDisplayCutout") != null; if (_extrasBundle.getString("UseSplashScreen") != null) { + SplashScreenLaunch = true; try { // try to get the splash theme (can't use R.style.UE4SplashTheme since we don't know the package name until runtime) int SplashThemeId = getResources().getIdentifier("UE4SplashTheme", "style", getPackageName()); @@ -1990,6 +1996,14 @@ public class GameActivity extends NativeActivity implements SurfaceHolder.Callba catch (Exception e) { e.printStackTrace(); } + + if (UseDisplayCutout) + { + // will not be true if not Android Pie or later + WindowManager.LayoutParams params = getWindow().getAttributes(); + params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(params); + } } //Check for target sdk. If 23 or higher then warn that permission handling may mean features don't work if user denies them. @@ -2299,7 +2313,7 @@ public class GameActivity extends NativeActivity implements SurfaceHolder.Callba VerifyOBBOnStartUp = false; Log.debug( "Did not find bVerifyOBBOnStartUp, using default."); } - + if(bundle.containsKey("com.epicgames.ue4.GameActivity.bShouldHideUI")) { ShouldHideUI = bundle.getBoolean("com.epicgames.ue4.GameActivity.bShouldHideUI"); @@ -2309,6 +2323,20 @@ public class GameActivity extends NativeActivity implements SurfaceHolder.Callba { Log.debug( "UI hiding not found. Leaving as " + ShouldHideUI); } + + if (SplashScreenLaunch == false && android.os.Build.VERSION.SDK_INT >= 28) + { + if(bundle.containsKey("com.epicgames.ue4.GameActivity.bUseDisplayCutout")) + { + UseDisplayCutout = bundle.getBoolean("com.epicgames.ue4.GameActivity.bUseDisplayCutout"); + Log.debug( "Display cutout set to " + UseDisplayCutout); + } + else + { + Log.debug( "Display cutout not found. Leaving as " + UseDisplayCutout); + } + } + if(bundle.containsKey("com.epicgames.ue4.GameActivity.BuildConfiguration")) { BuildConfiguration = bundle.getString("com.epicgames.ue4.GameActivity.BuildConfiguration"); @@ -2797,6 +2825,18 @@ public class GameActivity extends NativeActivity implements SurfaceHolder.Callba }); } + // only true if Android Pie or later + if (UseDisplayCutout) + { + // only on Android Pie and later + WindowManager.LayoutParams params = getWindow().getAttributes(); + if (params.layoutInDisplayCutoutMode != WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) + { + params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(params); + } + } + if(HasAllFiles) { Log.debug("==============> Resuming main init"); @@ -2875,6 +2915,17 @@ public class GameActivity extends NativeActivity implements SurfaceHolder.Callba }); } + // only true if Android Pie or later + if (UseDisplayCutout) + { + WindowManager.LayoutParams params = getWindow().getAttributes(); + if (params.layoutInDisplayCutoutMode != WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) + { + params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(params); + } + } + // restore screensaver state AndroidThunkJava_KeepScreenOn(bKeepScreenOn); diff --git a/Engine/Build/Android/Java/src/com/epicgames/ue4/SplashActivity.java b/Engine/Build/Android/Java/src/com/epicgames/ue4/SplashActivity.java index 01a0e12b68c2..3a0df9b35105 100644 --- a/Engine/Build/Android/Java/src/com/epicgames/ue4/SplashActivity.java +++ b/Engine/Build/Android/Java/src/com/epicgames/ue4/SplashActivity.java @@ -9,6 +9,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.view.View; +import android.view.WindowManager; public class SplashActivity extends Activity { @@ -18,6 +19,7 @@ public class SplashActivity extends Activity super.onCreate(savedInstanceState); boolean ShouldHideUI = false; + boolean UseDisplayCutout = false; try { ApplicationInfo ai = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA); Bundle bundle = ai.metaData; @@ -26,6 +28,10 @@ public class SplashActivity extends Activity { ShouldHideUI = bundle.getBoolean("com.epicgames.ue4.GameActivity.bShouldHideUI"); } + if(bundle.containsKey("com.epicgames.ue4.GameActivity.bUseDisplayCutout")) + { + UseDisplayCutout = bundle.getBoolean("com.epicgames.ue4.GameActivity.bUseDisplayCutout"); + } } catch (NameNotFoundException e) { @@ -48,6 +54,27 @@ public class SplashActivity extends Activity } } + // for now only allow on one device manufacturer - fix up later + if (!android.os.Build.MANUFACTURER.equals("HUAWEI")) + { + UseDisplayCutout = false; + } + + if (UseDisplayCutout) + { + // only do this on Android Pie and above + if (android.os.Build.VERSION.SDK_INT >= 28) + { + WindowManager.LayoutParams params = getWindow().getAttributes(); + params.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + getWindow().setAttributes(params); + } + else + { + UseDisplayCutout = false; + } + } + Intent intent = new Intent(this, GameActivity.class); intent.putExtras(getIntent()); intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); @@ -56,6 +83,10 @@ public class SplashActivity extends Activity { intent.putExtra("ShouldHideUI", "true"); } + if (UseDisplayCutout) + { + intent.putExtra("UseDisplayCutout", "true"); + } //pass down any extras added to this Activity's intent to the GameActivity intent (GCM data, for example) Intent intentFromActivity = getIntent(); diff --git a/Engine/Build/Commit.gitdeps.xml b/Engine/Build/Commit.gitdeps.xml index 94bc87e55c48..fd663d95277c 100644 --- a/Engine/Build/Commit.gitdeps.xml +++ b/Engine/Build/Commit.gitdeps.xml @@ -6954,7 +6954,9 @@ + + @@ -7178,14 +7180,14 @@ - - + + - - - - + + + + @@ -8554,7 +8556,7 @@ - + @@ -17515,76 +17517,76 @@ - + - + - - - - + + + + - + - + - - - + + + - + - + - + - + - - - + + + - + - + - - - - + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + @@ -17623,40 +17625,40 @@ - + - + - - - + + + - - + + - + - + - + - - - + + + - + - + - - - + + + @@ -17695,76 +17697,76 @@ - + - + - - - + + + - - - + + + - + - + - + - + - - - - + + + + - + - + - + - + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - + + - + @@ -29591,7 +29593,6 @@ - @@ -29826,7 +29827,7 @@ - + @@ -29852,19 +29853,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + @@ -29880,7 +29881,7 @@ - + @@ -29895,7 +29896,7 @@ - + @@ -29912,11 +29913,15 @@ + + + + @@ -29931,10 +29936,12 @@ - - - - + + + + + + @@ -29966,14 +29973,14 @@ - - - - - - - - + + + + + + + + @@ -29983,30 +29990,50 @@ + + + + + + + + + + + + + + + + + + - - + + - - - - + + + + + + @@ -30014,21 +30041,31 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + @@ -30037,35 +30074,44 @@ - + - - - - + + + + + + + - + + - + + - - + + + + + + @@ -30088,30 +30134,35 @@ - - - - - - - - - + + + + + + + + + + + - + + + - - - - + + + + - + + @@ -30122,79 +30173,91 @@ - - - - - - + + + + + + + + + - + - - - - - + + + + + - - - + + + + + - + - + - - - - - + + + + + - - - - - - - - - - - + + + + + + + + + + + + - - + + + - + + + - + + + + @@ -30204,11 +30267,32 @@ + + + + + + + + + + + + + + + + + + + + + @@ -30217,20 +30301,15 @@ - - - - + - - + - @@ -39603,6 +39682,7 @@ + @@ -39680,7 +39760,7 @@ - + @@ -39691,7 +39771,7 @@ - + @@ -39759,7 +39839,6 @@ - @@ -39793,7 +39872,6 @@ - @@ -39811,7 +39889,6 @@ - @@ -39822,13 +39899,13 @@ + - @@ -39971,11 +40048,13 @@ + + @@ -39991,7 +40070,6 @@ - @@ -40025,6 +40103,7 @@ + @@ -40049,7 +40128,6 @@ - @@ -40114,6 +40192,7 @@ + @@ -40168,13 +40247,14 @@ + - + @@ -40188,11 +40268,13 @@ + + @@ -40254,7 +40336,6 @@ - @@ -40275,7 +40356,7 @@ - + @@ -40378,6 +40459,7 @@ + @@ -40445,7 +40527,6 @@ - @@ -40502,6 +40583,7 @@ + @@ -40572,6 +40654,7 @@ + @@ -40602,6 +40685,7 @@ + @@ -40672,6 +40756,7 @@ + @@ -40705,7 +40790,7 @@ - + @@ -40733,6 +40818,7 @@ + @@ -40765,6 +40851,7 @@ + @@ -40997,7 +41084,7 @@ - + @@ -41031,6 +41118,7 @@ + @@ -41050,10 +41138,10 @@ + - @@ -41072,6 +41160,7 @@ + @@ -41111,7 +41200,6 @@ - @@ -41165,6 +41253,7 @@ + @@ -41216,7 +41305,6 @@ - @@ -41289,7 +41377,7 @@ - + @@ -41354,7 +41442,6 @@ - @@ -41367,6 +41454,7 @@ + @@ -41380,7 +41468,7 @@ - + @@ -41424,6 +41512,7 @@ + @@ -41563,7 +41652,7 @@ - + @@ -41576,7 +41665,7 @@ - + @@ -41679,6 +41768,7 @@ + @@ -41701,6 +41791,7 @@ + @@ -41779,7 +41870,7 @@ - + @@ -41805,7 +41896,7 @@ - + @@ -41877,6 +41968,7 @@ + @@ -41934,7 +42026,6 @@ - @@ -41966,7 +42057,7 @@ - + @@ -41975,6 +42066,7 @@ + @@ -42183,7 +42275,6 @@ - @@ -42264,7 +42355,6 @@ - @@ -42297,6 +42387,7 @@ + @@ -42337,7 +42428,6 @@ - @@ -42485,12 +42575,12 @@ - + - + @@ -42512,7 +42602,7 @@ - + @@ -42547,10 +42637,8 @@ - - - + @@ -42606,13 +42694,13 @@ - + - + @@ -42627,6 +42715,7 @@ + @@ -42682,6 +42771,7 @@ + @@ -42819,7 +42909,7 @@ - + @@ -42837,6 +42927,7 @@ + @@ -42874,6 +42965,7 @@ + @@ -42944,6 +43036,7 @@ + @@ -42972,6 +43065,7 @@ + @@ -43045,6 +43139,7 @@ + @@ -43055,7 +43150,7 @@ - + @@ -43069,7 +43164,6 @@ - @@ -43077,6 +43171,7 @@ + @@ -43165,7 +43260,7 @@ - + @@ -43196,7 +43291,7 @@ - + @@ -43205,7 +43300,6 @@ - @@ -43225,6 +43319,7 @@ + @@ -43368,6 +43463,8 @@ + + @@ -43441,6 +43538,7 @@ + @@ -43502,6 +43600,7 @@ + @@ -43550,7 +43649,7 @@ - + @@ -43566,7 +43665,7 @@ - + @@ -43597,7 +43696,7 @@ - + @@ -43665,13 +43764,13 @@ - + - + @@ -43699,13 +43798,11 @@ - - @@ -43736,10 +43833,12 @@ + + @@ -43760,7 +43859,6 @@ - @@ -43937,17 +44035,18 @@ - + + @@ -43977,6 +44076,7 @@ + @@ -44058,6 +44158,7 @@ + @@ -44102,7 +44203,7 @@ - + @@ -44134,6 +44235,7 @@ + @@ -44152,7 +44254,6 @@ - @@ -44161,6 +44262,7 @@ + @@ -44239,11 +44341,11 @@ + - @@ -44312,6 +44414,7 @@ + @@ -44358,6 +44461,7 @@ + @@ -44381,7 +44485,6 @@ - @@ -44410,7 +44513,6 @@ - @@ -44435,14 +44537,15 @@ - + - + + @@ -44478,7 +44581,7 @@ - + @@ -44529,7 +44632,7 @@ - + @@ -44581,7 +44684,6 @@ - @@ -44598,13 +44700,11 @@ - - @@ -44612,7 +44712,6 @@ - @@ -44641,7 +44740,7 @@ - + @@ -44653,6 +44752,7 @@ + @@ -44733,7 +44833,7 @@ - + @@ -44794,6 +44894,7 @@ + @@ -44998,7 +45099,8 @@ - + + @@ -45019,6 +45121,7 @@ + @@ -45058,6 +45161,7 @@ + @@ -45067,7 +45171,7 @@ - + @@ -45084,6 +45188,7 @@ + @@ -45134,12 +45239,14 @@ + + @@ -45152,7 +45259,6 @@ - @@ -45182,9 +45288,11 @@ + + @@ -45207,7 +45315,7 @@ - + @@ -45215,7 +45323,7 @@ - + @@ -45273,7 +45381,7 @@ - + @@ -45331,7 +45439,7 @@ - + @@ -45421,7 +45529,6 @@ - @@ -45443,6 +45550,7 @@ + @@ -45451,8 +45559,8 @@ - + @@ -45500,7 +45608,7 @@ - + @@ -45514,10 +45622,9 @@ - - + @@ -45625,7 +45732,7 @@ - + @@ -45636,7 +45743,6 @@ - @@ -45674,7 +45780,6 @@ - @@ -45711,7 +45816,6 @@ - @@ -45768,7 +45872,7 @@ - + @@ -45939,7 +46043,6 @@ - @@ -45990,6 +46093,7 @@ + @@ -46038,7 +46142,6 @@ - @@ -46048,13 +46151,12 @@ - + - @@ -46070,7 +46172,7 @@ - + @@ -46087,7 +46189,6 @@ - @@ -46105,7 +46206,7 @@ - + @@ -46190,6 +46291,7 @@ + @@ -46198,7 +46300,7 @@ - + @@ -46208,7 +46310,6 @@ - @@ -46279,6 +46380,7 @@ + @@ -46333,6 +46435,7 @@ + @@ -46340,7 +46443,8 @@ - + + @@ -46363,7 +46467,7 @@ - + @@ -46391,6 +46495,7 @@ + @@ -46422,10 +46527,11 @@ - + + @@ -46530,7 +46636,7 @@ - + @@ -46538,7 +46644,7 @@ - + @@ -46568,7 +46674,7 @@ - + @@ -46613,7 +46719,6 @@ - @@ -46622,7 +46727,7 @@ - + @@ -46650,7 +46755,7 @@ - + @@ -46686,7 +46791,6 @@ - @@ -46752,7 +46856,7 @@ - + @@ -46770,7 +46874,7 @@ - + @@ -46792,9 +46896,11 @@ + + @@ -46827,7 +46933,7 @@ - + @@ -46859,6 +46965,7 @@ + @@ -46968,7 +47075,7 @@ - + @@ -46978,6 +47085,7 @@ + @@ -47021,6 +47129,7 @@ + @@ -47028,6 +47137,7 @@ + @@ -47043,8 +47153,7 @@ - - + @@ -47064,7 +47173,6 @@ - @@ -47102,7 +47210,7 @@ - + @@ -47169,11 +47277,10 @@ - - + @@ -47187,6 +47294,7 @@ + @@ -47229,6 +47337,7 @@ + @@ -47244,12 +47353,14 @@ + + @@ -47263,6 +47374,7 @@ + @@ -47272,7 +47384,7 @@ - + @@ -47448,7 +47560,7 @@ - + @@ -47486,7 +47598,6 @@ - @@ -47545,7 +47656,7 @@ - + @@ -47573,7 +47684,7 @@ - + @@ -47589,6 +47700,7 @@ + @@ -47744,6 +47856,7 @@ + @@ -47859,7 +47972,7 @@ - + @@ -48037,12 +48150,11 @@ - - + @@ -48064,7 +48176,6 @@ - @@ -48108,7 +48219,6 @@ - @@ -48118,7 +48228,6 @@ - @@ -48132,14 +48241,13 @@ - + - + - @@ -48162,13 +48270,12 @@ - - + @@ -48209,11 +48316,12 @@ - + + @@ -48222,6 +48330,7 @@ + @@ -48235,6 +48344,7 @@ + @@ -48259,6 +48369,7 @@ + @@ -48331,6 +48442,7 @@ + @@ -48358,6 +48470,7 @@ + @@ -48568,7 +48681,7 @@ - + @@ -48579,11 +48692,10 @@ - + - @@ -48637,7 +48749,6 @@ - @@ -48661,7 +48772,7 @@ - + @@ -48693,13 +48804,13 @@ + - @@ -48714,7 +48825,6 @@ - @@ -48742,6 +48852,7 @@ + @@ -48778,7 +48889,6 @@ - @@ -48834,7 +48944,7 @@ - + @@ -48859,7 +48969,6 @@ - @@ -48890,10 +48999,10 @@ + - + - @@ -48921,7 +49030,6 @@ - @@ -48974,7 +49082,6 @@ - @@ -49117,7 +49224,7 @@ - + @@ -49164,7 +49271,6 @@ - @@ -49181,7 +49287,6 @@ - @@ -49200,7 +49305,6 @@ - @@ -49236,6 +49340,7 @@ + @@ -49246,7 +49351,6 @@ - @@ -49302,11 +49406,13 @@ + + @@ -49325,6 +49431,7 @@ + @@ -49395,7 +49502,7 @@ - + @@ -49416,7 +49523,7 @@ - + @@ -49513,6 +49620,7 @@ + @@ -49538,7 +49646,6 @@ - @@ -49554,7 +49661,9 @@ + + @@ -49595,9 +49704,11 @@ + + @@ -49605,7 +49716,7 @@ - + @@ -49613,7 +49724,6 @@ - @@ -49701,7 +49811,6 @@ - @@ -49712,7 +49821,7 @@ - + @@ -49802,10 +49911,12 @@ + + @@ -49859,7 +49970,7 @@ - + @@ -49888,7 +49999,6 @@ - @@ -49988,10 +50098,10 @@ + - @@ -50126,7 +50236,7 @@ - + @@ -50146,7 +50256,7 @@ - + @@ -50156,7 +50266,7 @@ - + @@ -50189,6 +50299,7 @@ + @@ -50227,7 +50338,6 @@ - @@ -50329,6 +50439,7 @@ + @@ -50360,6 +50471,7 @@ + @@ -50477,6 +50589,7 @@ + @@ -50565,10 +50678,8 @@ - - @@ -50588,7 +50699,6 @@ - @@ -50617,7 +50727,7 @@ - + @@ -50645,6 +50755,7 @@ + @@ -50675,7 +50786,6 @@ - @@ -50685,7 +50795,7 @@ - + @@ -50767,13 +50877,13 @@ + - @@ -50856,6 +50966,7 @@ + @@ -50869,7 +50980,7 @@ - + @@ -50915,6 +51026,7 @@ + @@ -50939,7 +51051,7 @@ - + @@ -50971,7 +51083,6 @@ - @@ -51053,7 +51164,6 @@ - @@ -51064,7 +51174,6 @@ - @@ -51081,6 +51190,7 @@ + @@ -51089,7 +51199,7 @@ - + @@ -51112,8 +51222,10 @@ + + @@ -51144,7 +51256,6 @@ - @@ -51181,7 +51292,7 @@ - + @@ -51204,7 +51315,6 @@ - @@ -51229,7 +51339,6 @@ - @@ -51272,7 +51381,6 @@ - @@ -51311,7 +51419,6 @@ - @@ -51323,6 +51430,7 @@ + @@ -51438,7 +51546,6 @@ - @@ -51645,11 +51752,11 @@ - + @@ -51704,7 +51811,7 @@ - + @@ -51752,6 +51859,7 @@ + @@ -51772,7 +51880,6 @@ - @@ -51812,6 +51919,8 @@ + + @@ -51838,6 +51947,7 @@ + @@ -51847,7 +51957,7 @@ - + @@ -51978,6 +52088,7 @@ + @@ -51989,7 +52100,6 @@ - @@ -51999,7 +52109,7 @@ - + @@ -52028,6 +52138,7 @@ + @@ -52088,6 +52199,7 @@ + @@ -52137,7 +52249,7 @@ - + @@ -52180,7 +52292,7 @@ - + @@ -52202,7 +52314,6 @@ - @@ -52241,7 +52352,7 @@ - + @@ -52282,6 +52393,7 @@ + @@ -52332,10 +52444,8 @@ - - @@ -52358,7 +52468,7 @@ - + @@ -52384,7 +52494,6 @@ - @@ -52460,7 +52569,7 @@ - + @@ -52476,7 +52585,7 @@ - + @@ -52496,6 +52605,7 @@ + @@ -52515,7 +52625,6 @@ - @@ -52542,7 +52651,6 @@ - @@ -52594,7 +52702,6 @@ - @@ -52623,7 +52730,7 @@ - + @@ -52635,7 +52742,7 @@ - + @@ -52696,7 +52803,7 @@ - + @@ -52716,6 +52823,7 @@ + @@ -52727,6 +52835,7 @@ + @@ -52760,7 +52869,6 @@ - @@ -52830,7 +52938,7 @@ - + @@ -52896,6 +53004,7 @@ + @@ -52929,6 +53038,7 @@ + @@ -52942,7 +53052,7 @@ - + @@ -52952,7 +53062,6 @@ - @@ -52992,7 +53101,6 @@ - @@ -53042,6 +53150,7 @@ + @@ -53073,6 +53182,7 @@ + @@ -53104,6 +53214,7 @@ + @@ -53200,14 +53311,13 @@ - + - @@ -53230,7 +53340,6 @@ - @@ -53273,7 +53382,6 @@ - @@ -53316,7 +53424,6 @@ - @@ -53333,6 +53440,7 @@ + @@ -53355,6 +53463,7 @@ + @@ -53387,6 +53496,7 @@ + @@ -53499,6 +53609,7 @@ + @@ -53622,6 +53733,7 @@ + @@ -53713,6 +53825,7 @@ + @@ -53747,7 +53860,7 @@ - + @@ -53821,7 +53934,6 @@ - @@ -53840,7 +53952,6 @@ - @@ -53892,7 +54003,7 @@ - + @@ -54026,7 +54137,6 @@ - @@ -54037,6 +54147,7 @@ + @@ -54060,6 +54171,7 @@ + @@ -54091,7 +54203,7 @@ - + @@ -54102,6 +54214,7 @@ + @@ -54114,7 +54227,7 @@ - + @@ -54151,6 +54264,7 @@ + @@ -54182,7 +54296,6 @@ - @@ -54260,6 +54373,7 @@ + @@ -54295,6 +54409,7 @@ + @@ -54304,7 +54419,6 @@ - @@ -54313,11 +54427,12 @@ - + + @@ -54409,8 +54524,10 @@ + + @@ -54457,6 +54574,7 @@ + @@ -54533,7 +54651,6 @@ - @@ -54610,7 +54727,6 @@ - @@ -54638,11 +54754,11 @@ + - @@ -54677,13 +54793,14 @@ + - + @@ -54709,10 +54826,9 @@ - + - @@ -54731,10 +54847,8 @@ - - @@ -54764,6 +54878,7 @@ + @@ -54785,6 +54900,7 @@ + @@ -54844,7 +54960,6 @@ - @@ -54857,6 +54972,7 @@ + @@ -54963,11 +55079,12 @@ - + - + + @@ -55034,6 +55151,7 @@ + @@ -55092,6 +55210,7 @@ + @@ -55109,10 +55228,10 @@ + - @@ -55122,6 +55241,7 @@ + @@ -55148,6 +55268,7 @@ + @@ -55159,26 +55280,26 @@ - + - + - + - + @@ -55204,6 +55325,7 @@ + @@ -55218,6 +55340,7 @@ + @@ -55255,7 +55378,6 @@ - @@ -55283,7 +55405,7 @@ - + @@ -55333,8 +55455,10 @@ + + @@ -55407,7 +55531,7 @@ - + @@ -55479,6 +55603,7 @@ + @@ -55512,6 +55637,7 @@ + @@ -55529,6 +55655,7 @@ + @@ -55567,8 +55694,8 @@ - - + + @@ -55586,12 +55713,11 @@ - + - @@ -55626,7 +55752,6 @@ - @@ -55643,22 +55768,22 @@ + - - + @@ -55668,7 +55793,6 @@ - @@ -55696,7 +55820,7 @@ - + @@ -55717,6 +55841,7 @@ + @@ -55732,7 +55857,7 @@ - + @@ -55744,6 +55869,7 @@ + @@ -55786,7 +55912,6 @@ - @@ -55821,6 +55946,7 @@ + @@ -55868,6 +55994,7 @@ + @@ -55892,7 +56019,7 @@ - + @@ -55921,6 +56048,7 @@ + @@ -55929,6 +56057,7 @@ + @@ -56007,7 +56136,6 @@ - @@ -56034,6 +56162,7 @@ + @@ -56063,6 +56192,7 @@ + @@ -56089,7 +56219,7 @@ - + @@ -56108,6 +56238,7 @@ + @@ -56116,7 +56247,7 @@ - + @@ -56144,6 +56275,7 @@ + @@ -56163,7 +56295,7 @@ - + @@ -56188,7 +56320,7 @@ - + @@ -56262,6 +56394,7 @@ + @@ -56289,6 +56422,7 @@ + @@ -56326,7 +56460,6 @@ - @@ -56396,7 +56529,7 @@ - + @@ -56426,7 +56559,6 @@ - @@ -56467,6 +56599,7 @@ + @@ -56535,13 +56668,13 @@ - + @@ -56660,7 +56793,7 @@ - + @@ -56675,7 +56808,7 @@ - + @@ -56704,7 +56837,6 @@ - @@ -56798,6 +56930,7 @@ + @@ -56816,6 +56949,7 @@ + @@ -56904,13 +57038,13 @@ - + @@ -56936,12 +57070,14 @@ - + + + @@ -57005,7 +57141,7 @@ - + @@ -57048,7 +57184,6 @@ - @@ -57090,8 +57225,9 @@ + - + @@ -57114,6 +57250,7 @@ + @@ -57259,6 +57396,7 @@ + @@ -57276,7 +57414,6 @@ - @@ -57402,7 +57539,7 @@ - + @@ -57531,7 +57668,7 @@ - + @@ -57673,10 +57810,9 @@ - - + @@ -57692,7 +57828,6 @@ - @@ -57725,7 +57860,6 @@ - @@ -57880,7 +58014,7 @@ - + @@ -57903,6 +58037,7 @@ + @@ -57961,7 +58096,7 @@ - + @@ -58006,7 +58141,7 @@ - + @@ -58033,7 +58168,6 @@ - @@ -58054,7 +58188,7 @@ - + @@ -58106,6 +58240,7 @@ + @@ -58162,6 +58297,7 @@ + @@ -58186,13 +58322,13 @@ - + - + @@ -58213,10 +58349,9 @@ - + - @@ -58236,9 +58371,8 @@ - + - @@ -58324,7 +58458,6 @@ - @@ -58353,7 +58486,7 @@ - + @@ -58382,7 +58515,6 @@ - @@ -58535,7 +58667,7 @@ - + @@ -58673,7 +58805,6 @@ - @@ -58713,7 +58844,7 @@ - + @@ -58738,7 +58869,7 @@ - + @@ -58879,7 +59010,7 @@ - + @@ -58922,7 +59053,7 @@ - + @@ -58976,6 +59107,7 @@ + @@ -59050,14 +59182,17 @@ + + + @@ -59093,7 +59228,7 @@ - + @@ -59195,7 +59330,7 @@ - + @@ -59258,8 +59393,7 @@ - - + @@ -59289,7 +59423,7 @@ - + @@ -59398,7 +59532,6 @@ - @@ -59429,12 +59562,12 @@ + - @@ -59467,7 +59600,6 @@ - @@ -59559,7 +59691,6 @@ - @@ -59568,8 +59699,7 @@ - - + @@ -59679,7 +59809,6 @@ - @@ -59716,7 +59845,7 @@ - + @@ -59734,7 +59863,6 @@ - @@ -59889,7 +60017,6 @@ - @@ -59897,7 +60024,6 @@ - @@ -60027,7 +60153,6 @@ - @@ -60073,12 +60198,11 @@ - + - @@ -60092,6 +60216,7 @@ + @@ -60099,7 +60224,6 @@ - @@ -60119,7 +60243,6 @@ - @@ -60160,7 +60283,7 @@ - + @@ -60176,6 +60299,7 @@ + @@ -60190,6 +60314,7 @@ + @@ -60235,7 +60360,7 @@ - + @@ -60271,6 +60396,7 @@ + @@ -60294,7 +60420,6 @@ - @@ -60323,7 +60448,6 @@ - @@ -60360,7 +60484,7 @@ - + @@ -60473,9 +60597,9 @@ - + @@ -60502,10 +60626,10 @@ + - @@ -60570,7 +60694,6 @@ - @@ -60601,6 +60724,7 @@ + @@ -60686,6 +60810,7 @@ + @@ -60714,7 +60839,6 @@ - @@ -60753,7 +60877,7 @@ - + @@ -60852,7 +60976,6 @@ - @@ -60916,7 +61039,6 @@ - @@ -60937,7 +61059,7 @@ - + @@ -60988,7 +61110,7 @@ - + @@ -61010,6 +61132,7 @@ + @@ -61018,7 +61141,6 @@ - @@ -61079,6 +61201,7 @@ + @@ -61087,6 +61210,7 @@ + @@ -61102,7 +61226,7 @@ - + @@ -61163,7 +61287,6 @@ - @@ -61181,9 +61304,9 @@ - + @@ -61228,6 +61351,7 @@ + @@ -61254,7 +61378,7 @@ - + @@ -61297,7 +61421,6 @@ - @@ -61312,7 +61435,7 @@ - + @@ -61342,6 +61465,7 @@ + @@ -61389,9 +61513,9 @@ - + - + @@ -61456,6 +61580,7 @@ + @@ -61570,7 +61695,6 @@ - @@ -61579,6 +61703,7 @@ + @@ -61604,7 +61729,7 @@ - + @@ -61614,8 +61739,9 @@ - + + @@ -61802,7 +61928,6 @@ - @@ -61950,7 +62075,7 @@ - + @@ -61972,7 +62097,7 @@ - + @@ -61983,6 +62108,7 @@ + @@ -62085,7 +62211,7 @@ - + @@ -62115,6 +62241,7 @@ + @@ -62152,7 +62279,7 @@ - + @@ -62189,7 +62316,6 @@ - @@ -62225,6 +62351,7 @@ + @@ -62248,10 +62375,10 @@ - + - + @@ -62272,7 +62399,6 @@ - @@ -62293,10 +62419,11 @@ - + + @@ -62306,7 +62433,7 @@ - + @@ -62333,13 +62460,13 @@ + - + - @@ -62406,7 +62533,7 @@ - + @@ -62429,6 +62556,7 @@ + @@ -62458,6 +62586,7 @@ + @@ -62470,7 +62599,7 @@ - + @@ -62543,7 +62672,7 @@ - + @@ -62618,7 +62747,6 @@ - @@ -62672,7 +62800,7 @@ - + @@ -62752,7 +62880,7 @@ - + @@ -62810,7 +62938,7 @@ - + @@ -62867,6 +62995,7 @@ + @@ -62888,7 +63017,7 @@ - + @@ -62921,7 +63050,7 @@ - + @@ -63038,6 +63167,7 @@ + @@ -63046,6 +63176,7 @@ + @@ -63076,7 +63207,6 @@ - @@ -63115,9 +63245,10 @@ - + + @@ -63139,6 +63270,7 @@ + @@ -63244,7 +63376,6 @@ - @@ -63269,7 +63400,7 @@ - + @@ -63278,6 +63409,7 @@ + @@ -63290,16 +63422,15 @@ - + - @@ -63321,7 +63452,7 @@ - + @@ -63359,9 +63490,10 @@ + - + @@ -63379,6 +63511,7 @@ + @@ -63412,6 +63545,7 @@ + @@ -63431,7 +63565,6 @@ - @@ -63525,6 +63658,7 @@ + @@ -63550,7 +63684,7 @@ - + @@ -63645,7 +63779,7 @@ - + @@ -63657,6 +63791,7 @@ + @@ -63767,7 +63902,7 @@ - + @@ -63841,7 +63976,7 @@ - + @@ -63867,7 +64002,6 @@ - @@ -63893,7 +64027,6 @@ - @@ -63999,6 +64132,7 @@ + @@ -64016,7 +64150,7 @@ - + @@ -64032,7 +64166,7 @@ - + @@ -64098,7 +64232,6 @@ - @@ -64118,7 +64251,7 @@ - + @@ -64150,7 +64283,7 @@ - + @@ -64185,7 +64318,7 @@ - + @@ -64210,7 +64343,6 @@ - @@ -64265,6 +64397,7 @@ + @@ -64282,7 +64415,7 @@ - + @@ -64312,7 +64445,7 @@ - + @@ -64344,6 +64477,7 @@ + @@ -64358,6 +64492,7 @@ + @@ -64380,7 +64515,7 @@ - + @@ -64407,6 +64542,7 @@ + @@ -64438,7 +64574,6 @@ - @@ -64466,7 +64601,6 @@ - @@ -64494,6 +64628,7 @@ + @@ -64506,6 +64641,7 @@ + @@ -64574,6 +64710,7 @@ + @@ -64639,7 +64776,6 @@ - @@ -64704,6 +64840,7 @@ + @@ -64776,7 +64913,7 @@ - + @@ -64787,7 +64924,7 @@ - + @@ -64798,7 +64935,7 @@ - + @@ -64815,7 +64952,7 @@ - + @@ -64851,6 +64988,7 @@ + @@ -64903,13 +65041,13 @@ + - @@ -64930,7 +65068,7 @@ - + @@ -65032,7 +65170,6 @@ - @@ -65057,7 +65194,7 @@ - + @@ -65125,7 +65262,7 @@ - + @@ -65149,6 +65286,7 @@ + @@ -65157,7 +65295,7 @@ - + @@ -65245,7 +65383,7 @@ - + @@ -65271,6 +65409,7 @@ + @@ -65287,7 +65426,7 @@ - + @@ -65388,6 +65527,7 @@ + @@ -65395,6 +65535,7 @@ + @@ -65423,7 +65564,7 @@ - + @@ -65471,6 +65612,7 @@ + @@ -65555,7 +65697,7 @@ - + @@ -65576,6 +65718,7 @@ + @@ -65584,7 +65727,7 @@ - + @@ -65654,7 +65797,6 @@ - @@ -65676,6 +65818,7 @@ + @@ -65764,6 +65907,7 @@ + @@ -65835,6 +65979,7 @@ + @@ -65885,7 +66030,6 @@ - @@ -65919,7 +66063,6 @@ - @@ -65948,7 +66091,6 @@ - @@ -66045,8 +66187,7 @@ - - + @@ -66063,13 +66204,12 @@ - + - @@ -66113,7 +66253,6 @@ - @@ -66190,6 +66329,7 @@ + @@ -66328,6 +66468,7 @@ + @@ -66335,6 +66476,7 @@ + @@ -66395,6 +66537,7 @@ + @@ -66409,6 +66552,7 @@ + @@ -66423,7 +66567,7 @@ - + @@ -66460,7 +66604,7 @@ - + @@ -66474,8 +66618,9 @@ + - + @@ -66503,6 +66648,7 @@ + @@ -66520,6 +66666,8 @@ + + @@ -66580,6 +66728,7 @@ + @@ -66642,6 +66791,7 @@ + @@ -66694,7 +66844,7 @@ - + @@ -66710,7 +66860,8 @@ - + + @@ -66724,7 +66875,7 @@ - + @@ -66785,6 +66936,7 @@ + @@ -66843,7 +66995,7 @@ - + @@ -66865,6 +67017,7 @@ + @@ -66917,8 +67070,9 @@ - + + @@ -66972,7 +67126,7 @@ - + @@ -67013,6 +67167,7 @@ + @@ -67027,6 +67182,7 @@ + @@ -67049,7 +67205,6 @@ - @@ -67098,6 +67253,7 @@ + @@ -67106,7 +67262,7 @@ - + @@ -67230,6 +67386,7 @@ + @@ -67251,7 +67408,7 @@ - + @@ -67267,14 +67424,12 @@ - - @@ -67383,7 +67538,7 @@ - + @@ -67435,6 +67590,7 @@ + @@ -67461,8 +67617,10 @@ + + @@ -67478,7 +67636,7 @@ - + @@ -67487,10 +67645,9 @@ - + - @@ -67501,6 +67658,7 @@ + @@ -67564,6 +67722,7 @@ + @@ -67619,27 +67778,28 @@ - + + - + - + @@ -67658,7 +67818,6 @@ - @@ -67669,6 +67828,7 @@ + @@ -67685,7 +67845,7 @@ - + @@ -67703,6 +67863,7 @@ + @@ -67721,6 +67882,7 @@ + @@ -67729,6 +67891,7 @@ + @@ -67746,10 +67909,7 @@ - - - @@ -67757,10 +67917,11 @@ - + + @@ -67782,17 +67943,14 @@ - - - @@ -67804,6 +67962,7 @@ + @@ -67816,6 +67975,7 @@ + @@ -67827,14 +67987,17 @@ + + - + + @@ -67851,6 +68014,7 @@ + @@ -67871,6 +68035,7 @@ + @@ -67880,10 +68045,10 @@ - + @@ -67898,12 +68063,13 @@ + - + @@ -67926,6 +68092,7 @@ + @@ -67950,6 +68117,7 @@ + @@ -67981,20 +68149,19 @@ - + - + - @@ -68006,6 +68173,7 @@ + @@ -68021,6 +68189,7 @@ + @@ -68034,7 +68203,6 @@ - @@ -68067,6 +68235,7 @@ + @@ -68145,7 +68314,6 @@ - @@ -68172,16 +68340,17 @@ + - + @@ -68192,8 +68361,8 @@ - + @@ -68202,7 +68371,6 @@ - @@ -68217,6 +68385,7 @@ + @@ -68244,9 +68413,7 @@ - - @@ -68254,6 +68421,7 @@ + @@ -68262,12 +68430,10 @@ - - @@ -68294,13 +68460,13 @@ - + @@ -68347,7 +68513,6 @@ - @@ -68356,27 +68521,28 @@ + + - - + @@ -68389,6 +68555,7 @@ + @@ -68422,6 +68589,7 @@ + @@ -68429,6 +68597,7 @@ + @@ -68455,7 +68624,6 @@ - @@ -68471,7 +68639,6 @@ - @@ -68494,12 +68661,13 @@ - + + @@ -68517,13 +68685,14 @@ + - + @@ -68537,20 +68706,17 @@ - - - + - @@ -68589,12 +68755,13 @@ - + - + + @@ -68608,13 +68775,16 @@ + + + @@ -68639,7 +68809,6 @@ - @@ -68652,6 +68821,7 @@ + @@ -68661,7 +68831,6 @@ - @@ -68675,6 +68844,7 @@ + @@ -68685,6 +68855,7 @@ + @@ -68692,12 +68863,14 @@ + + @@ -68716,14 +68889,16 @@ - + + + @@ -68749,6 +68924,7 @@ + @@ -68760,6 +68936,7 @@ + @@ -68768,9 +68945,9 @@ - + @@ -68785,7 +68962,6 @@ - @@ -68807,11 +68983,10 @@ - + - @@ -68837,25 +69012,26 @@ + - - + + @@ -68864,10 +69040,10 @@ + - @@ -68887,10 +69063,10 @@ - + @@ -68907,8 +69083,8 @@ - + @@ -68972,8 +69148,10 @@ + + @@ -69000,6 +69178,7 @@ + @@ -69016,12 +69195,13 @@ + - + @@ -69038,6 +69218,7 @@ + @@ -69052,6 +69233,7 @@ + @@ -69061,12 +69243,10 @@ - - @@ -69079,6 +69259,7 @@ + @@ -69091,7 +69272,6 @@ - @@ -69106,6 +69286,7 @@ + @@ -69113,7 +69294,6 @@ - @@ -69142,8 +69322,8 @@ + - @@ -69177,11 +69357,13 @@ + + @@ -69200,9 +69382,9 @@ + - @@ -69212,6 +69394,7 @@ + @@ -69225,6 +69408,7 @@ + @@ -69249,11 +69433,9 @@ - - @@ -69268,6 +69450,7 @@ + @@ -69276,6 +69459,7 @@ + @@ -69308,11 +69492,11 @@ + - @@ -69325,31 +69509,30 @@ - - - - + - + + + @@ -69369,13 +69552,16 @@ + + + @@ -69391,6 +69577,7 @@ + @@ -69398,18 +69585,17 @@ + - - - + @@ -69430,6 +69616,7 @@ + @@ -69444,7 +69631,6 @@ - @@ -69461,11 +69647,10 @@ - - + @@ -69481,15 +69666,14 @@ + - - @@ -69498,7 +69682,6 @@ - @@ -69511,13 +69694,13 @@ - + @@ -69532,7 +69715,6 @@ - @@ -69566,7 +69748,6 @@ - @@ -69598,6 +69779,7 @@ + @@ -69627,14 +69809,12 @@ - - @@ -69664,7 +69844,6 @@ - @@ -69676,6 +69855,7 @@ + @@ -69694,7 +69874,6 @@ - @@ -69726,11 +69905,11 @@ + - @@ -69748,12 +69927,12 @@ - + @@ -69784,22 +69963,24 @@ + + + - @@ -69807,7 +69988,6 @@ - @@ -69822,10 +70002,12 @@ + + @@ -69844,6 +70026,7 @@ + @@ -69871,7 +70054,6 @@ - @@ -69890,6 +70072,7 @@ + @@ -69907,9 +70090,8 @@ - - + @@ -69918,14 +70100,12 @@ - - @@ -69948,6 +70128,7 @@ + @@ -69965,13 +70146,13 @@ + - @@ -69980,6 +70161,8 @@ + + @@ -70024,6 +70207,7 @@ + @@ -70036,21 +70220,22 @@ + + + + - - - @@ -70083,7 +70268,9 @@ + + @@ -70101,19 +70288,19 @@ - - + + @@ -70149,7 +70336,9 @@ + + @@ -70188,6 +70377,7 @@ + @@ -70198,6 +70388,7 @@ + @@ -70227,10 +70418,12 @@ + + @@ -70239,10 +70432,10 @@ - + @@ -70252,7 +70445,7 @@ - + @@ -70262,7 +70455,7 @@ - + @@ -70297,6 +70490,7 @@ + @@ -70304,9 +70498,7 @@ - - @@ -70340,20 +70532,20 @@ + - - + + + - - - + @@ -70374,6 +70566,7 @@ + @@ -70392,19 +70585,17 @@ + - - - @@ -70428,14 +70619,15 @@ - + + @@ -70451,13 +70643,11 @@ - - - + @@ -70465,11 +70655,13 @@ + + @@ -70482,17 +70674,17 @@ + - - + @@ -70515,6 +70707,7 @@ + diff --git a/Engine/Config/BaseEngine.ini b/Engine/Config/BaseEngine.ini index d5279dbf026a..3128458e893f 100644 --- a/Engine/Config/BaseEngine.ini +++ b/Engine/Config/BaseEngine.ini @@ -6,7 +6,6 @@ EditorStartupMap=/Engine/Maps/Templates/Template_Default GameDefaultMap=/Engine/Maps/Entry ServerDefaultMap=/Engine/Maps/Entry GlobalDefaultGameMode="/Script/Engine.GameModeBase" -+SubLevelClassesToStrip=(ClassToStrip=/Script/Engine.WorldSettings,StripMode=ExactClass) ; Example for map prefix and game mode name setting ; +GameModeMapPrefixes=(Name="DM-",GameMode="/Script/GamePackage.DMGameMode") ; +GameModeClassAliases=(Name="DM",GameMode="/Script/GamePackage.DMGameMode") @@ -639,6 +638,10 @@ bUseStreamingPause=false +ClassRedirects=(OldName="/Script/GeometryCollectionCore.GeometryCollection",NewName="/Script/GeometryCollectionEngine.GeometryCollection") +ClassRedirects=(OldName="/Script/GeometryCollectionCore.GeometryCollectionCache",NewName="/Script/GeometryCollectionEngine.GeometryCollectionCache") ++FunctionRedirects=(OldName="Controller.OnPossess",NewName="Controller.ReceivePossess") ++FunctionRedirects=(OldName="Controller.OnUnPossess",NewName="Controller.ReceiveUnPossess") + ++FunctionRedirects=(OldName="PlayerController.ClientPlayForceFeedback",NewName="PlayerController.K2_ClientPlayForceFeedback") [CoreUObject.Metadata] ; Note: UnrealHeaderTool should be rerun after making changes to MetadataRedirects to catch any keys specified in class headers @@ -1894,6 +1897,7 @@ bBuildWithHiddenSymbolVisibility=false bSaveSymbols=false bAllowControllers=True bAllowIMU=True +bUseDisplayCutout=False [/Script/AndroidPlatformEditor.AndroidSDKSettings] SDKAPILevel=latest diff --git a/Engine/Config/BaseInput.ini b/Engine/Config/BaseInput.ini index 3165faef24c7..bd62d58c7bca 100644 --- a/Engine/Config/BaseInput.ini +++ b/Engine/Config/BaseInput.ini @@ -188,6 +188,7 @@ MaxScrollbackSize=1024 +ManualAutoCompleteList=(Command="StopRecordingSequence",Desc="Stop recording the current sequence. Only one sequence recording can be active at one time.") +ManualAutoCompleteList=(Command="FreezeRendering",Desc="Toggle freezing of most aspects of rendering (such as visibility calculations), useful in conjunction with ToggleDebugCamera to fly around and see how frustum and occlusion culling is working") +ManualAutoCompleteList=(Command="ProfileGPU",Desc="Profile one frame of rendering commands sent to the GPU") ++ManualAutoCompleteList=(Command="ProfileGPUHitches",Desc="Toggle profiling of GPU hitches.") +ManualAutoCompleteList=(Command="Automation",Desc="Run an automation command (e.g., Automation RunTests TestName)") +ManualAutoCompleteList=(Command="CsvProfile Start",Desc="Start CSV profiling.") +ManualAutoCompleteList=(Command="CsvProfile Stop",Desc="Stop CSV profiling.") diff --git a/Engine/Plugins/Developer/Concert/ConcertSync/ConcertSyncClient/Source/ConcertSyncClient/Private/ConcertClientSequencerManager.cpp b/Engine/Plugins/Developer/Concert/ConcertSync/ConcertSyncClient/Source/ConcertSyncClient/Private/ConcertClientSequencerManager.cpp index 0daa1350b6e4..aa81a47df76e 100644 --- a/Engine/Plugins/Developer/Concert/ConcertSync/ConcertSyncClient/Source/ConcertSyncClient/Private/ConcertClientSequencerManager.cpp +++ b/Engine/Plugins/Developer/Concert/ConcertSync/ConcertSyncClient/Source/ConcertSyncClient/Private/ConcertClientSequencerManager.cpp @@ -438,7 +438,7 @@ void FSequencerEventClient::ApplyEventToPlayers(const FConcertSequencerState& Ev if (Sequence && CurrentWorld) { Player = NewObject((UObject*)GetTransientPackage(), FName("ConcertSequencePlayer")); - Player->Initialize(Sequence, CurrentWorld, FMovieSceneSequencePlaybackSettings()); + Player->Initialize(Sequence, CurrentWorld->PersistentLevel, FMovieSceneSequencePlaybackSettings()); } SequencePlayers.Add(*EventState.SequenceObjectPath, Player); } diff --git a/Engine/Plugins/Developer/PerforceSourceControl/Source/PerforceSourceControl/Private/PerforceSourceControlOperations.cpp b/Engine/Plugins/Developer/PerforceSourceControl/Source/PerforceSourceControl/Private/PerforceSourceControlOperations.cpp index 366a9d2ebea2..2f810103cf56 100644 --- a/Engine/Plugins/Developer/PerforceSourceControl/Source/PerforceSourceControl/Private/PerforceSourceControlOperations.cpp +++ b/Engine/Plugins/Developer/PerforceSourceControl/Source/PerforceSourceControl/Private/PerforceSourceControlOperations.cpp @@ -756,7 +756,7 @@ static void ParseUpdateStatusResults(const FP4RecordSet& InRecords, const TArray int32 AtIndex = OtherOpenRecordValue.Find(TEXT("@")); FString OtherOpenUser = AtIndex == INDEX_NONE ? FString(TEXT("")) : OtherOpenRecordValue.Left(AtIndex); - BranchModification.OtherUserCheckedOut += OtherOpenUser + TEXT(" @ ") + BranchModification.BranchName; + BranchModification.OtherUserCheckedOut += OtherOpenUser + TEXT(" @ ") + Branch; if (OpenIdx < OtherOpenNum - 1) { diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/EdGraphNode_Reference.cpp b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/EdGraphNode_Reference.cpp index 10ffa6ef213b..6dd5dcd07f56 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/EdGraphNode_Reference.cpp +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/EdGraphNode_Reference.cpp @@ -1,6 +1,6 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. -#include "EdGraphNode_Reference.h" +#include "ReferenceViewer/EdGraphNode_Reference.h" #include "GenericPlatform/GenericPlatformFile.h" #include "EdGraph/EdGraphPin.h" #include "HAL/PlatformFilemanager.h" @@ -176,8 +176,8 @@ UObject* UEdGraphNode_Reference::GetJumpTargetForDoubleClick() const { if (Identifiers.Num() > 0 ) { - GetReferenceGraph()->SetGraphRoot(Identifiers, FIntPoint(NodePosX, NodePosY)); - GetReferenceGraph()->RebuildGraph(); + GetReferenceViewerGraph()->SetGraphRoot(Identifiers, FIntPoint(NodePosX, NodePosY)); + GetReferenceViewerGraph()->RebuildGraph(); } return NULL; } diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/EdGraph_ReferenceViewer.cpp b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/EdGraph_ReferenceViewer.cpp index f36676b4d8f5..a7fc210c2a78 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/EdGraph_ReferenceViewer.cpp +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/EdGraph_ReferenceViewer.cpp @@ -1,8 +1,8 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. -#include "EdGraph_ReferenceViewer.h" +#include "ReferenceViewer/EdGraph_ReferenceViewer.h" +#include "ReferenceViewer/EdGraphNode_Reference.h" #include "EdGraph/EdGraphPin.h" -#include "EdGraphNode_Reference.h" #include "ARFilter.h" #include "AssetRegistryModule.h" #include "AssetThumbnail.h" diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.cpp b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.cpp index a0de4341a4be..396c89bdf368 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.cpp +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.cpp @@ -1,6 +1,6 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. -#include "ReferenceViewerSchema.h" +#include "ReferenceViewer/ReferenceViewerSchema.h" #include "Textures/SlateIcon.h" #include "Misc/Attribute.h" #include "Framework/MultiBox/MultiBoxBuilder.h" @@ -106,13 +106,13 @@ void UReferenceViewerSchema::BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraph // Don't allow breaking any links } -FPinConnectionResponse UReferenceViewerSchema::MovePinLinks(UEdGraphPin& MoveFromPin, UEdGraphPin& MoveToPin, bool bIsItermediateMove) const +FPinConnectionResponse UReferenceViewerSchema::MovePinLinks(UEdGraphPin& MoveFromPin, UEdGraphPin& MoveToPin, bool bIsIntermediateMove, bool bNotifyLinkedNodes) const { // Don't allow moving any links return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, FString()); } -FPinConnectionResponse UReferenceViewerSchema::CopyPinLinks(UEdGraphPin& CopyFromPin, UEdGraphPin& CopyToPin, bool bIsItermediateCopy) const +FPinConnectionResponse UReferenceViewerSchema::CopyPinLinks(UEdGraphPin& CopyFromPin, UEdGraphPin& CopyToPin, bool bIsIntermediateCopy) const { // Don't allow copying any links return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, FString()); diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceNode.cpp b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceNode.cpp index 9b4edb243efe..a5b899360701 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceNode.cpp +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceNode.cpp @@ -5,7 +5,7 @@ #include "Widgets/Layout/SSpacer.h" #include "Widgets/Images/SImage.h" #include "Widgets/Layout/SBox.h" -#include "EdGraphNode_Reference.h" +#include "ReferenceViewer/EdGraphNode_Reference.h" #include "AssetThumbnail.h" #include "Widgets/Text/SInlineEditableTextBlock.h" #include "SCommentBubble.h" diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.cpp b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.cpp index 66204d912dee..f1298dd107a0 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.cpp +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/SReferenceViewer.cpp @@ -18,9 +18,9 @@ #include "Widgets/Input/SSpinBox.h" #include "EditorStyleSet.h" #include "Engine/Selection.h" -#include "EdGraph_ReferenceViewer.h" -#include "EdGraphNode_Reference.h" -#include "ReferenceViewerSchema.h" +#include "ReferenceViewer/EdGraph_ReferenceViewer.h" +#include "ReferenceViewer/EdGraphNode_Reference.h" +#include "ReferenceViewer/ReferenceViewerSchema.h" #include "AssetRegistryModule.h" #include "ICollectionManager.h" #include "CollectionManagerModule.h" diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/EdGraphNode_Reference.h b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Public/ReferenceViewer/EdGraphNode_Reference.h similarity index 53% rename from Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/EdGraphNode_Reference.h rename to Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Public/ReferenceViewer/EdGraphNode_Reference.h index 42b443d755e5..50a6d460616e 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/EdGraphNode_Reference.h +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Public/ReferenceViewer/EdGraphNode_Reference.h @@ -12,19 +12,21 @@ class UEdGraphPin; UCLASS() -class UEdGraphNode_Reference : public UEdGraphNode +class ASSETMANAGEREDITOR_API UEdGraphNode_Reference : public UEdGraphNode { GENERATED_UCLASS_BODY() - virtual void SetupReferenceNode(const FIntPoint& NodeLoc, const TArray& NewIdentifiers, const FAssetData& InAssetData); - virtual void SetReferenceNodeCollapsed(const FIntPoint& NodeLoc, int32 InNumReferencesExceedingMax); - virtual void AddReferencer(class UEdGraphNode_Reference* ReferencerNode); - // Returns first asset identifier - virtual FAssetIdentifier GetIdentifier() const; - virtual void GetAllIdentifiers(TArray& OutIdentifiers) const; - // Returns only the packages in this node, skips searchable names - virtual void GetAllPackageNames(TArray& OutPackageNames) const; - class UEdGraph_ReferenceViewer* GetReferenceViewerGraph() const; + /** Returns first asset identifier */ + FAssetIdentifier GetIdentifier() const; + + /** Returns all identifiers on this node including virtual things */ + void GetAllIdentifiers(TArray& OutIdentifiers) const; + + /** Returns only the packages in this node, skips searchable names */ + void GetAllPackageNames(TArray& OutPackageNames) const; + + /** Returns our owning graph */ + UEdGraph_ReferenceViewer* GetReferenceViewerGraph() const; // UEdGraphNode implementation virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; @@ -34,22 +36,20 @@ class UEdGraphNode_Reference : public UEdGraphNode virtual UObject* GetJumpTargetForDoubleClick() const override; // End UEdGraphNode implementation - void CacheAssetData(const FAssetData& AssetData); - bool UsesThumbnail() const; bool IsPackage() const; bool IsCollapsed() const; FAssetData GetAssetData() const; - virtual UEdGraphPin* GetDependencyPin(); - virtual UEdGraphPin* GetReferencerPin(); -protected: - class UEdGraph_ReferenceViewer* GetReferenceGraph() const - { - return CastChecked(GetOuter()); - } + UEdGraphPin* GetDependencyPin(); + UEdGraphPin* GetReferencerPin(); private: + void CacheAssetData(const FAssetData& AssetData); + void SetupReferenceNode(const FIntPoint& NodeLoc, const TArray& NewIdentifiers, const FAssetData& InAssetData); + void SetReferenceNodeCollapsed(const FIntPoint& NodeLoc, int32 InNumReferencesExceedingMax); + void AddReferencer(class UEdGraphNode_Reference* ReferencerNode); + TArray Identifiers; FText NodeTitle; @@ -61,6 +61,8 @@ private: UEdGraphPin* DependencyPin; UEdGraphPin* ReferencerPin; + + friend UEdGraph_ReferenceViewer; }; diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/EdGraph_ReferenceViewer.h b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Public/ReferenceViewer/EdGraph_ReferenceViewer.h similarity index 90% rename from Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/EdGraph_ReferenceViewer.h rename to Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Public/ReferenceViewer/EdGraph_ReferenceViewer.h index d6babe05b3dd..48c76368a938 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/EdGraph_ReferenceViewer.h +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Public/ReferenceViewer/EdGraph_ReferenceViewer.h @@ -14,7 +14,7 @@ class UEdGraphNode_Reference; class SReferenceViewer; UCLASS() -class UEdGraph_ReferenceViewer : public UEdGraph +class ASSETMANAGEREDITOR_API UEdGraph_ReferenceViewer : public UEdGraph { GENERATED_UCLASS_BODY() @@ -23,12 +23,21 @@ public: virtual void BeginDestroy() override; // End UObject implementation + /** Set reference viewer to focus on these assets */ void SetGraphRoot(const TArray& GraphRootIdentifiers, const FIntPoint& GraphRootOrigin = FIntPoint(ForceInitToZero)); + + /** Returns list of currently focused assets */ const TArray& GetCurrentGraphRootIdentifiers() const; - class UEdGraphNode_Reference* RebuildGraph(); - void SetReferenceViewer(TSharedPtr InViewer); + + /** If you're extending the reference viewer via GetAllGraphEditorContextMenuExtender you can use this to get the list of selected assets to use in your menu extender */ bool GetSelectedAssetsForMenuExtender(const class UEdGraphNode* Node, TArray& SelectedAssets) const; + /** Accessor for the thumbnail pool in this graph */ + const TSharedPtr& GetAssetThumbnailPool() const; + + /** Force the graph to rebuild */ + class UEdGraphNode_Reference* RebuildGraph(); + bool IsSearchDepthLimited() const; bool IsSearchBreadthLimited() const; bool IsShowSoftReferences() const; @@ -57,10 +66,8 @@ public: bool GetEnableCollectionFilter() const; void SetEnableCollectionFilter(bool bEnabled); - /** Accessor for the thumbnail pool in this graph */ - const TSharedPtr& GetAssetThumbnailPool() const; - private: + void SetReferenceViewer(TSharedPtr InViewer); UEdGraphNode_Reference* ConstructNodes(const TArray& GraphRootIdentifiers, const FIntPoint& GraphRootOrigin); int32 RecursivelyGatherSizes(bool bReferencers, const TArray& Identifiers, const TSet& AllowedPackageNames, int32 CurrentDepth, TSet& VisitedNames, TMap& OutNodeSizes) const; void GatherAssetData(const TSet& AllPackageNames, TMap& OutPackageToAssetDataMap) const; @@ -102,4 +109,6 @@ private: bool bIsShowManagementReferences; bool bIsShowSearchableNames; bool bIsShowNativePackages; + + friend SReferenceViewer; }; diff --git a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.h b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Public/ReferenceViewer/ReferenceViewerSchema.h similarity index 90% rename from Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.h rename to Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Public/ReferenceViewer/ReferenceViewerSchema.h index 0a8f37842fcd..7c4cd5db0774 100644 --- a/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Private/ReferenceViewer/ReferenceViewerSchema.h +++ b/Engine/Plugins/Editor/AssetManagerEditor/Source/AssetManagerEditor/Public/ReferenceViewer/ReferenceViewerSchema.h @@ -11,7 +11,7 @@ class FMenuBuilder; class FSlateRect; class UEdGraph; -UCLASS() +UCLASS(MinimalAPI) class UReferenceViewerSchema : public UEdGraphSchema { GENERATED_UCLASS_BODY() @@ -22,8 +22,8 @@ public: virtual FLinearColor GetPinTypeColor(const FEdGraphPinType& PinType) const override; virtual void BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotifcation) const override; virtual void BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) const override; - virtual FPinConnectionResponse MovePinLinks(UEdGraphPin& MoveFromPin, UEdGraphPin& MoveToPin, bool bIsItermeadiateMove = false) const override; - virtual FPinConnectionResponse CopyPinLinks(UEdGraphPin& CopyFromPin, UEdGraphPin& CopyToPin, bool bIsItermeadiateCopy = false) const override; + virtual FPinConnectionResponse MovePinLinks(UEdGraphPin& MoveFromPin, UEdGraphPin& MoveToPin, bool bIsIntermediateMove = false, bool bNotifyLinkedNodes = false) const override; + virtual FPinConnectionResponse CopyPinLinks(UEdGraphPin& CopyFromPin, UEdGraphPin& CopyToPin, bool bIsIntermediateCopy = false) const override; virtual class FConnectionDrawingPolicy* CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const override; virtual void DroppedAssetsOnGraph(const TArray& Assets, const FVector2D& GraphPosition, UEdGraph* Graph) const override; virtual void GetAssetsGraphHoverMessage(const TArray& Assets, const UEdGraph* HoverGraph, FString& OutTooltipText, bool& OutOkIcon) const override; diff --git a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagsEditorModule.cpp b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagsEditorModule.cpp index be5d316d0153..9e06aece162c 100644 --- a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagsEditorModule.cpp +++ b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/GameplayTagsEditorModule.cpp @@ -176,12 +176,18 @@ public: } } - void ShowNotification(const FText& TextToDisplay, float TimeToDisplay) + void ShowNotification(const FText& TextToDisplay, float TimeToDisplay, bool bLogError = false) { FNotificationInfo Info(TextToDisplay); Info.ExpireDuration = TimeToDisplay; FSlateNotificationManager::Get().AddNotification(Info); + + // Also log if error + if (bLogError) + { + UE_LOG(LogGameplayTags, Error, TEXT("%s"), *TextToDisplay.ToString()) + } } void MigrateSettings() @@ -324,6 +330,14 @@ public: UGameplayTagsSettings* Settings = GetMutableDefault(); UGameplayTagsDeveloperSettings* DevSettings = GetMutableDefault(); + FText ErrorText; + FString FixedString; + if (!Manager.IsValidGameplayTagString(NewTag, &ErrorText, &FixedString)) + { + ShowNotification(FText::Format(LOCTEXT("AddTagFailure_BadString", "Failed to add gameplay tag {0}: {1}, try {2} instead!"), FText::FromString(NewTag), ErrorText, FText::FromString(FixedString)), 10.0f, true); + return false; + } + FName NewTagName = FName(*NewTag); // Delete existing redirector @@ -332,7 +346,7 @@ public: // Already in the list as an explicit tag, ignore. Note we want to add if it is in implicit tag. (E.g, someone added A.B.C then someone tries to add A.B) if (Manager.IsDictionaryTag(NewTagName)) { - ShowNotification(FText::Format(LOCTEXT("AddTagFailure_AlreadyExists", "Failed to add gameplay tag {0}, already exists!"), FText::FromString(NewTag)), 10.0f); + ShowNotification(FText::Format(LOCTEXT("AddTagFailure_AlreadyExists", "Failed to add gameplay tag {0}, already exists!"), FText::FromString(NewTag)), 10.0f, true); return false; } @@ -357,7 +371,7 @@ public: { break; } - ShowNotification(FText::Format(LOCTEXT("AddRestrictedTagFailure", "Failed to add restricted gameplay tag {0}, {1} is not a restricted tag"), FText::FromString(NewTag), FText::FromString(AncestorTag)), 10.0f); + ShowNotification(FText::Format(LOCTEXT("AddRestrictedTagFailure", "Failed to add restricted gameplay tag {0}, {1} is not a restricted tag"), FText::FromString(NewTag), FText::FromString(AncestorTag)), 10.0f, true); return false; } @@ -388,7 +402,7 @@ public: break; } - ShowNotification(FText::Format(LOCTEXT("AddTagFailure_RestrictedTag", "Failed to add gameplay tag {0}, {1} is a restricted tag and does not allow non-restricted children"), FText::FromString(NewTag), FText::FromString(AncestorTag)), 10.0f); + ShowNotification(FText::Format(LOCTEXT("AddTagFailure_RestrictedTag", "Failed to add gameplay tag {0}, {1} is a restricted tag and does not allow non-restricted children"), FText::FromString(NewTag), FText::FromString(AncestorTag)), 10.0f, true); return false; } @@ -454,7 +468,7 @@ public: if (!bSuccess) { - ShowNotification(FText::Format(LOCTEXT("AddTagFailure", "Failed to add gameplay tag {0} to dictionary {1}!"), FText::FromString(NewTag), FText::FromName(TagSourceName)), 10.0f); + ShowNotification(FText::Format(LOCTEXT("AddTagFailure", "Failed to add gameplay tag {0} to dictionary {1}!"), FText::FromString(NewTag), FText::FromName(TagSourceName)), 10.0f, true); return false; } @@ -489,7 +503,7 @@ public: if (!Manager.GetTagEditorData(TagName, Comment, TagSourceName, bTagIsExplicit, bTagIsRestricted, bTagAllowsNonRestrictedChildren)) { - ShowNotification(FText::Format(LOCTEXT("RemoveTagFailureNoTag", "Cannot delete tag {0}, does not exist!"), FText::FromName(TagName)), 10.0f); + ShowNotification(FText::Format(LOCTEXT("RemoveTagFailureNoTag", "Cannot delete tag {0}, does not exist!"), FText::FromName(TagName)), 10.0f, true); return false; } @@ -500,19 +514,19 @@ public: // Check if the tag is implicitly defined if (!bTagIsExplicit || !TagSource) { - ShowNotification(FText::Format(LOCTEXT("RemoveTagFailureNoSource", "Cannot delete tag {0} as it is implicit, remove children manually"), FText::FromName(TagName)), 10.0f); + ShowNotification(FText::Format(LOCTEXT("RemoveTagFailureNoSource", "Cannot delete tag {0} as it is implicit, remove children manually"), FText::FromName(TagName)), 10.0f, true); return false; } if (bTagIsRestricted && !TagSource->SourceRestrictedTagList) { - ShowNotification(FText::Format(LOCTEXT("RemoveTagFailureBadSource", "Cannot delete tag {0} from source {1}, remove manually"), FText::FromName(TagName), FText::FromName(TagSourceName)), 10.0f); + ShowNotification(FText::Format(LOCTEXT("RemoveTagFailureBadSource", "Cannot delete tag {0} from source {1}, remove manually"), FText::FromName(TagName), FText::FromName(TagSourceName)), 10.0f, true); return false; } if (!bTagIsRestricted && !TagSource->SourceTagList) { - ShowNotification(FText::Format(LOCTEXT("RemoveTagFailureBadSource", "Cannot delete tag {0} from source {1}, remove manually"), FText::FromName(TagName), FText::FromName(TagSourceName)), 10.0f); + ShowNotification(FText::Format(LOCTEXT("RemoveTagFailureBadSource", "Cannot delete tag {0} from source {1}, remove manually"), FText::FromName(TagName), FText::FromName(TagSourceName)), 10.0f, true); return false; } @@ -553,7 +567,7 @@ public: if (Referencers.Num() > 0) { - ShowNotification(FText::Format(LOCTEXT("RemoveTagFailureBadSource_Referenced", "Cannot delete tag {0}, still referenced by {1} and possibly others"), FText::FromName(TagNameToDelete), FText::FromString(Referencers[0].ToString())), 10.0f); + ShowNotification(FText::Format(LOCTEXT("RemoveTagFailureBadSource_Referenced", "Cannot delete tag {0}, still referenced by {1} and possibly others"), FText::FromName(TagNameToDelete), FText::FromString(Referencers[0].ToString())), 10.0f, true); return false; } @@ -608,7 +622,7 @@ public: } } - ShowNotification(FText::Format(LOCTEXT("RemoveTagFailureNoTag", "Cannot delete tag {0}, does not exist!"), FText::FromName(TagName)), 10.0f); + ShowNotification(FText::Format(LOCTEXT("RemoveTagFailureNoTag", "Cannot delete tag {0}, does not exist!"), FText::FromName(TagName)), 10.0f, true); return false; } @@ -639,7 +653,7 @@ public: FGameplayTagContainer ChildTags = Manager.RequestGameplayTagDirectDescendantsInDictionary(ActualTag, EGameplayTagSelectionType::NonRestrictedOnly); if (!ChildTags.IsEmpty()) { - ShowNotification(LOCTEXT("ToggleAllowNonRestrictedChildrenFailure", "Cannot prevent non-restricted children since some already exist! Delete them first."), 10.0f); + ShowNotification(LOCTEXT("ToggleAllowNonRestrictedChildrenFailure", "Cannot prevent non-restricted children since some already exist! Delete them first."), 10.0f, true); return false; } } @@ -733,7 +747,7 @@ public: } else { - ShowNotification(FText::Format(LOCTEXT("RenameFailure", "Tag {0} redirector was created but original tag was not destroyed as it has children"), FText::FromString(TagToRename)), 10.0f); + ShowNotification(FText::Format(LOCTEXT("RenameFailure", "Tag {0} redirector was created but original tag was not destroyed as it has children"), FText::FromString(TagToRename)), 10.0f, true); } } diff --git a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagGraphPin.cpp b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagGraphPin.cpp index 453146fad325..05a63a5bf58e 100644 --- a/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagGraphPin.cpp +++ b/Engine/Plugins/Editor/GameplayTagsEditor/Source/GameplayTagsEditor/Private/SGameplayTagGraphPin.cpp @@ -5,6 +5,7 @@ #include "K2Node_CallFunction.h" #include "GameplayTagsModule.h" #include "Widgets/Layout/SScaleBox.h" +#include "K2Node_VariableSet.h" #define LOCTEXT_NAMESPACE "GameplayTagGraphPin" @@ -48,7 +49,7 @@ void SGameplayTagGraphPin::ParseDefaultValueData() FilterString.Empty(); if (UScriptStruct* PinStructType = Cast(GraphPinObj->PinType.PinSubCategoryObject.Get())) { - FilterString = UGameplayTagsManager::Get().GetCategoriesMetaFromStruct(PinStructType); + FilterString = UGameplayTagsManager::Get().GetCategoriesMetaFromField(PinStructType); } if (FilterString.IsEmpty()) @@ -60,6 +61,10 @@ void SGameplayTagGraphPin::ParseDefaultValueData() FilterString = UGameplayTagsManager::Get().GetCategoriesMetaFromFunction(ThisFunction); } } + else if (UK2Node_VariableSet* ThisVariable = Cast(GraphPinObj->GetOwningNode())) + { + FilterString = UGameplayTagsManager::Get().GetCategoriesMetaFromField(ThisVariable->GetPropertyForVariable()); + } } if (TagString.StartsWith(TEXT("(")) && TagString.EndsWith(TEXT(")"))) diff --git a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Sequencer/ControlRigSequenceExporter.cpp b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Sequencer/ControlRigSequenceExporter.cpp index 422567e7bf2f..3f86df06dd4a 100644 --- a/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Sequencer/ControlRigSequenceExporter.cpp +++ b/Engine/Plugins/Experimental/ControlRig/Source/ControlRigEditor/Private/Sequencer/ControlRigSequenceExporter.cpp @@ -213,7 +213,7 @@ void Convert(UControlRigSequence* Sequence, UAnimSequence* AnimSequence, USkelet LevelSequenceActor->LevelSequence = Sequence; LevelSequenceActor->PlaybackSettings.bRestoreState = true; LevelSequenceActor->SequencePlayer = NewObject(LevelSequenceActor, "AnimationPlayer"); - LevelSequenceActor->SequencePlayer->Initialize(Sequence, World, LevelSequenceActor->PlaybackSettings); + LevelSequenceActor->SequencePlayer->Initialize(Sequence, LevelSequenceActor->GetLevel(), LevelSequenceActor->PlaybackSettings); // Now set up our animation sequence AnimSequence->RecycleAnimSequence(); diff --git a/Engine/Plugins/Experimental/StructBox/Source/StructBox/Classes/StructBox.h b/Engine/Plugins/Experimental/StructBox/Source/StructBox/Classes/StructBox.h deleted file mode 100644 index 3355386f143c..000000000000 --- a/Engine/Plugins/Experimental/StructBox/Source/StructBox/Classes/StructBox.h +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/ObjectMacros.h" -#include "UObject/Class.h" -#include "StructBox.generated.h" - -USTRUCT(BlueprintType) -struct FStructBox -{ - GENERATED_USTRUCT_BODY() - - UPROPERTY() - UScriptStruct* ScriptStruct; - - uint8* StructMemory; - - FStructBox() - : StructMemory(nullptr) - {} - - bool IsValid() const - { - return ScriptStruct && StructMemory; - } - - void Destroy(UScriptStruct* ActualStruct); - void Create(UScriptStruct* ActualStruct); - - ~FStructBox(); - - bool Serialize(FArchive& Ar); - - bool Identical(const FStructBox* Other, uint32 PortFlags) const; - - void AddStructReferencedObjects(class FReferenceCollector& Collector) const; - - FStructBox& operator=(const FStructBox& Other); - -private: - FStructBox(const FStructBox&); -}; - -template<> -struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 -{ - enum - { - WithZeroConstructor = true, - WithCopy = true, - WithIdentical = true, - WithAddStructReferencedObjects = true, - WithSerializer = true - // TODO.. WithPostSerialize etc.. - }; -}; diff --git a/Engine/Plugins/Experimental/StructBox/Source/StructBox/Classes/StructBoxLibrary.h b/Engine/Plugins/Experimental/StructBox/Source/StructBox/Classes/StructBoxLibrary.h deleted file mode 100644 index 62e7990b91e1..000000000000 --- a/Engine/Plugins/Experimental/StructBox/Source/StructBox/Classes/StructBoxLibrary.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/Script.h" -#include "UObject/ObjectMacros.h" -#include "UObject/UnrealType.h" -#include "UObject/ScriptMacros.h" -#include "Kismet/BlueprintFunctionLibrary.h" -#include "StructBox.h" -#include "StructBoxLibrary.generated.h" - -USTRUCT(BlueprintInternalUseOnly) -struct FStubStruct -{ - GENERATED_USTRUCT_BODY() - - FStubStruct() { } -}; - -UCLASS() -class UStructBoxLibrary : public UBlueprintFunctionLibrary -{ - GENERATED_BODY() - -public: - UFUNCTION(BlueprintCallable, CustomThunk, Category = "StructBox", meta = (CustomStructureParam = "CustomStruct")) - static bool GetStructFromBox(const FStructBox& StructBox, FStubStruct& CustomStruct); - - DECLARE_FUNCTION(execGetStructFromBox) - { - PARAM_PASSED_BY_REF(StructBox, UStructProperty, FStructBox); - - Stack.MostRecentPropertyAddress = nullptr; - Stack.MostRecentProperty = nullptr; - - Stack.StepCompiledIn(NULL); - void* DstStructAddr = Stack.MostRecentPropertyAddress; - auto DstStructProperty = Cast(Stack.MostRecentProperty); - - bool bResult = false; - if (DstStructAddr && DstStructProperty && StructBox.IsValid()) - { - if (StructBox.ScriptStruct == DstStructProperty->Struct) - { - StructBox.ScriptStruct->CopyScriptStruct(DstStructAddr, StructBox.StructMemory); - bResult = true; - } - } - - P_FINISH; - - *(bool*)RESULT_PARAM = bResult; - } - - UFUNCTION(BlueprintCallable, CustomThunk, Category = "StructBox", meta = (CustomStructureParam = "CustomStruct")) - static void SetStructInBox(UPARAM(ref) FStructBox& StructBox, const FStubStruct& CustomStruct); - - DECLARE_FUNCTION(execSetStructInBox) - { - PARAM_PASSED_BY_REF(StructBox, UStructProperty, FStructBox); - - Stack.MostRecentPropertyAddress = nullptr; - Stack.MostRecentProperty = nullptr; - - Stack.StepCompiledIn(NULL); - void* SrcStructAddr = Stack.MostRecentPropertyAddress; - auto SrcStructProperty = Cast(Stack.MostRecentProperty); - - StructBox.Destroy(StructBox.ScriptStruct); - - if (SrcStructAddr && SrcStructProperty) - { - StructBox.ScriptStruct = SrcStructProperty->Struct; - // TODO: CHECK compatibility - StructBox.Create(StructBox.ScriptStruct); - StructBox.ScriptStruct->CopyScriptStruct(StructBox.StructMemory, SrcStructAddr); - } - - P_FINISH; - } -}; diff --git a/Engine/Plugins/Experimental/StructBox/Source/StructBox/Private/StructBox.cpp b/Engine/Plugins/Experimental/StructBox/Source/StructBox/Private/StructBox.cpp deleted file mode 100644 index 4715410a62b6..000000000000 --- a/Engine/Plugins/Experimental/StructBox/Source/StructBox/Private/StructBox.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#include "StructBox.h" - -void FStructBox::Destroy(UScriptStruct* ActualStruct) -{ - if (ActualStruct && StructMemory) - { - ActualStruct->DestroyStruct(StructMemory); - } - - if (StructMemory) - { - FMemory::Free(StructMemory); - StructMemory = nullptr; - } -} - -FStructBox::~FStructBox() -{ - ensure(ScriptStruct || !StructMemory); - Destroy(ScriptStruct); -} - -FStructBox& FStructBox::operator=(const FStructBox& Other) -{ - if (this != &Other) - { - Destroy(ScriptStruct); - - ScriptStruct = Other.ScriptStruct; - - if (Other.IsValid()) - { - Create(ScriptStruct); - ScriptStruct->CopyScriptStruct(StructMemory, Other.StructMemory); - } - } - - return *this; -} - -void FStructBox::Create(UScriptStruct* ActualStruct) -{ - check(NULL == StructMemory); - StructMemory = (uint8*)FMemory::Malloc(ActualStruct->GetStructureSize()); - ActualStruct->InitializeStruct(StructMemory); -} - -bool FStructBox::Serialize(FArchive& Ar) -{ - auto OldStruct = ScriptStruct; - Ar << ScriptStruct; - bool bValidBox = IsValid(); - Ar << bValidBox; - - if (Ar.IsLoading()) - { - if (OldStruct != ScriptStruct) - { - Destroy(OldStruct); - } - if (ScriptStruct && !StructMemory && bValidBox) - { - Create(ScriptStruct); - } - } - - ensure(bValidBox || !IsValid()); - if (IsValid() && bValidBox) - { - ScriptStruct->SerializeItem(Ar, StructMemory, nullptr); - } - - return true; -} - -bool FStructBox::Identical(const FStructBox* Other, uint32 PortFlags) const -{ - if (!Other) - { - return false; - } - - if (ScriptStruct != Other->ScriptStruct) - { - return false; - } - - if (!ScriptStruct) - { - return true; - } - - if (!StructMemory && !Other->StructMemory) - { - return true; - } - - return ScriptStruct->CompareScriptStruct(StructMemory, Other->StructMemory, PortFlags); -} - -void FStructBox::AddStructReferencedObjects(class FReferenceCollector& Collector) const -{ - Collector.AddReferencedObject(const_cast(this)->ScriptStruct); - if (ScriptStruct && StructMemory && ScriptStruct->RefLink) - { - ScriptStruct->SerializeBin(Collector.GetVerySlowReferenceCollectorArchive(), StructMemory); - } -} diff --git a/Engine/Plugins/Experimental/StructBox/Source/StructBox/Private/StructBoxLibrary.cpp b/Engine/Plugins/Experimental/StructBox/Source/StructBox/Private/StructBoxLibrary.cpp deleted file mode 100644 index e1f450a1c6a5..000000000000 --- a/Engine/Plugins/Experimental/StructBox/Source/StructBox/Private/StructBoxLibrary.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#include "StructBoxLibrary.h" - -bool UStructBoxLibrary::GetStructFromBox(const FStructBox& StructBox, FStubStruct& CustomStruct) -{ - check(0); - return false; -} - -void UStructBoxLibrary::SetStructInBox(FStructBox& StructBox, const FStubStruct& CustomStruct) -{ - check(0); -} diff --git a/Engine/Plugins/Experimental/StructBox/Source/StructBox/Private/StructBoxModule.cpp b/Engine/Plugins/Experimental/StructBox/Source/StructBox/Private/StructBoxModule.cpp deleted file mode 100644 index f8c79c8075c9..000000000000 --- a/Engine/Plugins/Experimental/StructBox/Source/StructBox/Private/StructBoxModule.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#include "CoreMinimal.h" -#include "Modules/ModuleManager.h" -#include "IStructBoxModule.h" - -class FStructBoxModule : public IStructBoxModuleInterface -{ - -}; - -IMPLEMENT_MODULE(FStructBoxModule, StructBox); diff --git a/Engine/Plugins/Experimental/StructBox/Source/StructBox/Public/IStructBoxModule.h b/Engine/Plugins/Experimental/StructBox/Source/StructBox/Public/IStructBoxModule.h deleted file mode 100644 index fe354ec8ad7c..000000000000 --- a/Engine/Plugins/Experimental/StructBox/Source/StructBox/Public/IStructBoxModule.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Modules/ModuleInterface.h" - -////////////////////////////////////////////////////////////////////////// -// IStructBoxModuleInterface - -class IStructBoxModuleInterface : public IModuleInterface -{ -}; diff --git a/Engine/Plugins/Experimental/StructBox/Source/StructBox/StructBox.Build.cs b/Engine/Plugins/Experimental/StructBox/Source/StructBox/StructBox.Build.cs deleted file mode 100644 index dc382629b9ce..000000000000 --- a/Engine/Plugins/Experimental/StructBox/Source/StructBox/StructBox.Build.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. -using System.IO; -using UnrealBuildTool; - -namespace UnrealBuildTool.Rules -{ - public class StructBox : ModuleRules - { - public StructBox(ReadOnlyTargetRules Target) : base(Target) - { - PublicDependencyModuleNames.AddRange( - new string[] - { - "Core", - "CoreUObject", - "Engine" - } - ); - } - } -} diff --git a/Engine/Plugins/Experimental/StructBox/StructBox.uplugin b/Engine/Plugins/Experimental/StructBox/StructBox.uplugin deleted file mode 100644 index 33ca058bd9f9..000000000000 --- a/Engine/Plugins/Experimental/StructBox/StructBox.uplugin +++ /dev/null @@ -1,25 +0,0 @@ -{ - "FileVersion" : 3, - "Version" : 1, - "VersionName" : "1.0", - "FriendlyName" : "StructBox", - "Description" : "Adds new struct type, that can contain any other struct", - "Category" : "Blueprints", - "CreatedBy" : "Epic Games, Inc.", - "CreatedByURL" : "http://epicgames.com", - "DocsURL" : "", - "MarketplaceURL" : "", - "SupportURL" : "", - "EnabledByDefault" : false, - "CanContainContent" : false, - "IsBetaVersion" : true, - "Installed" : false, - "Modules" : - [ - { - "Name" : "StructBox", - "Type" : "Runtime", - "LoadingPhase" : "Default" - } - ] -} \ No newline at end of file diff --git a/Engine/Plugins/FX/HoudiniNiagara/Source/HoudiniNiagara/Private/NiagaraDataInterfaceHoudiniCSV.cpp b/Engine/Plugins/FX/HoudiniNiagara/Source/HoudiniNiagara/Private/NiagaraDataInterfaceHoudiniCSV.cpp index 33afefaa6c76..c0e9ce198328 100644 --- a/Engine/Plugins/FX/HoudiniNiagara/Source/HoudiniNiagara/Private/NiagaraDataInterfaceHoudiniCSV.cpp +++ b/Engine/Plugins/FX/HoudiniNiagara/Source/HoudiniNiagara/Private/NiagaraDataInterfaceHoudiniCSV.cpp @@ -2065,7 +2065,7 @@ struct FNiagaraDataInterfaceParametersCS_HoudiniCSV : public FNiagaraDataInterfa Ar << LastSpawnTime; } - virtual void Set( FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface ) const override + virtual void Set( FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface, void* PerInstanceData) const override { check( IsInRenderingThread() ); diff --git a/Engine/Plugins/FX/Niagara/Config/BaseNiagara.ini b/Engine/Plugins/FX/Niagara/Config/BaseNiagara.ini index 31df92244a67..04a84f53c22a 100644 --- a/Engine/Plugins/FX/Niagara/Config/BaseNiagara.ini +++ b/Engine/Plugins/FX/Niagara/Config/BaseNiagara.ini @@ -16,9 +16,11 @@ ;+EnumRedirects=(OldName="ENiagaraSortMode",ValueChanges=(("ENiagaraSortMode::SortNone","ENiagaraSortMode::None"), ("ENiagaraSortMode::SortViewDepth","ENiagaraSortMode::ViewDepth"), ("ENiagaraSortMode::SortViewDistance","ENiagaraSortMode::ViewDistance")) ) [/Script/Niagara.NiagaraSettings] -+AdditionalParameterEnums=/Niagara/Functions/Localspace/ENiagaraCoordinateSpace.ENiagaraCoordinateSpace -+AdditionalParameterEnums=/Niagara/Functions/Localspace/ENiagaraOrientationAxis.ENiagaraOrientationAxis -+AdditionalParameterEnums=/Niagara/DynamicInputs/Bool/ENiagaraBooleanLogicOps.ENiagaraBooleanLogicOps ++AdditionalParameterEnums=/Niagara/Enums/ENiagaraCoordinateSpace.ENiagaraCoordinateSpace ++AdditionalParameterEnums=/Niagara/Enums/ENiagaraOrientationAxis.ENiagaraOrientationAxis ++AdditionalParameterEnums=/Niagara/Enums/ENiagaraBooleanLogicOps.ENiagaraBooleanLogicOps ++AdditionalParameterEnums=/Niagara/Enums/ENiagaraRandomnessMode.ENiagaraRandomnessMode ++AdditionalParameterEnums=/Niagara/Enums/ENiagaraExpansionMode.ENiagaraExpansionMode [/Script/NiagaraEditor.NiagaraEditorSettings] DefaultSystem=None @@ -30,11 +32,14 @@ DefaultModuleScript=/Niagara/DefaultAssets/DefaultModule.DefaultModule +GraphCreationShortcuts=(Name="Numeric::Add",Input=(Key=A,bShift=False,bCtrl=False,bAlt=False,bCmd=False)) +GraphCreationShortcuts=(Name="Numeric::Div",Input=(Key=D,bShift=False,bCtrl=False,bAlt=False,bCmd=False)) +GraphCreationShortcuts=(Name="Numeric::Pow",Input=(Key=E,bShift=False,bCtrl=False,bAlt=False,bCmd=False)) ++GraphCreationShortcuts=(Name="Numeric::Subtract",Input=(Key=S,bShift=False,bCtrl=False,bAlt=False,bCmd=False)) +GraphCreationShortcuts=(Name="If",Input=(Key=I,bShift=False,bCtrl=False,bAlt=False,bCmd=False)) +GraphCreationShortcuts=(Name="Numeric::Mul",Input=(Key=M,bShift=False,bCtrl=False,bAlt=False,bCmd=False)) +GraphCreationShortcuts=(Name="Numeric::Normalize",Input=(Key=N,bShift=False,bCtrl=False,bAlt=False,bCmd=False)) +GraphCreationShortcuts=(Name="Numeric::OneMinus",Input=(Key=O,bShift=False,bCtrl=False,bAlt=False,bCmd=False)) ++GraphCreationShortcuts=(Name="Lerp",Input=(Key=L,bShift=False,bCtrl=False,bAlt=False,bCmd=False)) +GraphCreationShortcuts=(Name="float",Input=(Key=One,bShift=False,bCtrl=False,bAlt=False,bCmd=False)) ++GraphCreationShortcuts=(Name="bool",Input=(Key=B,bShift=False,bCtrl=False,bAlt=False,bCmd=False)) +GraphCreationShortcuts=(Name="Vector2D",Input=(Key=Two,bShift=False,bCtrl=False,bAlt=False,bCmd=False)) +GraphCreationShortcuts=(Name="Vector",Input=(Key=Three,bShift=False,bCtrl=False,bAlt=False,bCmd=False)) +GraphCreationShortcuts=(Name="Vector4",Input=(Key=Four,bShift=False,bCtrl=False,bAlt=False,bCmd=False)) diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraCollision.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraCollision.h index cee3d60ff730..83125fa448d4 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraCollision.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraCollision.h @@ -105,7 +105,7 @@ struct FNiagaraDICollsionQueryResult int32 PhysicalMaterialIdx; float Friction; float Restitution; - + bool IsInsideMesh; }; class FNiagaraDICollisionQueryBatch @@ -157,6 +157,8 @@ public: } int32 SubmitQuery(FVector Position, FVector Direction, float CollisionSize, float DeltaSeconds); + int32 SubmitQuery(FVector StartPos, FVector EndPos, ECollisionChannel TraceChannel); + bool PerformQuery(FVector StartPos, FVector EndPos, FNiagaraDICollsionQueryResult &Result, ECollisionChannel TraceChannel); bool GetQueryResult(uint32 TraceID, FNiagaraDICollsionQueryResult &Result); private: diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraConstants.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraConstants.h index f3c1f1d66dc7..58be4e71c80c 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraConstants.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraConstants.h @@ -20,78 +20,87 @@ #define PARAM_MAP_RAPID_ITERATION_BASE_STR TEXT("Constants") -#define SYS_PARAM_ENGINE_DELTA_TIME INiagaraModule::GetVar_Engine_DeltaTime() -#define SYS_PARAM_ENGINE_INV_DELTA_TIME INiagaraModule::GetVar_Engine_InvDeltaTime() -#define SYS_PARAM_ENGINE_TIME INiagaraModule::GetVar_Engine_Time() -#define SYS_PARAM_ENGINE_REAL_TIME INiagaraModule::GetVar_Engine_RealTime() -#define SYS_PARAM_ENGINE_POSITION INiagaraModule::GetVar_Engine_Owner_Position() -#define SYS_PARAM_ENGINE_VELOCITY INiagaraModule::GetVar_Engine_Owner_Velocity() -#define SYS_PARAM_ENGINE_X_AXIS INiagaraModule::GetVar_Engine_Owner_XAxis() -#define SYS_PARAM_ENGINE_Y_AXIS INiagaraModule::GetVar_Engine_Owner_YAxis() -#define SYS_PARAM_ENGINE_Z_AXIS INiagaraModule::GetVar_Engine_Owner_ZAxis() -#define SYS_PARAM_ENGINE_SCALE INiagaraModule::GetVar_Engine_Owner_Scale() +#define SYS_PARAM_ENGINE_DELTA_TIME INiagaraModule::GetVar_Engine_DeltaTime() +#define SYS_PARAM_ENGINE_INV_DELTA_TIME INiagaraModule::GetVar_Engine_InvDeltaTime() +#define SYS_PARAM_ENGINE_TIME INiagaraModule::GetVar_Engine_Time() +#define SYS_PARAM_ENGINE_REAL_TIME INiagaraModule::GetVar_Engine_RealTime() +#define SYS_PARAM_ENGINE_POSITION INiagaraModule::GetVar_Engine_Owner_Position() +#define SYS_PARAM_ENGINE_VELOCITY INiagaraModule::GetVar_Engine_Owner_Velocity() +#define SYS_PARAM_ENGINE_X_AXIS INiagaraModule::GetVar_Engine_Owner_XAxis() +#define SYS_PARAM_ENGINE_Y_AXIS INiagaraModule::GetVar_Engine_Owner_YAxis() +#define SYS_PARAM_ENGINE_Z_AXIS INiagaraModule::GetVar_Engine_Owner_ZAxis() +#define SYS_PARAM_ENGINE_ROTATION INiagaraModule::GetVar_Engine_Owner_Rotation() +#define SYS_PARAM_ENGINE_SCALE INiagaraModule::GetVar_Engine_Owner_Scale() -#define SYS_PARAM_ENGINE_LOCAL_TO_WORLD INiagaraModule::GetVar_Engine_Owner_SystemLocalToWorld() -#define SYS_PARAM_ENGINE_WORLD_TO_LOCAL INiagaraModule::GetVar_Engine_Owner_SystemWorldToLocal() -#define SYS_PARAM_ENGINE_LOCAL_TO_WORLD_TRANSPOSED INiagaraModule::GetVar_Engine_Owner_SystemLocalToWorldTransposed() -#define SYS_PARAM_ENGINE_WORLD_TO_LOCAL_TRANSPOSED INiagaraModule::GetVar_Engine_Owner_SystemWorldToLocalTransposed() -#define SYS_PARAM_ENGINE_LOCAL_TO_WORLD_NO_SCALE INiagaraModule::GetVar_Engine_Owner_SystemLocalToWorldNoScale() -#define SYS_PARAM_ENGINE_WORLD_TO_LOCAL_NO_SCALE INiagaraModule::GetVar_Engine_Owner_SystemWorldToLocalNoScale() +#define SYS_PARAM_ENGINE_LOCAL_TO_WORLD INiagaraModule::GetVar_Engine_Owner_SystemLocalToWorld() +#define SYS_PARAM_ENGINE_WORLD_TO_LOCAL INiagaraModule::GetVar_Engine_Owner_SystemWorldToLocal() +#define SYS_PARAM_ENGINE_LOCAL_TO_WORLD_TRANSPOSED INiagaraModule::GetVar_Engine_Owner_SystemLocalToWorldTransposed() +#define SYS_PARAM_ENGINE_WORLD_TO_LOCAL_TRANSPOSED INiagaraModule::GetVar_Engine_Owner_SystemWorldToLocalTransposed() +#define SYS_PARAM_ENGINE_LOCAL_TO_WORLD_NO_SCALE INiagaraModule::GetVar_Engine_Owner_SystemLocalToWorldNoScale() +#define SYS_PARAM_ENGINE_WORLD_TO_LOCAL_NO_SCALE INiagaraModule::GetVar_Engine_Owner_SystemWorldToLocalNoScale() -#define SYS_PARAM_ENGINE_TIME_SINCE_RENDERED INiagaraModule::GetVar_Engine_Owner_TimeSinceRendered() -#define SYS_PARAM_ENGINE_MIN_DIST_TO_CAMERA INiagaraModule::GetVar_Engine_Owner_MinDistanceToCamera() +#define SYS_PARAM_ENGINE_TIME_SINCE_RENDERED INiagaraModule::GetVar_Engine_Owner_TimeSinceRendered() +#define SYS_PARAM_ENGINE_MIN_DIST_TO_CAMERA INiagaraModule::GetVar_Engine_Owner_MinDistanceToCamera() -#define SYS_PARAM_ENGINE_EXECUTION_STATE INiagaraModule::GetVar_Engine_Owner_ExecutionState() +#define SYS_PARAM_ENGINE_EXECUTION_STATE INiagaraModule::GetVar_Engine_Owner_ExecutionState() -#define SYS_PARAM_ENGINE_EXEC_COUNT INiagaraModule::GetVar_Engine_ExecutionCount() -#define SYS_PARAM_ENGINE_EMITTER_NUM_PARTICLES INiagaraModule::GetVar_Engine_Emitter_NumParticles() -#define SYS_PARAM_ENGINE_SYSTEM_NUM_EMITTERS_ALIVE INiagaraModule::GetVar_Engine_System_NumEmittersAlive() -#define SYS_PARAM_ENGINE_SYSTEM_NUM_EMITTERS INiagaraModule::GetVar_Engine_System_NumEmitters() -#define SYS_PARAM_ENGINE_NUM_SYSTEM_INSTANCES INiagaraModule::GetVar_Engine_NumSystemInstances() +#define SYS_PARAM_ENGINE_EXEC_COUNT INiagaraModule::GetVar_Engine_ExecutionCount() +#define SYS_PARAM_ENGINE_EMITTER_NUM_PARTICLES INiagaraModule::GetVar_Engine_Emitter_NumParticles() +#define SYS_PARAM_ENGINE_EMITTER_TOTAL_SPAWNED_PARTICLES INiagaraModule::GetVar_Engine_Emitter_TotalSpawnedParticles() +#define SYS_PARAM_ENGINE_SYSTEM_NUM_EMITTERS_ALIVE INiagaraModule::GetVar_Engine_System_NumEmittersAlive() +#define SYS_PARAM_ENGINE_SYSTEM_NUM_EMITTERS INiagaraModule::GetVar_Engine_System_NumEmitters() +#define SYS_PARAM_ENGINE_NUM_SYSTEM_INSTANCES INiagaraModule::GetVar_Engine_NumSystemInstances() -#define SYS_PARAM_ENGINE_GLOBAL_SPAWN_COUNT_SCALE INiagaraModule::GetVar_Engine_GlobalSpawnCountScale() -#define SYS_PARAM_ENGINE_GLOBAL_SYSTEM_COUNT_SCALE INiagaraModule::GetVar_Engine_GlobalSystemScale() +#define SYS_PARAM_ENGINE_GLOBAL_SPAWN_COUNT_SCALE INiagaraModule::GetVar_Engine_GlobalSpawnCountScale() +#define SYS_PARAM_ENGINE_GLOBAL_SYSTEM_COUNT_SCALE INiagaraModule::GetVar_Engine_GlobalSystemScale() -#define SYS_PARAM_ENGINE_SYSTEM_AGE INiagaraModule::GetVar_Engine_System_Age() +#define SYS_PARAM_ENGINE_SYSTEM_AGE INiagaraModule::GetVar_Engine_System_Age() +#define SYS_PARAM_ENGINE_SYSTEM_TICK_COUNT INiagaraModule::GetVar_Engine_System_TickCount() -#define SYS_PARAM_EMITTER_AGE INiagaraModule::GetVar_Emitter_Age() -#define SYS_PARAM_EMITTER_LOCALSPACE INiagaraModule::GetVar_Emitter_LocalSpace() -#define SYS_PARAM_EMITTER_SPAWNRATE INiagaraModule::GetVar_Emitter_SpawnRate() -#define SYS_PARAM_EMITTER_SPAWN_INTERVAL INiagaraModule::GetVar_Emitter_SpawnInterval() -#define SYS_PARAM_EMITTER_INTERP_SPAWN_START_DT INiagaraModule::GetVar_Emitter_InterpSpawnStartDt() -#define SYS_PARAM_EMITTER_SPAWN_GROUP INiagaraModule::GetVar_Emitter_SpawnGroup() +#define SYS_PARAM_EMITTER_AGE INiagaraModule::GetVar_Emitter_Age() +#define SYS_PARAM_EMITTER_LOCALSPACE INiagaraModule::GetVar_Emitter_LocalSpace() +#define SYS_PARAM_EMITTER_DETERMINISM INiagaraModule::GetVar_Emitter_Determinism() +#define SYS_PARAM_EMITTER_RANDOM_SEED INiagaraModule::GetVar_Emitter_RandomSeed() +#define SYS_PARAM_EMITTER_SPAWNRATE INiagaraModule::GetVar_Emitter_SpawnRate() +#define SYS_PARAM_EMITTER_SPAWN_INTERVAL INiagaraModule::GetVar_Emitter_SpawnInterval() +#define SYS_PARAM_EMITTER_INTERP_SPAWN_START_DT INiagaraModule::GetVar_Emitter_InterpSpawnStartDt() +#define SYS_PARAM_EMITTER_SPAWN_GROUP INiagaraModule::GetVar_Emitter_SpawnGroup() -#define SYS_PARAM_PARTICLES_ID INiagaraModule::GetVar_Particles_ID() -#define SYS_PARAM_PARTICLES_POSITION INiagaraModule::GetVar_Particles_Position() -#define SYS_PARAM_PARTICLES_VELOCITY INiagaraModule::GetVar_Particles_Velocity() -#define SYS_PARAM_PARTICLES_COLOR INiagaraModule::GetVar_Particles_Color() -#define SYS_PARAM_PARTICLES_SPRITE_ROTATION INiagaraModule::GetVar_Particles_SpriteRotation() -#define SYS_PARAM_PARTICLES_NORMALIZED_AGE INiagaraModule::GetVar_Particles_NormalizedAge() -#define SYS_PARAM_PARTICLES_SPRITE_SIZE INiagaraModule::GetVar_Particles_SpriteSize() -#define SYS_PARAM_PARTICLES_SPRITE_FACING INiagaraModule::GetVar_Particles_SpriteFacing() -#define SYS_PARAM_PARTICLES_SPRITE_ALIGNMENT INiagaraModule::GetVar_Particles_SpriteAlignment() -#define SYS_PARAM_PARTICLES_SUB_IMAGE_INDEX INiagaraModule::GetVar_Particles_SubImageIndex() -#define SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM INiagaraModule::GetVar_Particles_DynamicMaterialParameter() -#define SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_1 INiagaraModule::GetVar_Particles_DynamicMaterialParameter1() -#define SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_2 INiagaraModule::GetVar_Particles_DynamicMaterialParameter2() -#define SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_3 INiagaraModule::GetVar_Particles_DynamicMaterialParameter3() -#define SYS_PARAM_PARTICLES_SCALE INiagaraModule::GetVar_Particles_Scale() -#define SYS_PARAM_PARTICLES_LIFETIME INiagaraModule::GetVar_Particles_Lifetime() -#define SYS_PARAM_PARTICLES_MESH_ORIENTATION INiagaraModule::GetVar_Particles_MeshOrientation() -#define SYS_PARAM_PARTICLES_UV_SCALE INiagaraModule::GetVar_Particles_UVScale() -#define SYS_PARAM_PARTICLES_CAMERA_OFFSET INiagaraModule::GetVar_Particles_CameraOffset() -#define SYS_PARAM_PARTICLES_MATERIAL_RANDOM INiagaraModule::GetVar_Particles_MaterialRandom() -#define SYS_PARAM_PARTICLES_LIGHT_RADIUS INiagaraModule::GetVar_Particles_LightRadius() +#define SYS_PARAM_PARTICLES_UNIQUE_ID INiagaraModule::GetVar_Particles_UniqueID() +#define SYS_PARAM_PARTICLES_ID INiagaraModule::GetVar_Particles_ID() +#define SYS_PARAM_PARTICLES_POSITION INiagaraModule::GetVar_Particles_Position() +#define SYS_PARAM_PARTICLES_VELOCITY INiagaraModule::GetVar_Particles_Velocity() +#define SYS_PARAM_PARTICLES_COLOR INiagaraModule::GetVar_Particles_Color() +#define SYS_PARAM_PARTICLES_SPRITE_ROTATION INiagaraModule::GetVar_Particles_SpriteRotation() +#define SYS_PARAM_PARTICLES_NORMALIZED_AGE INiagaraModule::GetVar_Particles_NormalizedAge() +#define SYS_PARAM_PARTICLES_SPRITE_SIZE INiagaraModule::GetVar_Particles_SpriteSize() +#define SYS_PARAM_PARTICLES_SPRITE_FACING INiagaraModule::GetVar_Particles_SpriteFacing() +#define SYS_PARAM_PARTICLES_SPRITE_ALIGNMENT INiagaraModule::GetVar_Particles_SpriteAlignment() +#define SYS_PARAM_PARTICLES_SUB_IMAGE_INDEX INiagaraModule::GetVar_Particles_SubImageIndex() +#define SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM INiagaraModule::GetVar_Particles_DynamicMaterialParameter() +#define SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_1 INiagaraModule::GetVar_Particles_DynamicMaterialParameter1() +#define SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_2 INiagaraModule::GetVar_Particles_DynamicMaterialParameter2() +#define SYS_PARAM_PARTICLES_DYNAMIC_MATERIAL_PARAM_3 INiagaraModule::GetVar_Particles_DynamicMaterialParameter3() +#define SYS_PARAM_PARTICLES_SCALE INiagaraModule::GetVar_Particles_Scale() +#define SYS_PARAM_PARTICLES_LIFETIME INiagaraModule::GetVar_Particles_Lifetime() +#define SYS_PARAM_PARTICLES_MESH_ORIENTATION INiagaraModule::GetVar_Particles_MeshOrientation() +#define SYS_PARAM_PARTICLES_UV_SCALE INiagaraModule::GetVar_Particles_UVScale() +#define SYS_PARAM_PARTICLES_CAMERA_OFFSET INiagaraModule::GetVar_Particles_CameraOffset() +#define SYS_PARAM_PARTICLES_MATERIAL_RANDOM INiagaraModule::GetVar_Particles_MaterialRandom() +#define SYS_PARAM_PARTICLES_LIGHT_RADIUS INiagaraModule::GetVar_Particles_LightRadius() +#define SYS_PARAM_PARTICLES_LIGHT_EXPONENT INiagaraModule::GetVar_Particles_LightExponent() +#define SYS_PARAM_PARTICLES_LIGHT_ENABLED INiagaraModule::GetVar_Particles_LightEnabled() +#define SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING INiagaraModule::GetVar_Particles_LightVolumetricScattering() -#define SYS_PARAM_PARTICLES_RIBBONID INiagaraModule::GetVar_Particles_RibbonID() -#define SYS_PARAM_PARTICLES_RIBBONWIDTH INiagaraModule::GetVar_Particles_RibbonWidth() -#define SYS_PARAM_PARTICLES_RIBBONTWIST INiagaraModule::GetVar_Particles_RibbonTwist() -#define SYS_PARAM_PARTICLES_RIBBONFACING INiagaraModule::GetVar_Particles_RibbonFacing() -#define SYS_PARAM_PARTICLES_RIBBONLINKORDER INiagaraModule::GetVar_Particles_RibbonLinkOrder() +#define SYS_PARAM_PARTICLES_RIBBONID INiagaraModule::GetVar_Particles_RibbonID() +#define SYS_PARAM_PARTICLES_RIBBONWIDTH INiagaraModule::GetVar_Particles_RibbonWidth() +#define SYS_PARAM_PARTICLES_RIBBONTWIST INiagaraModule::GetVar_Particles_RibbonTwist() +#define SYS_PARAM_PARTICLES_RIBBONFACING INiagaraModule::GetVar_Particles_RibbonFacing() +#define SYS_PARAM_PARTICLES_RIBBONLINKORDER INiagaraModule::GetVar_Particles_RibbonLinkOrder() -#define SYS_PARAM_INSTANCE_ALIVE INiagaraModule::GetVar_DataInstance_Alive() +#define SYS_PARAM_INSTANCE_ALIVE INiagaraModule::GetVar_DataInstance_Alive() -#define TRANSLATOR_PARAM_BEGIN_DEFAULTS INiagaraModule::GetVar_BeginDefaults() +#define TRANSLATOR_PARAM_BEGIN_DEFAULTS INiagaraModule::GetVar_BeginDefaults() struct NIAGARA_API FNiagaraConstants { diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceCollisionQuery.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceCollisionQuery.h index 7430fe2f5a40..8cd1838cf9b7 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceCollisionQuery.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceCollisionQuery.h @@ -35,9 +35,6 @@ public: //UObject Interface virtual void PostInitProperties() override; virtual void PostLoad() override; -#if WITH_EDITOR - virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; -#endif //UObject Interface End /** Initializes the per instance data for this interface. Returns false if there was some error and the simulation should be disabled. */ @@ -48,10 +45,8 @@ public: /** Ticks the per instance data for this interface, if it has any. */ virtual bool PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds); virtual bool PerInstanceTickPostSimulate(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds); - virtual int32 PerInstanceDataSize()const { return sizeof(CQDIPerInstanceData); } - - + virtual void GetFunctions(TArray& OutFunctions)override; virtual void GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction &OutFunc) override; @@ -59,19 +54,20 @@ public: void SubmitQuery(FVectorVMContext& Context); void ReadQuery(FVectorVMContext& Context); void PerformQuery(FVectorVMContext& Context); + void PerformQuerySyncCPU(FVectorVMContext& Context); + void PerformQueryAsyncCPU(FVectorVMContext& Context); + void PerformQueryGPU(FVectorVMContext& Context); + void QuerySceneDepth(FVectorVMContext& Context); + void QueryMeshDistanceField(FVectorVMContext& Context); - virtual bool Equals(const UNiagaraDataInterface* Other) const override; virtual bool CanExecuteOnTarget(ENiagaraSimTarget Target) const override { return true; } virtual bool GetFunctionHLSL(const FName& DefinitionFunctionName, FString InstanceFunctionName, FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; virtual void GetParameterDefinitionHLSL(FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; - virtual FNiagaraDataInterfaceParametersCS* ConstructComputeParameters()const override; + virtual FNiagaraDataInterfaceParametersCS* ConstructComputeParameters() const override; - -protected: - virtual bool CopyToInternal(UNiagaraDataInterface* Destination) const override; - private: static FCriticalSection CriticalSection; + UEnum* TraceChannelEnum; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceCurlNoise.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceCurlNoise.h index 4ae56dce2dab..31d9fc83a012 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceCurlNoise.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceCurlNoise.h @@ -12,52 +12,37 @@ UCLASS(EditInlineNew, Category = "Curl Noise LUT", meta = (DisplayName = "Curl N class NIAGARA_API UNiagaraDataInterfaceCurlNoise : public UNiagaraDataInterface { GENERATED_UCLASS_BODY() - bool bGPUBufferDirty; public: UPROPERTY(EditAnywhere, Category = "Curl Noise") uint32 Seed; - + + // Precalculated when Seed changes. + FVector OffsetFromSeed; + //UObject Interface virtual void PostInitProperties()override; virtual void PostLoad() override; - virtual void BeginDestroy() override; - virtual bool IsReadyForFinishDestroy() override; #if WITH_EDITOR + virtual void PreEditChange(UProperty* PropertyAboutToChange) override; virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; #endif //UObject Interface End //UNiagaraDataInterface Interface - virtual void GetFunctions(TArray& OutFunctions)override; + virtual void GetFunctions(TArray& OutFunctions) override; virtual void GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction &OutFunc) override; virtual bool CanExecuteOnTarget(ENiagaraSimTarget Target)const override { return true; } + virtual bool Equals(const UNiagaraDataInterface* Other) const override; //UNiagaraDataInterface Interface End void SampleNoiseField(FVectorVMContext& Context); - virtual bool Equals(const UNiagaraDataInterface* Other) const override; - // GPU sim functionality virtual bool GetFunctionHLSL(const FName& DefinitionFunctionName, FString InstanceFunctionName, FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; virtual void GetParameterDefinitionHLSL(FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; - virtual FNiagaraDataInterfaceParametersCS* ConstructComputeParameters()const override; + virtual FNiagaraDataInterfaceParametersCS* ConstructComputeParameters() const override; - void ReleaseResource(); - FRWBuffer& GetGPUBuffer(); - static const FString CurlNoiseBufferName; protected: virtual bool CopyToInternal(UNiagaraDataInterface* Destination) const override; - void InitNoiseLUT(); - - VectorRegister NoiseTable[17][17][17]; - - template - void ReplicateBorder(T* DestBuffer); - - TUniquePtr GPUBuffer; - /** A fence which is used to keep track of the rendering thread releasing RHI resources. */ - FRenderCommandFence ReleaseResourcesFence; - - static const FName SampleNoiseFieldName; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceVectorField.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceVectorField.h index 457aa54a2b9f..57425512e5a8 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceVectorField.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataInterfaceVectorField.h @@ -7,35 +7,13 @@ class FNiagaraSystemInstance; -/*struct FNDIVectorField_InstanceData -{ - UVectorField* Field; - uint32 SizeX; - uint32 SizeY; - uint32 SizeZ; - float DeltaSeconds; - - FBox LocalBounds; - - FVector TilingAxes; - - FORCEINLINE_DEBUGGABLE bool ResetRequired(class UNiagaraDataInterfaceVectorField* Interface)const; - - FORCEINLINE_DEBUGGABLE bool Init(class UNiagaraDataInterfaceVectorField* Interface, FNiagaraSystemInstance* SystemInstance); - FORCEINLINE_DEBUGGABLE bool Cleanup(class UNiagaraDataInterfaceVectorField* Interface, FNiagaraSystemInstance* SystemInstance); - FORCEINLINE_DEBUGGABLE bool Tick(class UNiagaraDataInterfaceVectorField* Interface, FNiagaraSystemInstance* SystemInstance, float InDeltaSeconds); - void UpdateTransforms(const FMatrix& LocalToWorld, FMatrix& OutVolumeToWorld, FMatrix& OutWorldToVolume); - const void* Lock(); - void Unlock(); -};*/ - UCLASS(EditInlineNew, Category = "Vector Field", meta = (DisplayName = "Vector Field")) class NIAGARA_API UNiagaraDataInterfaceVectorField : public UNiagaraDataInterface { GENERATED_UCLASS_BODY() public: - /** Vector field used to sample from. */ + /** Vector field to sample from. */ UPROPERTY(EditAnywhere, Category = VectorField) UVectorField* Field; @@ -48,57 +26,47 @@ public: public: //~ UObject interface + + virtual void PostInitProperties() override; + virtual void PostLoad() override; #if WITH_EDITOR - virtual void PostInitProperties()override; virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; - virtual void PostLoad() override; + virtual void PreEditChange(UProperty* PropertyAboutToChange) override; #endif //~ UObject interface END //~ UNiagaraDataInterface interface - virtual bool InitPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance)override; - virtual void DestroyPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance)override; - virtual bool PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds) override; - virtual int32 PerInstanceDataSize()const override; - - virtual void GetFunctions(TArray& OutFunctions)override; - virtual void GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction &OutFunc)override; + // VM functionality + virtual void GetFunctions(TArray& OutFunctions) override; + virtual void GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction &OutFunc) override; virtual bool Equals(const UNiagaraDataInterface* Other) const override; - virtual bool CanExecuteOnTarget(ENiagaraSimTarget Target)const override; + virtual bool CanExecuteOnTarget(ENiagaraSimTarget Target) const override; + +#if WITH_EDITOR + // Editor functionality + virtual TArray GetErrors() override; +#endif // GPU sim functionality + virtual void GetParameterDefinitionHLSL(FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; virtual bool GetFunctionHLSL(const FName& DefinitionFunctionName, FString InstanceFunctionName, FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; - virtual void GetParameterDefinitionHLSL(FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; - virtual FNiagaraDataInterfaceParametersCS* ConstructComputeParameters()const override; + virtual FNiagaraDataInterfaceParametersCS* ConstructComputeParameters() const override; //~ UNiagaraDataInterface interface END - void SampleVectorField(FVectorVMContext& Context); - + // VM functions void GetFieldDimensions(FVectorVMContext& Context); + void GetFieldBounds(FVectorVMContext& Context); + void GetFieldTilingAxes(FVectorVMContext& Context); + void SampleVectorField(FVectorVMContext& Context); + + // + FVector GetTilingAxes() const; + FVector GetDimensions() const; + FVector GetMinBounds() const; + FVector GetMaxBounds() const; - void GetFieldBounds(FVectorVMContext& Context); - - bool bGPUBufferDirty; - - static const FString BufferBaseName; - static const FString DimentionsBaseName; - static const FString BoundsMinBaseName; - static const FString BoundsMaxBaseName; - - FRWBuffer& GetGPUBuffer(); - FORCEINLINE FVector GetDimentions()const { return FVector(SizeX, SizeY, SizeZ); } - FORCEINLINE FVector GetBoundsMin()const { return LocalBounds.Min; } - FORCEINLINE FVector GetBoundsMax()const { return LocalBounds.Max; } protected: - - const void* Lock(); - void Unlock(); + //~ UNiagaraDataInterface interface virtual bool CopyToInternal(UNiagaraDataInterface* Destination) const override; - void InitField(); - - uint32 SizeX, SizeY, SizeZ; - FVector TilingAxes; - FBox LocalBounds; - - FRWBuffer GPUBuffer; + //~ UNiagaraDataInterface interface END }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSet.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSet.h index aee801a96062..9c8039c326ae 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSet.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraDataSet.h @@ -42,6 +42,7 @@ public: void KillInstance(uint32 InstanceIdx); void CopyTo(FNiagaraDataBuffer& DestBuffer, int32 StartIdx, int32 NumInstances)const; void CopyTo(FNiagaraDataBuffer& DestBuffer)const; + void GPUCopyTo(FNiagaraDataBuffer& DestBuffer, float* GPUReadBackFloat, int* GPUReadBackInt, int32 StartIdx, int32 NumInstances)const; const TArray& GetFloatBuffer()const { return FloatData; } const TArray& GetInt32Buffer()const { return Int32Data; } @@ -86,6 +87,26 @@ public: return (int32*)GetComponentPtrInt32(ComponentIdx) + InstanceIdx; } + FORCEINLINE uint8* GetComponentPtrFloat(float* BasePtr, uint32 ComponentIdx) const + { + return (uint8*)BasePtr + FloatStride * ComponentIdx; + } + + FORCEINLINE uint8* GetComponentPtrInt32(int* BasePtr, uint32 ComponentIdx) const + { + return (uint8*)BasePtr + Int32Stride * ComponentIdx; + } + + FORCEINLINE float* GetInstancePtrFloat(float* BasePtr, uint32 ComponentIdx, uint32 InstanceIdx)const + { + return (float*)GetComponentPtrFloat(BasePtr, ComponentIdx) + InstanceIdx; + } + + FORCEINLINE int32* GetInstancePtrInt32(int* BasePtr, uint32 ComponentIdx, uint32 InstanceIdx)const + { + return (int32*)GetComponentPtrInt32(BasePtr, ComponentIdx) + InstanceIdx; + } + uint32 GetNumInstances()const { return NumInstances; } uint32 GetNumInstancesAllocated()const { return NumInstancesAllocated; } @@ -288,7 +309,7 @@ public: } } - void SetShaderParams(class FNiagaraShader *Shader, FRHICommandList &CommandList); + void SetShaderParams(class FNiagaraShader *Shader, FRHICommandList &CommandList, uint32& WriteBufferIdx, uint32& ReadBufferIdx); void UnsetShaderParams(class FNiagaraShader *Shader, FRHICommandList &CommandList); void Allocate(int32 NumInstances, bool bMaintainExisting=false) { @@ -326,6 +347,7 @@ public: } //UE_LOG(LogNiagara, Warning, TEXT("DataSetAllocate: Adding New Free IDs: %d - "), RequiredIDs - 1, ExistingNumIDs); } +#if 0 else if (RequiredIDs < ExistingNumIDs >> 1)//Configurable? { //If the max id we use has reduced significantly then we can shrink the tables. @@ -350,6 +372,7 @@ public: check(NumFreeIDs <= RequiredIDs); FreeIDsTable.SetNumUninitialized(NumFreeIDs); } +#endif else { //Drop in required size not great enough so just allocate same size. @@ -384,6 +407,18 @@ public: return CurrBuffer > 0 ? CurrBuffer - 1 : MaxBufferIdx; } + FORCEINLINE int32 GetCurrBufferIdx() const + { + return CurrBuffer; + } + + FORCEINLINE FNiagaraDataBuffer& GetDataByIndex(int32 InIdx) + { + check(InIdx < 3); + //CheckCorrectThread();//TODO: We should be able to enable these checks and have well defined GT/RT ownership but GPU sims fire these a lot currently. + return Data[InIdx]; + } + FORCEINLINE FNiagaraDataBuffer& CurrData() { //CheckCorrectThread();//TODO: We should be able to enable these checks and have well defined GT/RT ownership but GPU sims fire these a lot currently. @@ -532,6 +567,7 @@ public: void Dump(bool bCurr, int32 StartIdx = 0, int32 NumInstances = INDEX_NONE)const; void Dump(FNiagaraDataSet& Other, bool bCurr, int32 StartIdx = 0, int32 NumInstances = INDEX_NONE)const; + void DumpGPU(FNiagaraDataSet& Other, float* GPUReadBackFloat, int* GPUReadBackInt, int32 StartIdx = 0, int32 NumInstances = INDEX_NONE)const; FORCEINLINE const TArray &GetVariables() { return Variables; } void CheckForNaNs()const diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitter.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitter.h index 3e3a393c7e24..3d153d3d5876 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitter.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitter.h @@ -66,9 +66,8 @@ struct FNiagaraEventGeneratorProperties } - FNiagaraEventGeneratorProperties(FNiagaraDataSetProperties &Props, FName InEventGenerator, FName InSourceEmitter) + FNiagaraEventGeneratorProperties(FNiagaraDataSetProperties &Props, FName InEventGenerator) : ID(Props.ID.Name) - , SourceEmitter(InSourceEmitter) , SetProps(Props) { @@ -78,8 +77,8 @@ struct FNiagaraEventGeneratorProperties UPROPERTY(EditAnywhere, Category = "Event Receiver") int32 MaxEventsPerFrame; //TODO - More complex allocation so that we can grow dynamically if more space is needed ? + UPROPERTY() FName ID; - FName SourceEmitter; UPROPERTY() FNiagaraDataSetProperties SetProps; @@ -196,9 +195,18 @@ public: virtual void PostLoad() override; //End UObject Interface + /** Toggles whether or not the particles within this emitter are relative to the emitter origin or in global space. */ UPROPERTY(EditAnywhere, Category = "Emitter") bool bLocalSpace; + /** Toggles whether to globally make the random number generator be deterministic or non-deterministic. Any random calculation that is set to the emitter defaults will inherit this value. It is still possible to tweak individual random to be deterministic or not. In this case deterministic means that it will return the same results for the same configuration of the emitter as long as delta time is not variable. Any changes to the emitter's individual scripts will adjust the results. */ + UPROPERTY(EditAnywhere, Category = "Emitter") + bool bDeterminism; + + /** An emitter-based seed for the deterministic random number generator. */ + UPROPERTY(EditAnywhere, Category = "Emitter", meta = (EditCondition = "bDeterminism")) + int32 RandomSeed; + //UPROPERTY(EditAnywhere, Category = "Emitter") //float StartTime; //UPROPERTY(EditAnywhere, Category = "Emitter") @@ -256,6 +264,14 @@ public: UPROPERTY(EditAnywhere, Category = "Emitter") uint32 bRequiresPersistentIDs : 1; + /** Limits the delta time per tick to prevent simulation spikes due to frame lags. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Emitter", meta = (EditCondition = "bLimitDeltaTime")) + float MaxDeltaTimePerTick; + + /** Whether to limit the max tick delta time or not. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Emitter", meta = (InlineEditConditionToggle)) + uint32 bLimitDeltaTime : 1; + void NIAGARA_API GetScripts(TArray& OutScripts, bool bCompilableOnly = true); NIAGARA_API UNiagaraScript* GetScript(ENiagaraScriptUsage Usage, FGuid UsageId); @@ -332,6 +348,9 @@ public: void NIAGARA_API RemoveEventHandlerByUsageId(FGuid EventHandlerUsageId); + /* Gets whether or not the supplied event generator id matches an event generator which is shared between the particle spawn and update scrips. */ + bool IsEventGeneratorShared(FName EventGeneratorId) const; + protected: virtual void BeginDestroy() override; @@ -368,6 +387,9 @@ private: UPROPERTY() UNiagaraScript* GPUComputeScript; + UPROPERTY() + TArray SharedEventGeneratorIds; + #if WITH_EDITOR FOnPropertiesChanged OnPropertiesChangedDelegate; #endif diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstance.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstance.h index d7dddb83ce05..2b87c4f815b5 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstance.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstance.h @@ -47,7 +47,7 @@ public: void PreTick(); void Tick(float DeltaSeconds); - void PostProcessParticles(); + void PostTick(); bool HandleCompletion(bool bForce=false); bool RequiredPersistentID()const; @@ -73,6 +73,7 @@ public: void NIAGARA_API UpdateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel, TArray& ToBeAddedList, TArray& ToBeRemovedList); FORCEINLINE int32 GetNumParticles()const { return ParticleDataSet->GetNumInstances(); } + FORCEINLINE int32 GetTotalSpawnedParticles()const { return TotalSpawnedParticles; } NIAGARA_API const FNiagaraEmitterHandle& GetEmitterHandle() const; @@ -103,6 +104,9 @@ public: NIAGARA_API bool IsReadyToRun() const; void Dump()const; + + bool WaitForDebugInfo(); + private: void CheckForErrors(); @@ -116,6 +120,8 @@ private: uint32 Loops; int32 TickCount; + + int32 TotalSpawnedParticles; /** Typical resets must be deferred until the tick as the RT could still be using the current buffer. */ uint32 bResetPending : 1; @@ -153,6 +159,22 @@ private: FNiagaraParameterDirectBinding UpdateExecCountBinding; TArray> EventExecCountBindings; + /* + FNiagaraParameterDirectBinding SpawnTotalSpawnedParticlesBinding; + FNiagaraParameterDirectBinding UpdateTotalSpawnedParticlesBinding; + FNiagaraParameterDirectBinding GPUTotalSpawnedParticlesBinding; + */ + + FNiagaraParameterDirectBinding SpawnRandomSeedBinding; + FNiagaraParameterDirectBinding UpdateRandomSeedBinding; + FNiagaraParameterDirectBinding GPURandomSeedBinding; + + /* + FNiagaraParameterDirectBinding SpawnDeterminismBinding; + FNiagaraParameterDirectBinding UpdateDeterminismBinding; + FNiagaraParameterDirectBinding GPUDeterminismBinding; + */ + /** particle simulation data. Must be a shared ref as various things on the RT can have direct ref to it. */ FNiagaraDataSet* ParticleDataSet; @@ -166,7 +188,9 @@ private: TArray UpdateScriptEventDataSets; TArray SpawnScriptEventDataSets; TMap DataSetMap; - + + TArray UpdateEventGeneratorIsSharedByIndex; + TArray SpawnEventGeneratorIsSharedByIndex; FName OwnerSystemInstanceName; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstanceBatcher.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstanceBatcher.h index ca0dbde569b7..60cf8abacb7b 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstanceBatcher.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraEmitterInstanceBatcher.h @@ -94,11 +94,14 @@ public: void ExecuteAll(FRHICommandList &RHICmdList, FUniformBufferRHIParamRef ViewUniformBuffer); void TickSingle(FNiagaraComputeExecutionContext *Context, FRHICommandList &RHICmdList, FUniformBufferRHIParamRef ViewUniformBuffer) const; + void ProcessDebugInfo(FRHICommandList &RHICmdList, const FNiagaraComputeExecutionContext *Context) const; + void SetPrevDataStrideParams(const FNiagaraDataSet *Set, FNiagaraShader *Shader, FRHICommandList &RHICmdList) const; void SetupEventUAVs(const FNiagaraComputeExecutionContext *Context, uint32 NumInstances, FRHICommandList &RHICmdList) const; void UnsetEventUAVs(const FNiagaraComputeExecutionContext *Context, FRHICommandList &RHICmdList) const; - void SetDataInterfaceParameters(const TArray &DataInterfaces, FNiagaraShader *Shader, FRHICommandList &RHICmdList) const; + void SetDataInterfaceParameters(const TArray &DataInterfaces, FNiagaraShader *Shader, FRHICommandList &RHICmdList, const FNiagaraComputeExecutionContext *Context) const; + void UnsetDataInterfaceParameters(const TArray &DataInterfaces, FNiagaraShader* Shader, FRHICommandList &RHICmdList, const FNiagaraComputeExecutionContext *Context) const; void Run( const FNiagaraComputeExecutionContext *Context, uint32 UpdateStartInstance, diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraParameterCollection.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraParameterCollection.h index 03dadfcdaef2..ac6b16f422ba 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraParameterCollection.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraParameterCollection.h @@ -162,6 +162,8 @@ public: //~UObject interface virtual void PostLoad()override; //~UObject interface + + FName GetNamespace()const { return Namespace; } protected: void MakeNamespaceNameUnique(); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScript.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScript.h index bd4aded804bc..723b0d32ed55 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScript.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScript.h @@ -51,6 +51,15 @@ enum class ENiagaraModuleDependencyType : uint8 PostDependency }; +UENUM() +enum class ENiagaraModuleDependencyScriptConstraint : uint8 +{ + /** The module providing the dependency must be in the same script e.g. if the module requiring the dependency is in "Particle Spawn" the module providing the dependency must also be in "Particle Spawn". */ + SameScript, + /** The module providing the dependency can be in any script as long as it satisfies the dependency type, e.g. if the module requiring the dependency is in "Particle Spawn" the module providing the dependency could be in "Emitter Spawn". */ + AllScripts +}; + USTRUCT() struct FNiagaraModuleDependency { @@ -59,12 +68,24 @@ public: /** Specifies the provided id of the required dependent module (e.g. 'ProvidesNormalizedAge') */ UPROPERTY(AssetRegistrySearchable, EditAnywhere, Category = Script) FName Id; + /** Whether the dependency belongs before or after this module */ UPROPERTY(AssetRegistrySearchable, EditAnywhere, Category = Script) - ENiagaraModuleDependencyType Type; // e.g. PreDependency, - /** Detailed description of the dependency */ + ENiagaraModuleDependencyType Type; // e.g. PreDependency + + /** Specifies constraints related to the source script a modules provising a depency. */ + UPROPERTY(AssetRegistrySearchable, EditAnywhere, Category = Script) + ENiagaraModuleDependencyScriptConstraint ScriptConstraint; + + /** Detailed description of the dependency */ UPROPERTY(AssetRegistrySearchable, EditAnywhere, Category = Script, meta = (MultiLine = true)) FText Description; + + FNiagaraModuleDependency() + { + Type = ENiagaraModuleDependencyType::PreDependency; + ScriptConstraint = ENiagaraModuleDependencyScriptConstraint::SameScript; + } }; struct FNiagaraScriptDebuggerInfo @@ -72,6 +93,8 @@ struct FNiagaraScriptDebuggerInfo FNiagaraScriptDebuggerInfo(); FNiagaraScriptDebuggerInfo(FName InName, ENiagaraScriptUsage InUsage, const FGuid& InUsageId); + bool bWaitForGPU; + FName HandleName; ENiagaraScriptUsage Usage; @@ -83,6 +106,8 @@ struct FNiagaraScriptDebuggerInfo FNiagaraDataSet Frame; FNiagaraParameterStore Parameters; + + TAtomic bWritten; }; @@ -300,6 +325,15 @@ public: /** Dependencies required by this module from other modules on the stack */ UPROPERTY(EditAnywhere, Category = Script) TArray RequiredDependencies; + + /* If this script is no longer meant to be used, this option should be set.*/ + UPROPERTY(EditAnywhere, Category = "Script") + uint32 bDeprecated : 1; + + /* Which script to use if this is deprecated.*/ + UPROPERTY(EditAnywhere, Category = "Script", meta = (EditCondition = "bDeprecated")) + UNiagaraScript* DeprecationRecommendation; + #endif @@ -319,7 +353,7 @@ public: UPROPERTY(AssetRegistrySearchable, EditAnywhere, Category = Script) FText Keywords; - UPROPERTY(EditAnywhere, Category = Script) + UPROPERTY(EditAnywhere, Category = Script, DisplayName = "Script Metadata", meta = (ToolTip = "Script Metadata")) TMap ScriptMetaData; #endif diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScriptExecutionContext.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScriptExecutionContext.h index 18f4e9d622db..bd1fa9330d35 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScriptExecutionContext.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraScriptExecutionContext.h @@ -96,7 +96,18 @@ struct FNiagaraComputeExecutionContext , GPUDataReadback(nullptr) , AccumulatedSpawnRate(0) , NumIndicesPerInstance(0) + , PerInstanceData(nullptr) + , PerInstanceDataSize(0) + , PerInstanceDataInterfaceOffsets(nullptr) , PendingExecutionQueueMask(0) +#if WITH_EDITORONLY_DATA + , GPUDebugDataReadbackFloat(nullptr) + , GPUDebugDataReadbackInt(nullptr) + , GPUDebugDataReadbackCounts(nullptr) + , GPUDebugDataCurrBufferIdx(0xFFFFFFFF) + , GPUDebugDataFloatSize(0) + , GPUDebugDataIntSize(0) +#endif { } @@ -106,7 +117,26 @@ struct FNiagaraComputeExecutionContext if (GPUDataReadback) { delete GPUDataReadback; + GPUDataReadback = nullptr; } + +#if WITH_EDITORONLY_DATA + if (GPUDebugDataReadbackFloat) + { + delete GPUDebugDataReadbackFloat; + GPUDebugDataReadbackFloat = nullptr; + } + if (GPUDebugDataReadbackInt) + { + delete GPUDebugDataReadbackInt; + GPUDebugDataReadbackInt = nullptr; + } + if (GPUDebugDataReadbackCounts) + { + delete GPUDebugDataReadbackCounts; + GPUDebugDataReadbackCounts = nullptr; + } +#endif } void Reset() @@ -120,9 +150,10 @@ struct FNiagaraComputeExecutionContext ); } - void InitParams(UNiagaraScript* InGPUComputeScript, UNiagaraScript* InSpawnScript, UNiagaraScript *InUpdateScript, ENiagaraSimTarget SimTarget) + void InitParams(UNiagaraScript* InGPUComputeScript, UNiagaraScript* InSpawnScript, UNiagaraScript *InUpdateScript, ENiagaraSimTarget InSimTarget, const FString& InDebugSimName) { - CombinedParamStore.InitFromOwningContext(InGPUComputeScript, SimTarget, true); + DebugSimName = InDebugSimName; + CombinedParamStore.InitFromOwningContext(InGPUComputeScript, InSimTarget, true); GPUScript = InGPUComputeScript; SpawnScript = InSpawnScript; @@ -189,13 +220,31 @@ private: if (GPUDataReadback) { delete GPUDataReadback; + GPUDataReadback = nullptr; } - GPUDataReadback = nullptr; + +#if WITH_EDITORONLY_DATA + if (GPUDebugDataReadbackFloat) + { + delete GPUDebugDataReadbackFloat; + GPUDebugDataReadbackFloat = nullptr; + } + if (GPUDebugDataReadbackInt) + { + delete GPUDebugDataReadbackInt; + GPUDebugDataReadbackInt = nullptr; + } + if (GPUDebugDataReadbackCounts) + { + delete GPUDebugDataReadbackCounts; + GPUDebugDataReadbackCounts = nullptr; + } +#endif } public: const TArray &GetEventHandlers() const { return EventHandlerScriptProps; } - + FString DebugSimName; class FNiagaraDataSet *MainDataSet; TArrayUpdateEventWriteDataSets; TArray EventHandlerScriptProps; @@ -222,6 +271,21 @@ public: uint32 AccumulatedSpawnRate; uint32 NumIndicesPerInstance; // how many vtx indices per instance the renderer is going to have for its draw call + void* PerInstanceData; // Data stored on parent system instance + uint32 PerInstanceDataSize; // Size of data stored on parent system instance in bytes + TMap, int32>* PerInstanceDataInterfaceOffsets; + /** Ensures we only enqueue each context once per queue before they're dispatched. See SIMULATION_QUEUE_COUNT */ uint32 PendingExecutionQueueMask; + + +#if WITH_EDITORONLY_DATA + mutable FRHIGPUMemoryReadback *GPUDebugDataReadbackFloat; + mutable FRHIGPUMemoryReadback *GPUDebugDataReadbackInt; + mutable FRHIGPUMemoryReadback *GPUDebugDataReadbackCounts; + mutable int32 GPUDebugDataCurrBufferIdx; + mutable uint32 GPUDebugDataFloatSize; + mutable uint32 GPUDebugDataIntSize; + mutable TSharedPtr DebugInfo; +#endif }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraSystem.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraSystem.h index 5c6085ec9fe9..05ec7c92535e 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraSystem.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Classes/NiagaraSystem.h @@ -10,6 +10,7 @@ #include "NiagaraEmitterInstance.h" #include "NiagaraEmitterHandle.h" #include "NiagaraParameterCollection.h" +#include "NiagaraUserRedirectionParameterStore.h" #include "NiagaraSystem.generated.h" #if WITH_EDITORONLY_DATA @@ -124,9 +125,8 @@ public: } /** From the last compile, what are the variables that were exported out of the system for external use?*/ - const FNiagaraParameterStore& GetExposedParameters() const { return ExposedParameters; } - FNiagaraParameterStore& GetExposedParameters() { return ExposedParameters; } - + const FNiagaraUserRedirectionParameterStore& GetExposedParameters() const { return ExposedParameters; } + FNiagaraUserRedirectionParameterStore& GetExposedParameters() { return ExposedParameters; } /** Gets the System script which is used to populate the System parameters and parameter bindings. */ UNiagaraScript* GetSystemSpawnScript(); @@ -137,9 +137,6 @@ public: /** Are there any pending compile requests?*/ bool HasOutstandingCompilationRequests() const; - /** Returns whether this system has to be run in solo or not. */ - bool IsSolo()const; - FORCEINLINE bool NeedsWarmup()const { return WarmupTickCount > 0 && WarmupTickDelta > SMALL_NUMBER; } FORCEINLINE float GetWarmupTime()const { return WarmupTime; } FORCEINLINE int32 GetWarmupTickCount()const { return WarmupTickCount; } @@ -231,13 +228,17 @@ public: UPROPERTY(EditAnywhere, Category = "Debug") bool bDumpDebugEmitterInfo; + bool HasSystemScriptDIsWithPerInstanceData() const; + + const TArray& GetUserDINamesReadInSystemScripts() const; + private: #if WITH_EDITORONLY_DATA INiagaraModule::FMergeEmitterResults MergeChangesForEmitterHandle(FNiagaraEmitterHandle& EmitterHandle); bool QueryCompileComplete(bool bWait, bool bDoPost, bool bDoNotApply = false); #endif - void DetermineIfSolo(); + void UpdatePostCompileDIInfo(); protected: /** Handles to the emitter this System will simulate. */ @@ -270,7 +271,7 @@ protected: /** Variables exposed to the outside work for tweaking*/ UPROPERTY() - FNiagaraParameterStore ExposedParameters; + FNiagaraUserRedirectionParameterStore ExposedParameters; #if WITH_EDITORONLY_DATA @@ -299,9 +300,11 @@ protected: UPROPERTY(EditAnywhere, Category = Warmup) float WarmupTickDelta; + void InitEmitterSpawnAttributes(); UPROPERTY() - uint32 bSolo : 1; + bool bHasSystemScriptDIsWithPerInstanceData; - void InitEmitterSpawnAttributes(); + UPROPERTY() + TArray UserDINamesReadInSystemScripts; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraCollision.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraCollision.cpp index 94702e41da3c..5dc9572c5610 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraCollision.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraCollision.cpp @@ -181,13 +181,6 @@ void FNiagaraCollisionBatch::GenerateEventsFromResults(FNiagaraEmitterInstance * } } - - - - - - - int32 FNiagaraDICollisionQueryBatch::SubmitQuery(FVector Position, FVector Direction, float CollisionSize, float DeltaSeconds) { SCOPE_CYCLE_COUNTER(STAT_NiagaraCollision); @@ -229,6 +222,72 @@ int32 FNiagaraDICollisionQueryBatch::SubmitQuery(FVector Position, FVector Direc return Ret; } +int32 FNiagaraDICollisionQueryBatch::SubmitQuery(FVector StartPos, FVector EndPos, ECollisionChannel TraceChannel) +{ + SCOPE_CYCLE_COUNTER(STAT_NiagaraCollision); + if (!CollisionWorld) + { + return INDEX_NONE; + } + + FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(NiagaraAsync)); + QueryParams.OwnerTag = "Niagara"; + QueryParams.bFindInitialOverlaps = false; + QueryParams.bReturnFaceIndex = false; + QueryParams.bReturnPhysicalMaterial = true; + QueryParams.bTraceComplex = false; + QueryParams.bIgnoreTouches = true; + FTraceHandle Handle = CollisionWorld->AsyncLineTraceByChannel(EAsyncTraceType::Single, StartPos, EndPos, TraceChannel, QueryParams, FCollisionResponseParams::DefaultResponseParam, nullptr, TraceID); + FNiagaraCollisionTrace Trace; + Trace.CollisionTraceHandle = Handle; + Trace.SourceParticleIndex = TraceID; + + int32 TraceIdx = CollisionTraces[GetWriteBufferIdx()].Add(Trace); + IdToTraceIdx[GetWriteBufferIdx()].Add(TraceID) = TraceIdx; + + int32 Ret = TraceID; + TraceID++; + return Ret; +} + +bool FNiagaraDICollisionQueryBatch::PerformQuery(FVector StartPos, FVector EndPos, FNiagaraDICollsionQueryResult &Result, ECollisionChannel TraceChannel) +{ + SCOPE_CYCLE_COUNTER(STAT_NiagaraCollision); + if (!CollisionWorld) + { + return false; + } + + FCollisionQueryParams QueryParams(SCENE_QUERY_STAT(NiagaraSync)); + QueryParams.OwnerTag = "Niagara"; + QueryParams.bFindInitialOverlaps = false; + QueryParams.bReturnFaceIndex = false; + QueryParams.bReturnPhysicalMaterial = true; + QueryParams.bTraceComplex = false; + QueryParams.bIgnoreTouches = true; + FHitResult TraceResult; + bool ValidHit = CollisionWorld->LineTraceSingleByChannel(TraceResult, StartPos, EndPos, TraceChannel, QueryParams); + if (ValidHit) + { + Result.IsInsideMesh = TraceResult.bStartPenetrating; + Result.CollisionPos = TraceResult.ImpactPoint; + Result.CollisionNormal = TraceResult.ImpactNormal; + if (TraceResult.PhysMaterial.IsValid()) + { + Result.PhysicalMaterialIdx = TraceResult.PhysMaterial->GetUniqueID(); + Result.Friction = TraceResult.PhysMaterial->Friction; + Result.Restitution = TraceResult.PhysMaterial->Restitution; + } + else + { + Result.PhysicalMaterialIdx = -1; + Result.Friction = 0.0f; + Result.Restitution = 0.0f; + } + } + + return ValidHit; +} bool FNiagaraDICollisionQueryBatch::GetQueryResult(uint32 InTraceID, FNiagaraDICollsionQueryResult &Result) { @@ -262,7 +321,7 @@ bool FNiagaraDICollisionQueryBatch::GetQueryResult(uint32 InTraceID, FNiagaraDIC { // grab the first hit that blocks FHitResult *Hit = FHitResult::GetFirstBlockingHit(CurData.OutHits); - if (Hit && Hit->IsValidBlockingHit()) + if (Hit && Hit->bBlockingHit) { /* FNiagaraCollisionEventPayload Event; @@ -280,6 +339,7 @@ bool FNiagaraDICollisionQueryBatch::GetQueryResult(uint32 InTraceID, FNiagaraDIC Payloads.Add(Event); */ + Result.IsInsideMesh = Hit->bStartPenetrating; Result.CollisionPos = Hit->ImpactPoint;// -NormVel*(CurTrace.CollisionSize / 2); Result.CollisionNormal = Hit->ImpactNormal; Result.TraceID = InTraceID; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraCommon.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraCommon.cpp index 7c22624bbdce..57745a8bfa9b 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraCommon.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraCommon.cpp @@ -273,7 +273,7 @@ void FNiagaraUtilities::CollectScriptDataInterfaceParameters(const UObject& Owne bool FNiagaraScriptDataInterfaceCompileInfo::CanExecuteOnTarget(ENiagaraSimTarget SimTarget) const { - check(IsInGameThread()); + // Note that this can be called on non-game threads. We ensure that the data interface CDO object is already in existence at application init time. UNiagaraDataInterface* Obj = GetDefaultDataInterface(); if (Obj) { @@ -283,28 +283,13 @@ bool FNiagaraScriptDataInterfaceCompileInfo::CanExecuteOnTarget(ENiagaraSimTarge return false; } -bool FNiagaraScriptDataInterfaceCompileInfo::IsSystemSolo() const -{ - check(IsInGameThread()); - if (Name.ToString().StartsWith("User.")) - { - return true; - } - - UNiagaraDataInterface* Obj = GetDefaultDataInterface(); - if (Obj && Obj->PerInstanceDataSize() > 0) - { - return true; - } - return false; -} - UNiagaraDataInterface* FNiagaraScriptDataInterfaceCompileInfo::GetDefaultDataInterface() const { - check(IsInGameThread()); - UNiagaraDataInterface* Obj = CastChecked(const_cast(Type.GetClass())->GetDefaultObject(true)); + // Note that this can be called on non-game threads. We ensure that the data interface CDO object is already in existence at application init time, so we don't allow this to be auto-created. + UNiagaraDataInterface* Obj = CastChecked(const_cast(Type.GetClass())->GetDefaultObject(false)); return Obj; } + #if WITH_EDITORONLY_DATA void FNiagaraUtilities::PrepareRapidIterationParameters(const TArray& Scripts, const TMap& ScriptDependencyMap, const TMap& ScriptToEmitterNameMap) { @@ -350,7 +335,7 @@ void FNiagaraUtilities::PrepareRapidIterationParameters(const TArray& SourceParameterOffsets = Script->RapidIterationParameters.GetParameterOffests(); + const TMap& SourceParameterOffsets = Script->RapidIterationParameters.GetParameterOffsets(); for (auto ParameterOffsetIt = SourceParameterOffsets.CreateConstIterator(); ParameterOffsetIt; ++ParameterOffsetIt) { const FNiagaraVariable& SourceParameter = ParameterOffsetIt.Key(); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponent.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponent.cpp index b6c87ca87577..e29f2e6810cc 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponent.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraComponent.cpp @@ -495,7 +495,7 @@ bool UNiagaraComponent::InitializeSystem() #if WITH_EDITORONLY_DATA OnSystemInstanceChangedDelegate.Broadcast(); #endif - SystemInstance->Init(GetAsset(), bForceSolo); + SystemInstance->Init(bForceSolo); return true; } return false; @@ -1018,6 +1018,7 @@ void UNiagaraComponent::PostLoad() { Asset->ConditionalPostLoad(); #if WITH_EDITOR + PostLoadNormalizeOverrideNames(); SynchronizeWithSourceSystem(); AssetExposedParametersChangedHandle = Asset->GetExposedParameters().AddOnChangedHandler( FNiagaraParameterStore::FOnChanged::FDelegate::CreateUObject(this, &UNiagaraComponent::AssetExposedParametersChanged)); @@ -1075,19 +1076,22 @@ void UNiagaraComponent::SynchronizeWithSourceSystem() { OverrideParameters.Empty(); EditorOverridesValue.Empty(); +#if WITH_EDITORONLY_DATA + OnSynchronizedWithAssetParametersDelegate.Broadcast(); +#endif return; } TArray SourceVars; Asset->GetExposedParameters().GetParameters(SourceVars); - for (FNiagaraVariable& Param : SourceVars) { OverrideParameters.AddParameter(Param, true); } TArray ExistingVars; - OverrideParameters.GetParameters(ExistingVars); + OverrideParameters.GetUserParameters(ExistingVars); + Asset->GetExposedParameters().GetUserParameters(SourceVars); for (FNiagaraVariable ExistingVar : ExistingVars) { @@ -1118,6 +1122,7 @@ void UNiagaraComponent::SynchronizeWithSourceSystem() void UNiagaraComponent::AssetExposedParametersChanged() { SynchronizeWithSourceSystem(); + ReinitializeSystem(); } #endif @@ -1174,6 +1179,19 @@ void UNiagaraComponent::SetMaxSimTime(float InMaxTime) } #if WITH_EDITOR + +void UNiagaraComponent::PostLoadNormalizeOverrideNames() +{ + TMap ValueMap; + for (TPair Pair : EditorOverridesValue) + { + bool IsOldUserParam = Pair.Key.ToString().StartsWith(TEXT("User.")); + FName ValueName = IsOldUserParam ? (*Pair.Key.ToString().RightChop(5)) : Pair.Key; + ValueMap.Add(ValueName, Pair.Value); + } + EditorOverridesValue = ValueMap; +} + bool UNiagaraComponent::IsParameterValueOverriddenLocally(const FName& InParamName) { bool* FoundVar = EditorOverridesValue.Find(InParamName); @@ -1203,9 +1221,9 @@ void UNiagaraComponent::SetParameterValueOverriddenLocally(const FNiagaraVariabl Asset->GetExposedParameters().CopyParameterData(OverrideParameters, InParam); } - if (bRequiresSystemInstanceReset) + if (bRequiresSystemInstanceReset && SystemInstance) { - SystemInstance->Reset(FNiagaraSystemInstance::EResetMode::ResetSystem, true); + SystemInstance->Reset(FNiagaraSystemInstance::EResetMode::ResetAll, true); } } @@ -1229,8 +1247,15 @@ void UNiagaraComponent::SetAsset(UNiagaraSystem* InAsset) #if WITH_EDITOR SynchronizeWithSourceSystem(); - AssetExposedParametersChangedHandle = Asset->GetExposedParameters().AddOnChangedHandler( - FNiagaraParameterStore::FOnChanged::FDelegate::CreateUObject(this, &UNiagaraComponent::AssetExposedParametersChanged)); + if (Asset != nullptr) + { + AssetExposedParametersChangedHandle = Asset->GetExposedParameters().AddOnChangedHandler( + FNiagaraParameterStore::FOnChanged::FDelegate::CreateUObject(this, &UNiagaraComponent::AssetExposedParametersChanged)); + } + else + { + AssetExposedParametersChangedHandle.Reset(); + } #endif //Force a reinit. diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraConstants.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraConstants.cpp index ea881f30ad46..a46ce0261ee5 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraConstants.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraConstants.cpp @@ -35,6 +35,8 @@ void FNiagaraConstants::Init() SystemParameters.Add(SYS_PARAM_ENGINE_Y_AXIS); SystemParameters.Add(SYS_PARAM_ENGINE_Z_AXIS); + SystemParameters.Add(SYS_PARAM_ENGINE_ROTATION); + SystemParameters.Add(SYS_PARAM_ENGINE_LOCAL_TO_WORLD); SystemParameters.Add(SYS_PARAM_ENGINE_WORLD_TO_LOCAL); SystemParameters.Add(SYS_PARAM_ENGINE_LOCAL_TO_WORLD_TRANSPOSED); @@ -49,14 +51,18 @@ void FNiagaraConstants::Init() SystemParameters.Add(SYS_PARAM_ENGINE_EXEC_COUNT); SystemParameters.Add(SYS_PARAM_ENGINE_EMITTER_NUM_PARTICLES); + SystemParameters.Add(SYS_PARAM_ENGINE_EMITTER_TOTAL_SPAWNED_PARTICLES); SystemParameters.Add(SYS_PARAM_ENGINE_SYSTEM_NUM_EMITTERS_ALIVE); SystemParameters.Add(SYS_PARAM_ENGINE_SYSTEM_NUM_EMITTERS); SystemParameters.Add(SYS_PARAM_ENGINE_NUM_SYSTEM_INSTANCES); SystemParameters.Add(SYS_PARAM_ENGINE_GLOBAL_SPAWN_COUNT_SCALE); SystemParameters.Add(SYS_PARAM_ENGINE_GLOBAL_SYSTEM_COUNT_SCALE); SystemParameters.Add(SYS_PARAM_ENGINE_SYSTEM_AGE); + SystemParameters.Add(SYS_PARAM_ENGINE_SYSTEM_TICK_COUNT); SystemParameters.Add(SYS_PARAM_EMITTER_AGE); SystemParameters.Add(SYS_PARAM_EMITTER_LOCALSPACE); + SystemParameters.Add(SYS_PARAM_EMITTER_DETERMINISM); + SystemParameters.Add(SYS_PARAM_EMITTER_RANDOM_SEED); SystemParameters.Add(SYS_PARAM_EMITTER_SPAWN_GROUP); } @@ -74,6 +80,7 @@ void FNiagaraConstants::Init() UpdatedSystemParameters.Add(FName(TEXT("System X Axis")), SYS_PARAM_ENGINE_X_AXIS); UpdatedSystemParameters.Add(FName(TEXT("System Y Axis")), SYS_PARAM_ENGINE_Y_AXIS); UpdatedSystemParameters.Add(FName(TEXT("System Z Axis")), SYS_PARAM_ENGINE_Z_AXIS); + UpdatedSystemParameters.Add(FName(TEXT("System Rotation")), SYS_PARAM_ENGINE_ROTATION); UpdatedSystemParameters.Add(FName(TEXT("System Local To World")), SYS_PARAM_ENGINE_LOCAL_TO_WORLD); UpdatedSystemParameters.Add(FName(TEXT("System World To Local")), SYS_PARAM_ENGINE_WORLD_TO_LOCAL); @@ -93,11 +100,14 @@ void FNiagaraConstants::Init() UpdatedSystemParameters.Add(FName(TEXT("Delta Time")), SYS_PARAM_ENGINE_DELTA_TIME); UpdatedSystemParameters.Add(FName(TEXT("Emitter Age")), SYS_PARAM_EMITTER_AGE); UpdatedSystemParameters.Add(FName(TEXT("Emitter Local Space")), SYS_PARAM_EMITTER_LOCALSPACE); + UpdatedSystemParameters.Add(FName(TEXT("Emitter Random Seed")), SYS_PARAM_EMITTER_RANDOM_SEED); + UpdatedSystemParameters.Add(FName(TEXT("Emitter Determinism")), SYS_PARAM_EMITTER_DETERMINISM); UpdatedSystemParameters.Add(FName(TEXT("Effect Position")), SYS_PARAM_ENGINE_POSITION); UpdatedSystemParameters.Add(FName(TEXT("Effect Velocity")), SYS_PARAM_ENGINE_VELOCITY); UpdatedSystemParameters.Add(FName(TEXT("Effect X Axis")), SYS_PARAM_ENGINE_X_AXIS); UpdatedSystemParameters.Add(FName(TEXT("Effect Y Axis")), SYS_PARAM_ENGINE_Y_AXIS); UpdatedSystemParameters.Add(FName(TEXT("Effect Z Axis")), SYS_PARAM_ENGINE_Z_AXIS); + UpdatedSystemParameters.Add(FName(TEXT("Effect Rotation")), SYS_PARAM_ENGINE_ROTATION); UpdatedSystemParameters.Add(FName(TEXT("Effect Local To World")), SYS_PARAM_ENGINE_LOCAL_TO_WORLD); UpdatedSystemParameters.Add(FName(TEXT("Effect World To Local")), SYS_PARAM_ENGINE_WORLD_TO_LOCAL); @@ -118,6 +128,7 @@ void FNiagaraConstants::Init() SystemStrMap.Add(SYS_PARAM_ENGINE_TIME, LOCTEXT("EngineTimeDesc", "Time in seconds since level began play, but IS paused when the game is paused, and IS dilated/clamped.")); SystemStrMap.Add(SYS_PARAM_ENGINE_REAL_TIME, LOCTEXT("EngineRealTimeDesc", "Time in seconds since level began play, but IS NOT paused when the game is paused, and IS NOT dilated/clamped.")); SystemStrMap.Add(SYS_PARAM_ENGINE_SYSTEM_AGE, LOCTEXT("EngineSystemTimeDesc", "Time in seconds since the system was first created. Managed by the NiagaraSystemInstance in code.")); + SystemStrMap.Add(SYS_PARAM_ENGINE_SYSTEM_TICK_COUNT, LOCTEXT("EngineSystemTickCount", "The current tick of this system simulation.")); SystemStrMap.Add(SYS_PARAM_ENGINE_POSITION, LOCTEXT("EnginePositionDesc", "The owning component's position in world space.")); SystemStrMap.Add(SYS_PARAM_ENGINE_SCALE, LOCTEXT("EngineScaleDesc", "The owning component's scale in world space.")); @@ -125,6 +136,7 @@ void FNiagaraConstants::Init() SystemStrMap.Add(SYS_PARAM_ENGINE_X_AXIS, LOCTEXT("XAxisDesc", "The X-axis of the owning component.")); SystemStrMap.Add(SYS_PARAM_ENGINE_Y_AXIS, LOCTEXT("YAxisDesc", "The Y-axis of the owning component.")); SystemStrMap.Add(SYS_PARAM_ENGINE_Z_AXIS, LOCTEXT("ZAxisDesc", "The Z-axis of the owning component.")); + SystemStrMap.Add(SYS_PARAM_ENGINE_ROTATION, LOCTEXT("EngineRotationDesc", "The owning component's rotation in world space.")); SystemStrMap.Add(SYS_PARAM_ENGINE_LOCAL_TO_WORLD, LOCTEXT("LocalToWorldDesc", "Owning component's local space to world space transform matrix.")); SystemStrMap.Add(SYS_PARAM_ENGINE_WORLD_TO_LOCAL, LOCTEXT("WorldToLocalDesc", "Owning component's world space to local space transform matrix.")); @@ -139,6 +151,7 @@ void FNiagaraConstants::Init() SystemStrMap.Add(SYS_PARAM_ENGINE_EXEC_COUNT, LOCTEXT("ExecCountDesc", "The index of this particle in the read buffer.")); SystemStrMap.Add(SYS_PARAM_ENGINE_EMITTER_NUM_PARTICLES, LOCTEXT("EmitterNumParticles", "The number of particles for this emitter at the beginning of simulation. Should only be used in Emitter scripts.")); + SystemStrMap.Add(SYS_PARAM_ENGINE_EMITTER_TOTAL_SPAWNED_PARTICLES, LOCTEXT("EmitterTotalSpawnedParticles", "The total number of particles spawned for this emitter at the beginning of this simulation. Should only be used by the particle spawn script the assign unique IDs.")); SystemStrMap.Add(SYS_PARAM_ENGINE_SYSTEM_NUM_EMITTERS_ALIVE, LOCTEXT("SystemNumEmittersAlive", "The number of emitters still alive attached to this system. Should only be used in System scripts.")); SystemStrMap.Add(SYS_PARAM_ENGINE_SYSTEM_NUM_EMITTERS, LOCTEXT("SystemNumEmitters", "The number of emitters attached to this system. Should only be used in System scripts.")); SystemStrMap.Add(SYS_PARAM_ENGINE_NUM_SYSTEM_INSTANCES, LOCTEXT("SystemNumInstances", "The number of instances of the this system currently ticking. Should only be used in System scripts.")); @@ -148,6 +161,7 @@ void FNiagaraConstants::Init() if (Attributes.Num() == 0) { + Attributes.Add(SYS_PARAM_PARTICLES_UNIQUE_ID); Attributes.Add(SYS_PARAM_PARTICLES_ID); Attributes.Add(SYS_PARAM_PARTICLES_POSITION); Attributes.Add(SYS_PARAM_PARTICLES_VELOCITY); @@ -201,6 +215,8 @@ void FNiagaraConstants::Init() AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_UV_SCALE, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_UV_SCALE)); AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_MATERIAL_RANDOM, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_MATERIAL_RANDOM)); AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_LIGHT_RADIUS, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_LIGHT_RADIUS)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_LIGHT_EXPONENT, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_LIGHT_EXPONENT)); + AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING)); AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONID, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_RIBBONID)); AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONWIDTH, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_RIBBONWIDTH)); AttrDataSetKeyMap.Add(SYS_PARAM_PARTICLES_RIBBONTWIST, GetAttributeAsDataSetKey(SYS_PARAM_PARTICLES_RIBBONTWIST)); @@ -309,6 +325,16 @@ void FNiagaraConstants::Init() Var.SetValue(100.0f); AttrDefaultsValueMap.Add(SYS_PARAM_PARTICLES_LIGHT_RADIUS, Var); + AttrDefaultsStrMap.Add(SYS_PARAM_PARTICLES_LIGHT_EXPONENT, TEXT("10.0")); + Var = SYS_PARAM_PARTICLES_LIGHT_EXPONENT; + Var.SetValue(10.0f); + AttrDefaultsValueMap.Add(SYS_PARAM_PARTICLES_LIGHT_EXPONENT, Var); + + AttrDefaultsStrMap.Add(SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING, TEXT("1.0")); + Var = SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING; + Var.SetValue(1.0f); + AttrDefaultsValueMap.Add(SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING, Var); + AttrDefaultsStrMap.Add(SYS_PARAM_PARTICLES_RIBBONID, TEXT("0")); Var = SYS_PARAM_PARTICLES_RIBBONID; Var.SetValue(FNiagaraID()); @@ -358,6 +384,8 @@ void FNiagaraConstants::Init() AttrDescStrMap.Add(SYS_PARAM_PARTICLES_UV_SCALE, LOCTEXT("UVScalerParamDesc", "Used to multiply the generated UVs for Sprite renderers.")); AttrDescStrMap.Add(SYS_PARAM_PARTICLES_MATERIAL_RANDOM, LOCTEXT("MaterialRandomParamDesc", "Used to drive the Particle Random node in the Material Editor. Without this set, any Particle Randoms will get 0.0.")); AttrDescStrMap.Add(SYS_PARAM_PARTICLES_LIGHT_RADIUS, LOCTEXT("LightRadiusParamDesc", "Used to drive the radius of the light when using a Light renderer.")); + AttrDescStrMap.Add(SYS_PARAM_PARTICLES_LIGHT_EXPONENT, LOCTEXT("LightExponentParamDesc", "Used to drive the attenuation of the light when using a Light renderer without inverse squared falloff enabled.")); + AttrDescStrMap.Add(SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING, LOCTEXT("LightVolumetricScatteringParamDesc", "Used to drive the volumetric scattering intensity of the light when using a Light renderer.")); AttrDescStrMap.Add(SYS_PARAM_INSTANCE_ALIVE, LOCTEXT("AliveParamDesc", "Used to determine whether or not this particle instance is still valid or if it can be deleted.")); AttrDescStrMap.Add(SYS_PARAM_PARTICLES_RIBBONID, LOCTEXT("RibbonIDDesc", "Sets the ribbon id for a particle. Particles with the same ribbon id will be connected into a ribbon.")); AttrDescStrMap.Add(SYS_PARAM_PARTICLES_RIBBONWIDTH, LOCTEXT("RibbonWidthDesc", "Sets the ribbon width for a particle, in UE4 units.")); @@ -365,6 +393,7 @@ void FNiagaraConstants::Init() AttrDescStrMap.Add(SYS_PARAM_PARTICLES_RIBBONFACING, LOCTEXT("RibbonFacingDesc", "Sets the facing vector of the ribbon at the particle position.")); AttrDescStrMap.Add(SYS_PARAM_PARTICLES_RIBBONLINKORDER, LOCTEXT("RibbonLinkOrderDesc", "Explicit order for linking particles within a ribbon. Particles of the same ribbon id will be connected into a ribbon in incrementing order of this attribute value.")); AttrDescStrMap.Add(SYS_PARAM_PARTICLES_ID, LOCTEXT("IDDesc", "Engine managed particle attribute that is a persistent ID for each particle.")); + AttrDescStrMap.Add(SYS_PARAM_PARTICLES_UNIQUE_ID, LOCTEXT("UniqueIDDesc", "Engine managed particle attribute that is a unique ID for each particle. The ID is incremented for each new particle spawned.")); } if (AttrMetaData.Num() == 0) @@ -394,8 +423,11 @@ void FNiagaraConstants::Init() if (EngineManagedAttributes.Num() == 0) { EngineManagedAttributes.Add(SYS_PARAM_PARTICLES_ID); + // NOTE(mv): UniqueID needs to be distinct from ID, as the latter is not guaranteed to be contiguous and will reuse labels + // It is unique and sequential, never resetting until the simulation is reset. + // It needs to be engine managed, otherwise the scripts cannot write to it when it isn't referenced in any scripts. + EngineManagedAttributes.Add(SYS_PARAM_PARTICLES_UNIQUE_ID); } - } const TArray& FNiagaraConstants::GetEngineConstants() diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterface.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterface.cpp index e2f68e66a851..67dfe673e9d5 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterface.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterface.cpp @@ -187,7 +187,7 @@ struct FNiagaraDataInterfaceParametersCS_Curve : public FNiagaraDataInterfacePar Ar << CurveLUT; } - virtual void Set(FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface) const override + virtual void Set(FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface, void* PerInstanceData) const override { check(IsInRenderingThread()); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCollisionQuery.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCollisionQuery.cpp index 75c1cd351dbc..631b1a8385ee 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCollisionQuery.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCollisionQuery.cpp @@ -5,6 +5,7 @@ #include "NiagaraCustomVersion.h" #include "NiagaraWorldManager.h" #include "ShaderParameterUtils.h" +#include "GlobalDistanceFieldParameters.h" #include "Shader.h" ////////////////////////////////////////////////////////////////////////// @@ -15,6 +16,7 @@ FCriticalSection UNiagaraDataInterfaceCollisionQuery::CriticalSection; UNiagaraDataInterfaceCollisionQuery::UNiagaraDataInterfaceCollisionQuery(FObjectInitializer const& ObjectInitializer) : Super(ObjectInitializer) { + TraceChannelEnum = FindObject(ANY_PACKAGE, TEXT("ECollisionChannel"), true); } bool UNiagaraDataInterfaceCollisionQuery::InitPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* InSystemInstance) @@ -47,35 +49,6 @@ void UNiagaraDataInterfaceCollisionQuery::PostLoad() const int32 NiagaraVer = GetLinkerCustomVersion(FNiagaraCustomVersion::GUID); } -#if WITH_EDITOR - -void UNiagaraDataInterfaceCollisionQuery::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) -{ - Super::PostEditChangeProperty(PropertyChangedEvent); -} - -#endif - - -bool UNiagaraDataInterfaceCollisionQuery::CopyToInternal(UNiagaraDataInterface* Destination) const -{ - if (!Super::CopyToInternal(Destination)) - { - return false; - } - return true; -} - -bool UNiagaraDataInterfaceCollisionQuery::Equals(const UNiagaraDataInterface* Other) const -{ - if (!Super::Equals(Other)) - { - return false; - } - return true; -} - - void UNiagaraDataInterfaceCollisionQuery::GetFunctions(TArray& OutFunctions) { FNiagaraFunctionSignature Sig3; @@ -121,6 +94,78 @@ void UNiagaraDataInterfaceCollisionQuery::GetFunctions(TArray= 0.0f && d_front <= 0.0f && VelocityDot < 0.0f)\n\ + {\n\ + OutCollisionValid = true;\n\ + Out_CollisionPos = In_SamplePos + (WorldNormal*d_back);\n\ + Out_CollisionNormal = WorldNormal;\n\ + }\n\ + }\n\ + }\ + \n}\n\n"); + OutHLSL += TEXT("void ") + DistanceFieldFunction + TEXT("(in float3 InPosition, in float3 In_TraceEndPos, out bool OutCollisionValid, out float3 Out_CollisionPos, out float3 Out_CollisionNormal)\n{\n\ + float DistanceToNearestSurface = GetDistanceToNearestSurfaceGlobal(InPosition);\n\ + if (DistanceToNearestSurface < length(In_TraceEndPos - InPosition))\n\ + {\n\ + OutCollisionValid = true;\n\ + Out_CollisionNormal = normalize(GetDistanceFieldGradientGlobal(InPosition));\n\ + Out_CollisionPos = InPosition - Out_CollisionNormal * DistanceToNearestSurface;\n\ + }\n\ + else\n\ + {\n\ + OutCollisionValid = false;\n\ + Out_CollisionNormal = float3(0.0, 0.0, 1.0);\n\ + Out_CollisionPos = InPosition;\n\ + }\n}\n\n"); + OutHLSL += TEXT("void ") + InstanceFunctionName + TEXT("(in float3 In_SamplePos, in float3 In_TraceEndPos, in float CollisionDepthBounds, ") + + TEXT("in float ParticleRadius, in bool UseMeshDistanceField, out bool OutCollisionValid, out float3 Out_CollisionPos, out float3 Out_CollisionNormal) \n{\n"); + OutHLSL += TEXT("\ + if (UseMeshDistanceField)\n\ + {\n\ + ") + DistanceFieldFunction + TEXT("(In_SamplePos, In_TraceEndPos, OutCollisionValid, Out_CollisionPos, Out_CollisionNormal);\n\ + }\n\ + else\n\ + {\n\ + ") + SceneDepthFunction + TEXT("(In_SamplePos, In_TraceEndPos, CollisionDepthBounds, ParticleRadius, OutCollisionValid, Out_CollisionPos, Out_CollisionNormal);\n\ + }\n}\n\n"); + } + else if (DefinitionFunctionName == TEXT("QuerySceneDepthGPU")) + { + OutHLSL += TEXT("void ") + InstanceFunctionName + TEXT("(in float3 In_SamplePos, out float Out_SceneDepth, out float3 Out_CameraPosWorld, out bool Out_IsInsideView, out float3 Out_WorldPos, out float3 Out_WorldNormal) \n{\n"); + OutHLSL += TEXT("\ + Out_SceneDepth = -1;\n\ + Out_WorldPos = float3(0.0, 0.0, 0.0);\n\ + Out_WorldNormal = float3(0.0, 0.0, 1.0);\n\ + Out_IsInsideView = true;\n\ + Out_CameraPosWorld.xyz = View.WorldCameraOrigin.xyz;\n\ + float4 SamplePosition = float4(In_SamplePos + View.PreViewTranslation, 1);\n\ + float4 ClipPosition = mul(SamplePosition, View.TranslatedWorldToClip);\n\ + float2 ScreenPosition = ClipPosition.xy / ClipPosition.w;\n\ + // Check if the sample is inside the view.\n\ + if (all(abs(ScreenPosition.xy) <= float2(1, 1)))\n\ + {\n\ + // Sample the depth buffer to get a world position near the sample position.\n\ + float2 ScreenUV = ScreenPosition * View.ScreenPositionScaleBias.xy + View.ScreenPositionScaleBias.wz;\n\ + float SceneDepth = CalcSceneDepth(ScreenUV);\n\ + Out_SceneDepth = SceneDepth;\n\ + // Reconstruct world position.\n\ + Out_WorldPos = WorldPositionFromSceneDepth(ScreenPosition.xy, SceneDepth);\n\ + // Sample the normal buffer\n\ + Out_WorldNormal = Texture2DSampleLevel(SceneTexturesStruct.GBufferATexture, SceneTexturesStruct.GBufferATextureSampler, ScreenUV, 0).xyz * 2.0 - 1.0;\n\ + }\n\ + else\n\ + {\n\ + Out_IsInsideView = false;\n\ + }\n}\n\n"); + } + else if (DefinitionFunctionName == TEXT("QueryMeshDistanceFieldGPU")) + { + OutHLSL += TEXT("void ") + InstanceFunctionName + TEXT("(in float3 In_SamplePos, out float Out_DistanceToNearestSurface, out float3 Out_FieldGradient) \n{\n"); + OutHLSL += TEXT("\ + Out_DistanceToNearestSurface = GetDistanceToNearestSurfaceGlobal(In_SamplePos);\n\ + Out_FieldGradient = GetDistanceFieldGradientGlobal(In_SamplePos);\ + \n}\n\n"); + } return true; } @@ -204,6 +344,11 @@ void UNiagaraDataInterfaceCollisionQuery::GetParameterDefinitionHLSL(FNiagaraDat DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceCollisionQuery, SubmitQuery); DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceCollisionQuery, ReadQuery); DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceCollisionQuery, PerformQuery); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceCollisionQuery, PerformQuerySyncCPU); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceCollisionQuery, PerformQueryAsyncCPU); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceCollisionQuery, PerformQueryGPU); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceCollisionQuery, QuerySceneDepth); +DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceCollisionQuery, QueryMeshDistanceField); void UNiagaraDataInterfaceCollisionQuery::GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction &OutFunc) { @@ -220,6 +365,26 @@ void UNiagaraDataInterfaceCollisionQuery::GetVMExternalFunction(const FVMExterna { NDI_FUNC_BINDER(UNiagaraDataInterfaceCollisionQuery, PerformQuery)::Bind(this, OutFunc); } + else if (BindingInfo.Name == TEXT("PerformCollisionQuerySyncCPU")) + { + NDI_FUNC_BINDER(UNiagaraDataInterfaceCollisionQuery, PerformQuerySyncCPU)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == TEXT("PerformCollisionQueryAsyncCPU")) + { + NDI_FUNC_BINDER(UNiagaraDataInterfaceCollisionQuery, PerformQueryAsyncCPU)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == TEXT("PerformCollisionQueryGPUShader")) + { + NDI_FUNC_BINDER(UNiagaraDataInterfaceCollisionQuery, PerformQueryGPU)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == TEXT("QuerySceneDepthGPU")) + { + NDI_FUNC_BINDER(UNiagaraDataInterfaceCollisionQuery, QuerySceneDepth)::Bind(this, OutFunc); + } + else if (BindingInfo.Name == TEXT("QueryMeshDistanceFieldGPU")) + { + NDI_FUNC_BINDER(UNiagaraDataInterfaceCollisionQuery, QueryMeshDistanceField)::Bind(this, OutFunc); + } else { UE_LOG(LogNiagara, Error, TEXT("Could not find data interface external function. %s\n"), @@ -326,6 +491,240 @@ void UNiagaraDataInterfaceCollisionQuery::PerformQuery(FVectorVMContext& Context } +void UNiagaraDataInterfaceCollisionQuery::PerformQuerySyncCPU(FVectorVMContext & Context) +{ + VectorVM::FExternalFuncInputHandler StartPosParamX(Context); + VectorVM::FExternalFuncInputHandler StartPosParamY(Context); + VectorVM::FExternalFuncInputHandler StartPosParamZ(Context); + + VectorVM::FExternalFuncInputHandler EndPosParamX(Context); + VectorVM::FExternalFuncInputHandler EndPosParamY(Context); + VectorVM::FExternalFuncInputHandler EndPosParamZ(Context); + + VectorVM::FExternalFuncInputHandler TraceChannelParam(Context); + + VectorVM::FUserPtrHandler InstanceData(Context); + + VectorVM::FExternalFuncRegisterHandler OutQueryValid(Context); + VectorVM::FExternalFuncRegisterHandler OutInsideMesh(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionPosX(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionPosY(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionPosZ(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionNormX(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionNormY(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionNormZ(Context); + VectorVM::FExternalFuncRegisterHandler OutFriction(Context); + VectorVM::FExternalFuncRegisterHandler OutRestitution(Context); + + FScopeLock ScopeLock(&CriticalSection); + for (int32 i = 0; i < Context.NumInstances; ++i) + { + FVector Pos(StartPosParamX.GetAndAdvance(), StartPosParamY.GetAndAdvance(), StartPosParamZ.GetAndAdvance()); + FVector Dir(EndPosParamX.GetAndAdvance(), EndPosParamY.GetAndAdvance(), EndPosParamZ.GetAndAdvance()); + ECollisionChannel TraceChannel = TraceChannelParam.GetAndAdvance(); + ensure(!Pos.ContainsNaN()); + FNiagaraDICollsionQueryResult Res; + bool Valid = InstanceData->CollisionBatch.PerformQuery(Pos, Dir, Res, TraceChannel); + if (Valid) + { + *OutQueryValid.GetDestAndAdvance() = 0xFFFFFFFF; //->SetValue(true); + *OutInsideMesh.GetDestAndAdvance() = Res.IsInsideMesh ? 0xFFFFFFFF : 0; + *OutCollisionPosX.GetDestAndAdvance() = Res.CollisionPos.X; + *OutCollisionPosY.GetDestAndAdvance() = Res.CollisionPos.Y; + *OutCollisionPosZ.GetDestAndAdvance() = Res.CollisionPos.Z; + *OutCollisionNormX.GetDestAndAdvance() = Res.CollisionNormal.X; + *OutCollisionNormY.GetDestAndAdvance() = Res.CollisionNormal.Y; + *OutCollisionNormZ.GetDestAndAdvance() = Res.CollisionNormal.Z; + *OutFriction.GetDestAndAdvance() = Res.Friction; + *OutRestitution.GetDestAndAdvance() = Res.Restitution; + } + else + { + *OutQueryValid.GetDestAndAdvance() = 0; //->SetValue(false); + *OutInsideMesh.GetDestAndAdvance() = 0; + *OutCollisionPosX.GetDestAndAdvance() = 0.0f; + *OutCollisionPosY.GetDestAndAdvance() = 0.0f; + *OutCollisionPosZ.GetDestAndAdvance() = 0.0f; + *OutCollisionNormX.GetDestAndAdvance() = 0.0f; + *OutCollisionNormY.GetDestAndAdvance() = 0.0f; + *OutCollisionNormZ.GetDestAndAdvance() = 0.0f; + *OutFriction.GetDestAndAdvance() = 0.0f; + *OutRestitution.GetDestAndAdvance() = 0.0f; + } + } +} + +void UNiagaraDataInterfaceCollisionQuery::PerformQueryAsyncCPU(FVectorVMContext & Context) +{ + VectorVM::FExternalFuncInputHandler InIDParam(Context); + VectorVM::FExternalFuncInputHandler StartPosParamX(Context); + VectorVM::FExternalFuncInputHandler StartPosParamY(Context); + VectorVM::FExternalFuncInputHandler StartPosParamZ(Context); + + VectorVM::FExternalFuncInputHandler EndPosParamX(Context); + VectorVM::FExternalFuncInputHandler EndPosParamY(Context); + VectorVM::FExternalFuncInputHandler EndPosParamZ(Context); + + VectorVM::FExternalFuncInputHandler TraceChannelParam(Context); + + VectorVM::FUserPtrHandler InstanceData(Context); + + VectorVM::FExternalFuncRegisterHandler OutQueryID(Context); + + VectorVM::FExternalFuncRegisterHandler OutQueryValid(Context); + VectorVM::FExternalFuncRegisterHandler OutInsideMesh(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionPosX(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionPosY(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionPosZ(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionNormX(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionNormY(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionNormZ(Context); + VectorVM::FExternalFuncRegisterHandler OutFriction(Context); + VectorVM::FExternalFuncRegisterHandler OutRestitution(Context); + + FScopeLock ScopeLock(&CriticalSection); + for (int32 i = 0; i < Context.NumInstances; ++i) + { + FVector Pos(StartPosParamX.GetAndAdvance(), StartPosParamY.GetAndAdvance(), StartPosParamZ.GetAndAdvance()); + FVector Dir(EndPosParamX.GetAndAdvance(), EndPosParamY.GetAndAdvance(), EndPosParamZ.GetAndAdvance()); + ECollisionChannel TraceChannel = TraceChannelParam.GetAndAdvance(); + ensure(!Pos.ContainsNaN()); + *OutQueryID.GetDestAndAdvance() = InstanceData->CollisionBatch.SubmitQuery(Pos, Dir, TraceChannel); + + // try to retrieve a query with the supplied query ID + FNiagaraDICollsionQueryResult Res; + int32 ID = InIDParam.GetAndAdvance(); + bool Valid = InstanceData->CollisionBatch.GetQueryResult(ID, Res); + if (Valid) + { + *OutQueryValid.GetDestAndAdvance() = 0xFFFFFFFF; //->SetValue(true); + *OutInsideMesh.GetDestAndAdvance() = Res.IsInsideMesh ? 0xFFFFFFFF : 0; + *OutCollisionPosX.GetDestAndAdvance() = Res.CollisionPos.X; + *OutCollisionPosY.GetDestAndAdvance() = Res.CollisionPos.Y; + *OutCollisionPosZ.GetDestAndAdvance() = Res.CollisionPos.Z; + *OutCollisionNormX.GetDestAndAdvance() = Res.CollisionNormal.X; + *OutCollisionNormY.GetDestAndAdvance() = Res.CollisionNormal.Y; + *OutCollisionNormZ.GetDestAndAdvance() = Res.CollisionNormal.Z; + *OutFriction.GetDestAndAdvance() = Res.Friction; + *OutRestitution.GetDestAndAdvance() = Res.Restitution; + } + else + { + *OutQueryValid.GetDestAndAdvance() = 0; //->SetValue(false); + *OutInsideMesh.GetDestAndAdvance() = 0; + *OutCollisionPosX.GetDestAndAdvance() = 0.0f; + *OutCollisionPosY.GetDestAndAdvance() = 0.0f; + *OutCollisionPosZ.GetDestAndAdvance() = 0.0f; + *OutCollisionNormX.GetDestAndAdvance() = 0.0f; + *OutCollisionNormY.GetDestAndAdvance() = 0.0f; + *OutCollisionNormZ.GetDestAndAdvance() = 0.0f; + *OutFriction.GetDestAndAdvance() = 0.0f; + *OutRestitution.GetDestAndAdvance() = 0.0f; + } + } +} + + +void UNiagaraDataInterfaceCollisionQuery::PerformQueryGPU(FVectorVMContext& Context) +{ + UE_LOG(LogNiagara, Error, TEXT("GPU only function 'PerformQueryGPU' called on CPU VM, check your module code to fix.")); + + VectorVM::FExternalFuncInputHandler StartPosParamX(Context); + VectorVM::FExternalFuncInputHandler StartPosParamY(Context); + VectorVM::FExternalFuncInputHandler StartPosParamZ(Context); + VectorVM::FExternalFuncInputHandler EndPosParamX(Context); + VectorVM::FExternalFuncInputHandler EndPosParamY(Context); + VectorVM::FExternalFuncInputHandler EndPosParamZ(Context); + VectorVM::FExternalFuncInputHandler SceneDepthBoundsParam(Context); + VectorVM::FExternalFuncInputHandler ParticleRadiusParam(Context); + VectorVM::FExternalFuncInputHandler UseDistanceFieldParam(Context); + + VectorVM::FUserPtrHandler InstanceData(Context); + + VectorVM::FExternalFuncRegisterHandler OutQueryValid(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionPosX(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionPosY(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionPosZ(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionNormX(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionNormY(Context); + VectorVM::FExternalFuncRegisterHandler OutCollisionNormZ(Context); + + FScopeLock ScopeLock(&CriticalSection); + for (int32 i = 0; i < Context.NumInstances; ++i) + { + *OutQueryValid.GetDestAndAdvance() = 0;// ->SetValue(false); + *OutCollisionPosX.GetDestAndAdvance() = 0.0f; + *OutCollisionPosY.GetDestAndAdvance() = 0.0f; + *OutCollisionPosZ.GetDestAndAdvance() = 0.0f; + *OutCollisionNormX.GetDestAndAdvance() = 0.0f; + *OutCollisionNormY.GetDestAndAdvance() = 0.0f; + *OutCollisionNormZ.GetDestAndAdvance() = 1.0f; + } +} + +void UNiagaraDataInterfaceCollisionQuery::QuerySceneDepth(FVectorVMContext & Context) +{ + UE_LOG(LogNiagara, Error, TEXT("GPU only function 'QuerySceneDepthGPU' called on CPU VM, check your module code to fix.")); + + VectorVM::FExternalFuncInputHandler SamplePosParamX(Context); + VectorVM::FExternalFuncInputHandler SamplePosParamY(Context); + VectorVM::FExternalFuncInputHandler SamplePosParamZ(Context); + + VectorVM::FUserPtrHandler InstanceData(Context); + + VectorVM::FExternalFuncRegisterHandler OutSceneDepth(Context); + VectorVM::FExternalFuncRegisterHandler OutCameraPosX(Context); + VectorVM::FExternalFuncRegisterHandler OutCameraPosY(Context); + VectorVM::FExternalFuncRegisterHandler OutCameraPosZ(Context); + VectorVM::FExternalFuncRegisterHandler OutIsInsideView(Context); + VectorVM::FExternalFuncRegisterHandler OutWorldPosX(Context); + VectorVM::FExternalFuncRegisterHandler OutWorldPosY(Context); + VectorVM::FExternalFuncRegisterHandler OutWorldPosZ(Context); + VectorVM::FExternalFuncRegisterHandler OutWorldNormX(Context); + VectorVM::FExternalFuncRegisterHandler OutWorldNormY(Context); + VectorVM::FExternalFuncRegisterHandler OutWorldNormZ(Context); + + FScopeLock ScopeLock(&CriticalSection); + for (int32 i = 0; i < Context.NumInstances; ++i) + { + *OutSceneDepth.GetDestAndAdvance() = -1; + *OutIsInsideView.GetDestAndAdvance() = 0; + *OutWorldPosX.GetDestAndAdvance() = 0.0f; + *OutWorldPosY.GetDestAndAdvance() = 0.0f; + *OutWorldPosZ.GetDestAndAdvance() = 0.0f; + *OutWorldNormX.GetDestAndAdvance() = 0.0f; + *OutWorldNormY.GetDestAndAdvance() = 0.0f; + *OutWorldNormZ.GetDestAndAdvance() = 1.0f; + *OutCameraPosX.GetDestAndAdvance() = 0.0f; + *OutCameraPosY.GetDestAndAdvance() = 0.0f; + *OutCameraPosZ.GetDestAndAdvance() = 0.0f; + } +} + +void UNiagaraDataInterfaceCollisionQuery::QueryMeshDistanceField(FVectorVMContext& Context) +{ + UE_LOG(LogNiagara, Error, TEXT("GPU only function 'QueryMeshDistanceFieldGPU' called on CPU VM, check your module code to fix.")); + + VectorVM::FExternalFuncInputHandler SamplePosParamX(Context); + VectorVM::FExternalFuncInputHandler SamplePosParamY(Context); + VectorVM::FExternalFuncInputHandler SamplePosParamZ(Context); + + VectorVM::FUserPtrHandler InstanceData(Context); + + VectorVM::FExternalFuncRegisterHandler OutSurfaceDistance(Context); + VectorVM::FExternalFuncRegisterHandler OutFieldGradientX(Context); + VectorVM::FExternalFuncRegisterHandler OutFieldGradientY(Context); + VectorVM::FExternalFuncRegisterHandler OutFieldGradientZ(Context); + + FScopeLock ScopeLock(&CriticalSection); + for (int32 i = 0; i < Context.NumInstances; ++i) + { + *OutSurfaceDistance.GetDestAndAdvance() = -1; + *OutFieldGradientX.GetDestAndAdvance() = 0.0f; + *OutFieldGradientY.GetDestAndAdvance() = 0.0f; + *OutFieldGradientZ.GetDestAndAdvance() = 1.0f; + } +} void UNiagaraDataInterfaceCollisionQuery::SubmitQuery(FVectorVMContext& Context) { @@ -438,31 +837,44 @@ struct FNiagaraDataInterfaceParametersCS_CollisionQuery : public FNiagaraDataInt virtual void Bind(const FNiagaraDataInterfaceParamRef& ParamRef, const class FShaderParameterMap& ParameterMap) override { PassUniformBuffer.Bind(ParameterMap, FSceneTexturesUniformParameters::StaticStructMetadata.GetShaderVariableName()); - check(PassUniformBuffer.IsBound()); + + GlobalDistanceFieldParameters.Bind(ParameterMap); + if (GlobalDistanceFieldParameters.IsBound()) + { + GNiagaraViewDataManager.SetGlobalDistanceFieldUsage(); + } } - virtual void Serialize(FArchive& Ar)override + virtual void Serialize(FArchive& Ar) override { Ar << PassUniformBuffer; + Ar << GlobalDistanceFieldParameters; } - virtual void Set(FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface) const override + virtual void Set(FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface, void* PerInstanceData) const override { check(IsInRenderingThread()); const FComputeShaderRHIParamRef ComputeShaderRHI = Shader->GetComputeShader(); - + TUniformBufferRef SceneTextureUniformParams = GNiagaraViewDataManager.GetSceneTextureUniformParameters(); SetUniformBufferParameter(RHICmdList, ComputeShaderRHI, PassUniformBuffer/*Shader->GetUniformBufferParameter(SceneTexturesUniformBufferStruct)*/, SceneTextureUniformParams); + if (GlobalDistanceFieldParameters.IsBound()) + { + GNiagaraViewDataManager.SetGlobalDistanceFieldUsage(); + GlobalDistanceFieldParameters.Set(RHICmdList, ComputeShaderRHI, *GNiagaraViewDataManager.GetGlobalDistanceFieldParameters()); + } } private: /** The SceneDepthTexture parameter for depth buffer collision. */ FShaderUniformBufferParameter PassUniformBuffer; + + FGlobalDistanceFieldParameters GlobalDistanceFieldParameters; }; -FNiagaraDataInterfaceParametersCS* UNiagaraDataInterfaceCollisionQuery::ConstructComputeParameters()const +FNiagaraDataInterfaceParametersCS* UNiagaraDataInterfaceCollisionQuery::ConstructComputeParameters() const { return new FNiagaraDataInterfaceParametersCS_CollisionQuery(); } \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCurlNoise.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCurlNoise.cpp index 76663e6b474e..56699e39e247 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCurlNoise.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceCurlNoise.cpp @@ -3,17 +3,311 @@ #include "NiagaraDataInterfaceCurlNoise.h" #include "NiagaraShader.h" #include "ShaderParameterUtils.h" +#include "Math/IntVector.h" + +static const FName SampleNoiseFieldName(TEXT("SampleNoiseField")); +static const FString OffsetFromSeedBaseName(TEXT("OffsetFromSeed_")); + +// ----------------------------------------------------- start noise helpers ----------------------------------------------------- +// Fairly straightforward CPU implementation of the equivalent HLSL code found in Engine/Shaders/Private/Random.ush +// Also contains a partial implementation of some math types and functions found in HLSL but not in UE + +// TODO(mv): Move this to a separate NiagaraMath unit. +struct FNiagaraUIntVector +{ + uint32 X; + uint32 Y; + uint32 Z; + + FNiagaraUIntVector() : X(0), Y(0), Z(0) {} + FNiagaraUIntVector(uint32 X, uint32 Y, uint32 Z) : X(X), Y(Y), Z(Z) {} + FNiagaraUIntVector(uint32 Value) : X(Value), Y(Value), Z(Value) {} + FNiagaraUIntVector(FIntVector Values) : X(Values.X), Y(Values.Y), Z(Values.Z) {} + + FNiagaraUIntVector operator+(FNiagaraUIntVector Rhs) + { + return FNiagaraUIntVector{ X + Rhs.X, Y + Rhs.Y, Z + Rhs.Z }; + } + + FNiagaraUIntVector operator*(FNiagaraUIntVector Rhs) + { + return FNiagaraUIntVector{ X * Rhs.X, Y * Rhs.Y, Z * Rhs.Z }; + } + + FNiagaraUIntVector operator>>(uint32 Shift) + { + return FNiagaraUIntVector(X >> Shift, Y >> Shift, Z >> Shift); + } + + FNiagaraUIntVector operator&(FNiagaraUIntVector Rhs) + { + return FNiagaraUIntVector{ X & Rhs.X, Y & Rhs.Y, Z & Rhs.Z }; + } + + uint32& operator[](int Index) + { + if (Index == 0) return X; + if (Index == 1) return Y; + if (Index == 2) return Z; + check(false); + return X; + } + + const uint32& operator[](int Index) const + { + if (Index == 0) return X; + if (Index == 1) return Y; + if (Index == 2) return Z; + check(false); + return X; + } +}; + +FNiagaraUIntVector Rand3DPCG16(FIntVector p) +{ + FNiagaraUIntVector v = FNiagaraUIntVector(p); + + v = v * 1664525u + 1013904223u; + + v.X += v.Y*v.Z; + v.Y += v.Z*v.X; + v.Z += v.X*v.Y; + v.X += v.Y*v.Z; + v.Y += v.Z*v.X; + v.Z += v.X*v.Y; + + return v >> 16u; +} + +FVector NiagaraVectorFrac(FVector v) +{ + return FVector(FMath::Frac(v.X), FMath::Frac(v.Y), FMath::Frac(v.Z)); +} + +FVector NoiseTileWrap(FVector v, bool bTiling, float RepeatSize) +{ + FVector vv = bTiling ? (NiagaraVectorFrac(v / RepeatSize) * RepeatSize) : v; + return vv; +} + +struct FNiagaraMatrix4x3 +{ + FVector Row0; + FVector Row1; + FVector Row2; + FVector Row3; + + FNiagaraMatrix4x3() : Row0(FVector()), Row1(FVector()), Row2(FVector()), Row3(FVector()) {} + FNiagaraMatrix4x3(FVector Row0, FVector Row1, FVector Row2, FVector Row3) : Row0(Row0), Row1(Row1), Row2(Row2), Row3(Row3) {} + + FVector& operator[](int Row) + { + if (Row == 0) return Row0; + if (Row == 1) return Row1; + if (Row == 2) return Row2; + if (Row == 3) return Row3; + check(false); + return Row0; + } + const FVector& operator[](int Row) const + { + if (Row == 0) return Row0; + if (Row == 1) return Row1; + if (Row == 2) return Row2; + if (Row == 3) return Row3; + check(false); + return Row0; + } +}; + +FVector NiagaraVectorFloor(FVector v) { + return FVector(FGenericPlatformMath::FloorToFloat(v.X), + FGenericPlatformMath::FloorToFloat(v.Y), + FGenericPlatformMath::FloorToFloat(v.Z)); +}; + +FVector NiagaraVectorStep(FVector v, FVector u) +{ + return FVector(u.X >= v.X ? 1 : 0, + u.Y >= v.Y ? 1 : 0, + u.Z >= v.Z ? 1 : 0); +} + +FVector NiagaraVectorSwizzle(FVector v, uint32 x, uint32 y, uint32 z) +{ + return FVector(v[x], v[y], v[z]); +} + +FVector NiagaraVectorMin(FVector u, FVector v) +{ + return FVector(u.X < v.X ? u.X : v.X, + u.Y < v.Y ? u.Y : v.Y, + u.Z < v.Z ? u.Z : v.Z); +} + +FVector NiagaraVectorMax(FVector u, FVector v) +{ + return FVector(u.X > v.X ? u.X : v.X, + u.Y > v.Y ? u.Y : v.Y, + u.Z > v.Z ? u.Z : v.Z); +} + +FNiagaraMatrix4x3 SimplexCorners(FVector v) +{ + FVector tet = NiagaraVectorFloor(v + v.X / 3 + v.Y / 3 + v.Z / 3); + FVector base = tet - tet.X / 6 - tet.Y / 6 - tet.Z / 6; + FVector f = v - base; + + FVector g = NiagaraVectorStep(NiagaraVectorSwizzle(f, 1, 2, 0), f), h = FVector(1.0f) - NiagaraVectorSwizzle(g, 2, 0, 1); + FVector a1 = NiagaraVectorMin(g, h) - 1. / 6., a2 = NiagaraVectorMax(g, h) - 1. / 3.; + + return FNiagaraMatrix4x3(base, base + a1, base + a2, base + 0.5); +} + +FVector4 NiagaraVector4Saturate(FVector4 v) { + return FVector4(FMath::Clamp(v.X, 0.0f, 1.0f), FMath::Clamp(v.Y, 0.0f, 1.0f), FMath::Clamp(v.Z, 0.0f, 1.0f), FMath::Clamp(v.W, 0.0f, 1.0f)); +} + +FVector4 SimplexSmooth(FNiagaraMatrix4x3 f) +{ + const float scale = 1024. / 375.; + FVector4 d = FVector4(FVector::DotProduct(f[0], f[0]), FVector::DotProduct(f[1], f[1]), FVector::DotProduct(f[2], f[2]), FVector::DotProduct(f[3], f[3])); + FVector4 s = NiagaraVector4Saturate(2.0f * d); + return scale * (FVector4(1.0f, 1.0f, 1.0f, 1.0f) + s * (FVector4(-3.0f, -3.0f, -3.0f, -3.0f) + s * (FVector4(3.0f, 3.0f, 3.0f, 3.0f) - s))); +} + +struct FNiagaraMatrix3x4 +{ + FVector4 row0; + FVector4 row1; + FVector4 row2; + + FNiagaraMatrix3x4() : row0(FVector4(0.0, 0.0, 0.0, 0.0)), row1(FVector4(0.0, 0.0, 0.0, 0.0)), row2(FVector4(0.0, 0.0, 0.0, 0.0)) {} + FNiagaraMatrix3x4(FVector4 row0, FVector4 row1, FVector4 row2) : row0(row0), row1(row1), row2(row2) {} + + FVector4& operator[](int row) + { + if (row == 0) return row0; + if (row == 1) return row1; + if (row == 2) return row2; + check(false); + return row0; + } + const FVector4& operator[](int row) const + { + if (row == 0) return row0; + if (row == 1) return row1; + if (row == 2) return row2; + check(false); + return row0; + } +}; + +FNiagaraMatrix3x4 SimplexDSmooth(FNiagaraMatrix4x3 f) +{ + const float scale = 1024. / 375.; + FVector4 d = FVector4(FVector::DotProduct(f[0], f[0]), FVector::DotProduct(f[1], f[1]), FVector::DotProduct(f[2], f[2]), FVector::DotProduct(f[3], f[3])); + FVector4 s = NiagaraVector4Saturate(2 * d); + s = -12 * FVector4(scale, scale, scale, scale) + s * (24 * FVector4(scale, scale, scale, scale) - s * 12 * scale); + + return FNiagaraMatrix3x4( + s * FVector4(f[0][0], f[1][0], f[2][0], f[3][0]), + s * FVector4(f[0][1], f[1][1], f[2][1], f[3][1]), + s * FVector4(f[0][2], f[1][2], f[2][2], f[3][2])); +} + +FNiagaraUIntVector FNiagaraUIntVectorSwizzle(FNiagaraUIntVector v, int x, int y, int z) +{ + return FNiagaraUIntVector(v[x], v[y], v[z]); +} + +FVector FNiagaraUIntVectorToFVector(FNiagaraUIntVector v) +{ + return FVector(v.X, v.Y, v.Z); +} + +FVector MulFVector4AndFNiagaraMatrix4x3(FVector4 lhs, FNiagaraMatrix4x3 rhs) +{ + return FVector(lhs[0] * rhs[0][0] + lhs[1] * rhs[1][0] + lhs[2] * rhs[2][0] + lhs[3] * rhs[3][0], + lhs[0] * rhs[0][1] + lhs[1] * rhs[1][1] + lhs[2] * rhs[2][1] + lhs[3] * rhs[3][1], + lhs[0] * rhs[0][2] + lhs[1] * rhs[1][2] + lhs[2] * rhs[2][2] + lhs[3] * rhs[3][2]); +} + +FVector MulFNiagaraMatrix3x4FAndVector4(FNiagaraMatrix3x4 lhs, FVector4 rhs) +{ + return FVector(lhs[0][0] * rhs[0] + lhs[0][1] * rhs[1] + lhs[0][2] * rhs[2] + lhs[0][3] * rhs[3], + lhs[1][0] * rhs[0] + lhs[1][1] * rhs[1] + lhs[1][2] * rhs[2] + lhs[1][3] * rhs[3], + lhs[2][0] * rhs[0] + lhs[2][1] * rhs[1] + lhs[2][2] * rhs[2] + lhs[2][3] * rhs[3]); +} + +#define MGradientMask FNiagaraUIntVector(0x8000, 0x4000, 0x2000) +#define MGradientScale FVector(1. / 0x4000, 1. / 0x2000, 1. / 0x1000) + +FNiagaraMatrix3x4 JacobianSimplex_ALU(FVector v) +{ + FNiagaraMatrix4x3 T = SimplexCorners(v); + FNiagaraUIntVector rand; + FNiagaraMatrix4x3 gvec[3], fv; + FNiagaraMatrix3x4 grad; + + fv[0] = v - T[0]; + rand = Rand3DPCG16(FIntVector(NiagaraVectorFloor(6 * T[0] + 0.5))); + gvec[0][0] = FVector(FNiagaraUIntVectorToFVector(FNiagaraUIntVectorSwizzle(rand, 0, 0, 0) & MGradientMask)) * MGradientScale - 1; + gvec[1][0] = FVector(FNiagaraUIntVectorToFVector(FNiagaraUIntVectorSwizzle(rand, 1, 1, 1) & MGradientMask)) * MGradientScale - 1; + gvec[2][0] = FVector(FNiagaraUIntVectorToFVector(FNiagaraUIntVectorSwizzle(rand, 2, 2, 2) & MGradientMask)) * MGradientScale - 1; + grad[0][0] = FVector::DotProduct(gvec[0][0], fv[0]); + grad[1][0] = FVector::DotProduct(gvec[1][0], fv[0]); + grad[2][0] = FVector::DotProduct(gvec[2][0], fv[0]); + + fv[1] = v - T[1]; + rand = Rand3DPCG16(FIntVector(NiagaraVectorFloor(6 * T[1] + 0.5))); + gvec[0][1] = FVector(FNiagaraUIntVectorToFVector(FNiagaraUIntVectorSwizzle(rand, 0, 0, 0) & MGradientMask)) * MGradientScale - 1; + gvec[1][1] = FVector(FNiagaraUIntVectorToFVector(FNiagaraUIntVectorSwizzle(rand, 1, 1, 1) & MGradientMask)) * MGradientScale - 1; + gvec[2][1] = FVector(FNiagaraUIntVectorToFVector(FNiagaraUIntVectorSwizzle(rand, 2, 2, 2) & MGradientMask)) * MGradientScale - 1; + grad[0][1] = FVector::DotProduct(gvec[0][1], fv[1]); + grad[1][1] = FVector::DotProduct(gvec[1][1], fv[1]); + grad[2][1] = FVector::DotProduct(gvec[2][1], fv[1]); + + fv[2] = v - T[2]; + rand = Rand3DPCG16(FIntVector(NiagaraVectorFloor(6 * T[2] + 0.5))); + gvec[0][2] = FVector(FNiagaraUIntVectorToFVector(FNiagaraUIntVectorSwizzle(rand, 0, 0, 0) & MGradientMask)) * MGradientScale - 1; + gvec[1][2] = FVector(FNiagaraUIntVectorToFVector(FNiagaraUIntVectorSwizzle(rand, 1, 1, 1) & MGradientMask)) * MGradientScale - 1; + gvec[2][2] = FVector(FNiagaraUIntVectorToFVector(FNiagaraUIntVectorSwizzle(rand, 2, 2, 2) & MGradientMask)) * MGradientScale - 1; + grad[0][2] = FVector::DotProduct(gvec[0][2], fv[2]); + grad[1][2] = FVector::DotProduct(gvec[1][2], fv[2]); + grad[2][2] = FVector::DotProduct(gvec[2][2], fv[2]); + + fv[3] = v - T[3]; + rand = Rand3DPCG16(FIntVector(NiagaraVectorFloor(6 * T[3] + 0.5))); + gvec[0][3] = FVector(FNiagaraUIntVectorToFVector(FNiagaraUIntVectorSwizzle(rand, 0, 0, 0) & MGradientMask)) * MGradientScale - 1; + gvec[1][3] = FVector(FNiagaraUIntVectorToFVector(FNiagaraUIntVectorSwizzle(rand, 1, 1, 1) & MGradientMask)) * MGradientScale - 1; + gvec[2][3] = FVector(FNiagaraUIntVectorToFVector(FNiagaraUIntVectorSwizzle(rand, 2, 2, 2) & MGradientMask)) * MGradientScale - 1; + grad[0][3] = FVector::DotProduct(gvec[0][3], fv[3]); + grad[1][3] = FVector::DotProduct(gvec[1][3], fv[3]); + grad[2][3] = FVector::DotProduct(gvec[2][3], fv[3]); + + FVector4 sv = SimplexSmooth(fv); + FNiagaraMatrix3x4 ds = SimplexDSmooth(fv); + + FNiagaraMatrix3x4 jacobian; + jacobian[0] = FVector4(MulFVector4AndFNiagaraMatrix4x3(sv, gvec[0]) + MulFNiagaraMatrix3x4FAndVector4(ds, grad[0]), Dot4(sv, grad[0])); + jacobian[1] = FVector4(MulFVector4AndFNiagaraMatrix4x3(sv, gvec[1]) + MulFNiagaraMatrix3x4FAndVector4(ds, grad[1]), Dot4(sv, grad[1])); + jacobian[2] = FVector4(MulFVector4AndFNiagaraMatrix4x3(sv, gvec[2]) + MulFNiagaraMatrix3x4FAndVector4(ds, grad[2]), Dot4(sv, grad[2])); + + return jacobian; +} + +#undef MGradientMask +#undef MGradientScale + +// ----------------------------------------------------- end noise helpers ----------------------------------------------------- -const FName UNiagaraDataInterfaceCurlNoise::SampleNoiseFieldName(TEXT("SampleNoiseField")); -const FString UNiagaraDataInterfaceCurlNoise::CurlNoiseBufferName(TEXT("CurlNoiseBuffer_")); UNiagaraDataInterfaceCurlNoise::UNiagaraDataInterfaceCurlNoise(FObjectInitializer const& ObjectInitializer) : Super(ObjectInitializer) - , bGPUBufferDirty(true) , Seed(0) - , GPUBuffer(new FRWBuffer) { - + OffsetFromSeed = FNiagaraUIntVectorToFVector(Rand3DPCG16(FIntVector(Seed, Seed, Seed))) / 100.0; } void UNiagaraDataInterfaceCurlNoise::PostInitProperties() @@ -29,33 +323,28 @@ void UNiagaraDataInterfaceCurlNoise::PostInitProperties() void UNiagaraDataInterfaceCurlNoise::PostLoad() { Super::PostLoad(); - InitNoiseLUT(); -} - -void UNiagaraDataInterfaceCurlNoise::BeginDestroy() -{ - Super::BeginDestroy(); - - if (FApp::CanEverRender() && !HasAnyFlags(RF_ClassDefaultObject)) - { - ReleaseResource(); - } -} - -bool UNiagaraDataInterfaceCurlNoise::IsReadyForFinishDestroy() -{ - return ReleaseResourcesFence.IsFenceComplete(); + OffsetFromSeed = FNiagaraUIntVectorToFVector(Rand3DPCG16(FIntVector(Seed, Seed, Seed))) / 100.0; } #if WITH_EDITOR +void UNiagaraDataInterfaceCurlNoise::PreEditChange(UProperty* PropertyAboutToChange) +{ + Super::PreEditChange(PropertyAboutToChange); + + // Flush the rendering thread before making any changes to make sure the + // data read by the compute shader isn't subject to a race condition. + // TODO(mv): Solve properly using something like a RT Proxy. + FlushRenderingCommands(); +} void UNiagaraDataInterfaceCurlNoise::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UNiagaraDataInterfaceCurlNoise, Seed)) { - InitNoiseLUT(); + // NOTE: Calculate the offset based on the seed on-change instead of on every invocation for every particle... + OffsetFromSeed = FNiagaraUIntVectorToFVector(Rand3DPCG16(FIntVector(Seed, Seed, Seed))) / 100.0; } } @@ -69,7 +358,7 @@ bool UNiagaraDataInterfaceCurlNoise::CopyToInternal(UNiagaraDataInterface* Desti } UNiagaraDataInterfaceCurlNoise* DestinationCurlNoise = CastChecked(Destination); DestinationCurlNoise->Seed = Seed; - DestinationCurlNoise->InitNoiseLUT(); + DestinationCurlNoise->OffsetFromSeed = OffsetFromSeed; return true; } @@ -115,298 +404,76 @@ void UNiagaraDataInterfaceCurlNoise::SampleNoiseField(FVectorVMContext& Context) VectorVM::FExternalFuncRegisterHandler OutSampleY(Context); VectorVM::FExternalFuncRegisterHandler OutSampleZ(Context); - const VectorRegister One = MakeVectorRegister(1.0f, 1.0f, 1.0f, 1.0f); - const VectorRegister Zero = MakeVectorRegister(0.0f, 0.0f, 0.0f, 0.0f); - // We use 15,15,15 here because the value will be between 0 and 1. 1 should indicate the maximal index of the - // real table and we pad that out to handle border work. So our values for the Cxyz below should not - // ever be 17 in any dimension as that would overflow the array. - const VectorRegister VecSize = MakeVectorRegister(15.0f, 15.0f, 15.0f, 15.0f); - - float Di = 0.2f; // Hard-coded scale TODO remove! - const VectorRegister Div = MakeVectorRegister(Di, Di, Di, Di); - for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) { - VectorRegister InCoords = MakeVectorRegister(XParam.GetAndAdvance(), YParam.GetAndAdvance(), ZParam.GetAndAdvance(), 0.0f); - VectorRegister Dst = MakeVectorRegister(0.0f, 0.0f, 0.0f, 0.0f); + FVector InCoords = FVector(XParam.GetAndAdvance(), YParam.GetAndAdvance(), ZParam.GetAndAdvance()); - VectorRegister Coords = VectorMod(VectorAbs(VectorMultiply(InCoords, Div)), VecSize); - Coords = VectorMin(Coords, VecSize); - Coords = VectorMax(Coords, Zero); - const float *CoordPtr = reinterpret_cast(&Coords); - const int32 Cx = CoordPtr[0]; - const int32 Cy = CoordPtr[1]; - const int32 Cz = CoordPtr[2]; - - VectorRegister Frac = VectorFractional(Coords); - VectorRegister Alpha = VectorReplicate(Frac, 0); - VectorRegister OneMinusAlpha = VectorSubtract(One, Alpha); - - // Trilinear interpolation, as defined by https://en.wikipedia.org/wiki/Trilinear_interpolation - ensure(Cx + 1 < 17); - ensure(Cy + 1 < 17); - ensure(Cz + 1 < 17); - VectorRegister C00 = VectorMultiplyAdd(NoiseTable[Cx][Cy][Cz], OneMinusAlpha, VectorMultiply(NoiseTable[Cx + 1][Cy][Cz], Alpha)); // (x0, y0, z0)(1-a) + (x1, y0, z0)a - VectorRegister C01 = VectorMultiplyAdd(NoiseTable[Cx][Cy][Cz + 1], OneMinusAlpha, VectorMultiply(NoiseTable[Cx + 1][Cy][Cz + 1], Alpha)); // (x0, y0, z1)(1-a) + (x1, y0, z1)a - VectorRegister C10 = VectorMultiplyAdd(NoiseTable[Cx][Cy + 1][Cz], OneMinusAlpha, VectorMultiply(NoiseTable[Cx + 1][Cy + 1][Cz], Alpha)); // (x0, y1, z0)(1-a) + (x1, y1, z0)a - VectorRegister C11 = VectorMultiplyAdd(NoiseTable[Cx][Cy + 1][Cz + 1], OneMinusAlpha, VectorMultiply(NoiseTable[Cx + 1][Cy + 1][Cz + 1], Alpha)); // (x0, y1, z1)(1-a) + (x1, y1, z1)a - - Alpha = VectorReplicate(Frac, 1); - OneMinusAlpha = VectorSubtract(One, Alpha); - VectorRegister C0 = VectorMultiplyAdd(C00, OneMinusAlpha, VectorMultiply(C10, Alpha)); - VectorRegister C1 = VectorMultiplyAdd(C01, OneMinusAlpha, VectorMultiply(C11, Alpha)); - - Alpha = VectorReplicate(Frac, 2); - OneMinusAlpha = VectorSubtract(One, Alpha); - VectorRegister ZV = VectorMultiplyAdd(C0, OneMinusAlpha, VectorMultiply(C1, Alpha)); - - Dst = VectorAdd(Dst, ZV); - - float *RegPtr = reinterpret_cast(&Dst); - *OutSampleX.GetDestAndAdvance() = RegPtr[0]; - *OutSampleY.GetDestAndAdvance() = RegPtr[1]; - *OutSampleZ.GetDestAndAdvance() = RegPtr[2]; + // See comments to JacobianSimplex_ALU in Random.ush + FNiagaraMatrix3x4 J = JacobianSimplex_ALU(InCoords + OffsetFromSeed); + *OutSampleX.GetDestAndAdvance() = J[1][2] - J[2][1]; + *OutSampleY.GetDestAndAdvance() = J[2][0] - J[0][2]; + *OutSampleZ.GetDestAndAdvance() = J[0][1] - J[1][0]; } } bool UNiagaraDataInterfaceCurlNoise::GetFunctionHLSL(const FName& DefinitionFunctionName, FString InstanceFunctionName, FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) { - FString BufferName = CurlNoiseBufferName + ParamInfo.DataInterfaceHLSLSymbol; - OutHLSL += TEXT("void ") + InstanceFunctionName + TEXT("(in float3 In_XYZ, out float3 Out_Value) \n{\n"); - OutHLSL += TEXT("\t float3 a = trunc((In_XYZ*0.2) / 15.0);\n"); - OutHLSL += TEXT("\t float3 ModXYZ = (In_XYZ*0.2) - a*15.0;\n"); - OutHLSL += TEXT("\t int3 IntCoord = abs(int3(ModXYZ.x, ModXYZ.y, ModXYZ.z));\n"); - OutHLSL += TEXT("\t float3 frc = frac(ModXYZ);\n"); - // Trilinear interpolation, as defined by https://en.wikipedia.org/wiki/Trilinear_interpolation - // HLSL lerp is defined as lerp(x, y, s) = x*(1-s) + y*s - OutHLSL += TEXT("\t float3 V1 = ") + BufferName + TEXT("[IntCoord.x + IntCoord.y*17 + IntCoord.z*17*17].xyz;\n"); // x0,y0,z0 - OutHLSL += TEXT("\t float3 V2 = ") + BufferName + TEXT("[IntCoord.x+1 + IntCoord.y*17 + IntCoord.z*17*17].xyz;\n"); // x1,y0,z0 - OutHLSL += TEXT("\t float3 C00 = lerp(V1, V2, frc.xxx);\n"); - OutHLSL += TEXT("\t V1 = ") + BufferName + TEXT("[IntCoord.x + IntCoord.y*17 + (IntCoord.z+1)*17*17].xyz;\n"); // x0, y0, z1 - OutHLSL += TEXT("\t V2 = ") + BufferName + TEXT("[IntCoord.x+1 + IntCoord.y*17 + (IntCoord.z+1)*17*17].xyz;\n"); //x1, y0, z1 - OutHLSL += TEXT("\t float3 C01 = lerp(V1, V2, frc.xxx);\n"); - OutHLSL += TEXT("\t V1 = ") + BufferName + TEXT("[IntCoord.x + (IntCoord.y+1)*17 + IntCoord.z*17*17].xyz;\n"); //x0, y1, z0 - OutHLSL += TEXT("\t V2 = ") + BufferName + TEXT("[IntCoord.x+1 + (IntCoord.y+1)*17 + IntCoord.z*17*17].xyz;\n"); // x1, y1, z0 - OutHLSL += TEXT("\t float3 C10 = lerp(V1, V2, frc.xxx);\n"); - OutHLSL += TEXT("\t V1 = ") + BufferName + TEXT("[IntCoord.x + (IntCoord.y+1)*17 + (IntCoord.z+1)*17*17].xyz;\n"); // x0, y1, z1 - OutHLSL += TEXT("\t V2 = ") + BufferName + TEXT("[IntCoord.x+1 + (IntCoord.y+1)*17 + (IntCoord.z+1)*17*17].xyz;\n"); //x1, y1, z1 - OutHLSL += TEXT("\t float3 C11 = lerp(V1, V2, frc.xxx);\n"); - OutHLSL += TEXT("\t float3 C0 = lerp(C00, C10, frc.yyy);\n"); - OutHLSL += TEXT("\t float3 C1 = lerp(C01, C11, frc.yyy);\n"); - OutHLSL += TEXT("\t Out_Value = lerp(C0, C1, frc.zzz);\n"); - OutHLSL += TEXT("\n}\n"); + static const TCHAR *FormatSample = TEXT(R"( + void {FunctionName}(float3 In_XYZ, out float3 Out_Value) + { + // NOTE(mv): The comments in random.ush claims that the unused part is optimized away, so it only uses 6 out of 12 values in our case. + float3x4 J = JacobianSimplex_ALU(In_XYZ + {OffsetFromSeedName}, false, 1.0); + Out_Value = float3(J[1][2]-J[2][1], J[2][0]-J[0][2], J[0][1]-J[1][0]); // See comments to JacobianSimplex_ALU in Random.ush + } + )"); + TMap ArgsSample = { + {TEXT("FunctionName"), InstanceFunctionName}, + {TEXT("OffsetFromSeedName"), OffsetFromSeedBaseName + ParamInfo.DataInterfaceHLSLSymbol}, + }; + OutHLSL += FString::Format(FormatSample, ArgsSample); return true; } void UNiagaraDataInterfaceCurlNoise::GetParameterDefinitionHLSL(FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) { - FString BufferName = CurlNoiseBufferName + ParamInfo.DataInterfaceHLSLSymbol; - OutHLSL += TEXT("Buffer ") + BufferName + TEXT(";\n"); + static const TCHAR *FormatDeclarations = TEXT(R"( + float3 {OffsetFromSeedName}; + )"); + + TMap ArgsDeclarations = { + {TEXT("OffsetFromSeedName"), OffsetFromSeedBaseName + ParamInfo.DataInterfaceHLSLSymbol}, + }; + OutHLSL += FString::Format(FormatDeclarations, ArgsDeclarations); } -void UNiagaraDataInterfaceCurlNoise::ReleaseResource() -{ - ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(NiagaraCurlNoiseReleaseCommand, - FRWBuffer*,GPUBuffer,GPUBuffer.Get(), - { - GPUBuffer->Release(); - }); - // Insert a fence to signal when these commands completed - ReleaseResourcesFence.BeginFence(); -} - -FRWBuffer& UNiagaraDataInterfaceCurlNoise::GetGPUBuffer() -{ - check(IsInRenderingThread()); - - if (bGPUBufferDirty) - { - GPUBuffer->Release(); - uint32 BufferSize = 17 * 17 * 17 * sizeof(float) * 4; - //int32 *BufferData = static_cast(RHILockVertexBuffer(GPUBuffer.Buffer.Buffer, 0, BufferSize, EResourceLockMode::RLM_WriteOnly)); - TResourceArray TempTable; - TempTable.AddUninitialized(17 * 17 * 17); - for (int z = 0; z < 17; z++) - { - for (int y = 0; y < 17; y++) - { - for (int x = 0; x < 17; x++) - { - float *R = (float*)( &(NoiseTable[x][y][z]) ); - TempTable[x + y * 17 + z * 17 * 17] = FVector4(R[0], R[1], R[2], 0.0f); - } - } - } - GPUBuffer->Initialize(sizeof(float) * 4, 17 * 17 * 17, EPixelFormat::PF_A32B32G32R32F, BUF_Static, TEXT("CurlnoiseTable"), &TempTable); - //FPlatformMemory::Memcpy(BufferData, TempTable, BufferSize); - //RHIUnlockVertexBuffer(GPUBuffer.Buffer.Buffer); - bGPUBufferDirty = false; - } - - return *GPUBuffer; -} - - -// replicate a border for filtering -template -void UNiagaraDataInterfaceCurlNoise::ReplicateBorder(T* DestBuffer) -{ - uint32 Extent = 17; - uint32 Square = Extent*Extent; - uint32 Last = Extent - 1; - - for (uint32 z = 0; z < Extent; ++z) - { - for (uint32 y = 0; y < Extent; ++y) - { - DestBuffer[Last + y * Extent + z * Square] = DestBuffer[0 + y * Extent + z * Square]; - } - } - for (uint32 z = 0; z < Extent; ++z) - { - for (uint32 x = 0; x < Extent; ++x) - { - DestBuffer[x + Last * Extent + z * Square] = DestBuffer[x + 0 * Extent + z * Square]; - } - } - for (uint32 y = 0; y < Extent; ++y) - { - for (uint32 x = 0; x < Extent; ++x) - { - DestBuffer[x + y * Extent + Last * Square] = DestBuffer[x + y * Extent + 0 * Square]; - } - } -} - -void UNiagaraDataInterfaceCurlNoise::InitNoiseLUT() -{ - // seed random stream - FRandomStream RandStream(Seed); - - // random noise - float TempTable[17][17][17]; - for (int z = 0; z < 17; z++) - { - for (int y = 0; y < 17; y++) - { - for (int x = 0; x < 17; x++) - { - float f1 = RandStream.FRandRange(-1.0f, 1.0f); - TempTable[x][y][z] = f1; - } - } - } - - // pad - /* - for (int i = 0; i < 17; i++) - { - for (int j = 0; j < 17; j++) - { - TempTable[i][j][16] = TempTable[i][j][0]; - TempTable[i][16][j] = TempTable[i][0][j]; - TempTable[16][j][i] = TempTable[0][j][i]; - } - } - */ - ReplicateBorder(&TempTable[0][0][0]); - - // compute gradients - FVector TempTable2[17][17][17]; - for (int z = 0; z < 16; z++) - { - for (int y = 0; y < 16; y++) - { - for (int x = 0; x < 16; x++) - { - FVector XGrad = FVector(1.0f, 0.0f, TempTable[x][y][z] - TempTable[x + 1][y][z]); - FVector YGrad = FVector(0.0f, 1.0f, TempTable[x][y][z] - TempTable[x][y + 1][z]); - FVector ZGrad = FVector(0.0f, 1.0f, TempTable[x][y][z] - TempTable[x][y][z + 1]); - - FVector Grad = FVector(XGrad.Z, YGrad.Z, ZGrad.Z); - TempTable2[x][y][z] = Grad; - } - } - } - - /* - // pad - for (int i = 0; i < 17; i++) - { - for (int j = 0; j < 17; j++) - { - TempTable2[i][j][16] = TempTable2[i][j][0]; - TempTable2[i][16][j] = TempTable2[i][0][j]; - TempTable2[16][j][i] = TempTable2[0][j][i]; - } - } - */ - ReplicateBorder(&TempTable2[0][0][0]); - - // http://prideout.net/blog/?p=63 ??? - // compute curl of gradient field - for (int z = 0; z < 16; z++) - { - for (int y = 0; y < 16; y++) - { - for (int x = 0; x < 16; x++) - { - FVector Dy = TempTable2[x][y][z] - TempTable2[x][y + 1][z]; - FVector Sy = TempTable2[x][y][z] + TempTable2[x][y + 1][z]; - FVector Dx = TempTable2[x][y][z] - TempTable2[x + 1][y][z]; - FVector Sx = TempTable2[x][y][z] + TempTable2[x + 1][y][z]; - FVector Dz = TempTable2[x][y][z] - TempTable2[x][y][z + 1]; - FVector Sz = TempTable2[x][y][z] + TempTable2[x][y][z + 1]; - FVector Dir = FVector(Dy.Z - Sz.Y, Dz.X - Sx.Z, Dx.Y - Sy.X); - - NoiseTable[x][y][z] = MakeVectorRegister(Dir.X, Dir.Y, Dir.Z, 0.0f); - } - } - } - - // pad - /* - for (int i = 0; i < 17; i++) - { - for (int j = 0; j < 17; j++) - { - NoiseTable[i][j][16] = NoiseTable[i][j][0]; - NoiseTable[i][16][j] = NoiseTable[i][0][j]; - NoiseTable[16][j][i] = NoiseTable[0][j][i]; - } - }*/ - ReplicateBorder(&NoiseTable[0][0][0]); - - bGPUBufferDirty = true; -} - - struct FNiagaraDataInterfaceParametersCS_CurlNoise : public FNiagaraDataInterfaceParametersCS { virtual void Bind(const FNiagaraDataInterfaceParamRef& ParamRef, const class FShaderParameterMap& ParameterMap) override { - CurlNoiseBuffer.Bind(ParameterMap, *(UNiagaraDataInterfaceCurlNoise::CurlNoiseBufferName + ParamRef.ParameterInfo.DataInterfaceHLSLSymbol)); + OffsetFromSeed.Bind(ParameterMap, *(OffsetFromSeedBaseName + ParamRef.ParameterInfo.DataInterfaceHLSLSymbol)); } virtual void Serialize(FArchive& Ar)override { - Ar << CurlNoiseBuffer; + Ar << OffsetFromSeed; } - virtual void Set(FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface) const override + virtual void Set(FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface, void* PerInstanceData) const override { check(IsInRenderingThread()); + // Get shader and DI const FComputeShaderRHIParamRef ComputeShaderRHI = Shader->GetComputeShader(); - UNiagaraDataInterfaceCurlNoise* CurlNoiseDI = CastChecked(DataInterface); - FRWBuffer& GPUBuffer = CurlNoiseDI->GetGPUBuffer(); + UNiagaraDataInterfaceCurlNoise* CNDI = CastChecked(DataInterface); - RHICmdList.SetShaderResourceViewParameter(ComputeShaderRHI, CurlNoiseBuffer.GetBaseIndex(), GPUBuffer.SRV); + // Note: There is a flush in PreEditChange to make sure everything is synced up at this point + + // Set parameters + SetShaderValue(RHICmdList, ComputeShaderRHI, OffsetFromSeed, CNDI->OffsetFromSeed); } private: - - FShaderResourceParameter CurlNoiseBuffer; + FShaderParameter OffsetFromSeed; }; FNiagaraDataInterfaceParametersCS* UNiagaraDataInterfaceCurlNoise::ConstructComputeParameters()const diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceTexture.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceTexture.cpp index df5258ed0423..0779de359092 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceTexture.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceTexture.cpp @@ -373,7 +373,7 @@ struct FNiagaraDataInterfaceParametersCS_Texture : public FNiagaraDataInterfaceP } } - virtual void Set(FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface) const override + virtual void Set(FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface, void* PerInstanceData) const override { check(IsInRenderingThread()); @@ -388,20 +388,33 @@ struct FNiagaraDataInterfaceParametersCS_Texture : public FNiagaraDataInterfaceP SetShaderValue(RHICmdList, ComputeShaderRHI, Dimensions, TexDims); return; } + FTextureRHIParamRef TextureRHI = Texture->TextureReference.TextureReferenceRHI->GetReferencedTexture(); - SetTextureParameter( - RHICmdList, - ComputeShaderRHI, - TextureParam, - SamplerParam, - Texture->Resource->SamplerStateRHI, - TextureRHI - ); - TexDims[0] = TextureDI->Texture->GetSurfaceWidth(); - TexDims[1] = TextureDI->Texture->GetSurfaceHeight(); + if (TextureRHI != nullptr) + { + SetTextureParameter( + RHICmdList, + ComputeShaderRHI, + TextureParam, + SamplerParam, + Texture->Resource->SamplerStateRHI, + TextureRHI + ); + TexDims[0] = TextureDI->Texture->GetSurfaceWidth(); + TexDims[1] = TextureDI->Texture->GetSurfaceHeight(); + + } + else + { + TexDims[0] = 0.0f; + TexDims[1] = 0.0f; + SetShaderValue(RHICmdList, ComputeShaderRHI, Dimensions, TexDims); + return; + } + SetShaderValue(RHICmdList, ComputeShaderRHI, Dimensions, TexDims); } - + private: diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVectorField.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVectorField.cpp index ca9764a77c84..ca0da124574c 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVectorField.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataInterfaceVectorField.cpp @@ -1,317 +1,59 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "NiagaraDataInterfaceVectorField.h" -#include "Internationalization/Internationalization.h" #include "VectorField/VectorFieldStatic.h" #include "VectorField/VectorFieldAnimated.h" -#include "Math/Float16Color.h" #include "NiagaraShader.h" #include "ShaderParameterUtils.h" +//#include "Internationalization/Internationalization.h" #define LOCTEXT_NAMESPACE "NiagaraDataInterfaceVectorField" +// Global HLSL variable base names, used by HLSL. +static const FString SamplerBaseName(TEXT("VectorFieldSampler_")); +static const FString TextureBaseName(TEXT("VectorFieldTexture_")); +static const FString TilingAxesBaseName(TEXT("TilingAxes_")); +static const FString DimensionsBaseName(TEXT("Dimensions_")); +static const FString MinBoundsBaseName(TEXT("MinBounds_")); +static const FString MaxBoundsBaseName(TEXT("MaxBounds_")); -const FString UNiagaraDataInterfaceVectorField::BufferBaseName(TEXT("VectorFieldBuffer_")); -const FString UNiagaraDataInterfaceVectorField::DimentionsBaseName(TEXT("Dimentions_")); -const FString UNiagaraDataInterfaceVectorField::BoundsMinBaseName(TEXT("BoundsMin_")); -const FString UNiagaraDataInterfaceVectorField::BoundsMaxBaseName(TEXT("BoundsMax_")); +// Global VM function names, also used by the shaders code generation methods. +static const FName SampleVectorFieldName("SampleField"); +static const FName GetVectorFieldTilingAxesName("FieldTilingAxes"); +static const FName GetVectorFieldDimensionsName("FieldDimensions"); +static const FName GetVectorFieldBoundsName("FieldBounds"); -#if 0 -////////////////////////////////////////////////////////////////////////// -//FNDIVectorField_InstanceData +/*--------------------------------------------------------------------------------------------------------------------------*/ -bool FNDIVectorField_InstanceData::Init(UNiagaraDataInterfaceVectorField* Interface, FNiagaraSystemInstance* SystemInstance) -{ - UE_LOG(LogNiagara, Log, TEXT("FNDIVectorField_InstanceData %p"), this); - check(SystemInstance); - /* - USkeletalMesh* PrevMesh = Mesh; - Component = nullptr; - Mesh = nullptr; - Transform = FMatrix::Identity; - TransformInverseTransposed = FMatrix::Identity; - PrevTransform = FMatrix::Identity; - PrevTransformInverseTransposed = FMatrix::Identity; - */ - DeltaSeconds = 0.0f; - Field = Interface->Field; - - UVectorFieldStatic* StaticVectorField = Cast(Field); - UVectorFieldAnimated* AnimatedVectorField = Cast(Field); - SizeX = 0; - SizeY = 0; - SizeZ = 0; - - /* - OutParameters.WorldToVolume[Index] = VectorFieldInstance->WorldToVolume; - OutParameters.VolumeToWorld[Index] = VectorFieldInstance->VolumeToWorldNoScale; - OutParameters.VolumeSize[Index] = FVector4(Resource->SizeX, Resource->SizeY, Resource->SizeZ, 0); - OutParameters.IntensityAndTightness[Index] = FVector4(Intensity, Tightness, 0, 0); - OutParameters.TilingAxes[Index].X = VectorFieldInstance->bTileX ? 1.0f : 0.0f; - OutParameters.TilingAxes[Index].Y = VectorFieldInstance->bTileY ? 1.0f : 0.0f; - OutParameters.TilingAxes[Index].Z = VectorFieldInstance->bTileZ ? 1.0f : 0.0f; - */ - TilingAxes.X = Interface->bTileX ? 1.0f : 0.0f; - TilingAxes.Y = Interface->bTileY ? 1.0f : 0.0f; - TilingAxes.Z = Interface->bTileZ ? 1.0f : 0.0f; - - - if (StaticVectorField) - { - SizeX = (uint32)StaticVectorField->SizeX; - SizeY = (uint32)StaticVectorField->SizeY; - SizeZ = (uint32)StaticVectorField->SizeZ; - LocalBounds = StaticVectorField->Bounds; - } - else if (AnimatedVectorField) - { - SizeX = (uint32)AnimatedVectorField->VolumeSizeX; - SizeY = (uint32)AnimatedVectorField->VolumeSizeY; - SizeZ = (uint32)AnimatedVectorField->VolumeSizeZ; - LocalBounds = AnimatedVectorField->Bounds; - } -#if 0 - USkeletalMeshComponent* NewSkelComp = nullptr; - if (Interface->Source) - { - ASkeletalMeshActor* MeshActor = Cast(Interface->Source); - USkeletalMeshComponent* SourceComp = nullptr; - if (MeshActor != nullptr) - { - SourceComp = MeshActor->GetSkeletalMeshComponent(); - } - else - { - SourceComp = Interface->Source->FindComponentByClass(); - } - - if (SourceComp) - { - Mesh = SourceComp->SkeletalMesh; - NewSkelComp = SourceComp; - } - else - { - Component = Interface->Source->GetRootComponent(); - } - } - else - { - if (UNiagaraComponent* SimComp = SystemInstance->GetComponent()) - { - if (USkeletalMeshComponent* ParentComp = Cast(SimComp->GetAttachParent())) - { - NewSkelComp = ParentComp; - Mesh = ParentComp->SkeletalMesh; - } - else if (USkeletalMeshComponent* OuterComp = SimComp->GetTypedOuter()) - { - NewSkelComp = OuterComp; - Mesh = OuterComp->SkeletalMesh; - } - else if (AActor* Owner = SimComp->GetAttachmentRootActor()) - { - TArray SourceComps = Owner->GetComponentsByClass(USkeletalMeshComponent::StaticClass()); - for (UActorComponent* ActorComp : SourceComps) - { - USkeletalMeshComponent* SourceComp = Cast(ActorComp); - if (SourceComp) - { - USkeletalMesh* PossibleMesh = SourceComp->SkeletalMesh; - if (PossibleMesh != nullptr/* && PossibleMesh->bAllowCPUAccess*/) - { - Mesh = PossibleMesh; - NewSkelComp = SourceComp; - break; - } - } - } - } - - if (!Component.IsValid()) - { - Component = SimComp; - } - } - } - - if (NewSkelComp) - { - Component = NewSkelComp; - } - - check(Component.IsValid()); - - if (!Mesh && Interface->DefaultMesh) - { - Mesh = Interface->DefaultMesh; - } - - if (Component.IsValid() && Mesh) - { - PrevTransform = Transform; - PrevTransformInverseTransposed = TransformInverseTransposed; - Transform = Component->GetComponentToWorld().ToMatrixWithScale(); - TransformInverseTransposed = Transform.InverseFast().GetTransposed(); - } - - if (!Mesh) - { - UE_LOG(LogNiagara, Log, TEXT("SkeletalMesh data interface has no valid mesh. Failed InitPerInstanceData - %s"), *Interface->GetFullName()); - return false; - } - - // if (!Mesh->bAllowCPUAccess) - // { - // UE_LOG(LogNiagara, Log, TEXT("SkeletalMesh data interface using a mesh that does not allow CPU access. Failed InitPerInstanceData - Mesh: %s"), *Mesh->GetFullName()); - // return false; - // } - - if (!Component.IsValid()) - { - UE_LOG(LogNiagara, Log, TEXT("SkeletalMesh data interface has no valid component. Failed InitPerInstanceData - %s"), *Interface->GetFullName()); - return false; - } - - // bIsAreaWeightedSampling = Mesh->bSupportUniformlyDistributedSampling; - - // //Init the instance filter - // ValidSections.Empty(); - // FStaticMeshLODResources& Res = Mesh->RenderData->LODResources[0]; - // for (int32 i = 0; i < Res.Sections.Num(); ++i) - // { - // if (Interface->SectionFilter.AllowedMaterialSlots.Num() == 0 || Interface->SectionFilter.AllowedMaterialSlots.Contains(Res.Sections[i].MaterialIndex)) - // { - // ValidSections.Add(i); - // } - // } - // - // if (GetValidSections().Num() == 0) - // { - // UE_LOG(LogNiagara, Log, TEXT("StaticMesh data interface has a section filter preventing any spawning. Failed InitPerInstanceData - %s"), *Interface->GetFullName()); - // return false; - // } - // - // Sampler.Init(&Res, this); - - if (NewSkelComp) - { - TWeakObjectPtr SkelWeakCompPtr = NewSkelComp; - SkinningData = SystemInstance->GetWorldManager()->GetSkeletalMeshGeneratedData().GetCachedSkinningData(SkelWeakCompPtr); - } -#endif - - return true; -} - -void FNDIVectorField_InstanceData::UpdateTransforms(const FMatrix& LocalToWorld, FMatrix& OutVolumeToWorld, FMatrix& OutWorldToVolume) -{ - const FVector VolumeOffset = LocalBounds.Min; - const FVector VolumeScale = LocalBounds.Max - LocalBounds.Min; - //VolumeToWorldNoScale = LocalToWorld.GetMatrixWithoutScale().RemoveTranslation(); - OutVolumeToWorld = FScaleMatrix(VolumeScale) * FTranslationMatrix(VolumeOffset) - * LocalToWorld; - OutWorldToVolume = OutVolumeToWorld.InverseFast(); -} - -const void* FNDIVectorField_InstanceData::Lock() -{ - UVectorFieldStatic* StaticVectorField = Cast(Field); - UVectorFieldAnimated* AnimatedVectorField = Cast(Field); - - if (StaticVectorField) - { - return StaticVectorField->SourceData.LockReadOnly(); - } - return nullptr; -} - -void FNDIVectorField_InstanceData::Unlock() -{ - UVectorFieldStatic* StaticVectorField = Cast(Field); - UVectorFieldAnimated* AnimatedVectorField = Cast(Field); - - if (StaticVectorField) - { - StaticVectorField->SourceData.Unlock(); - } -} - - -bool FNDIVectorField_InstanceData::ResetRequired(UNiagaraDataInterfaceVectorField* Interface)const -{ - //USceneComponent* Comp = Component.Get(); - //if (!Comp) - //{ - // //The component we were bound to is no longer valid so we have to trigger a reset. - // return true; - //} - - //if (USkeletalMeshComponent* SkelComp = Cast(Comp)) - //{ - // if (!SkelComp->SkeletalMesh) - // { - // return true; - // } - //} - //else - //{ - if (!Interface->Field || Interface->Field != Field) - { - return true; - } - //} - - return false; -} - -bool FNDIVectorField_InstanceData::Tick(UNiagaraDataInterfaceVectorField* Interface, FNiagaraSystemInstance* SystemInstance, float InDeltaSeconds) -{ - if (ResetRequired(Interface)) - { - return true; - } - else - { - DeltaSeconds = InDeltaSeconds; - /*if (Component.IsValid() && Mesh) - { - PrevTransform = Transform; - PrevTransformInverseTransposed = TransformInverseTransposed; - Transform = Component->GetComponentToWorld().ToMatrixWithScale(); - TransformInverseTransposed = Transform.InverseFast().GetTransposed(); - } - else - { - PrevTransform = FMatrix::Identity; - PrevTransformInverseTransposed = FMatrix::Identity; - Transform = FMatrix::Identity; - TransformInverseTransposed = FMatrix::Identity; - }*/ - return false; - } -} -#endif - -//Instance Data END -////////////////////////////////////////////////////////////////////////// UNiagaraDataInterfaceVectorField::UNiagaraDataInterfaceVectorField(FObjectInitializer const& ObjectInitializer) : Super(ObjectInitializer) , Field(nullptr) , bTileX(false) , bTileY(false) , bTileZ(false) - , bGPUBufferDirty(true) { } +/*--------------------------------------------------------------------------------------------------------------------------*/ + + #if WITH_EDITOR +void UNiagaraDataInterfaceVectorField::PreEditChange(UProperty* PropertyAboutToChange) +{ + Super::PreEditChange(PropertyAboutToChange); + + // Flush the rendering thread before making any changes to make sure the + // data read by the compute shader isn't subject to a race condition. + // TODO(mv): Solve properly using something like a RT Proxy. + FlushRenderingCommands(); +} void UNiagaraDataInterfaceVectorField::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) { Super::PostEditChangeProperty(PropertyChangedEvent); - InitField(); } +#endif //WITH_EDITOR + void UNiagaraDataInterfaceVectorField::PostLoad() { @@ -320,7 +62,6 @@ void UNiagaraDataInterfaceVectorField::PostLoad() { Field->ConditionalPostLoad(); } - InitField(); } void UNiagaraDataInterfaceVectorField::PostInitProperties() @@ -330,15 +71,11 @@ void UNiagaraDataInterfaceVectorField::PostInitProperties() //Can we register data interfaces as regular types and fold them into the FNiagaraVariable framework for UI and function calls etc? if (HasAnyFlags(RF_ClassDefaultObject)) { - FNiagaraTypeRegistry::Register(FNiagaraTypeDefinition(GetClass()), true, false, false); + FNiagaraTypeRegistry::Register(FNiagaraTypeDefinition(GetClass()), /*bCanBeParameter*/ true, /*bCanBePayload*/ false, /*bIsUserDefined*/ false); } } -#endif //WITH_EDITOR - -static const FName SampleVectorFieldName("SampleField"); -static const FName GetVectorDimsName("FieldDimensions"); -static const FName GetVectorFieldBoundsName("FieldBounds"); +/*--------------------------------------------------------------------------------------------------------------------------*/ void UNiagaraDataInterfaceVectorField::GetFunctions(TArray& OutFunctions) { @@ -356,7 +93,7 @@ void UNiagaraDataInterfaceVectorField::GetFunctions(TArray InstData(Context); - if (!InstData.Get()) + else if (BindingInfo.Name == GetVectorFieldTilingAxesName && BindingInfo.GetNumInputs() == 0 && BindingInfo.GetNumOutputs() == 3) { - UE_LOG(LogNiagara, Warning, TEXT("FNDIVectorField_InstanceData has invalid instance data. %s"), *GetPathName()); - }*/ - - VectorVM::FExternalFuncRegisterHandler OutSizeX(Context); - VectorVM::FExternalFuncRegisterHandler OutSizeY(Context); - VectorVM::FExternalFuncRegisterHandler OutSizeZ(Context); - - for (int32 i = 0; i < Context.NumInstances; ++i) - { - *OutSizeX.GetDest() = (float)SizeX; - *OutSizeY.GetDest() = (float)SizeY; - *OutSizeZ.GetDest() = (float)SizeZ; - - OutSizeX.Advance(); - OutSizeY.Advance(); - OutSizeZ.Advance(); + OutFunc = FVMExternalFunction::CreateUObject(this, &UNiagaraDataInterfaceVectorField::GetFieldTilingAxes); } } -void UNiagaraDataInterfaceVectorField::GetFieldBounds(FVectorVMContext& Context) -{ - /*VectorVM::FUserPtrHandler InstData(Context); - if (!InstData.Get()) - { - UE_LOG(LogNiagara, Warning, TEXT("FNDIVectorField_InstanceData has invalid instance data. %s"), *GetPathName()); - }*/ - - VectorVM::FExternalFuncRegisterHandler OutMinX(Context); - VectorVM::FExternalFuncRegisterHandler OutMinY(Context); - VectorVM::FExternalFuncRegisterHandler OutMinZ(Context); - VectorVM::FExternalFuncRegisterHandler OutMaxX(Context); - VectorVM::FExternalFuncRegisterHandler OutMaxY(Context); - VectorVM::FExternalFuncRegisterHandler OutMaxZ(Context); - - for (int32 i = 0; i < Context.NumInstances; ++i) - { - *OutMinX.GetDest() = LocalBounds.Min.X; - *OutMinY.GetDest() = LocalBounds.Min.Y; - *OutMinZ.GetDest() = LocalBounds.Min.Z; - *OutMaxX.GetDest() = LocalBounds.Max.X; - *OutMaxY.GetDest() = LocalBounds.Max.Y; - *OutMaxZ.GetDest() = LocalBounds.Max.Z; - - OutMinX.Advance(); - OutMinY.Advance(); - OutMinZ.Advance(); - OutMaxX.Advance(); - OutMaxY.Advance(); - OutMaxZ.Advance(); - } -} - -/* -* Compute the influence of vector fields on a particle at the given position. -* @param OutForce - Force to apply to the particle. -* @param OutVelocity - Direct velocity influence on the particle. -* @param Position - Position of the particle. -* @param PerParticleScale - Amount by which to scale the influence on this particle. -void EvaluateVectorFields(out float3 OutForce, out float4 OutVelocity, float3 Position, float PerParticleScale) -{ - float3 TotalForce = 0; - float3 WeightedVelocity = 0; - float TotalWeight = 0; - float FinalWeight = 0; - - for (int VectorFieldIndex = 0; VectorFieldIndex < VectorFields.Count; ++VectorFieldIndex) - { - float2 IntensityAndTightness = VectorFields.IntensityAndTightness[VectorFieldIndex].xy; - float Intensity = IntensityAndTightness.x * PerParticleScale; - float Tightness = IntensityAndTightness.y; - float3 VolumeSize = VectorFields.VolumeSize[VectorFieldIndex].xyz; - float3 VolumeUV = mul(float4(Position.xyz, 1), VectorFields.WorldToVolume[VectorFieldIndex]).xyz; - //Tile the UVs if needed. TilingAxes will be 1.0 or 0.0 in each channel depending on which axes are being tiled, if any. - VolumeUV -= floor(VolumeUV * VectorFields.TilingAxes[VectorFieldIndex].xyz); - - float3 AxisWeights = - saturate(VolumeUV * VolumeSize.xyz) * - saturate((1.0f - VolumeUV) * VolumeSize.xyz); - float DistanceWeight = min(AxisWeights.x, min(AxisWeights.y, AxisWeights.z)); - - // @todo compat hack: Some compilers only allow constant indexing into a texture array - // float3 VectorSample = Texture3DSample(VectorFieldTextures[VectorFieldIndex], VectorFieldTexturesSampler, saturate(VolumeUV)).xyz; - float3 VectorSample = SampleVectorFieldTexture(VectorFieldIndex, saturate(VolumeUV)); - - float3 Vec = mul(float4(VectorSample, 0), VectorFields.VolumeToWorld[VectorFieldIndex]).xyz; - TotalForce += (Vec * DistanceWeight * Intensity); - WeightedVelocity += (Vec * Intensity * DistanceWeight * Tightness); - TotalWeight += (DistanceWeight * Tightness); - FinalWeight = max(FinalWeight, DistanceWeight * Tightness); - } - - // Forces are additive. - OutForce = TotalForce; - // Velocities use a weighted average. - OutVelocity.xyz = WeightedVelocity / (TotalWeight + 0.001f); - OutVelocity.w = FinalWeight; -} -*/ - -void UNiagaraDataInterfaceVectorField::InitField() -{ - UVectorFieldStatic* StaticVectorField = Cast(Field); - UVectorFieldAnimated* AnimatedVectorField = Cast(Field); - SizeX = 0; - SizeY = 0; - SizeZ = 0; - - /* - OutParameters.WorldToVolume[Index] = VectorFieldInstance->WorldToVolume; - OutParameters.VolumeToWorld[Index] = VectorFieldInstance->VolumeToWorldNoScale; - OutParameters.VolumeSize[Index] = FVector4(Resource->SizeX, Resource->SizeY, Resource->SizeZ, 0); - OutParameters.IntensityAndTightness[Index] = FVector4(Intensity, Tightness, 0, 0); - OutParameters.TilingAxes[Index].X = VectorFieldInstance->bTileX ? 1.0f : 0.0f; - OutParameters.TilingAxes[Index].Y = VectorFieldInstance->bTileY ? 1.0f : 0.0f; - OutParameters.TilingAxes[Index].Z = VectorFieldInstance->bTileZ ? 1.0f : 0.0f; - */ - TilingAxes.X = bTileX ? 1.0f : 0.0f; - TilingAxes.Y = bTileY ? 1.0f : 0.0f; - TilingAxes.Z = bTileZ ? 1.0f : 0.0f; - - - if (StaticVectorField) - { - SizeX = (uint32)StaticVectorField->SizeX; - SizeY = (uint32)StaticVectorField->SizeY; - SizeZ = (uint32)StaticVectorField->SizeZ; - LocalBounds = StaticVectorField->Bounds; - } - else if (AnimatedVectorField) - { - SizeX = (uint32)AnimatedVectorField->VolumeSizeX; - SizeY = (uint32)AnimatedVectorField->VolumeSizeY; - SizeZ = (uint32)AnimatedVectorField->VolumeSizeZ; - LocalBounds = AnimatedVectorField->Bounds; - } -} - -const void* UNiagaraDataInterfaceVectorField::Lock() -{ - UVectorFieldStatic* StaticVectorField = Cast(Field); - UVectorFieldAnimated* AnimatedVectorField = Cast(Field); - - if (StaticVectorField) - { - return StaticVectorField->SourceData.LockReadOnly(); - } - return nullptr; -} - -void UNiagaraDataInterfaceVectorField::Unlock() -{ - UVectorFieldStatic* StaticVectorField = Cast(Field); - UVectorFieldAnimated* AnimatedVectorField = Cast(Field); - - if (StaticVectorField) - { - StaticVectorField->SourceData.Unlock(); - } -} - -void UNiagaraDataInterfaceVectorField::SampleVectorField(FVectorVMContext& Context) -{ - // Input arguments... - VectorVM::FExternalFuncInputHandler XParam(Context); - VectorVM::FExternalFuncInputHandler YParam(Context); - VectorVM::FExternalFuncInputHandler ZParam(Context); - - // User pointer buffer (as requested) - //VectorVM::FUserPtrHandler InstData(Context); - - // Outputs... - VectorVM::FExternalFuncRegisterHandler OutSampleX(Context); - VectorVM::FExternalFuncRegisterHandler OutSampleY(Context); - VectorVM::FExternalFuncRegisterHandler OutSampleZ(Context); - - /*UE_LOG(LogNiagara, Log, TEXT("Registers: In: %d %d %d %d Out: %d %d %d"), - XParam.RegisterIndex, YParam.RegisterIndex, ZParam.RegisterIndex, InstData.RegisterIndex, - OutSampleX.RegisterIndex, OutSampleY.RegisterIndex, OutSampleZ.RegisterIndex);*/ - - UVectorFieldStatic* StaticVectorField = Cast(Field); - UVectorFieldAnimated* AnimatedVectorField = Cast(Field); - - //FMatrix VolumeToWorld; - //FMatrix WorldToVolume; - //FMatrix LocalToWorld = FMatrix::Identity; - //InstData->UpdateTransforms(LocalToWorld, VolumeToWorld, WorldToVolume); - - const FFloat16Color* Data = (const FFloat16Color*)Lock(); - - if (Data == nullptr || Field == nullptr || (StaticVectorField == nullptr && AnimatedVectorField == nullptr) || AnimatedVectorField != nullptr) - { - // Set the default vector to positive X axis... - for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) - { - VectorRegister Dst = MakeVectorRegister(1.0f, 0.0f, 0.0f, 0.0f); - - float *RegPtr = reinterpret_cast(&Dst); - *OutSampleX.GetDest() = RegPtr[0]; - *OutSampleY.GetDest() = RegPtr[1]; - *OutSampleZ.GetDest() = RegPtr[2]; - - XParam.Advance(); - YParam.Advance(); - ZParam.Advance(); - OutSampleX.Advance(); - OutSampleY.Advance(); - OutSampleZ.Advance(); - } - } - else if (StaticVectorField != nullptr) - { - FVector VolumeSize((float)SizeX, (float)SizeY, (float)SizeZ); - uint32 VolumeSizeX = (uint32)SizeX; - uint32 VolumeSizeY = (uint32)SizeY; - uint32 VolumeSizeZ = (uint32)SizeZ; - - for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) - { - FVector Pos(XParam.Get(), YParam.Get(), ZParam.Get()); - - // float3 VolumeUV = mul(float4(Position.xyz, 1), VectorFields.WorldToVolume[VectorFieldIndex]).xyz; - //FNDITransformHandler Handler; - FVector VolumeUV = Pos; - //Handler.TransformPosition(VolumeUV, WorldToVolume); - - // VolumeUV -= floor(VolumeUV * VectorFields.TilingAxes[VectorFieldIndex].xyz); - FVector TiledVolume = VolumeUV * TilingAxes; - VolumeUV.X -= FMath::FloorToFloat(TiledVolume.X); - VolumeUV.Y -= FMath::FloorToFloat(TiledVolume.Y); - VolumeUV.Z -= FMath::FloorToFloat(TiledVolume.Z); - - // Saturate(VolumeUV) - VolumeUV.X = FMath::Clamp(VolumeUV.X, 0.0f, 1.0f); - VolumeUV.Y = FMath::Clamp(VolumeUV.Y, 0.0f, 1.0f); - VolumeUV.Z = FMath::Clamp(VolumeUV.Z, 0.0f, 1.0f); - - // Sample texture... - VolumeUV = VolumeUV * VolumeSize; - - /** - int3 IntCoord = int3(ModXYZ.x, ModXYZ.y, ModXYZ.z);\n"); - float3 frc = frac(ModXYZ);\n"); - */ - FVector VecIndex; - FVector VecFrac; - VecFrac.X = FGenericPlatformMath::Modf(VolumeUV.X, &VecIndex.X); - VecFrac.Y = FGenericPlatformMath::Modf(VolumeUV.Y, &VecIndex.Y); - VecFrac.Z = FGenericPlatformMath::Modf(VolumeUV.Z, &VecIndex.Z); - - uint32 IndexX = (uint32)VecIndex.X; - uint32 IndexY = (uint32)VecIndex.Y; - uint32 IndexZ = (uint32)VecIndex.Z; - -#define VF_MakeIndex(X, Y, Z) ((X) + ((Y) * VolumeSizeX) + ((Z) * VolumeSizeX * VolumeSizeY)) -#define VF_Tile(I, Range) (((I) >= Range) ? 0 : (I)) - - // Trilinear interpolation, as defined by https://en.wikipedia.org/wiki/Trilinear_interpolation - /** - float3 frc = frac(ModXYZ); - float3 V1 = Buffer[IntCoord.x + IntCoord.y*VolumeSizeX + IntCoord.z*VolumeSizeX*VolumeSizeY].xyz; // V(x0, y0, z0) - float3 V2 = Buffer[IntCoord.x + 1 + IntCoord.y*VolumeSizeX + IntCoord.z*VolumeSizeX*VolumeSizeY].xyz; // V(x1, y0, z0) - float3 C00 = lerp(V1, V2, frc.xxx); - V1 = Buffer[IntCoord.x + IntCoord.y*VolumeSizeX + (IntCoord.z+1)*VolumeSizeX*VolumeSizeY].xyz; // V(x0, y0, z1) - V2 = Buffer[IntCoord.x + 1 + IntCoord.y*VolumeSizeX + (IntCoord.z+1)*VolumeSizeX*VolumeSizeY].xyz; // V(x1, y0, z1) - float3 C01 = lerp(V1, V2, frc.xxx); - V1 = Buffer[IntCoord.x + (IntCoord.y+1)*VolumeSizeX + IntCoord.z*VolumeSizeX*VolumeSizeY].xyz; // V(x0, y1, z0) - V2 = Buffer[IntCoord.x + 1 + (IntCoord.y+1)*VolumeSizeX + IntCoord.z*VolumeSizeX*VolumeSizeY].xyz; // V(x1, y1, z0) - float3 C10 = lerp(V1, V2, frc.xxx); - V1 = Buffer[IntCoord.x + (IntCoord.y+1)*VolumeSizeX + (IntCoord.z+1)*VolumeSizeX*VolumeSizeY].xyz; // V(x0, y1, z1) - V2 = Buffer[IntCoord.x + 1 + (IntCoord.y+1)*VolumeSizeX + (IntCoord.z+1)*VolumeSizeX*VolumeSizeY].xyz; // V(x1, y1, z1) - float3 C11 = lerp(V1, V2, frc.xxx); - float3 C0 = lerp(C00, C10, frc.yyy); - float3 C1 = lerp(c01, C11, frc.yyy); - Out_Value = lerp(C0, C1, frc.zzz); - */ - uint32 Index = VF_MakeIndex(VF_Tile(IndexX, VolumeSizeX), VF_Tile(IndexY, VolumeSizeY), VF_Tile(IndexZ, VolumeSizeZ)); - check(Index < (uint32)(VolumeSizeX*VolumeSizeY*VolumeSizeZ)); - FVector V1 ((float)Data[Index].R, (float)Data[Index].G, (float)Data[Index].B); // V(x0, y0, z0) - Index = VF_MakeIndex(VF_Tile(IndexX + 1, VolumeSizeX), VF_Tile(IndexY, VolumeSizeY), VF_Tile(IndexZ, VolumeSizeZ)); - check(Index < (uint32)(VolumeSizeX*VolumeSizeY*VolumeSizeZ)); - FVector V2((float)Data[Index].R, (float)Data[Index].G, (float)Data[Index].B); // V(x1, y0, z0) - FVector C00 = FMath::Lerp(V1, V2, VecFrac.X); - - Index = VF_MakeIndex(IndexX, VF_Tile(IndexY, VolumeSizeY), VF_Tile(IndexZ + 1, VolumeSizeZ)); - check(Index < (uint32)(VolumeSizeX*VolumeSizeY*VolumeSizeZ)); - V1 = FVector((float)Data[Index].R, (float)Data[Index].G, (float)Data[Index].B);// V(x0, y0, z1) - Index = VF_MakeIndex(VF_Tile(IndexX + 1, VolumeSizeX), VF_Tile(IndexY, VolumeSizeY), VF_Tile(IndexZ + 1, VolumeSizeZ)); - check(Index < (uint32)(VolumeSizeX*VolumeSizeY*VolumeSizeZ)); - V1 = FVector((float)Data[Index].R, (float)Data[Index].G, (float)Data[Index].B);// V(x1, y0, z1) - FVector C01 = FMath::Lerp(V1, V2, VecFrac.X); - - Index = VF_MakeIndex(VF_Tile(IndexX, VolumeSizeX), VF_Tile(IndexY + 1, VolumeSizeY), VF_Tile(IndexZ, VolumeSizeZ)); - check(Index < (uint32)(VolumeSizeX*VolumeSizeY*VolumeSizeZ)); - V1 = FVector((float)Data[Index].R, (float)Data[Index].G, (float)Data[Index].B);// V(x0, y1, z0) - Index = VF_MakeIndex(VF_Tile(IndexX + 1, VolumeSizeX), VF_Tile(IndexY + 1, VolumeSizeY), VF_Tile(IndexZ, VolumeSizeZ)); - check(Index < (uint32)(VolumeSizeX*VolumeSizeY*VolumeSizeZ)); - V1 = FVector((float)Data[Index].R, (float)Data[Index].G, (float)Data[Index].B); // V(x1, y1, z0) - FVector C10 = FMath::Lerp(V1, V2, VecFrac.X); - - Index = VF_MakeIndex(VF_Tile(IndexX, VolumeSizeX), VF_Tile(IndexY + 1, VolumeSizeY), VF_Tile(IndexZ + 1, VolumeSizeZ)); - check(Index < (uint32)(VolumeSizeX*VolumeSizeY*VolumeSizeZ)); - V1 = FVector((float)Data[Index].R, (float)Data[Index].G, (float)Data[Index].B);// V(x0, y1, z1) - Index = VF_MakeIndex(VF_Tile(IndexX + 1, VolumeSizeX), VF_Tile(IndexY + 1, VolumeSizeY), VF_Tile(IndexZ + 1, VolumeSizeZ)); - check(Index < (uint32)(VolumeSizeX*VolumeSizeY*VolumeSizeZ)); - V1 = FVector((float)Data[Index].R, (float)Data[Index].G, (float)Data[Index].B);// V(x1, y1, z1) - FVector C11 = FMath::Lerp(V1, V2, VecFrac.X); - - FVector C0 = FMath::Lerp(C00, C10, VecFrac.Y); - FVector C1 = FMath::Lerp(C01, C11, VecFrac.Y); - FVector C = FMath::Lerp(C0, C1, VecFrac.Z); - - // Write final output... - *OutSampleX.GetDest() = C.X; - *OutSampleY.GetDest() = C.Y; - *OutSampleZ.GetDest() = C.Z; - - XParam.Advance(); - YParam.Advance(); - ZParam.Advance(); - OutSampleX.Advance(); - OutSampleY.Advance(); - OutSampleZ.Advance(); - } - } - - Unlock(); -} - - -bool UNiagaraDataInterfaceVectorField::CanExecuteOnTarget(ENiagaraSimTarget Target)const -{ - return true; -} - -bool UNiagaraDataInterfaceVectorField::CopyToInternal(UNiagaraDataInterface* Destination) const -{ - if (!Super::CopyToInternal(Destination)) - { - return false; - } - - UNiagaraDataInterfaceVectorField* OtherTyped = CastChecked(Destination); - OtherTyped->Field = Field; - OtherTyped->bTileX = bTileX; - OtherTyped->bTileY = bTileY; - OtherTyped->bTileZ = bTileZ; - OtherTyped->bGPUBufferDirty = true; - return true; -} - bool UNiagaraDataInterfaceVectorField::Equals(const UNiagaraDataInterface* Other) const { if (!Super::Equals(Other)) @@ -763,179 +160,229 @@ bool UNiagaraDataInterfaceVectorField::Equals(const UNiagaraDataInterface* Other && OtherTyped->bTileZ == bTileZ; } - -int32 UNiagaraDataInterfaceVectorField::PerInstanceDataSize()const +bool UNiagaraDataInterfaceVectorField::CanExecuteOnTarget(ENiagaraSimTarget Target) const { - return 0; + return true; } -bool UNiagaraDataInterfaceVectorField::InitPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) -{ - //FNDIVectorField_InstanceData* Inst = new (PerInstanceData) FNDIVectorField_InstanceData(); - //return Inst->Init(this, SystemInstance); - return false; -} +/*--------------------------------------------------------------------------------------------------------------------------*/ -void UNiagaraDataInterfaceVectorField::DestroyPerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) +#if WITH_EDITOR +TArray UNiagaraDataInterfaceVectorField::GetErrors() { - /*FNDIVectorField_InstanceData* Inst = (FNDIVectorField_InstanceData*)PerInstanceData; - Inst->~FNDIVectorField_InstanceData();*/ + UVectorFieldStatic* StaticVectorField = Cast(Field); + UVectorFieldAnimated* AnimatedVectorField = Cast(Field); + + // TODO(mv): Improve error messages? + TArray Errors; + if (StaticVectorField != nullptr && !StaticVectorField->bAllowCPUAccess) + { + FNiagaraDataInterfaceError CPUAccessNotAllowedError( + FText::Format( + LOCTEXT("CPUAccessNotAllowedError", "This Vector Field needs CPU access in order to be used properly.({0})"), + FText::FromString(StaticVectorField->GetName()) + ), + LOCTEXT("CPUAccessNotAllowedErrorSummary", "CPU access error"), + FNiagaraDataInterfaceFix::CreateLambda( + [=]() + { + StaticVectorField->SetCPUAccessEnabled(); + return true; + } + ) + ); + Errors.Add(CPUAccessNotAllowedError); + } + else if (AnimatedVectorField != nullptr) + { + FNiagaraDataInterfaceError AnimatedVectorFieldsNotSupportedError( + LOCTEXT("AnimatedVectorFieldsNotSupportedErrorSummary", "Invalid vector field type."), + LOCTEXT("AnimatedVectorFieldsNotSupportedError", "Animated vector fields are not supported."), + nullptr + ); + Errors.Add(AnimatedVectorFieldsNotSupportedError); + } + else if (Field == nullptr) + { + FNiagaraDataInterfaceError VectorFieldNotLoadedError( + LOCTEXT("VectorFieldNotLoadedErrorSummary", "No Vector Field is loaded."), + LOCTEXT("VectorFieldNotLoadedError", "No Vector Field is loaded."), + nullptr + ); + Errors.Add(VectorFieldNotLoadedError); + } + return Errors; } +#endif // WITH_EDITOR -bool UNiagaraDataInterfaceVectorField::PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float InDeltaSeconds) +/*--------------------------------------------------------------------------------------------------------------------------*/ + +void UNiagaraDataInterfaceVectorField::GetParameterDefinitionHLSL(FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) { - /*FNDIVectorField_InstanceData* Inst = (FNDIVectorField_InstanceData*)PerInstanceData; - return Inst->Tick(this, SystemInstance, InDeltaSeconds);*/ - return false; + static const TCHAR *FormatDeclarations = TEXT(R"( + float3 {TilingAxesName}; + float3 {DimensionsName}; + float3 {MinBoundsName}; + float3 {MaxBoundsName}; + Texture3D {TextureName}; + SamplerState {SamplerName}; + )"); + TMap ArgsDeclarations = { + { TEXT("TilingAxesName"), TilingAxesBaseName + ParamInfo.DataInterfaceHLSLSymbol }, + { TEXT("DimensionsName"), DimensionsBaseName + ParamInfo.DataInterfaceHLSLSymbol }, + { TEXT("MinBoundsName"), MinBoundsBaseName + ParamInfo.DataInterfaceHLSLSymbol }, + { TEXT("MaxBoundsName"), MaxBoundsBaseName + ParamInfo.DataInterfaceHLSLSymbol }, + { TEXT("TextureName"), TextureBaseName + ParamInfo.DataInterfaceHLSLSymbol }, + { TEXT("SamplerName"), SamplerBaseName + ParamInfo.DataInterfaceHLSLSymbol } + }; + OutHLSL += FString::Format(FormatDeclarations, ArgsDeclarations); } bool UNiagaraDataInterfaceVectorField::GetFunctionHLSL(const FName& DefinitionFunctionName, FString InstanceFunctionName, FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) { if (DefinitionFunctionName == SampleVectorFieldName) { - FString BufferName = "VectorFieldLUT_" + ParamInfo.DataInterfaceHLSLSymbol; - OutHLSL += TEXT("void ") + InstanceFunctionName + TEXT("(float3 In_SamplePoint, out float3 Out_Value) \n{\n"); - OutHLSL += TEXT("\t int3 IntCoord = int3(0,0,0);\n"); - OutHLSL += TEXT("\t int3 Dimensions = int3(1,1,1);\n"); - OutHLSL += TEXT("\t Out_Value = ") + BufferName + TEXT("[IntCoord.x + IntCoord.y*Dimensions.x + IntCoord.z*Dimensions.x*Dimensions.y].xyz;\n"); - OutHLSL += TEXT("\n}\n"); + static const TCHAR *FormatSample = TEXT(R"( + void {FunctionName}(float3 In_SamplePoint, out float3 Out_Sample) + { + float3 SamplePoint = (In_SamplePoint - {MinBoundsName}) / ({MaxBoundsName} - {MinBoundsName}); + Out_Sample = Texture3DSample({TextureName}, {SamplerName}, SamplePoint).xyz; + } + )"); + TMap ArgsSample = { + {TEXT("FunctionName"), InstanceFunctionName}, + {TEXT("TextureName"), TextureBaseName + ParamInfo.DataInterfaceHLSLSymbol}, + {TEXT("MinBoundsName"), MinBoundsBaseName + ParamInfo.DataInterfaceHLSLSymbol}, + {TEXT("MaxBoundsName"), MaxBoundsBaseName + ParamInfo.DataInterfaceHLSLSymbol}, + {TEXT("SamplerName"), SamplerBaseName + ParamInfo.DataInterfaceHLSLSymbol} + }; + OutHLSL += FString::Format(FormatSample, ArgsSample); return true; } - else if (DefinitionFunctionName == GetVectorDimsName) + else if (DefinitionFunctionName == GetVectorFieldTilingAxesName) { - FString DimsVar = DimentionsBaseName + ParamInfo.DataInterfaceHLSLSymbol; - OutHLSL += TEXT("void ") + InstanceFunctionName + TEXT("(out float3 Out_Value) \n{\n"); - OutHLSL += TEXT("\t Out_Value = ") + DimsVar + TEXT(";\n"); - OutHLSL += TEXT("\n}\n"); + static const TCHAR *FormatTilingAxes = TEXT(R"( + void {FunctionName}(out float3 Out_TilingAxes) + { + Out_TilingAxes = {TilingAxesName}; + } + )"); + TMap ArgsTilingAxes = { + {TEXT("FunctionName"), InstanceFunctionName}, + {TEXT("TilingAxesName"), TilingAxesBaseName + ParamInfo.DataInterfaceHLSLSymbol} + }; + OutHLSL += FString::Format(FormatTilingAxes, ArgsTilingAxes); + return true; + } + else if (DefinitionFunctionName == GetVectorFieldDimensionsName) + { + static const TCHAR *FormatDimensions = TEXT(R"( + void {FunctionName}(out float3 Out_Dimensions) + { + Out_Dimensions = {DimensionsName}; + } + )"); + TMap ArgsDimensions = { + {TEXT("FunctionName"), InstanceFunctionName}, + {TEXT("DimensionsName"), DimensionsBaseName + ParamInfo.DataInterfaceHLSLSymbol} + }; + OutHLSL += FString::Format(FormatDimensions, ArgsDimensions); return true; } else if (DefinitionFunctionName == GetVectorFieldBoundsName) { - FString BoundsMinVar = BoundsMinBaseName + ParamInfo.DataInterfaceHLSLSymbol; - FString BoundsMaxVar = BoundsMaxBaseName + ParamInfo.DataInterfaceHLSLSymbol; - OutHLSL += TEXT("void ") + InstanceFunctionName + TEXT("(out float3 Out_MinBounds, out float3 Out_MaxBounds) \n{\n"); - OutHLSL += TEXT("\t Out_MinBounds = ") + BoundsMinVar + TEXT(";\n"); - OutHLSL += TEXT("\t Out_MaxBounds = ") + BoundsMaxVar + TEXT(";\n"); - OutHLSL += TEXT("\n}\n"); + static const TCHAR *FormatBounds = TEXT(R"( + void {FunctionName}(out float3 Out_MinBounds, out float3 Out_MaxBounds) + { + Out_MinBounds = {MinBoundsName}; + Out_MaxBounds = {MaxBoundsName}; + } + )"); + TMap ArgsBounds = { + {TEXT("FunctionName"), InstanceFunctionName}, + {TEXT("MinBoundsName"), MinBoundsBaseName + ParamInfo.DataInterfaceHLSLSymbol}, + {TEXT("MaxBoundsName"), MaxBoundsBaseName + ParamInfo.DataInterfaceHLSLSymbol} + }; + OutHLSL += FString::Format(FormatBounds, ArgsBounds); return true; } return false; } -void UNiagaraDataInterfaceVectorField::GetParameterDefinitionHLSL(FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) -{ - OutHLSL += TEXT("Buffer ") + BufferBaseName + ParamInfo.DataInterfaceHLSLSymbol + TEXT(";\n"); - OutHLSL += TEXT("float3 ") + DimentionsBaseName + ParamInfo.DataInterfaceHLSLSymbol + TEXT(";\n"); -} - -FRWBuffer& UNiagaraDataInterfaceVectorField::GetGPUBuffer() -{ - check(IsInRenderingThread()); - - //TODO: Doing this here is not really threadsafe. Need to move to an RT proxy style model? - if (bGPUBufferDirty) - { - UVectorFieldStatic* StaticVectorField = Cast(Field); - UVectorFieldAnimated* AnimatedVectorField = Cast(Field); - - uint32 Width = 10; - uint32 Height = 10; - uint32 Depth = 10; - const FFloat16Color* Data = nullptr; - - if (StaticVectorField) - { - Width = StaticVectorField->SizeX; - Height = StaticVectorField->SizeY; - Depth = StaticVectorField->SizeZ; - } - - GPUBuffer.Release(); - uint32 BufferSize = Width * Height * Depth * sizeof(float) * 4; - TResourceArray TempTable; - TempTable.AddUninitialized(Width*Height*Depth); - - if (StaticVectorField) - { - Data = (const FFloat16Color*)StaticVectorField->SourceData.LockReadOnly(); - - // TODO sckime - can we just send this down as half4's ??? - for (uint32 z = 0; z < Depth; z++) - { - for (uint32 y = 0; y < Height; y++) - { - for (uint32 x = 0; x < Width; x++) - { - uint32 Index = x + (y* Height) + (z * Width * Height); - TempTable[Index] = FVector4(Data[Index].R, Data[Index].G, Data[Index].B, 0.0f); - } - } - } - StaticVectorField->SourceData.Unlock(); - } - else - { - // We don't support the other types for now. - for (uint32 z = 0; z < Depth; z++) - { - for (uint32 y = 0; y < Height; y++) - { - for (uint32 x = 0; x < Width; x++) - { - uint32 Index = x + (y* Height) + (z * Width * Height); - TempTable[Index] = FVector4(1.0f, 0.0f, 0.0f, 0.0f); - } - } - } - } - - GPUBuffer.Initialize(sizeof(float) * 4, Width*Height*Depth, EPixelFormat::PF_A32B32G32R32F, BUF_Static, TEXT("CurlnoiseTable"), &TempTable); - //FPlatformMemory::Memcpy(BufferData, TempTable, BufferSize); - //RHIUnlockVertexBuffer(GPUBuffer.Buffer.Buffer); - bGPUBufferDirty = false; - } - - return GPUBuffer; -} - struct FNiagaraDataInterfaceParametersCS_VectorField : public FNiagaraDataInterfaceParametersCS { virtual void Bind(const FNiagaraDataInterfaceParamRef& ParamRef, const class FShaderParameterMap& ParameterMap) override { - VectorFieldBuffer.Bind(ParameterMap, *(UNiagaraDataInterfaceVectorField::BufferBaseName + ParamRef.ParameterInfo.DataInterfaceHLSLSymbol)); - Dimentions.Bind(ParameterMap, *(UNiagaraDataInterfaceVectorField::DimentionsBaseName + ParamRef.ParameterInfo.DataInterfaceHLSLSymbol)); - BoundsMin.Bind(ParameterMap, *(UNiagaraDataInterfaceVectorField::BoundsMinBaseName + ParamRef.ParameterInfo.DataInterfaceHLSLSymbol)); - BoundsMax.Bind(ParameterMap, *(UNiagaraDataInterfaceVectorField::BoundsMaxBaseName + ParamRef.ParameterInfo.DataInterfaceHLSLSymbol)); + VectorFieldSampler.Bind(ParameterMap, *(SamplerBaseName + ParamRef.ParameterInfo.DataInterfaceHLSLSymbol)); + VectorFieldTexture.Bind(ParameterMap, *(TextureBaseName + ParamRef.ParameterInfo.DataInterfaceHLSLSymbol)); + TilingAxes.Bind(ParameterMap, *(TilingAxesBaseName + ParamRef.ParameterInfo.DataInterfaceHLSLSymbol)); + Dimensions.Bind(ParameterMap, *(DimensionsBaseName + ParamRef.ParameterInfo.DataInterfaceHLSLSymbol)); + MinBounds.Bind(ParameterMap, *(MinBoundsBaseName + ParamRef.ParameterInfo.DataInterfaceHLSLSymbol)); + MaxBounds.Bind(ParameterMap, *(MaxBoundsBaseName + ParamRef.ParameterInfo.DataInterfaceHLSLSymbol)); } virtual void Serialize(FArchive& Ar)override { - Ar << VectorFieldBuffer; - Ar << Dimentions; - Ar << BoundsMin; - Ar << BoundsMax; + Ar << VectorFieldSampler; + Ar << VectorFieldTexture; + Ar << TilingAxes; + Ar << Dimensions; + Ar << MinBounds; + Ar << MaxBounds; } - virtual void Set(FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface) const override + virtual void Set(FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface, void* PerInstanceData) const override { check(IsInRenderingThread()); + // Different sampler states used by the computer shader to sample 3D vector field. + // Encoded as bitflags. To sample: + // 1st bit: X-axis tiling flag + // 2nd bit: Y-axis tiling flag + // 3rd bit: Z-axis tiling flag + static FSamplerStateRHIParamRef SamplerStates[8] = { nullptr }; + if (SamplerStates[0] == nullptr) + { + SamplerStates[0] = TStaticSamplerState::GetRHI(); + SamplerStates[1] = TStaticSamplerState::GetRHI(); + SamplerStates[2] = TStaticSamplerState::GetRHI(); + SamplerStates[3] = TStaticSamplerState::GetRHI(); + SamplerStates[4] = TStaticSamplerState::GetRHI(); + SamplerStates[5] = TStaticSamplerState::GetRHI(); + SamplerStates[6] = TStaticSamplerState::GetRHI(); + SamplerStates[7] = TStaticSamplerState::GetRHI(); + } + + // Get shader and DI const FComputeShaderRHIParamRef ComputeShaderRHI = Shader->GetComputeShader(); UNiagaraDataInterfaceVectorField* VFDI = CastChecked(DataInterface); - FRWBuffer& VFBuffer = VFDI->GetGPUBuffer(); + + // Note: There is a flush in PreEditChange to make sure everything is synced up at this point - RHICmdList.SetShaderResourceViewParameter(ComputeShaderRHI, VectorFieldBuffer.GetBaseIndex(), VFBuffer.SRV); - SetShaderValue(RHICmdList, ComputeShaderRHI, Dimentions, VFDI->GetDimentions()); - SetShaderValue(RHICmdList, ComputeShaderRHI, BoundsMin, VFDI->GetBoundsMin()); - SetShaderValue(RHICmdList, ComputeShaderRHI, BoundsMax, VFDI->GetBoundsMax()); + // Get and set 3D texture handle from the currently bound vector field. + UVectorFieldStatic* StaticVectorField = Cast(VFDI->Field); + FRHITexture* VolumeTextureRHI = StaticVectorField ? (FRHITexture*)StaticVectorField->GetVolumeTextureRef() : (FRHITexture*)GBlackVolumeTexture->TextureRHI; + SetTextureParameter(RHICmdList, ComputeShaderRHI, VectorFieldTexture, VolumeTextureRHI); + + // Get and set sampler state + FSamplerStateRHIParamRef SamplerState = SamplerStates[int(VFDI->bTileX) + 2 * int(VFDI->bTileY) + 4 * int(VFDI->bTileZ)]; + SetSamplerParameter(RHICmdList, ComputeShaderRHI, VectorFieldSampler, SamplerState); + + // + SetShaderValue(RHICmdList, ComputeShaderRHI, TilingAxes, VFDI->GetTilingAxes()); + SetShaderValue(RHICmdList, ComputeShaderRHI, Dimensions, VFDI->GetDimensions()); + SetShaderValue(RHICmdList, ComputeShaderRHI, MinBounds, VFDI->GetMinBounds()); + SetShaderValue(RHICmdList, ComputeShaderRHI, MaxBounds, VFDI->GetMaxBounds()); } private: - FShaderResourceParameter VectorFieldBuffer; - FShaderParameter Dimentions; - FShaderParameter BoundsMin; - FShaderParameter BoundsMax; + FShaderResourceParameter VectorFieldSampler; + FShaderResourceParameter VectorFieldTexture; + FShaderParameter TilingAxes; + FShaderParameter Dimensions; + FShaderParameter MinBounds; + FShaderParameter MaxBounds; }; FNiagaraDataInterfaceParametersCS* UNiagaraDataInterfaceVectorField::ConstructComputeParameters()const @@ -943,4 +390,275 @@ FNiagaraDataInterfaceParametersCS* UNiagaraDataInterfaceVectorField::ConstructCo return new FNiagaraDataInterfaceParametersCS_VectorField(); } +/*--------------------------------------------------------------------------------------------------------------------------*/ + +void UNiagaraDataInterfaceVectorField::GetFieldTilingAxes(FVectorVMContext& Context) +{ + VectorVM::FExternalFuncRegisterHandler OutSizeX(Context); + VectorVM::FExternalFuncRegisterHandler OutSizeY(Context); + VectorVM::FExternalFuncRegisterHandler OutSizeZ(Context); + + FVector Tilings = GetTilingAxes(); + for (int32 i = 0; i < Context.NumInstances; ++i) + { + *OutSizeX.GetDest() = Tilings.X; + *OutSizeY.GetDest() = Tilings.Y; + *OutSizeZ.GetDest() = Tilings.Z; + + OutSizeX.Advance(); + OutSizeY.Advance(); + OutSizeZ.Advance(); + } +} + +void UNiagaraDataInterfaceVectorField::GetFieldDimensions(FVectorVMContext& Context) +{ + VectorVM::FExternalFuncRegisterHandler OutSizeX(Context); + VectorVM::FExternalFuncRegisterHandler OutSizeY(Context); + VectorVM::FExternalFuncRegisterHandler OutSizeZ(Context); + + FVector Dim = GetDimensions(); + for (int32 i = 0; i < Context.NumInstances; ++i) + { + *OutSizeX.GetDest() = Dim.X; + *OutSizeY.GetDest() = Dim.Y; + *OutSizeZ.GetDest() = Dim.Z; + + OutSizeX.Advance(); + OutSizeY.Advance(); + OutSizeZ.Advance(); + } +} + +void UNiagaraDataInterfaceVectorField::GetFieldBounds(FVectorVMContext& Context) +{ + VectorVM::FExternalFuncRegisterHandler OutMinX(Context); + VectorVM::FExternalFuncRegisterHandler OutMinY(Context); + VectorVM::FExternalFuncRegisterHandler OutMinZ(Context); + VectorVM::FExternalFuncRegisterHandler OutMaxX(Context); + VectorVM::FExternalFuncRegisterHandler OutMaxY(Context); + VectorVM::FExternalFuncRegisterHandler OutMaxZ(Context); + + FVector MinBounds = GetMinBounds(); + FVector MaxBounds = GetMaxBounds(); + for (int32 i = 0; i < Context.NumInstances; ++i) + { + *OutMinX.GetDest() = MinBounds.X; + *OutMinY.GetDest() = MinBounds.Y; + *OutMinZ.GetDest() = MinBounds.Z; + *OutMaxX.GetDest() = MaxBounds.X; + *OutMaxY.GetDest() = MaxBounds.Y; + *OutMaxZ.GetDest() = MaxBounds.Z; + + OutMinX.Advance(); + OutMinY.Advance(); + OutMinZ.Advance(); + OutMaxX.Advance(); + OutMaxY.Advance(); + OutMaxZ.Advance(); + } +} + +void UNiagaraDataInterfaceVectorField::SampleVectorField(FVectorVMContext& Context) +{ + // Input arguments... + VectorVM::FExternalFuncInputHandler XParam(Context); + VectorVM::FExternalFuncInputHandler YParam(Context); + VectorVM::FExternalFuncInputHandler ZParam(Context); + + // Outputs... + VectorVM::FExternalFuncRegisterHandler OutSampleX(Context); + VectorVM::FExternalFuncRegisterHandler OutSampleY(Context); + VectorVM::FExternalFuncRegisterHandler OutSampleZ(Context); + + UVectorFieldStatic* StaticVectorField = Cast(Field); + UVectorFieldAnimated* AnimatedVectorField = Cast(Field); + + if (StaticVectorField != nullptr && StaticVectorField->bAllowCPUAccess) + { + const FVector4 TilingAxes = FVector4(bTileX ? 1.0f : 0.0f, bTileY ? 1.0f : 0.0f, bTileZ ? 1.0f : 0.0f, 0.0); + + const uint32 SizeX = (uint32)StaticVectorField->SizeX; + const uint32 SizeY = (uint32)StaticVectorField->SizeY; + const uint32 SizeZ = (uint32)StaticVectorField->SizeZ; + const FVector4 Size(SizeX, SizeY, SizeZ, 1.0f); + + const FVector4 MinBounds(StaticVectorField->Bounds.Min.X, StaticVectorField->Bounds.Min.Y, StaticVectorField->Bounds.Min.Z, 1.0f); + const FVector4 MaxBounds(StaticVectorField->Bounds.Max.X, StaticVectorField->Bounds.Max.Y, StaticVectorField->Bounds.Max.Z, 1.0f); + + const FVector4 *Data = StaticVectorField->CPUData.GetData(); + + check(Data != nullptr); // TODO(mv): Guard better? This shouldn't happen + + // Math helper + static auto FVector4Clamp = [](FVector4 v, FVector4 a, FVector4 b) { + return FVector4(FMath::Clamp(v.X, a.X, b.X), + FMath::Clamp(v.Y, a.Y, b.Y), + FMath::Clamp(v.Z, a.Z, b.Z), + FMath::Clamp(v.W, a.W, b.W)); + }; + + static auto FVector4Floor = [](FVector4 v) { + return FVector4(FGenericPlatformMath::FloorToFloat(v.X), + FGenericPlatformMath::FloorToFloat(v.Y), + FGenericPlatformMath::FloorToFloat(v.Z), + FGenericPlatformMath::FloorToFloat(v.W)); + }; + + for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) + { + // Position in Volume Space + FVector4 Pos(XParam.Get(), YParam.Get(), ZParam.Get(), 0.0f); + + // Normalize position + Pos = (Pos - MinBounds) / (MaxBounds - MinBounds); + + // Scaled position + Pos = Pos * Size; + + // Offset by half a cell size due to sample being in the center of its cell + Pos = Pos - FVector4(0.5f, 0.5f, 0.5f, 0.0f); + + // + FVector4 Index0 = FVector4Floor(Pos); + FVector4 Index1 = Index0 + FVector4(1.0f, 1.0f, 1.0f, 0.0f); + + // + FVector4 Fraction = Pos - Index0; + + Index0 = Index0 - TilingAxes*FVector4Floor(Index0 / Size)*Size; + Index1 = Index1 - TilingAxes*FVector4Floor(Index1 / Size)*Size; + + Index0 = FVector4Clamp(Index0, FVector4(0.0f), Size - FVector4(1.0f, 1.0f, 1.0f, 0.0f)); + Index1 = FVector4Clamp(Index1, FVector4(0.0f), Size - FVector4(1.0f, 1.0f, 1.0f, 0.0f)); + + // Sample by regular trilinear interpolation: + + // TODO(mv): Optimize indexing for cache? Periodicity is problematic... + // TODO(mv): Vectorize? + // Fetch corners + FVector4 V000 = Data[int(Index0.X + SizeX * Index0.Y + SizeX * SizeY * Index0.Z)]; + FVector4 V100 = Data[int(Index1.X + SizeX * Index0.Y + SizeX * SizeY * Index0.Z)]; + FVector4 V010 = Data[int(Index0.X + SizeX * Index1.Y + SizeX * SizeY * Index0.Z)]; + FVector4 V110 = Data[int(Index1.X + SizeX * Index1.Y + SizeX * SizeY * Index0.Z)]; + FVector4 V001 = Data[int(Index0.X + SizeX * Index0.Y + SizeX * SizeY * Index1.Z)]; + FVector4 V101 = Data[int(Index1.X + SizeX * Index0.Y + SizeX * SizeY * Index1.Z)]; + FVector4 V011 = Data[int(Index0.X + SizeX * Index1.Y + SizeX * SizeY * Index1.Z)]; + FVector4 V111 = Data[int(Index1.X + SizeX * Index1.Y + SizeX * SizeY * Index1.Z)]; + + // Blend x-axis + FVector4 V00 = FMath::Lerp(V000, V100, Fraction.X); + FVector4 V01 = FMath::Lerp(V001, V101, Fraction.X); + FVector4 V10 = FMath::Lerp(V010, V110, Fraction.X); + FVector4 V11 = FMath::Lerp(V011, V111, Fraction.X); + + // Blend y-axis + FVector4 V0 = FMath::Lerp(V00, V10, Fraction.Y); + FVector4 V1 = FMath::Lerp(V01, V11, Fraction.Y); + + // Blend z-axis + FVector4 V = FMath::Lerp(V0, V1, Fraction.Z); + + // Write final output... + *OutSampleX.GetDest() = V.X; + *OutSampleY.GetDest() = V.Y; + *OutSampleZ.GetDest() = V.Z; + + XParam.Advance(); + YParam.Advance(); + ZParam.Advance(); + OutSampleX.Advance(); + OutSampleY.Advance(); + OutSampleZ.Advance(); + } + } + else + { + // TODO(mv): Add warnings? + if (StaticVectorField != nullptr && !StaticVectorField->bAllowCPUAccess) + { + // No access to static vector data + } + else if (AnimatedVectorField != nullptr) + { + // Animated vector field not supported + } + else if (Field == nullptr) + { + // Vector field not loaded + } + + // Set the default vector to positive X axis corresponding to a velocity of 100 cm/s + // Rationale: Setting to the zero vector can be visually confusing and likely to cause problems elsewhere + for (int32 InstanceIdx = 0; InstanceIdx < Context.NumInstances; ++InstanceIdx) + { + *OutSampleX.GetDest() = 0.0f; + *OutSampleY.GetDest() = 0.0f; + *OutSampleZ.GetDest() = 0.0f; + + XParam.Advance(); + YParam.Advance(); + ZParam.Advance(); + OutSampleX.Advance(); + OutSampleY.Advance(); + OutSampleZ.Advance(); + } + } + +} + +/*--------------------------------------------------------------------------------------------------------------------------*/ + +FVector UNiagaraDataInterfaceVectorField::GetTilingAxes() const +{ + return FVector(float(bTileX), float(bTileY), float(bTileZ)); +} + +FVector UNiagaraDataInterfaceVectorField::GetDimensions() const +{ + UVectorFieldStatic* StaticVectorField = Cast(Field); + if (StaticVectorField) + { + return FVector(StaticVectorField->SizeX, StaticVectorField->SizeY, StaticVectorField->SizeZ); + } + return FVector{ 1.0f, 1.0f, 1.0f }; // Matches GBlackVolumeTexture +} + +FVector UNiagaraDataInterfaceVectorField::GetMinBounds() const +{ + UVectorFieldStatic* StaticVectorField = Cast(Field); + if (StaticVectorField) + { + return StaticVectorField->Bounds.Min; + } + return FVector{-1.0f, -1.0f, -1.0f}; +} + +FVector UNiagaraDataInterfaceVectorField::GetMaxBounds() const +{ + UVectorFieldStatic* StaticVectorField = Cast(Field); + if (StaticVectorField) + { + return StaticVectorField->Bounds.Max; + } + return FVector{1.0f, 1.0f, 1.0f}; +} + +/*--------------------------------------------------------------------------------------------------------------------------*/ + +bool UNiagaraDataInterfaceVectorField::CopyToInternal(UNiagaraDataInterface* Destination) const +{ + if (!Super::CopyToInternal(Destination)) + { + return false; + } + + UNiagaraDataInterfaceVectorField* OtherTyped = CastChecked(Destination); + OtherTyped->Field = Field; + OtherTyped->bTileX = bTileX; + OtherTyped->bTileY = bTileY; + OtherTyped->bTileZ = bTileZ; + return true; +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataSet.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataSet.cpp index 5cfdf2286864..41d0115509f0 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataSet.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraDataSet.cpp @@ -13,16 +13,21 @@ DECLARE_CYCLE_STAT(TEXT("InitRenderData"), STAT_InitRenderData, STATGROUP_Niagar ////////////////////////////////////////////////////////////////////////// -void FNiagaraDataSet::SetShaderParams(FNiagaraShader *Shader, FRHICommandList &CommandList) +void FNiagaraDataSet::SetShaderParams(FNiagaraShader *Shader, FRHICommandList &CommandList, uint32& WriteBufferIdx, uint32& ReadBufferIdx) { check(IsInRenderingThread()); - + WriteBufferIdx = INDEX_NONE; + ReadBufferIdx = INDEX_NONE; if (Shader->FloatInputBufferParam.IsBound()) { CommandList.TransitionResource(EResourceTransitionAccess::EReadable, EResourceTransitionPipeline::EComputeToCompute, PrevData().GetGPUBufferFloat()->UAV); if (PrevData().GetNumInstancesAllocated() > 0) { CommandList.SetShaderResourceViewParameter(Shader->GetComputeShader(), Shader->FloatInputBufferParam.GetBaseIndex(), PrevData().GetGPUBufferFloat()->SRV); + if (ReadBufferIdx == INDEX_NONE) + { + ReadBufferIdx = GetPrevBufferIdx(); + } } else { @@ -35,6 +40,10 @@ void FNiagaraDataSet::SetShaderParams(FNiagaraShader *Shader, FRHICommandList &C if (PrevData().GetNumInstancesAllocated() > 0) { CommandList.SetShaderResourceViewParameter(Shader->GetComputeShader(), Shader->IntInputBufferParam.GetBaseIndex(), PrevData().GetGPUBufferInt()->SRV); + if (ReadBufferIdx == INDEX_NONE) + { + ReadBufferIdx = GetPrevBufferIdx(); + } } else { @@ -45,11 +54,20 @@ void FNiagaraDataSet::SetShaderParams(FNiagaraShader *Shader, FRHICommandList &C { CommandList.TransitionResource(EResourceTransitionAccess::EWritable, EResourceTransitionPipeline::EGfxToCompute, CurrData().GetGPUBufferFloat()->UAV); CommandList.SetUAVParameter(Shader->GetComputeShader(), Shader->FloatOutputBufferParam.GetUAVIndex(), CurrData().GetGPUBufferFloat()->UAV); + + if (WriteBufferIdx == INDEX_NONE) + { + WriteBufferIdx = GetCurrBufferIdx(); + } } if (Shader->IntOutputBufferParam.IsUAVBound()) { CommandList.TransitionResource(EResourceTransitionAccess::EWritable, EResourceTransitionPipeline::EGfxToCompute, CurrData().GetGPUBufferInt()->UAV); CommandList.SetUAVParameter(Shader->GetComputeShader(), Shader->IntOutputBufferParam.GetUAVIndex(), CurrData().GetGPUBufferInt()->UAV); + if (WriteBufferIdx == INDEX_NONE) + { + WriteBufferIdx = GetCurrBufferIdx(); + } } if (Shader->ComponentBufferSizeWriteParam.IsBound()) @@ -131,6 +149,29 @@ void FNiagaraDataSet::Dump(FNiagaraDataSet& Other, bool bCurr, int32 StartIdx , DataBuffer.CopyTo(OtherDataBuffer, StartIdx, NumInstances); } +void FNiagaraDataSet::DumpGPU(FNiagaraDataSet& Other, float* GPUReadBackFloat, int* GPUReadBackInt, int32 StartIdx, int32 NumInstances)const +{ + check(IsInRenderingThread()); + Other.Reset(); + Other.Variables = Variables; + Other.VariableLayouts = VariableLayouts; + + Other.TotalFloatComponents = TotalFloatComponents; + Other.TotalInt32Components = TotalInt32Components; + + const FNiagaraDataBuffer& DataBuffer = CurrData(); + FNiagaraDataBuffer& OtherDataBuffer = Other.CurrData(); + + if (OtherDataBuffer.GetNumInstancesAllocated() != DataBuffer.GetNumInstancesAllocated()) + { + Other.Finalize(); + OtherDataBuffer.Allocate(DataBuffer.GetNumInstancesAllocated()); + } + + DataBuffer.GPUCopyTo(OtherDataBuffer, GPUReadBackFloat, GPUReadBackInt, StartIdx, NumInstances); +} + + void FNiagaraDataSet::Dump(bool bCurr, int32 StartIdx, int32 NumInstances)const { @@ -454,6 +495,66 @@ void FNiagaraDataBuffer::CopyTo(FNiagaraDataBuffer& DestBuffer, int32 InStartIdx } } +void FNiagaraDataBuffer::GPUCopyTo(FNiagaraDataBuffer& DestBuffer, float* GPUReadBackFloat, int* GPUReadBackInt, int32 InStartIdx, int32 InNumInstances)const +{ + if (InStartIdx < 0 || (uint32)InStartIdx > NumInstances) + { + InStartIdx = NumInstances; + } + if (InNumInstances < 0 || ((uint32)InNumInstances + (uint32)InStartIdx) > NumInstances) + { + InNumInstances = NumInstances - InStartIdx; + } + + if (InNumInstances != 0) + { + if (DestBuffer.NumInstancesAllocated != NumInstancesAllocated) + { + DestBuffer.Allocate(NumInstancesAllocated); + } + + if (GPUReadBackFloat) + { + for (uint32 CompIdx = 0; CompIdx < Owner->TotalFloatComponents; ++CompIdx) + { + const float* SrcStart = GetInstancePtrFloat(GPUReadBackFloat, CompIdx, InStartIdx); + const float* SrcEnd = GetInstancePtrFloat(GPUReadBackFloat, CompIdx, InStartIdx + InNumInstances); + float* Dst = DestBuffer.GetInstancePtrFloat(CompIdx, 0); + size_t Count = SrcEnd - SrcStart; + FMemory::Memcpy(Dst, SrcStart, Count * sizeof(float)); + + if (Count > 0) + { + for (size_t i = 0; i < Count; i++) + { + check(SrcStart[i] == Dst[i]); + } + } + } + } + if (GPUReadBackInt) + { + for (uint32 CompIdx = 0; CompIdx < Owner->TotalInt32Components; ++CompIdx) + { + const int32* SrcStart = GetInstancePtrInt32(GPUReadBackInt, CompIdx, InStartIdx); + const int32* SrcEnd = GetInstancePtrInt32(GPUReadBackInt, CompIdx, InStartIdx + InNumInstances); + int32* Dst = DestBuffer.GetInstancePtrInt32(CompIdx, 0); + size_t Count = SrcEnd - SrcStart; + FMemory::Memcpy(Dst, SrcStart, Count * sizeof(int32)); + + if (Count > 0) + { + for (size_t i = 0; i < Count; i++) + { + check(SrcStart[i] == Dst[i]); + } + } + } + } + DestBuffer.SetNumInstances(InNumInstances); + } +} + void FNiagaraDataBuffer::CopyTo(FNiagaraDataBuffer& DestBuffer)const { DestBuffer.FloatStride = FloatStride; @@ -463,3 +564,4 @@ void FNiagaraDataBuffer::CopyTo(FNiagaraDataBuffer& DestBuffer)const DestBuffer.NumInstancesAllocated = NumInstancesAllocated; DestBuffer.NumInstances = NumInstances; } + diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitter.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitter.cpp index 94c339884f5b..53cd1a496498 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitter.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitter.cpp @@ -48,7 +48,7 @@ void FNiagaraEmitterScriptProperties::InitDataSetAccess() for (FNiagaraDataSetProperties &WriteID : Script->GetVMExecutableData().WriteDataSets) { - FNiagaraEventGeneratorProperties Props(WriteID, "", ""); + FNiagaraEventGeneratorProperties Props(WriteID, ""); EventGenerators.Add(Props); } } @@ -86,6 +86,8 @@ UNiagaraEmitter::UNiagaraEmitter(const FObjectInitializer& Initializer) , bUseMinDetailLevel(false) , bUseMaxDetailLevel(false) , bRequiresPersistentIDs(false) +, MaxDeltaTimePerTick(0.125) +, bLimitDeltaTime(true) #if WITH_EDITORONLY_DATA , ThumbnailImageOutOfDate(true) #endif @@ -329,6 +331,18 @@ void UNiagaraEmitter::PostEditChangeProperty(struct FPropertyChangedEvent& Prope UNiagaraSystem::RequestCompileForEmitter(this); #endif } + if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraEmitter, bDeterminism)) + { + if (GraphSource != nullptr) + { + GraphSource->MarkNotSynchronized(TEXT("Emitter Determinism changed.")); + } + +#if WITH_EDITORONLY_DATA + UNiagaraSystem::RequestCompileForEmitter(this); +#endif + } + ThumbnailImageOutOfDate = true; ChangeId = FGuid::NewGuid(); OnPropertiesChangedDelegate.Broadcast(); @@ -531,6 +545,20 @@ void UNiagaraEmitter::OnPostCompile() SpawnScriptProps.InitDataSetAccess(); UpdateScriptProps.InitDataSetAccess(); + TSet SpawnIds; + TSet UpdateIds; + for (const FNiagaraEventGeneratorProperties& SpawnGeneratorProps : SpawnScriptProps.EventGenerators) + { + SpawnIds.Add(SpawnGeneratorProps.ID); + } + for (const FNiagaraEventGeneratorProperties& UpdateGeneratorProps : UpdateScriptProps.EventGenerators) + { + UpdateIds.Add(UpdateGeneratorProps.ID); + } + + SharedEventGeneratorIds.Empty(); + SharedEventGeneratorIds.Append(SpawnIds.Intersect(UpdateIds).Array()); + for (int32 i = 0; i < EventHandlerScriptProps.Num(); i++) { if (EventHandlerScriptProps[i].Script) @@ -762,6 +790,11 @@ void UNiagaraEmitter::RemoveEventHandlerByUsageId(FGuid EventHandlerUsageId) #endif } +bool UNiagaraEmitter::IsEventGeneratorShared(FName EventGeneratorId) const +{ + return SharedEventGeneratorIds.Contains(EventGeneratorId); +} + void UNiagaraEmitter::BeginDestroy() { #if WITH_EDITOR diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstance.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstance.cpp index 70752717eaa9..d7226648f262 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstance.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstance.cpp @@ -17,12 +17,11 @@ DECLARE_DWORD_COUNTER_STAT(TEXT("Num Custom Events"), STAT_NiagaraNumCustomEvents, STATGROUP_Niagara); //DECLARE_CYCLE_STAT(TEXT("Tick"), STAT_NiagaraTick, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("Simulate"), STAT_NiagaraSimulate, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("Spawn"), STAT_NiagaraSpawn, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("Spawn"), STAT_NiagaraEvents, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("Kill"), STAT_NiagaraKill, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("Event Handling"), STAT_NiagaraEventHandle, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("Error Check"), STAT_NiagaraEmitterErrorCheck, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Emitter Simulate [CNC]"), STAT_NiagaraSimulate, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Emitter Spawn [CNC]"), STAT_NiagaraSpawn, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Emitter Post Tick [CNC]"), STAT_NiagaraEmitterPostTick, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Emitter Event Handling [CNC]"), STAT_NiagaraEventHandle, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Emitter Error Check [CNC]"), STAT_NiagaraEmitterErrorCheck, STATGROUP_Niagara); static int32 GbDumpParticleData = 0; static FAutoConsoleVariableRef CVarNiagaraDumpParticleData( @@ -229,6 +228,8 @@ void FNiagaraEmitterInstance::Init(int32 InEmitterIdx, FName InSystemInstanceNam ensure(CachedEmitter->UpdateScriptProps.DataSetAccessSynchronized()); UpdateScriptEventDataSets.Empty(); + UpdateEventGeneratorIsSharedByIndex.SetNumZeroed(CachedEmitter->UpdateScriptProps.EventGenerators.Num()); + int32 UpdateEventGeneratorIndex = 0; for (const FNiagaraEventGeneratorProperties &GeneratorProps : CachedEmitter->UpdateScriptProps.EventGenerators) { FNiagaraDataSet *Set = FNiagaraEventDataSetMgr::CreateEventDataSet(ParentSystemInstance->GetIDName(), EmitterHandle.GetIdName(), GeneratorProps.SetProps.ID.Name); @@ -236,10 +237,14 @@ void FNiagaraEmitterInstance::Init(int32 InEmitterIdx, FName InSystemInstanceNam Set->AddVariables(GeneratorProps.SetProps.Variables); Set->Finalize(); UpdateScriptEventDataSets.Add(Set); + UpdateEventGeneratorIsSharedByIndex[UpdateEventGeneratorIndex] = CachedEmitter->IsEventGeneratorShared(GeneratorProps.ID); + UpdateEventGeneratorIndex++; } ensure(CachedEmitter->SpawnScriptProps.DataSetAccessSynchronized()); SpawnScriptEventDataSets.Empty(); + SpawnEventGeneratorIsSharedByIndex.SetNumZeroed(CachedEmitter->SpawnScriptProps.EventGenerators.Num()); + int32 SpawnEventGeneratorIndex = 0; for (const FNiagaraEventGeneratorProperties &GeneratorProps : CachedEmitter->SpawnScriptProps.EventGenerators) { FNiagaraDataSet *Set = FNiagaraEventDataSetMgr::CreateEventDataSet(ParentSystemInstance->GetIDName(), EmitterHandle.GetIdName(), GeneratorProps.SetProps.ID.Name); @@ -247,6 +252,8 @@ void FNiagaraEmitterInstance::Init(int32 InEmitterIdx, FName InSystemInstanceNam Set->AddVariables(GeneratorProps.SetProps.Variables); Set->Finalize(); SpawnScriptEventDataSets.Add(Set); + SpawnEventGeneratorIsSharedByIndex[SpawnEventGeneratorIndex] = CachedEmitter->IsEventGeneratorShared(GeneratorProps.ID); + SpawnEventGeneratorIndex++; } SpawnExecContext.Init(CachedEmitter->SpawnScriptProps.Script, CachedEmitter->SimTarget); @@ -256,7 +263,7 @@ void FNiagaraEmitterInstance::Init(int32 InEmitterIdx, FName InSystemInstanceNam if (CachedEmitter->SimTarget == ENiagaraSimTarget::GPUComputeSim) { GPUExecContext = new FNiagaraComputeExecutionContext(); - GPUExecContext->InitParams(CachedEmitter->GetGPUComputeScript(), CachedEmitter->SpawnScriptProps.Script, CachedEmitter->UpdateScriptProps.Script, CachedEmitter->SimTarget); + GPUExecContext->InitParams(CachedEmitter->GetGPUComputeScript(), CachedEmitter->SpawnScriptProps.Script, CachedEmitter->UpdateScriptProps.Script, CachedEmitter->SimTarget, CachedEmitter->GetUniqueEmitterName()); GPUExecContext->MainDataSet = &Data; GPUExecContext->RTGPUScript = CachedEmitter->GetGPUComputeScript()->GetRenderThreadScript(); GPUExecContext->RTSpawnScript = CachedEmitter->SpawnScriptProps.Script->GetRenderThreadScript(); @@ -304,6 +311,16 @@ void FNiagaraEmitterInstance::Init(int32 InEmitterIdx, FName InSystemInstanceNam EmitterAgeBindingGPU.Init(GPUExecContext->CombinedParamStore, EmitterAgeParam); } + // Initialize the random seed + FNiagaraVariable EmitterRandomSeedParam = CachedEmitter->ToEmitterParameter(SYS_PARAM_EMITTER_RANDOM_SEED); + SpawnRandomSeedBinding.Init(SpawnExecContext.Parameters, EmitterRandomSeedParam); + UpdateRandomSeedBinding.Init(UpdateExecContext.Parameters, EmitterRandomSeedParam); + if (CachedEmitter->SimTarget == ENiagaraSimTarget::GPUComputeSim && GPUExecContext != nullptr) + { + GPURandomSeedBinding.Init(GPUExecContext->CombinedParamStore, CachedEmitter->ToEmitterParameter(EmitterRandomSeedParam)); + } + + // Initialize the exec count SpawnExecCountBinding.Init(SpawnExecContext.Parameters, SYS_PARAM_ENGINE_EXEC_COUNT); UpdateExecCountBinding.Init(UpdateExecContext.Parameters, SYS_PARAM_ENGINE_EXEC_COUNT); EventExecCountBindings.SetNum(NumEvents); @@ -319,7 +336,7 @@ void FNiagaraEmitterInstance::Init(int32 InEmitterIdx, FName InSystemInstanceNam } else { - //Init accessors for PostProcessParticles + //Init accessors for PostTick PositionAccessor = FNiagaraDataSetAccessor(Data, FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), PositionName)); SizeAccessor = FNiagaraDataSetAccessor(Data, FNiagaraVariable(FNiagaraTypeDefinition::GetVec2Def(), SizeName)); MeshScaleAccessor = FNiagaraDataSetAccessor(Data, FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), MeshScaleName)); @@ -342,6 +359,7 @@ void FNiagaraEmitterInstance::ResetSimulation() Age = 0; Loops = 0; TickCount = 0; + TotalSpawnedParticles = 0; CachedBounds.Init(); ParticleDataSet->ResetBuffers(); @@ -713,12 +731,12 @@ TOptional FNiagaraEmitterInstance::CalculateDynamicBounds() return Ret; } -/** Look for dead particles and move from the end of the list to the dead location, compacting in the process - * Also calculates bounds; Kill will be removed from this once we do conditional write +/** + * Do any post work such as calculating dynamic bounds. */ -void FNiagaraEmitterInstance::PostProcessParticles() +void FNiagaraEmitterInstance::PostTick() { - SCOPE_CYCLE_COUNTER(STAT_NiagaraKill); + SCOPE_CYCLE_COUNTER(STAT_NiagaraEmitterPostTick); checkSlow(CachedEmitter); CachedBounds.Init(); @@ -868,12 +886,30 @@ void FNiagaraEmitterInstance::PreTick() ParticleDataSet->SetIDAcquireTag(TickCount); } +bool FNiagaraEmitterInstance::WaitForDebugInfo() +{ + if (CachedEmitter->SimTarget == ENiagaraSimTarget::GPUComputeSim) + { + + FNiagaraComputeExecutionContext* DebugContext = GPUExecContext; + + ENQUEUE_RENDER_COMMAND(CaptureCommand)([DebugContext](FRHICommandListImmediate& RHICmdList) + { + NiagaraEmitterInstanceBatcher::Get()->ProcessDebugInfo(RHICmdList, DebugContext); + } + ); + return true; + } + return false; +} + void FNiagaraEmitterInstance::Tick(float DeltaSeconds) { SCOPE_CYCLE_COUNTER(STAT_NiagaraTick); SimpleTimer TickTime; + if (HandleCompletion()) { CPUTimeMS = TickTime.GetElapsedMilliseconds(); @@ -884,6 +920,9 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) FNiagaraDataSet& Data = *ParticleDataSet; Age += DeltaSeconds; + + //UE_LOG(LogNiagara, Warning, TEXT("Emitter Tick %f"), Age); + if (ExecutionState == ENiagaraExecutionState::InactiveClear) { Data.ResetBuffers(); @@ -925,9 +964,13 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) Binding.SetValue(Age); } + SpawnRandomSeedBinding.SetValue(CachedEmitter->RandomSeed); + UpdateRandomSeedBinding.SetValue(CachedEmitter->RandomSeed); + if (CachedEmitter->SimTarget == ENiagaraSimTarget::GPUComputeSim && GPUExecContext != nullptr) { EmitterAgeBindingGPU.SetValue(Age); + GPURandomSeedBinding.SetValue(CachedEmitter->RandomSeed); } } @@ -971,6 +1014,11 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) uint32 EventSpawnNum = CalculateEventSpawnCount(EventHandlerProps, EventSpawnCounts[i], EventSet[i]); EventSpawnTotal += EventSpawnNum; EventHandlerSpawnCounts[i] = EventSpawnNum; + if (CachedEmitter->SimTarget == ENiagaraSimTarget::GPUComputeSim && GPUExecContext != nullptr) { + // NOTE(mv): Separate particle count path for GPU emitters, as they early out.. + // not actually reached yet due to GPU events not working. CPU events are counted further down... + TotalSpawnedParticles += EventSpawnNum; + } } } @@ -979,9 +1027,29 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) */ if (CachedEmitter->SimTarget == ENiagaraSimTarget::GPUComputeSim && GPUExecContext != nullptr) { + //FNiagaraComputeExecutionContext *ComputeContext = new FNiagaraComputeExecutionContext(); + GPUExecContext->MainDataSet = &Data; + GPUExecContext->RTGPUScript = CachedEmitter->GetGPUComputeScript()->GetRenderThreadScript(); + GPUExecContext->RTSpawnScript = CachedEmitter->SpawnScriptProps.Script->GetRenderThreadScript(); + GPUExecContext->RTUpdateScript = CachedEmitter->UpdateScriptProps.Script->GetRenderThreadScript(); GPUExecContext->SpawnRateInstances = SpawnTotal; GPUExecContext->EventSpawnTotal = EventSpawnTotal; GPUExecContext->NumIndicesPerInstance = CachedEmitter->GetRenderers()[0]->GetNumIndicesPerInstance(); + +#if WITH_EDITORONLY_DATA + if (ParentSystemInstance->ShouldCaptureThisFrame()) + { + TSharedPtr DebugInfo = ParentSystemInstance->GetActiveCaptureWrite(CachedIDName, ENiagaraScriptUsage::ParticleGPUComputeScript, FGuid()); + if (DebugInfo) + { + //Data.Dump(DebugInfo->Frame, true, 0, OrigNumParticles); + //DebugInfo->Frame.Dump(true, 0, OrigNumParticles); + DebugInfo->Parameters = GPUExecContext->CombinedParamStore; + GPUExecContext->DebugInfo = DebugInfo; + } + } +#endif + ParentSystemInstance->GetPerInstanceDataAndOffsets(GPUExecContext->PerInstanceData, GPUExecContext->PerInstanceDataSize, GPUExecContext->PerInstanceDataInterfaceOffsets); bool bOnlySetOnce = false; for (FNiagaraSpawnInfo& Info : SpawnInfos) @@ -998,6 +1066,9 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) UE_LOG(LogNiagara, Log, TEXT("Multiple spawns are happening this frame. Only doing the first!")); break; } + + // NOTE(mv): Separate particle count path for GPU emitters, as they early out.. + TotalSpawnedParticles += Info.Count; } //GPUExecContext.UpdateInterfaces = CachedEmitter->UpdateScriptProps.Script->GetCachedDefaultDataInterfaces(); @@ -1066,7 +1137,7 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) //TODO: These current limits can be improved relatively easily. Though perf in at these counts will obviously be an issue anyway. if (CachedEmitter->SimTarget == ENiagaraSimTarget::CPUSim && AllocationSize > GMaxNiagaraCPUParticlesPerEmitter) { - UE_LOG(LogNiagara, Warning, TEXT("Emitter %s has attemted to exceed the max CPU particle count! | Max: %d | Requested: %u"), *CachedEmitter->GetUniqueEmitterName(), GMaxNiagaraCPUParticlesPerEmitter, AllocationSize); + UE_LOG(LogNiagara, Warning, TEXT("Emitter %s has attempted to exceed the max CPU particle count! | Max: %d | Requested: %u"), *CachedEmitter->GetUniqueEmitterName(), GMaxNiagaraCPUParticlesPerEmitter, AllocationSize); //For now we completely bail out of spawning new particles. Possibly should improve this in future. AllocationSize = OrigNumParticles; SpawnTotal = 0; @@ -1075,14 +1146,33 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) //Allocate space for prev frames particles and any new one's we're going to spawn. Data.Allocate(AllocationSize); + + int32 SpawnEventGeneratorIndex = 0; for (FNiagaraDataSet* SpawnEventDataSet : SpawnScriptEventDataSets) { - SpawnEventDataSet->Allocate(SpawnTotal + EventSpawnTotal); + int32 NumToAllocate = SpawnTotal + EventSpawnTotal; + if (SpawnEventGeneratorIsSharedByIndex[SpawnEventGeneratorIndex]) + { + // For shared event data sets we need to allocate storage for the current particles since + // the same data set will be used in the update execution. + NumToAllocate += OrigNumParticles; + } + SpawnEventDataSet->Allocate(NumToAllocate); + SpawnEventGeneratorIndex++; } + + int32 UpdateEventGeneratorIndex = 0; for (FNiagaraDataSet* UpdateEventDataSet : UpdateScriptEventDataSets) { - UpdateEventDataSet->Allocate(OrigNumParticles); + if (UpdateEventGeneratorIsSharedByIndex[UpdateEventGeneratorIndex] == false) + { + // We only allocate update event data sets if they're not shared, because shared event datasets will have already + // been allocated as part of the spawn event data set handling. + UpdateEventDataSet->Allocate(OrigNumParticles); + } + UpdateEventGeneratorIndex++; } + TArray> DataSetExecInfos; DataSetExecInfos.Emplace(&Data, 0, false, true); @@ -1105,8 +1195,8 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) DataSetExecInfos[0].StartInstance = 0; for (FNiagaraDataSet* EventDataSet : UpdateScriptEventDataSets) { - DataSetExecInfos.Emplace(EventDataSet, 0, false, true); EventDataSet->SetNumInstances(OrigNumParticles); + DataSetExecInfos.Emplace(EventDataSet, 0, false, true); } UpdateExecContext.Execute(OrigNumParticles, DataSetExecInfos); int32 DeltaParticles = Data.GetNumInstances() - OrigNumParticles; @@ -1126,12 +1216,13 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) #if WITH_EDITORONLY_DATA if (ParentSystemInstance->ShouldCaptureThisFrame()) { - FNiagaraScriptDebuggerInfo* DebugInfo = ParentSystemInstance->GetActiveCaptureWrite(CachedIDName, ENiagaraScriptUsage::ParticleUpdateScript, FGuid()); + TSharedPtr DebugInfo = ParentSystemInstance->GetActiveCaptureWrite(CachedIDName, ENiagaraScriptUsage::ParticleUpdateScript, FGuid()); if (DebugInfo) { Data.Dump(DebugInfo->Frame, true, 0, OrigNumParticles); //DebugInfo->Frame.Dump(true, 0, OrigNumParticles); DebugInfo->Parameters = UpdateExecContext.Parameters; + DebugInfo->bWritten = true; } } #endif @@ -1152,6 +1243,12 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) int32 OrigNum = Data.GetNumInstances(); Data.SetNumInstances(OrigNum + Num); + // NOTE(mv): Updates the count after setting the variable, such that the TotalSpawnedParticles value read + // in the script has the count at the start of the frame. + // This way UniqueID = TotalSpawnedParticles + ExecIndex provide unique and sequential identifiers. + // NOTE(mv): Only for CPU particles, as GPU particles early outs further up and has a separate increment. + TotalSpawnedParticles += Num; + SpawnExecCountBinding.SetValue(Num); DataSetExecInfos.SetNum(1, false); DataSetExecInfos[0].StartInstance = OrigNum; @@ -1212,12 +1309,13 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) int32 TotalNumSpawned = NumAfterSpawn - NumBeforeSpawn; if (ParentSystemInstance->ShouldCaptureThisFrame()) { - FNiagaraScriptDebuggerInfo* DebugInfo = ParentSystemInstance->GetActiveCaptureWrite(CachedIDName, ENiagaraScriptUsage::ParticleSpawnScript, FGuid()); + TSharedPtr DebugInfo = ParentSystemInstance->GetActiveCaptureWrite(CachedIDName, ENiagaraScriptUsage::ParticleSpawnScript, FGuid()); if (DebugInfo) { Data.Dump(DebugInfo->Frame, true, NumBeforeSpawn, TotalNumSpawned); //DebugInfo->Frame.Dump(true, 0, Num); DebugInfo->Parameters = SpawnExecContext.Parameters; + DebugInfo->bWritten = true; } } #endif @@ -1270,12 +1368,13 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) if (ParentSystemInstance->ShouldCaptureThisFrame()) { FGuid EventGuid = EventExecContexts[EventScriptIdx].Script->GetUsageId(); - FNiagaraScriptDebuggerInfo* DebugInfo = ParentSystemInstance->GetActiveCaptureWrite(CachedIDName, ENiagaraScriptUsage::ParticleEventScript, EventGuid); + TSharedPtr DebugInfo = ParentSystemInstance->GetActiveCaptureWrite(CachedIDName, ENiagaraScriptUsage::ParticleEventScript, EventGuid); if (DebugInfo) { Data.Dump(DebugInfo->Frame, true, EventSpawnStart, ActualEventNumToSpawn); //DebugInfo->Frame.Dump(true, 0, ActualEventNumToSpawn); DebugInfo->Parameters = EventExecContexts[EventScriptIdx].Parameters; + DebugInfo->bWritten = true; } } #endif @@ -1333,12 +1432,13 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) if (ParentSystemInstance->ShouldCaptureThisFrame()) { FGuid EventGuid = EventExecContexts[EventScriptIdx].Script->GetUsageId(); - FNiagaraScriptDebuggerInfo* DebugInfo = ParentSystemInstance->GetActiveCaptureWrite(CachedIDName, ENiagaraScriptUsage::ParticleEventScript, EventGuid); + TSharedPtr DebugInfo = ParentSystemInstance->GetActiveCaptureWrite(CachedIDName, ENiagaraScriptUsage::ParticleEventScript, EventGuid); if (DebugInfo) { Data.Dump(DebugInfo->Frame, true, 0, NumInstancesPrev); //DebugInfo->Frame.Dump(true, 0, NumInstancesPrev); DebugInfo->Parameters = EventExecContexts[EventScriptIdx].Parameters; + DebugInfo->bWritten = true; } } #endif @@ -1392,7 +1492,7 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) // if (ParentSystemInstance->ShouldCaptureThisFrame()) // { // FGuid EventGuid = EventExecContexts[EventScriptIdx].Script->GetUsageId(); -// FNiagaraScriptDebuggerInfo* DebugInfo = ParentSystemInstance->GetActiveCaptureWrite(CachedIDName, ENiagaraScriptUsage::ParticleEventScript, EventGuid); +// TSharedPtr DebugInfo = ParentSystemInstance->GetActiveCaptureWrite(CachedIDName, ENiagaraScriptUsage::ParticleEventScript, EventGuid); // if (DebugInfo) // { // Data.Dump(DebugInfo->Frame, true, Index, 1); @@ -1407,7 +1507,7 @@ void FNiagaraEmitterInstance::Tick(float DeltaSeconds) // } } - PostProcessParticles(); + PostTick(); SpawnExecContext.PostTick(); UpdateExecContext.PostTick(); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstanceBatcher.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstanceBatcher.cpp index 1c82c2c7a4c5..8120b3257820 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstanceBatcher.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraEmitterInstanceBatcher.cpp @@ -26,6 +26,7 @@ uint32 FNiagaraComputeExecutionContext::TickCounter = 0; void NiagaraEmitterInstanceBatcher::Queue(FNiagaraComputeExecutionContext *InContext) { + //UE_LOG(LogNiagara, Warning, TEXT("Submitted!")); //SimulationQueue[CurQueueIndex]->Add(InContext); ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER(QueueNiagaraDispatch, TArray*, Queue, &SimulationQueue[0], @@ -58,6 +59,8 @@ void NiagaraEmitterInstanceBatcher::Remove(FNiagaraComputeExecutionContext *InCo void NiagaraEmitterInstanceBatcher::ExecuteAll(FRHICommandList &RHICmdList, FUniformBufferRHIParamRef ViewUniformBuffer) { + SCOPED_DRAW_EVENT(RHICmdList, NiagaraEmitterInstanceBatcher_ExecuteAll); + const uint32 TickedQueueIndex = (CurQueueIndex ^ 0x1); const uint32 TickedQueueIndexMask = (1 << TickedQueueIndex); @@ -91,6 +94,13 @@ void NiagaraEmitterInstanceBatcher::TickSingle(FNiagaraComputeExecutionContext * return; } +#if WITH_EDITORONLY_DATA + if (Context->DebugInfo.IsValid()) + { + ProcessDebugInfo(RHICmdList, Context); + } +#endif // WITH_EDITORONLY_DATA + uint32 PrevNumInstances = Context->MainDataSet->PrevData().GetNumInstances(); uint32 NewNumInstances = Context->SpawnRateInstances + Context->EventSpawnTotal + PrevNumInstances; Context->EventSpawnTotal = GetEventSpawnTotal(Context); @@ -193,21 +203,31 @@ void NiagaraEmitterInstanceBatcher::ResolveDatasetWrites(FRHICommandList &RHICmd } else if (Context->GPUDataReadback->IsReady()) { + bool bSuccessfullyRead = false; { SCOPE_CYCLE_COUNTER(STAT_NiagaraGPUReadback_RT); int32 *NumInstancesAfterSim = static_cast(Context->GPUDataReadback->RetrieveData(64 * sizeof(int32))); - int32 ExistingDataCount = Context->MainDataSet->CurrData().GetNumInstances();// index 1 is always the count - int32 NewExistingDataCount = NumInstancesAfterSim[1] + Context->AccumulatedSpawnRate; - Context->MainDataSet->CurrData().SetNumInstances(NewExistingDataCount); - FString PathName = Context->GPUScript->GetOutermost()->GetPathName(); - // UE_LOG(LogNiagara, Log, TEXT("GPU Syncup %s : Was(%d) Now(%d)"), *PathName, ExistingDataCount, NewExistingDataCount ); - SET_DWORD_STAT(STAT_NiagaraGPUParticles, NewExistingDataCount); - SET_DWORD_STAT(STAT_NiagaraReadbackLatency, 0); - - Context->GPUDataReadback->Finish(); - - Context->AccumulatedSpawnRate = 0; + if (NumInstancesAfterSim) + { + int32 ExistingDataCount = Context->MainDataSet->CurrData().GetNumInstances();// index 1 is always the count + int32 NewExistingDataCount = NumInstancesAfterSim[1] + Context->AccumulatedSpawnRate; + Context->MainDataSet->CurrData().SetNumInstances(NewExistingDataCount); + FString PathName = Context->GPUScript->GetOutermost()->GetPathName(); + // UE_LOG(LogNiagara, Log, TEXT("GPU Syncup %s : Was(%d) Now(%d)"), *PathName, ExistingDataCount, NewExistingDataCount ); + SET_DWORD_STAT(STAT_NiagaraGPUParticles, NewExistingDataCount); + SET_DWORD_STAT(STAT_NiagaraReadbackLatency, 0); + + Context->GPUDataReadback->Finish(); + + Context->AccumulatedSpawnRate = 0; + bSuccessfullyRead = true; + } + else + { + UE_LOG(LogNiagara, Warning, TEXT("GPUDataReadback said it was ready, but returned an invalid buffer. Skipping this time..")); + } } + if (bSuccessfullyRead) { SCOPE_CYCLE_COUNTER(STAT_NiagaraAllocateGPUReadback_RT); // The following code seems to take significant time on d3d12 @@ -216,8 +236,83 @@ void NiagaraEmitterInstanceBatcher::ResolveDatasetWrites(FRHICommandList &RHICmd Context->GPUDataReadback->Insert(RHICmdList); } } + } +void NiagaraEmitterInstanceBatcher::ProcessDebugInfo(FRHICommandList &RHICmdList, const FNiagaraComputeExecutionContext *Context) const +{ +#if WITH_EDITORONLY_DATA + // This method may be called from one of two places: in the tick or as part of a paused frame looking for the debug info that was submitted previously... + // Note that PrevData is where we expect the data to be for rendering, as per NiagaraEmitterInstanceBatcher::TickSingle + if (Context->DebugInfo.IsValid()) + { + + // Fire off the readback if not already doing so + if (!Context->GPUDebugDataReadbackFloat && !Context->GPUDebugDataReadbackInt && !Context->GPUDebugDataReadbackCounts) + { + // Do nothing.., handled in Run + } + // We may not have floats or ints, but we should have at least one of the two + else if ((Context->GPUDebugDataReadbackFloat == nullptr || Context->GPUDebugDataReadbackFloat->IsReady()) + && (Context->GPUDebugDataReadbackInt == nullptr || Context->GPUDebugDataReadbackInt->IsReady()) + && Context->GPUDebugDataReadbackCounts->IsReady() + ) + { + //UE_LOG(LogNiagara, Warning, TEXT("Read back!")); + + int32 *NumInstancesAfterSim = static_cast(Context->GPUDebugDataReadbackCounts->RetrieveData(64 * sizeof(int32))); + int32 NewExistingDataCount = NumInstancesAfterSim[1]; + { + float* FloatDataBuffer = nullptr; + if (Context->GPUDebugDataReadbackFloat) + { + FloatDataBuffer = static_cast(Context->GPUDebugDataReadbackFloat->RetrieveData(Context->GPUDebugDataFloatSize)); + } + int* IntDataBuffer = nullptr; + if (Context->GPUDebugDataReadbackInt) + { + IntDataBuffer = static_cast(Context->GPUDebugDataReadbackInt->RetrieveData(Context->GPUDebugDataIntSize)); + } + Context->MainDataSet->DumpGPU(Context->DebugInfo->Frame, FloatDataBuffer, IntDataBuffer, 0, NewExistingDataCount); + Context->DebugInfo->bWritten = true; + + if (Context->GPUDebugDataReadbackFloat) + { + Context->GPUDebugDataReadbackFloat->Finish(); + } + if (Context->GPUDebugDataReadbackInt) + { + Context->GPUDebugDataReadbackInt->Finish(); + } + Context->GPUDebugDataReadbackCounts->Finish(); + } + { + // The following code seems to take significant time on d3d12 + // Clear out the readback buffers... + if (Context->GPUDebugDataReadbackFloat) + { + delete Context->GPUDebugDataReadbackFloat; + Context->GPUDebugDataReadbackFloat = nullptr; + } + if (Context->GPUDebugDataReadbackInt) + { + delete Context->GPUDebugDataReadbackInt; + Context->GPUDebugDataReadbackInt = nullptr; + } + delete Context->GPUDebugDataReadbackCounts; + Context->GPUDebugDataReadbackCounts = nullptr; + Context->GPUDebugDataFloatSize = 0; + Context->GPUDebugDataIntSize = 0; + } + + // We've updated the debug info directly, now we need to no longer keep asking and querying because this frame is done! + Context->DebugInfo.Reset(); + } + } +#endif // WITH_EDITORONLY_DATA +} + + /* Resize data set buffers and set number of instances * Allocates one additional instance at the end, which is a scratch instance; by setting the default index from AcquireIndex in the shader @@ -231,6 +326,7 @@ void NiagaraEmitterInstanceBatcher::ResizeCurrentBuffer(FRHICommandList &RHICmdL // if (NewNumInstances > PrevNumInstances) { + //UE_LOG(LogNiagara, Warning, TEXT("Resize up!")); Context->MainDataSet->CurrData().AllocateGPU(NewNumInstances + 1, RHICmdList); Context->MainDataSet->CurrData().SetNumInstances(NewNumInstances); } @@ -239,6 +335,7 @@ void NiagaraEmitterInstanceBatcher::ResizeCurrentBuffer(FRHICommandList &RHICmdL // else if (Context->MainDataSet->CurrData().GetNumInstances() < Context->MainDataSet->PrevData().GetNumInstances()) { + //UE_LOG(LogNiagara, Warning, TEXT("Resize down!")); Context->MainDataSet->CurrData().AllocateGPU(PrevNumInstances + 1, RHICmdList); Context->MainDataSet->CurrData().SetNumInstances(PrevNumInstances); } @@ -247,7 +344,7 @@ void NiagaraEmitterInstanceBatcher::ResizeCurrentBuffer(FRHICommandList &RHICmdL /* Set shader parameters for data interfaces */ -void NiagaraEmitterInstanceBatcher::SetDataInterfaceParameters(const TArray &DataInterfaces, FNiagaraShader* Shader, FRHICommandList &RHICmdList) const +void NiagaraEmitterInstanceBatcher::SetDataInterfaceParameters(const TArray &DataInterfaces, FNiagaraShader* Shader, FRHICommandList &RHICmdList, const FNiagaraComputeExecutionContext *Context) const { // set up data interface buffers, as defined by the DIs during compilation // @@ -257,7 +354,44 @@ void NiagaraEmitterInstanceBatcher::SetDataInterfaceParameters(const TArrayGetDIParameters()[InterfaceIndex]; if (DIParam.Parameters) { - DIParam.Parameters->Set(RHICmdList, Shader, Interface); + void* PerInstanceData = nullptr; + int32* OffsetFound = nullptr; + if (Context->PerInstanceDataSize != 0 && Context->PerInstanceDataInterfaceOffsets != nullptr) + { + OffsetFound = Context->PerInstanceDataInterfaceOffsets->Find(Interface); + if (OffsetFound != nullptr) + { + PerInstanceData = (*OffsetFound) + (uint8*)Context->PerInstanceData; + } + } + DIParam.Parameters->Set(RHICmdList, Shader, Interface, PerInstanceData); + } + + InterfaceIndex++; + } +} + +void NiagaraEmitterInstanceBatcher::UnsetDataInterfaceParameters(const TArray &DataInterfaces, FNiagaraShader* Shader, FRHICommandList &RHICmdList, const FNiagaraComputeExecutionContext *Context) const +{ + // set up data interface buffers, as defined by the DIs during compilation + // + uint32 InterfaceIndex = 0; + for (UNiagaraDataInterface* Interface : DataInterfaces) + { + FNiagaraDataInterfaceParamRef& DIParam = Shader->GetDIParameters()[InterfaceIndex]; + if (DIParam.Parameters) + { + void* PerInstanceData = nullptr; + int32* OffsetFound = nullptr; + if (Context->PerInstanceDataSize != 0 && Context->PerInstanceDataInterfaceOffsets != nullptr) + { + OffsetFound = Context->PerInstanceDataInterfaceOffsets->Find(Interface); + if (OffsetFound != nullptr) + { + PerInstanceData = (*OffsetFound) + (uint8*)Context->PerInstanceData; + } + } + DIParam.Parameters->Unset(RHICmdList, Shader, Interface, PerInstanceData); } InterfaceIndex++; @@ -272,9 +406,14 @@ void NiagaraEmitterInstanceBatcher::Run(const FNiagaraComputeExecutionContext *C { if (TotalNumInstances == 0) { + SCOPED_DRAW_EVENTF(RHICmdList, NiagaraGPUSimulationCS, TEXT("Niagara Gpu Sim - %s - NumInstances: %u"), + *Context->DebugSimName, + TotalNumInstances); return; } + //UE_LOG(LogNiagara, Warning, TEXT("Run")); + FNiagaraDataSet *DataSet = Context->MainDataSet; const FNiagaraParameterStore& ParameterStore = Context->CombinedParamStore; const FRHIUniformBufferLayout& CBufferLayout = Context->CBufferLayout; @@ -304,11 +443,14 @@ void NiagaraEmitterInstanceBatcher::Run(const FNiagaraComputeExecutionContext *C RHICmdList.SetShaderUniformBuffer(Shader->GetComputeShader(), Shader->ViewUniformBufferParam.GetBaseIndex(), ViewUniformBuffer); } - SetDataInterfaceParameters(ParameterStore.GetDataInterfaces(), Shader, RHICmdList); + SetDataInterfaceParameters(ParameterStore.GetDataInterfaces(), Shader, RHICmdList, Context); // set the shader and data set params // - DataSet->SetShaderParams(Shader, RHICmdList); + uint32 WriteBufferIdx = 0; + uint32 ReadBufferIdx = 0; + DataSet->SetShaderParams(Shader, RHICmdList, WriteBufferIdx, ReadBufferIdx); + // set the index buffer uav // @@ -350,13 +492,50 @@ void NiagaraEmitterInstanceBatcher::Run(const FNiagaraComputeExecutionContext *C // if (TotalNumInstances) { - SCOPED_DRAW_EVENTF(RHICmdList, NiagaraGPUSimulationCS, TEXT("Niagara GPU Simulation")); + SCOPED_DRAW_EVENTF(RHICmdList, NiagaraGPUSimulationCS, TEXT("Niagara Gpu Sim - %s - NumInstances: %u"), + *Context->DebugSimName, + TotalNumInstances); SCOPED_GPU_STAT(RHICmdList, NiagaraGPUSimulation); DispatchComputeShader(RHICmdList, Shader, NumThreadGroups, 1, 1); } +#if WITH_EDITORONLY_DATA + // Check to see if we need to queue up a debug dump.. + if (Context->DebugInfo.IsValid()) + { + //UE_LOG(LogNiagara, Warning, TEXT("Queued up!")); + + if (!Context->GPUDebugDataReadbackFloat && !Context->GPUDebugDataReadbackInt && !Context->GPUDebugDataReadbackCounts && Context->MainDataSet != nullptr) + { + FRWBuffer &DatasetIndexBufferWrite = Context->MainDataSet->GetCurDataSetIndices(); + + Context->GPUDebugDataCurrBufferIdx = Context->MainDataSet->GetCurrBufferIdx(); + Context->GPUDebugDataFloatSize = 0; + Context->GPUDebugDataIntSize = 0; + + if (Context->MainDataSet->GetNumFloatComponents() > 0) + { + Context->GPUDebugDataReadbackFloat = new FRHIGPUMemoryReadback(Context->MainDataSet->GetDataByIndex(WriteBufferIdx).GetGPUBufferFloat()->Buffer, TEXT("Niagara GPU Debug Info Float Emitter Readback")); + Context->GPUDebugDataReadbackFloat->Insert(RHICmdList); + Context->GPUDebugDataFloatSize = Context->MainDataSet->GetDataByIndex(WriteBufferIdx).GetGPUBufferFloat()->NumBytes; + } + + if (Context->MainDataSet->GetNumInt32Components() > 0) + { + Context->GPUDebugDataReadbackInt = new FRHIGPUMemoryReadback(Context->MainDataSet->GetDataByIndex(WriteBufferIdx).GetGPUBufferInt()->Buffer, TEXT("Niagara GPU Debug Info Int Emitter Readback")); + Context->GPUDebugDataReadbackInt->Insert(RHICmdList); + Context->GPUDebugDataIntSize = Context->MainDataSet->GetDataByIndex(WriteBufferIdx).GetGPUBufferInt()->NumBytes; + } + + Context->GPUDebugDataReadbackCounts = new FRHIGPUMemoryReadback(DatasetIndexBufferWrite.Buffer, TEXT("Niagara GPU Emitter Readback")); + Context->GPUDebugDataReadbackCounts->Insert(RHICmdList); + } + } +#endif // WITH_EDITORONLY_DATA + // Unset UAV parameters and transition resources (TODO: resource transition should be moved to the renderer) // + UnsetDataInterfaceParameters(ParameterStore.GetDataInterfaces(), Shader, RHICmdList, Context); DataSet->UnsetShaderParams(Shader, RHICmdList); Shader->OutputIndexBufferParam.UnsetUAV(RHICmdList, Shader->GetComputeShader()); } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraLightRendererProperties.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraLightRendererProperties.cpp index 87c793de6add..c6d67bd17503 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraLightRendererProperties.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraLightRendererProperties.cpp @@ -4,7 +4,7 @@ #include "NiagaraRenderer.h" #include "NiagaraConstants.h" UNiagaraLightRendererProperties::UNiagaraLightRendererProperties() - : RadiusScale(1.0f), ColorAdd(FVector(0.0f, 0.0f, 0.0f)) + : bUseInverseSquaredFalloff(1), bAffectsTranslucency(0), bOverrideRenderingEnabled(0), RadiusScale(1.0f), ColorAdd(FVector(0.0f, 0.0f, 0.0f)) { } @@ -16,6 +16,9 @@ void UNiagaraLightRendererProperties::PostInitProperties() PositionBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_POSITION); ColorBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_COLOR); RadiusBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_LIGHT_RADIUS); + LightExponentBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_LIGHT_EXPONENT); + LightRenderingEnabledBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_LIGHT_ENABLED); + VolumetricScatteringBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING); } } @@ -27,6 +30,9 @@ void UNiagaraLightRendererProperties::InitCDOPropertiesAfterModuleStartup() CDO->PositionBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_POSITION); CDO->ColorBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_COLOR); CDO->RadiusBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_LIGHT_RADIUS); + CDO->LightExponentBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_LIGHT_EXPONENT); + CDO->LightRenderingEnabledBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_LIGHT_ENABLED); + CDO->VolumetricScatteringBinding = FNiagaraConstants::GetAttributeDefaultBinding(SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING); } NiagaraRenderer* UNiagaraLightRendererProperties::CreateEmitterRenderer(ERHIFeatureLevel::Type FeatureLevel) @@ -56,6 +62,9 @@ const TArray& UNiagaraLightRendererProperties::GetOptionalAttr Attrs.Add(SYS_PARAM_PARTICLES_POSITION); Attrs.Add(SYS_PARAM_PARTICLES_COLOR); Attrs.Add(SYS_PARAM_PARTICLES_LIGHT_RADIUS); + Attrs.Add(SYS_PARAM_PARTICLES_LIGHT_EXPONENT); + Attrs.Add(SYS_PARAM_PARTICLES_LIGHT_ENABLED); + Attrs.Add(SYS_PARAM_PARTICLES_LIGHT_VOLUMETRIC_SCATTERING); } return Attrs; } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraModule.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraModule.cpp index a7c9c7851085..10fda4bbe829 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraModule.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraModule.cpp @@ -69,6 +69,7 @@ FNiagaraVariable INiagaraModule::Engine_Owner_XAxis; FNiagaraVariable INiagaraModule::Engine_Owner_YAxis; FNiagaraVariable INiagaraModule::Engine_Owner_ZAxis; FNiagaraVariable INiagaraModule::Engine_Owner_Scale; +FNiagaraVariable INiagaraModule::Engine_Owner_Rotation; FNiagaraVariable INiagaraModule::Engine_Owner_SystemLocalToWorld; FNiagaraVariable INiagaraModule::Engine_Owner_SystemWorldToLocal; @@ -84,6 +85,8 @@ FNiagaraVariable INiagaraModule::Engine_Owner_ExecutionState; FNiagaraVariable INiagaraModule::Engine_ExecutionCount; FNiagaraVariable INiagaraModule::Engine_Emitter_NumParticles; +FNiagaraVariable INiagaraModule::Engine_Emitter_TotalSpawnedParticles; +FNiagaraVariable INiagaraModule::Engine_System_TickCount; FNiagaraVariable INiagaraModule::Engine_System_NumEmittersAlive; FNiagaraVariable INiagaraModule::Engine_System_NumEmitters; FNiagaraVariable INiagaraModule::Engine_NumSystemInstances; @@ -95,11 +98,14 @@ FNiagaraVariable INiagaraModule::Engine_System_Age; FNiagaraVariable INiagaraModule::Emitter_Age; FNiagaraVariable INiagaraModule::Emitter_LocalSpace; +FNiagaraVariable INiagaraModule::Emitter_Determinism; +FNiagaraVariable INiagaraModule::Emitter_RandomSeed; FNiagaraVariable INiagaraModule::Emitter_SpawnRate; FNiagaraVariable INiagaraModule::Emitter_SpawnInterval; FNiagaraVariable INiagaraModule::Emitter_InterpSpawnStartDt; FNiagaraVariable INiagaraModule::Emitter_SpawnGroup; +FNiagaraVariable INiagaraModule::Particles_UniqueID; FNiagaraVariable INiagaraModule::Particles_ID; FNiagaraVariable INiagaraModule::Particles_Position; FNiagaraVariable INiagaraModule::Particles_Velocity; @@ -121,6 +127,9 @@ FNiagaraVariable INiagaraModule::Particles_UVScale; FNiagaraVariable INiagaraModule::Particles_CameraOffset; FNiagaraVariable INiagaraModule::Particles_MaterialRandom; FNiagaraVariable INiagaraModule::Particles_LightRadius; +FNiagaraVariable INiagaraModule::Particles_LightExponent; +FNiagaraVariable INiagaraModule::Particles_LightEnabled; +FNiagaraVariable INiagaraModule::Particles_LightVolumetricScattering; FNiagaraVariable INiagaraModule::Particles_RibbonID; FNiagaraVariable INiagaraModule::Particles_RibbonWidth; FNiagaraVariable INiagaraModule::Particles_RibbonTwist; @@ -167,6 +176,7 @@ void INiagaraModule::StartupModule() Engine_Owner_YAxis = FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Engine.Owner.SystemYAxis")); Engine_Owner_ZAxis = FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Engine.Owner.SystemZAxis")); Engine_Owner_Scale = FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Engine.Owner.Scale")); + Engine_Owner_Rotation = FNiagaraVariable(FNiagaraTypeDefinition::GetQuatDef(), TEXT("Engine.Owner.Rotation")); Engine_Owner_SystemLocalToWorld = FNiagaraVariable(FNiagaraTypeDefinition::GetMatrix4Def(), TEXT("Engine.Owner.SystemLocalToWorld")); Engine_Owner_SystemWorldToLocal = FNiagaraVariable(FNiagaraTypeDefinition::GetMatrix4Def(), TEXT("Engine.Owner.SystemWorldToLocal")); @@ -182,6 +192,8 @@ void INiagaraModule::StartupModule() Engine_ExecutionCount = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Engine.ExecutionCount")); Engine_Emitter_NumParticles = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Engine.Emitter.NumParticles")); + Engine_Emitter_TotalSpawnedParticles = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Engine.Emitter.TotalSpawnedParticles")); + Engine_System_TickCount = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Engine.System.TickCount")); Engine_System_NumEmittersAlive = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Engine.System.NumEmittersAlive")); Engine_System_NumEmitters = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Engine.System.NumEmitters")); Engine_NumSystemInstances = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Engine.NumSystemInstances")); @@ -192,11 +204,14 @@ void INiagaraModule::StartupModule() Engine_System_Age = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Engine.System.Age")); Emitter_Age = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Emitter.Age")); Emitter_LocalSpace = FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("Emitter.LocalSpace")); + Emitter_RandomSeed = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Emitter.RandomSeed")); + Emitter_Determinism = FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("Emitter.Determinism")); Emitter_SpawnRate = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Emitter.SpawnRate")); Emitter_SpawnInterval = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Emitter.SpawnInterval")); Emitter_InterpSpawnStartDt = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Emitter.InterpSpawnStartDt")); Emitter_SpawnGroup = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Emitter.SpawnGroup")); + Particles_UniqueID = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Particles.UniqueID")); Particles_ID = FNiagaraVariable(FNiagaraTypeDefinition::GetIDDef(), TEXT("Particles.ID")); Particles_Position = FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Particles.Position")); Particles_Velocity = FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Particles.Velocity")); @@ -218,6 +233,9 @@ void INiagaraModule::StartupModule() Particles_CameraOffset = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Particles.CameraOffset")); Particles_MaterialRandom = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Particles.MaterialRandom")); Particles_LightRadius = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Particles.LightRadius")); + Particles_LightExponent = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Particles.LightExponent")); + Particles_LightEnabled = FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("Particles.LightEnabled")); + Particles_LightVolumetricScattering = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Particles.LightVolumetricScattering")); Particles_RibbonID = FNiagaraVariable(FNiagaraTypeDefinition::GetIDDef(), TEXT("Particles.RibbonID")); Particles_RibbonWidth = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Particles.RibbonWidth")); Particles_RibbonTwist = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Particles.RibbonTwist")); @@ -598,6 +616,8 @@ void FNiagaraTypeDefinition::RecreateUserDefinedTypeRegistry() for (FSoftObjectPath AssetRef : TotalStructAssets) { + FName AssetRefPathNamePreResolve = AssetRef.GetAssetPathName(); + UObject* Obj = AssetRef.ResolveObject(); if (Obj == nullptr) { @@ -613,6 +633,11 @@ void FNiagaraTypeDefinition::RecreateUserDefinedTypeRegistry() { FNiagaraTypeRegistry::Register(ScriptStruct, ParamRefFound != nullptr, PayloadRefFound != nullptr, true); } + if (Obj->GetPathName() != AssetRefPathNamePreResolve.ToString()) + { + UE_LOG(LogNiagara, Warning, TEXT("Additional parameter/payload enum has moved from where it was in settings (this may cause errors at runtime): Was: \"%s\" Now: \"%s\""), *AssetRefPathNamePreResolve.ToString(), *Obj->GetPathName()); + } + } else { @@ -623,6 +648,7 @@ void FNiagaraTypeDefinition::RecreateUserDefinedTypeRegistry() for (FSoftObjectPath AssetRef : Settings->AdditionalParameterEnums) { + FName AssetRefPathNamePreResolve = AssetRef.GetAssetPathName(); UObject* Obj = AssetRef.ResolveObject(); if (Obj == nullptr) { @@ -631,13 +657,18 @@ void FNiagaraTypeDefinition::RecreateUserDefinedTypeRegistry() if (Obj != nullptr) { - const FSoftObjectPath* ParamRefFound = Settings->AdditionalParameterEnums.FindByPredicate([&](const FStringAssetReference& Ref) { return Ref.ToString() == AssetRef.ToString(); }); + const FSoftObjectPath* ParamRefFound = Settings->AdditionalParameterEnums.FindByPredicate([&](const FSoftObjectPath& Ref) { return Ref.ToString() == AssetRef.ToString(); }); const FSoftObjectPath* PayloadRefFound = nullptr; UEnum* Enum = Cast(Obj); if (Enum != nullptr) { FNiagaraTypeRegistry::Register(Enum, ParamRefFound != nullptr, PayloadRefFound != nullptr, true); } + + if (Obj->GetPathName() != AssetRefPathNamePreResolve.ToString()) + { + UE_LOG(LogNiagara, Warning, TEXT("Additional parameter/payload enum has moved from where it was in settings (this may cause errors at runtime): Was: \"%s\" Now: \"%s\""), *AssetRefPathNamePreResolve.ToString(), *Obj->GetPathName()); + } } else { @@ -757,27 +788,10 @@ FNiagaraTypeDefinition FNiagaraTypeDefinition::GetNumericOutputType(const TArray { return SortedTypeDefinitions[0]; } + + return FNiagaraTypeDefinition::GetGenericNumericDef(); } -////////////////////////////////////////////////////////////////////////// - -template<> -void FNiagaraVariable::SetValue(const bool& Data) -{ - check(TypeDef == FNiagaraTypeDefinition::GetBoolDef()); - AllocateData(); - FNiagaraBool* BoolStruct = (FNiagaraBool*)GetData(); - BoolStruct->SetValue(Data); -} - -template<> -bool FNiagaraVariable::GetValue() const -{ - check(TypeDef == FNiagaraTypeDefinition::GetBoolDef()); - check(IsDataAllocated()); - FNiagaraBool* BoolStruct = (FNiagaraBool*)GetData(); - return BoolStruct->GetValue(); -} ////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraParameterStore.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraParameterStore.cpp index 9695e00dd427..3fec3de091ae 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraParameterStore.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraParameterStore.cpp @@ -389,8 +389,33 @@ void FNiagaraParameterStore::CopyParametersTo(FNiagaraParameterStore& DestStore, while (It) { FNiagaraVariable Parameter = It->Key; - int32 DestIndex = DestStore.IndexOf(Parameter); int32 SrcIndex = It->Value; + ++It; + + if (Parameter.IsValid() == false) + { + FString StoreDebugName; +#if WITH_EDITORONLY_DATA + StoreDebugName = DebugName.IsEmpty() == false ? DebugName : TEXT("Unknown"); +#else + StoreDebugName = TEXT("Unknown"); +#endif + FString StoreName; + if (Owner != nullptr) + { + StoreName = Owner->GetPathName() + TEXT(".") + StoreDebugName; + } + else + { + StoreName = StoreDebugName; + } + + UE_LOG(LogNiagara, Error, TEXT("Invalid parameter found while attempting to copy parameters from one parameter store to another. Parameter Store: %s Parameter Name: %s Parameter Type: %s"), + *StoreName, *Parameter.GetName().ToString(), Parameter.GetType().IsValid() ? *Parameter.GetType().GetName() : TEXT("Unknown")); + continue; + } + + int32 DestIndex = DestStore.IndexOf(Parameter); bool bWrite = false; if (DestIndex == INDEX_NONE) { @@ -431,7 +456,6 @@ void FNiagaraParameterStore::CopyParametersTo(FNiagaraParameterStore& DestStore, } } } - ++It; } DestStore.OnLayoutChange(); } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRenderer.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRenderer.cpp index 9f7fd4d0f1f2..c62424b1a7b5 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRenderer.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRenderer.cpp @@ -231,41 +231,49 @@ FNiagaraDynamicDataBase *NiagaraRendererLights::GenerateVertexData(const FNiagar FNiagaraDataSetIterator PosItr(Data, Properties->PositionBinding.DataSetVariable); FNiagaraDataSetIterator ColItr(Data, Properties->ColorBinding.DataSetVariable); FNiagaraDataSetIterator RadiusItr(Data, Properties->RadiusBinding.DataSetVariable); + FNiagaraDataSetIterator ExponentItr(Data, Properties->LightExponentBinding.DataSetVariable); + FNiagaraDataSetIterator ScatteringItr(Data, Properties->VolumetricScatteringBinding.DataSetVariable); + FNiagaraDataSetIterator EnabledItr(Data, Properties->LightRenderingEnabledBinding.DataSetVariable); FNiagaraDynamicDataLights *DynamicData = new FNiagaraDynamicDataLights; + const FMatrix& LocalToWorldMatrix = Proxy->GetLocalToWorld(); FVector DefaultColor = FVector(Properties->ColorBinding.DefaultValueIfNonExistent.GetValue()); - FVector DefaultPos = FVector4(Proxy->GetLocalToWorld().GetOrigin()); + FVector DefaultPos = FVector4(LocalToWorldMatrix.GetOrigin()); float DefaultRadius = Properties->RadiusBinding.DefaultValueIfNonExistent.GetValue(); + float DefaultScattering = Properties->VolumetricScatteringBinding.DefaultValueIfNonExistent.GetValue(); DynamicData->LightArray.Empty(); for (uint32 ParticleIndex = 0; ParticleIndex < Data.GetNumInstances(); ParticleIndex++) { - SimpleLightData LightData; - LightData.LightEntry.Radius = (RadiusItr.IsValid() ? (*RadiusItr) : DefaultRadius) * Properties->RadiusScale; //LightPayload->RadiusScale * (Size.X + Size.Y) / 2.0f; - LightData.LightEntry.Color = (ColItr.IsValid() ? FVector((*ColItr)) : DefaultColor) + Properties->ColorAdd; //FVector(Particle.Color) * Particle.Color.A * LightPayload->ColorScale; - LightData.LightEntry.Exponent = 1.0; - LightData.LightEntry.bAffectTranslucency = true; - LightData.PerViewEntry.Position = PosItr.IsValid() ? (*PosItr) : DefaultPos; + bool ShouldRenderParticleLight = !Properties->bOverrideRenderingEnabled || (EnabledItr.IsValid() ? (*EnabledItr) : true); + float LightRadius = (RadiusItr.IsValid() ? (*RadiusItr) : DefaultRadius) * Properties->RadiusScale; + if (ShouldRenderParticleLight && LightRadius > 0) + { + SimpleLightData LightData; + LightData.LightEntry.Radius = LightRadius; + LightData.LightEntry.Color = (ColItr.IsValid() ? FVector((*ColItr)) : DefaultColor) + Properties->ColorAdd; + LightData.LightEntry.Exponent = Properties->bUseInverseSquaredFalloff ? 0 : (ExponentItr.IsValid() ? (*ExponentItr) : 1); + LightData.LightEntry.bAffectTranslucency = Properties->bAffectsTranslucency; + LightData.LightEntry.VolumetricScatteringIntensity = ScatteringItr.IsValid() ? (*ScatteringItr) : DefaultScattering; + LightData.PerViewEntry.Position = PosItr.IsValid() ? (*PosItr) : DefaultPos; + if (bLocalSpace) + { + LightData.PerViewEntry.Position = LocalToWorldMatrix.TransformPosition(LightData.PerViewEntry.Position); + } - DynamicData->LightArray.Add(LightData); + DynamicData->LightArray.Add(LightData); + } PosItr.Advance(); ColItr.Advance(); RadiusItr.Advance(); - } - - if (bLocalSpace) - { - FMatrix Mat = Proxy->GetLocalToWorld(); - for (uint32 ParticleIndex = 0; ParticleIndex < Data.GetNumInstances(); ParticleIndex++) - { - DynamicData->LightArray[ParticleIndex].PerViewEntry.Position = Mat.TransformPosition(DynamicData->LightArray[ParticleIndex].PerViewEntry.Position); - } + ExponentItr.Advance(); + ScatteringItr.Advance(); + EnabledItr.Advance(); } CPUTimeMS = VertexDataTimer.GetElapsedMilliseconds(); - return DynamicData; } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererMeshes.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererMeshes.cpp index eb579e87c05b..93aff0c47e8b 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererMeshes.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererMeshes.cpp @@ -8,8 +8,8 @@ #include "Async/ParallelFor.h" #include "Engine/StaticMesh.h" -DECLARE_CYCLE_STAT(TEXT("Generate Mesh Vertex Data"), STAT_NiagaraGenMeshVertexData, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("Render Meshes"), STAT_NiagaraRenderMeshes, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Generate Mesh Vertex Data [GT]"), STAT_NiagaraGenMeshVertexData, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Render Meshes [RT]"), STAT_NiagaraRenderMeshes, STATGROUP_Niagara); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererRibbons.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererRibbons.cpp index 7985fc8b6b83..85eb6d3e410e 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererRibbons.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererRibbons.cpp @@ -6,18 +6,18 @@ #include "NiagaraDataSet.h" #include "NiagaraStats.h" -DECLARE_CYCLE_STAT(TEXT("Generate Ribbon Vertex Data"), STAT_NiagaraGenRibbonVertexData, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("Render Ribbons"), STAT_NiagaraRenderRibbons, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Generate Ribbon Vertex Data [GT]"), STAT_NiagaraGenRibbonVertexData, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Render Ribbons [RT]"), STAT_NiagaraRenderRibbons, STATGROUP_Niagara); DECLARE_CYCLE_STAT(TEXT("Genereate GPU Buffers"), STAT_NiagaraGenRibbonGpuBuffers, STATGROUP_Niagara); struct FNiagaraDynamicDataRibbon : public FNiagaraDynamicDataBase { - TArray VertexData; TArray IndexData; TArray SortedIndices; TArray TotalDistances; + TArray MultiRibbonIndices; TArray PackedPerRibbonDataByIndex; //Direct ptr to the dataset. ONLY FOR USE BE GPU EMITTERS. @@ -108,9 +108,6 @@ void NiagaraRendererRibbons::GetDynamicMeshElements(const TArrayRTParticleData.GetFloatBuffer().Num() / sizeof(float); FNiagaraGlobalReadBuffer::FAllocation ParticleData; - int32 SizeInBytes = DynamicDataRibbon->VertexData.GetTypeSize() * DynamicDataRibbon->VertexData.Num(); - FGlobalDynamicVertexBuffer::FAllocation LocalDynamicVertexAllocation = FGlobalDynamicVertexBuffer::Get().Allocate(SizeInBytes); - if (DynamicDataRibbon->DataSet->GetSimTarget() == ENiagaraSimTarget::CPUSim) { ParticleData = FNiagaraGlobalReadBuffer::Get().AllocateFloat(TotalFloatSize); @@ -140,7 +137,6 @@ void NiagaraRendererRibbons::GetDynamicMeshElements(const TArrayVertexData.GetData(), SizeInBytes); FMemory::Memcpy(DynamicIndexAllocation.Buffer, DynamicDataRibbon->IndexData.GetData(), DynamicDataRibbon->IndexData.Num() * sizeof(int16)); // Compute the per-view uniform buffers. @@ -192,7 +188,6 @@ void NiagaraRendererRibbons::GetDynamicMeshElements(const TArraySortedIndices.Num()) { @@ -217,6 +212,14 @@ void NiagaraRendererRibbons::GetDynamicMeshElements(const TArrayMultiRibbonIndices.Num(), EPixelFormat::PF_R32_UINT, BUF_Volatile); + void* MultiRibbonIndexPtr = RHILockVertexBuffer(MultiRibbonIndicesBuffer.Buffer, 0, DynamicDataRibbon->MultiRibbonIndices.Num() * sizeof(uint32), RLM_WriteOnly); + FMemory::Memcpy(MultiRibbonIndexPtr, DynamicDataRibbon->MultiRibbonIndices.GetData(), DynamicDataRibbon->MultiRibbonIndices.Num() * sizeof(uint32)); + RHIUnlockVertexBuffer(MultiRibbonIndicesBuffer.Buffer); + CollectorResources.VertexFactory.SetMultiRibbonIndicesSRV(MultiRibbonIndicesBuffer.SRV); + // Copy the packed u data for stable age based uv generation. FReadBuffer PackedPerRibbonDataByIndexBuffer; PackedPerRibbonDataByIndexBuffer.Initialize(sizeof(float), DynamicDataRibbon->PackedPerRibbonDataByIndex.Num(), EPixelFormat::PF_R32_FLOAT, BUF_Volatile); @@ -252,7 +255,7 @@ void NiagaraRendererRibbons::GetDynamicMeshElements(const TArray 0); MeshElement.NumInstances = 1; MeshElement.MinVertexIndex = 0; - MeshElement.MaxVertexIndex = DynamicDataRibbon->VertexData.Num() - 1; + MeshElement.MaxVertexIndex = 0; MeshElement.PrimitiveUniformBufferResource = &WorldSpacePrimitiveUniformBuffer; Collector.AddMesh(ViewIndex, MeshBatch); @@ -280,8 +283,12 @@ int NiagaraRendererRibbons::GetDynamicDataSize() uint32 Size = sizeof(FNiagaraDynamicDataRibbon); if (DynamicDataRender) { - Size += (static_cast(DynamicDataRender))->VertexData.GetAllocatedSize(); - Size += (static_cast(DynamicDataRender))->IndexData.GetAllocatedSize(); + FNiagaraDynamicDataRibbon* RibbonDynamicData = static_cast(DynamicDataRender); + Size += RibbonDynamicData->IndexData.GetAllocatedSize(); + Size += RibbonDynamicData->SortedIndices.GetAllocatedSize(); + Size += RibbonDynamicData->TotalDistances.GetAllocatedSize(); + Size += RibbonDynamicData->MultiRibbonIndices.GetAllocatedSize(); + Size += RibbonDynamicData->PackedPerRibbonDataByIndex.GetAllocatedSize(); } return Size; @@ -381,12 +388,8 @@ FNiagaraDynamicDataBase *NiagaraRendererRibbons::GenerateVertexData(const FNiaga return nullptr; } FNiagaraDynamicDataRibbon* DynamicData = new FNiagaraDynamicDataRibbon; - TArray& VertexData = DynamicData->VertexData; TArray& IndexData = DynamicData->IndexData; - VertexData.Empty(); - IndexData.Empty(); - // TODO : deal with the dynamic vertex material parameter should the user have specified it as an output... int32 NumTotalVerts = 0; @@ -495,10 +498,7 @@ FNiagaraDynamicDataBase *NiagaraRendererRibbons::GenerateVertexData(const FNiaga PrevDir = NormDir; DynamicData->TotalDistances.Add(TotalDistance); - FNiagaraRibbonVertex NewVertex; - NewVertex.RibbonIndex = RibbonIndex; - VertexData.Add(NewVertex); - VertexData.Add(NewVertex); + DynamicData->MultiRibbonIndices.Add(RibbonIndex); if (i < NumIndices - 1) { diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererSprites.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererSprites.cpp index d2fb80696cc3..a894d8ef245f 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererSprites.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraRendererSprites.cpp @@ -6,8 +6,8 @@ #include "NiagaraDataSet.h" #include "NiagaraStats.h" -DECLARE_CYCLE_STAT(TEXT("Generate Sprite Vertex Data"), STAT_NiagaraGenSpriteVertexData, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("Render Sprites"), STAT_NiagaraRenderSprites, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Generate Sprite Vertex Data [GT]"), STAT_NiagaraGenSpriteVertexData, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Render Sprites [RT]"), STAT_NiagaraRenderSprites, STATGROUP_Niagara); DECLARE_CYCLE_STAT(TEXT("Genereate GPU Buffers"), STAT_NiagaraGenSpriteGpuBuffers, STATGROUP_Niagara); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScript.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScript.cpp index 4fb81db4e640..f4f40ec6c40b 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScript.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScript.cpp @@ -16,6 +16,9 @@ #include "UObject/Linker.h" #include "HAL/PlatformFilemanager.h" #include "Misc/FileHelper.h" +#include "UObject/EditorObjectVersion.h" +#include "UObject/ReleaseObjectVersion.h" + #if WITH_EDITOR #include "NiagaraScriptDerivedData.h" @@ -26,13 +29,21 @@ DECLARE_STATS_GROUP(TEXT("Niagara Detailed"), STATGROUP_NiagaraDetailed, STATCAT_Advanced); -FNiagaraScriptDebuggerInfo::FNiagaraScriptDebuggerInfo() : FrameLastWriteId(-1) +FNiagaraScriptDebuggerInfo::FNiagaraScriptDebuggerInfo() : bWaitForGPU(false), FrameLastWriteId(-1), bWritten(false) { } -FNiagaraScriptDebuggerInfo::FNiagaraScriptDebuggerInfo(FName InName, ENiagaraScriptUsage InUsage, const FGuid& InUsageId) : HandleName(InName), Usage(InUsage), UsageId(InUsageId), FrameLastWriteId(-1) +FNiagaraScriptDebuggerInfo::FNiagaraScriptDebuggerInfo(FName InName, ENiagaraScriptUsage InUsage, const FGuid& InUsageId) : HandleName(InName), Usage(InUsage), UsageId(InUsageId), FrameLastWriteId(-1), bWritten(false) { + if (InUsage == ENiagaraScriptUsage::ParticleGPUComputeScript) + { + bWaitForGPU = true; + } + else + { + bWaitForGPU = false; + } } @@ -230,6 +241,10 @@ void UNiagaraScript::ComputeVMCompilationId(FNiagaraVMExecutableDataId& Id) cons { Id.AdditionalDefines.Add(TEXT("Emitter.Localspace")); } + if (Emitter->bDeterminism) + { + Id.AdditionalDefines.Add(TEXT("Emitter.Determinism")); + } } if (UNiagaraSystem* System = Cast(Obj)) @@ -243,6 +258,10 @@ void UNiagaraScript::ComputeVMCompilationId(FNiagaraVMExecutableDataId& Id) cons { Id.AdditionalDefines.Add(Emitter->GetUniqueEmitterName() + TEXT(".Localspace")); } + if (Emitter->bDeterminism) + { + Id.AdditionalDefines.Add(Emitter->GetUniqueEmitterName() + TEXT(".Determinism")); + } } } } @@ -476,7 +495,23 @@ void UNiagaraScript::GenerateStatScopeIDs() void UNiagaraScript::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) { + Super::PostEditChangeProperty(PropertyChangedEvent); + + FName PropertyName; + if (PropertyChangedEvent.Property) + { + PropertyName = PropertyChangedEvent.Property->GetFName(); + } + CacheResourceShadersForRendering(true); + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraScript, bDeprecated) || PropertyName == GET_MEMBER_NAME_CHECKED(UNiagaraScript, DeprecationRecommendation)) + { + if (Source) + { + Source->MarkNotSynchronized(TEXT("Deprecation changed.")); + } + } } #endif @@ -1034,9 +1069,12 @@ void UNiagaraScript::CacheResourceShadersForRendering(bool bRegenerateId, bool b //if (ScriptResourcesByFeatureLevel[FeatureLevel]) { EShaderPlatform ShaderPlatform = GShaderPlatformForFeatureLevel[CacheFeatureLevel]; - ResourceToCache = ScriptResourcesByFeatureLevel[CacheFeatureLevel]; - CacheShadersForResources(ShaderPlatform, &ScriptResource, true); - ScriptResourcesByFeatureLevel[CacheFeatureLevel] = &ScriptResource; + if (IsFeatureLevelSupported(ShaderPlatform, ERHIFeatureLevel::SM5) || IsFeatureLevelSupported(ShaderPlatform, ERHIFeatureLevel::ES3_1)) + { + ResourceToCache = ScriptResourcesByFeatureLevel[CacheFeatureLevel]; + CacheShadersForResources(ShaderPlatform, &ScriptResource, true); + ScriptResourcesByFeatureLevel[CacheFeatureLevel] = &ScriptResource; + } } } } @@ -1063,6 +1101,14 @@ void UNiagaraScript::SyncAliases(const TMap& RenameMap) // Now handle any Parameters overall.. for (int32 i = 0; i < GetVMExecutableData().Parameters.Parameters.Num(); i++) { + if (GetVMExecutableData().Parameters.Parameters[i].IsValid() == false) + { + const FNiagaraVariable& InvalidParameter = GetVMExecutableData().Parameters.Parameters[i]; + UE_LOG(LogNiagara, Error, TEXT("Invalid parameter found while syncing script aliases. Script: %s Parameter Name: %s Parameter Type: %s"), + *GetPathName(), *InvalidParameter.GetName().ToString(), InvalidParameter.GetType().IsValid() ? *InvalidParameter.GetType().GetName() : TEXT("Unknown")); + continue; + } + FNiagaraVariable Var = GetVMExecutableData().Parameters.Parameters[i]; FNiagaraVariable NewVar = FNiagaraVariable::ResolveAliases(Var, RenameMap); if (NewVar.GetName() != Var.GetName()) @@ -1199,6 +1245,11 @@ NIAGARA_API bool UNiagaraScript::DidScriptCompilationSucceed(bool bGPUScript) co void SerializeNiagaraShaderMaps(const TMap>* PlatformScriptResourcesToSave, FArchive& Ar, TArray& OutLoadedResources) { + Ar.UsingCustomVersion(FFortniteMainBranchObjectVersion::GUID); + Ar.UsingCustomVersion(FRenderingObjectVersion::GUID); + Ar.UsingCustomVersion(FEditorObjectVersion::GUID); + Ar.UsingCustomVersion(FReleaseObjectVersion::GUID); + // SCOPED_LOADTIMER(SerializeInlineShaderMaps); if (Ar.IsSaving()) { diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptExecutionContext.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptExecutionContext.cpp index bc9e63981ec3..f99ef48cbb4f 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptExecutionContext.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraScriptExecutionContext.cpp @@ -16,6 +16,14 @@ DECLARE_CYCLE_STAT(TEXT("Rebind DInterface Func Table"), STAT_NiagaraRebindDataI uint32 FNiagaraScriptExecutionContext::TickCounter = 0; +static int32 GbExecVMScripts = 1; +static FAutoConsoleVariableRef CVarNiagaraExecVMScripts( + TEXT("fx.ExecVMScripts"), + GbExecVMScripts, + TEXT("If > 0 VM scripts will be executed, otherwise they won't, useful for looking at the bytecode for a crashing compiled script. \n"), + ECVF_Default +); + FNiagaraScriptExecutionContext::FNiagaraScriptExecutionContext() : Script(nullptr) { @@ -164,7 +172,7 @@ bool FNiagaraScriptExecutionContext::Execute(uint32 NumInstances, TArrayGetVMExecutableData().ByteCode.GetData(), diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSpriteRendererProperties.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSpriteRendererProperties.cpp index 6aabc144090a..cced3693c162 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSpriteRendererProperties.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSpriteRendererProperties.cpp @@ -145,10 +145,10 @@ void UNiagaraSpriteRendererProperties::FixMaterial(UMaterial* InMaterial) InMaterial->ForceRecompileForRendering(); } -#undef LOCTEXT_NAMESPACE - #endif // WITH_EDITORONLY_DATA +#undef LOCTEXT_NAMESPACE + diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraStats.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraStats.h index 5292094576c8..2c1e931d1cdd 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraStats.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraStats.h @@ -8,11 +8,18 @@ #include "NiagaraModule.h" DECLARE_STATS_GROUP(TEXT("Niagara"), STATGROUP_Niagara, STATCAT_Advanced); -DECLARE_CYCLE_STAT(TEXT("Render Total"), STAT_NiagaraRender, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("Render Total (GT)"), STAT_NiagaraRenderGT, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Render Total [RT]"), STAT_NiagaraRender, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Render Total [GT]"), STAT_NiagaraRenderGT, STATGROUP_Niagara); DECLARE_DWORD_COUNTER_STAT(TEXT("NumParticles"), STAT_NiagaraNumParticles, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("Constant Setup"), STAT_NiagaraConstants, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("Tick"), STAT_NiagaraTick, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Constant Setup [CNC]"), STAT_NiagaraConstants, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("Emitter Tick [CNC]"), STAT_NiagaraTick, STATGROUP_Niagara); DECLARE_DWORD_COUNTER_STAT(TEXT("NumSystems"), STAT_NiagaraNumSystems, STATGROUP_Niagara); DECLARE_MEMORY_STAT(TEXT("Niagara particle data memory"), STAT_NiagaraParticleMemory, STATGROUP_Niagara); DECLARE_MEMORY_STAT(TEXT("Niagara vertex buffer memory"), STAT_NiagaraVBMemory, STATGROUP_Niagara); + + +DECLARE_STATS_GROUP(TEXT("NiagaraOverview"), STATGROUP_NiagaraOverview, STATCAT_Advanced); +DECLARE_CYCLE_STAT(TEXT("GT Total"), STAT_NiagaraOverview_GT, STATGROUP_NiagaraOverview); +DECLARE_CYCLE_STAT(TEXT("GT Concurrent Total"), STAT_NiagaraOverview_GT_CNC, STATGROUP_NiagaraOverview); +DECLARE_CYCLE_STAT(TEXT("RT Total"), STAT_NiagaraOverview_RT, STATGROUP_NiagaraOverview); +DECLARE_CYCLE_STAT(TEXT("RT Concurrent Total"), STAT_NiagaraOverview_RT_CNC, STATGROUP_NiagaraOverview); \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystem.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystem.cpp index d18ca5132485..24b5093948f8 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystem.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystem.cpp @@ -42,6 +42,7 @@ UNiagaraSystem::UNiagaraSystem(const FObjectInitializer& ObjectInitializer) , WarmupTime(0.0f) , WarmupTickCount(0) , WarmupTickDelta(1.0f / 15.0f) +, bHasSystemScriptDIsWithPerInstanceData(false) { } @@ -174,8 +175,6 @@ void UNiagaraSystem::PostEditChangeProperty(struct FPropertyChangedEvent& Proper FNiagaraSystemUpdateContext(this, true); ThumbnailImageOutOfDate = true; - - DetermineIfSolo(); if (PropertyChangedEvent.Property != nullptr) { @@ -229,6 +228,10 @@ void UNiagaraSystem::PostLoad() } } +#if UE_EDITOR + ExposedParameters.RecreateRedirections(); +#endif + #if WITH_EDITORONLY_DATA TArray AllSystemScripts; @@ -391,8 +394,6 @@ void UNiagaraSystem::PostLoad() } #endif #endif - - DetermineIfSolo(); } #if WITH_EDITORONLY_DATA @@ -546,46 +547,42 @@ bool UNiagaraSystem::HasOutstandingCompilationRequests() const return ActiveCompilations.Num() > 0; } - -bool UNiagaraSystem::IsSolo()const +bool UNiagaraSystem::HasSystemScriptDIsWithPerInstanceData() const { - return bSolo; + return bHasSystemScriptDIsWithPerInstanceData; } -void UNiagaraSystem::DetermineIfSolo() +const TArray& UNiagaraSystem::GetUserDINamesReadInSystemScripts() const { - //Determine if we can update normally or have to update solo. - bSolo = false; - //If our scripts have any interfaces that require instance data. - UNiagaraScript* SystemSpawn = GetSystemSpawnScript(); - if (SystemSpawn->GetVMExecutableData().IsValid()) - { - for (int32 i = 0; !bSolo && i < SystemSpawn->GetVMExecutableData().DataInterfaceInfo.Num(); ++i) - { - FNiagaraScriptDataInterfaceCompileInfo& Info = SystemSpawn->GetVMExecutableData().DataInterfaceInfo[i]; - if (Info.IsSystemSolo())//Temp hack to force solo on any systems with system scrips needing user (aka per instance) interfaces. - { - bSolo = true; - break; - } - } - } + return UserDINamesReadInSystemScripts; +} - UNiagaraScript* SystemUpdate = GetSystemUpdateScript(); - if (SystemUpdate->GetVMExecutableData().IsValid()) +void CheckDICompileInfo(const TArray& ScriptDICompileInfos, bool& bOutbHasSystemDIsWithPerInstanceData, TArray& OutUserDINamesReadInSystemScripts) +{ + for (const FNiagaraScriptDataInterfaceCompileInfo& ScriptDICompileInfo : ScriptDICompileInfos) { - for (int32 i = 0; !bSolo && i < SystemUpdate->GetVMExecutableData().DataInterfaceInfo.Num(); ++i) + UNiagaraDataInterface* DefaultDataInterface = ScriptDICompileInfo.GetDefaultDataInterface(); + if (DefaultDataInterface != nullptr && DefaultDataInterface->PerInstanceDataSize() > 0) { - FNiagaraScriptDataInterfaceCompileInfo& Info = SystemUpdate->GetVMExecutableData().DataInterfaceInfo[i]; - if (Info.IsSystemSolo())//Temp hack to force solo on any systems with system scrips needing user (aka per instance) interfaces. - { - bSolo = true; - break; - } + bOutbHasSystemDIsWithPerInstanceData = true; + } + + if (ScriptDICompileInfo.RegisteredParameterMapRead.ToString().StartsWith(TEXT("User."))) + { + OutUserDINamesReadInSystemScripts.AddUnique(ScriptDICompileInfo.RegisteredParameterMapRead); } } } +void UNiagaraSystem::UpdatePostCompileDIInfo() +{ + bHasSystemScriptDIsWithPerInstanceData = false; + UserDINamesReadInSystemScripts.Empty(); + + CheckDICompileInfo(SystemSpawnScript->GetVMExecutableData().DataInterfaceInfo, bHasSystemScriptDIsWithPerInstanceData, UserDINamesReadInSystemScripts); + CheckDICompileInfo(SystemUpdateScript->GetVMExecutableData().DataInterfaceInfo, bHasSystemScriptDIsWithPerInstanceData, UserDINamesReadInSystemScripts); +} + bool UNiagaraSystem::IsValid()const { if (!SystemSpawnScript || !SystemUpdateScript) @@ -901,7 +898,7 @@ bool UNiagaraSystem::QueryCompileComplete(bool bWait, bool bDoPost, bool bDoNotA ActiveCompilations[ActiveCompileIdx].RootObjects.Empty(); - DetermineIfSolo(); + UpdatePostCompileDIInfo(); UE_LOG(LogNiagara, Log, TEXT("Compiling System %s took %f sec (wall time), %f sec (combined time)."), *GetFullName(), (float)(FPlatformTime::Seconds() - ActiveCompilations[ActiveCompileIdx].StartTime), CombinedCompileTime); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemInstance.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemInstance.cpp index ac5faa487212..dae91642cd41 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemInstance.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemInstance.cpp @@ -14,16 +14,17 @@ #include "Templates/AlignmentTemplates.h" -DECLARE_CYCLE_STAT(TEXT("System Activate (GT)"), STAT_NiagaraSystemActivate, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System Deactivate (GT)"), STAT_NiagaraSystemDeactivate, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System Complete (GT)"), STAT_NiagaraSystemComplete, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("Parallel Tick"), STAT_NiagaraParallelTick, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System Reset (GT)"), STAT_NiagaraSystemReset, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System Reinit (GT)"), STAT_NiagaraSystemReinit, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System Init Emitters (GT)"), STAT_NiagaraSystemInitEmitters, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System Advance Simulation "), STAT_NiagaraSystemAdvanceSim, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System SetSolo "), STAT_NiagaraSystemSetSolo, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System PreSimulateTick "), STAT_NiagaraSystemPreSimulateTick, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Activate [GT]"), STAT_NiagaraSystemActivate, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Deactivate [GT]"), STAT_NiagaraSystemDeactivate, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Complete [GT]"), STAT_NiagaraSystemComplete, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Reset [GT]"), STAT_NiagaraSystemReset, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Reinit [GT]"), STAT_NiagaraSystemReinit, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Init Emitters [GT]"), STAT_NiagaraSystemInitEmitters, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Advance Simulation [GT] "), STAT_NiagaraSystemAdvanceSim, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System SetSolo[GT] "), STAT_NiagaraSystemSetSolo, STATGROUP_Niagara); + +DECLARE_CYCLE_STAT(TEXT("System PreSimulateTick [CNC]"), STAT_NiagaraSystemPreSimulateTick, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Instance Tick [CNC]"), STAT_NiagaraSystemInstanceTick, STATGROUP_Niagara); /** Safety time to allow for the LastRenderTime coming back from the RT. */ @@ -39,6 +40,7 @@ FNiagaraSystemInstance::FNiagaraSystemInstance(UNiagaraComponent* InComponent) : SystemInstanceIndex(INDEX_NONE) , Component(InComponent) , Age(0.0f) + , TickCount(0) , ID(FGuid::NewGuid()) , IDName(*ID.ToString()) , InstanceParameters(Component) @@ -54,7 +56,7 @@ FNiagaraSystemInstance::FNiagaraSystemInstance(UNiagaraComponent* InComponent) SystemBounds.Init(); } -void FNiagaraSystemInstance::Init(UNiagaraSystem* InSystem, bool bInForceSolo) +void FNiagaraSystemInstance::Init(bool bInForceSolo) { bForceSolo = bInForceSolo; ActualExecutionState = ENiagaraExecutionState::Inactive; @@ -139,6 +141,8 @@ bool FNiagaraSystemInstance::RequestCapture(const FGuid& RequestId) return false; } + UE_LOG(LogNiagara, Warning, TEXT("Capture requested!")); + bWasSoloPriorToCaptureRequest = bSolo; SetSolo(true); @@ -156,7 +160,10 @@ bool FNiagaraSystemInstance::RequestCapture(const FGuid& RequestId) for (UNiagaraScript* Script : Scripts) { - TempCaptureHolder->Add(MakeShared(Handle.GetIdName(), Script->GetUsage(), Script->GetUsageId())); + TSharedPtr DebugInfoPtr = MakeShared(Handle.GetIdName(), Script->GetUsage(), Script->GetUsageId()); + DebugInfoPtr->bWritten = false; + + TempCaptureHolder->Add(DebugInfoPtr); } } CapturedFrames.Add(RequestId, TempCaptureHolder); @@ -190,6 +197,27 @@ bool FNiagaraSystemInstance::QueryCaptureResults(const FGuid& RequestId, TArray< TArray>* Array = FoundEntry->Get(); OutCaptureResults.SetNum(Array->Num()); + bool bWaitForGPU = false; + { + for (int32 i = 0; i < FoundEntry->Get()->Num(); i++) + { + if ((*Array)[i]->bWaitForGPU && (*Array)[i]->bWritten == false) + { + bWaitForGPU = true; + } + } + + if (bWaitForGPU) + { + for (TSharedRef CachedEmitter : Emitters) + { + CachedEmitter->WaitForDebugInfo(); + } + return false; + } + } + + for (int32 i = 0; i < FoundEntry->Get()->Num(); i++) { OutCaptureResults[i] = (*Array)[i]; @@ -206,7 +234,7 @@ TArray>* FNia return CurrentCapture.Get(); } -FNiagaraScriptDebuggerInfo* FNiagaraSystemInstance::GetActiveCaptureWrite(const FName& InHandleName, ENiagaraScriptUsage InUsage, const FGuid& InUsageId) +TSharedPtr FNiagaraSystemInstance::GetActiveCaptureWrite(const FName& InHandleName, ENiagaraScriptUsage InUsage, const FGuid& InUsageId) { if (CurrentCapture.IsValid()) { @@ -217,7 +245,7 @@ FNiagaraScriptDebuggerInfo* FNiagaraSystemInstance::GetActiveCaptureWrite(const if (FoundEntry != nullptr) { - return FoundEntry->Get(); + return *FoundEntry; } } return nullptr; @@ -241,7 +269,7 @@ void FNiagaraSystemInstance::SetSolo(bool bInSolo) if (bInSolo) { TSharedPtr NewSoloSim = MakeShared(); - NewSoloSim->Init(System, Component->GetWorld(), this); + NewSoloSim->Init(System, Component->GetWorld(), true); NewSoloSim->TransferInstance(SystemSimulation.Get(), this); @@ -344,14 +372,16 @@ void FNiagaraSystemInstance::SetPaused(bool bInPaused) } FNiagaraSystemSimulation* SystemSim = GetSystemSimulation().Get(); - check(SystemSim); - if (bInPaused) + if (SystemSim) { - SystemSim->PauseInstance(this); - } - else - { - SystemSim->UnpauseInstance(this); + if (bInPaused) + { + SystemSim->PauseInstance(this); + } + else + { + SystemSim->UnpauseInstance(this); + } } bPaused = bInPaused; @@ -361,7 +391,6 @@ void FNiagaraSystemInstance::Reset(FNiagaraSystemInstance::EResetMode Mode, bool { SCOPE_CYCLE_COUNTER(STAT_NiagaraSystemReset); - FNiagaraSystemSimulation* SystemSim = GetSystemSimulation().Get(); if (Mode == EResetMode::None) { // Right now we don't support binding with reset mode none. @@ -376,9 +405,9 @@ void FNiagaraSystemInstance::Reset(FNiagaraSystemInstance::EResetMode Mode, bool SetPaused(false); - if (SystemSim) + if (SystemSimulation.IsValid()) { - SystemSim->RemoveInstance(this); + SystemSimulation->RemoveInstance(this); } else { @@ -405,6 +434,8 @@ void FNiagaraSystemInstance::Reset(FNiagaraSystemInstance::EResetMode Mode, bool { //UE_LOG(LogNiagara, Log, TEXT("FNiagaraSystemInstance::ReInit")); ReInitInternal(); + // If the system was reinitialized successfully than we need to force a rebind of the parameters. + bBindParams = IsComplete() == false; } if (bBindParams) @@ -412,7 +443,6 @@ void FNiagaraSystemInstance::Reset(FNiagaraSystemInstance::EResetMode Mode, bool BindParameters(); } - SystemSim = GetSystemSimulation().Get(); SetRequestedExecutionState(ENiagaraExecutionState::Active); SetActualExecutionState(ENiagaraExecutionState::Active); @@ -422,7 +452,7 @@ void FNiagaraSystemInstance::Reset(FNiagaraSystemInstance::EResetMode Mode, bool if (!IsComplete()) { bPendingSpawn = true; - SystemSim->AddInstance(this); + SystemSimulation->AddInstance(this); UNiagaraSystem* System = GetSystem(); if (System->NeedsWarmup()) @@ -434,6 +464,7 @@ void FNiagaraSystemInstance::Reset(FNiagaraSystemInstance::EResetMode Mode, bool //Reset age to zero. Age = 0.0f; + TickCount = 0; } } @@ -445,6 +476,7 @@ void FNiagaraSystemInstance::Reset(FNiagaraSystemInstance::EResetMode Mode, bool void FNiagaraSystemInstance::ResetInternal(bool bResetSimulations) { Age = 0; + TickCount = 0; UNiagaraSystem* System = GetSystem(); if (System == nullptr || Component == nullptr || IsDisabled()) { @@ -491,15 +523,15 @@ UNiagaraParameterCollectionInstance* FNiagaraSystemInstance::GetParameterCollect return SystemSimulation->GetParameterCollectionInstance(Collection); } -void FNiagaraSystemInstance::AdvanceSimulation(int32 TickCount, float TickDeltaSeconds) +void FNiagaraSystemInstance::AdvanceSimulation(int32 TickCountToSimulate, float TickDeltaSeconds) { - if (TickCount > 0) + if (TickCountToSimulate > 0) { SCOPE_CYCLE_COUNTER(STAT_NiagaraSystemAdvanceSim); bool bWasSolo = bSolo; SetSolo(true); - for (int32 TickIdx = 0; TickIdx < TickCount; ++TickIdx) + for (int32 TickIdx = 0; TickIdx < TickCountToSimulate; ++TickIdx) { ComponentTick(TickDeltaSeconds); } @@ -528,10 +560,35 @@ bool FNiagaraSystemInstance::IsReadyToRun() const return bAllReadyToRun; } +bool DoSystemDataInterfacesRequireSolo(const UNiagaraSystem& System, const UNiagaraComponent& Component) +{ + if (System.HasSystemScriptDIsWithPerInstanceData()) + { + return true; + } + + const TArray& UserDINamesReadInSystemScripts = System.GetUserDINamesReadInSystemScripts(); + if (UserDINamesReadInSystemScripts.Num() > 0) + { + TArray OverrideParameterVariables; + Component.GetOverrideParameters().GetParameters(OverrideParameterVariables); + for (const FNiagaraVariable& OverrideParameterVariable : OverrideParameterVariables) + { + if (OverrideParameterVariable.IsDataInterface() && UserDINamesReadInSystemScripts.Contains(OverrideParameterVariable.GetName())) + { + return true; + } + } + } + + return false; +} + void FNiagaraSystemInstance::ReInitInternal() { SCOPE_CYCLE_COUNTER(STAT_NiagaraSystemReinit); Age = 0; + TickCount = 0; UNiagaraSystem* System = GetSystem(); if (System == nullptr || Component == nullptr) { @@ -557,13 +614,13 @@ void FNiagaraSystemInstance::ReInitInternal() } /** Do we need to run in solo mode? */ - bSolo = bForceSolo || System->IsSolo(); + bSolo = bForceSolo || DoSystemDataInterfacesRequireSolo(*System, *Component); if (bSolo) { if (!SystemSimulation.IsValid()) { SystemSimulation = MakeShared(); - SystemSimulation->Init(System, Component->GetWorld(), this); + SystemSimulation->Init(System, Component->GetWorld(), true); } } else @@ -577,6 +634,7 @@ void FNiagaraSystemInstance::ReInitInternal() InstanceParameters.Reset(); InstanceParameters.AddParameter(SYS_PARAM_ENGINE_POSITION, true, false); + InstanceParameters.AddParameter(SYS_PARAM_ENGINE_ROTATION, true, false); InstanceParameters.AddParameter(SYS_PARAM_ENGINE_SCALE, true, false); InstanceParameters.AddParameter(SYS_PARAM_ENGINE_VELOCITY, true, false); InstanceParameters.AddParameter(SYS_PARAM_ENGINE_X_AXIS, true, false); @@ -598,6 +656,7 @@ void FNiagaraSystemInstance::ReInitInternal() InstanceParameters.AddParameter(SYS_PARAM_ENGINE_SYSTEM_NUM_EMITTERS, true, false); InstanceParameters.AddParameter(SYS_PARAM_ENGINE_SYSTEM_NUM_EMITTERS_ALIVE, true, false); InstanceParameters.AddParameter(SYS_PARAM_ENGINE_SYSTEM_AGE); + InstanceParameters.AddParameter(SYS_PARAM_ENGINE_SYSTEM_TICK_COUNT); // This is required for user default data interface's (like say static meshes) to be set up properly. // Additionally, it must happen here for data to be properly found below. @@ -605,15 +664,26 @@ void FNiagaraSystemInstance::ReInitInternal() System->GetExposedParameters().CopyParametersTo(InstanceParameters, bOnlyAdd, FNiagaraParameterStore::EDataInterfaceCopyMethod::Reference); TArray NumParticleVars; + TArray TotalSpawnedParticlesVars; for (int32 i = 0; i < Emitters.Num(); i++) { TSharedRef Simulation = Emitters[i]; FString EmitterName = Simulation->GetEmitterHandle().GetInstance()->GetUniqueEmitterName(); - FNiagaraVariable Var = SYS_PARAM_ENGINE_EMITTER_NUM_PARTICLES; - FString ParamName = Var.GetName().ToString().Replace(TEXT("Emitter"), *EmitterName); - Var.SetName(*ParamName); - InstanceParameters.AddParameter(Var, true, false); - NumParticleVars.Add(Var); + + { + FNiagaraVariable Var = SYS_PARAM_ENGINE_EMITTER_NUM_PARTICLES; + FString ParamName = Var.GetName().ToString().Replace(TEXT("Emitter"), *EmitterName); + Var.SetName(*ParamName); + InstanceParameters.AddParameter(Var, true, false); + NumParticleVars.Add(Var); + } + { + FNiagaraVariable Var = SYS_PARAM_ENGINE_EMITTER_TOTAL_SPAWNED_PARTICLES; + FString ParamName = Var.GetName().ToString().Replace(TEXT("Emitter"), *EmitterName); + Var.SetName(*ParamName); + InstanceParameters.AddParameter(Var, true, false); + TotalSpawnedParticlesVars.Add(Var); + } } // Make sure all parameters are added before initializing the bindings, otherwise parameter store layout changes might invalidate the bindings. @@ -624,6 +694,8 @@ void FNiagaraSystemInstance::ReInitInternal() OwnerYAxisParam.Init(InstanceParameters, SYS_PARAM_ENGINE_Y_AXIS); OwnerZAxisParam.Init(InstanceParameters, SYS_PARAM_ENGINE_Z_AXIS); + OwnerRotationParam.Init(InstanceParameters, SYS_PARAM_ENGINE_ROTATION); + OwnerTransformParam.Init(InstanceParameters, SYS_PARAM_ENGINE_LOCAL_TO_WORLD); OwnerInverseParam.Init(InstanceParameters, SYS_PARAM_ENGINE_WORLD_TO_LOCAL); OwnerTransposeParam.Init(InstanceParameters, SYS_PARAM_ENGINE_LOCAL_TO_WORLD_TRANSPOSED); @@ -635,6 +707,7 @@ void FNiagaraSystemInstance::ReInitInternal() OwnerInverseDeltaSecondsParam.Init(InstanceParameters, SYS_PARAM_ENGINE_INV_DELTA_TIME); SystemAgeParam.Init(InstanceParameters, SYS_PARAM_ENGINE_SYSTEM_AGE); + SystemTickCountParam.Init(InstanceParameters, SYS_PARAM_ENGINE_SYSTEM_TICK_COUNT); OwnerEngineTimeParam.Init(InstanceParameters, SYS_PARAM_ENGINE_TIME); OwnerEngineRealtimeParam.Init(InstanceParameters, SYS_PARAM_ENGINE_REAL_TIME); @@ -652,6 +725,12 @@ void FNiagaraSystemInstance::ReInitInternal() ParameterNumParticleBindings[i].Init(InstanceParameters, NumParticleVars[i]); } + ParameterTotalSpawnedParticlesBindings.SetNum(TotalSpawnedParticlesVars.Num()); + for (int32 i = 0; i < TotalSpawnedParticlesVars.Num(); i++) + { + ParameterTotalSpawnedParticlesBindings[i].Init(InstanceParameters, TotalSpawnedParticlesVars[i]); + } + // rebind now after all parameters have been added InstanceParameters.Rebind(); @@ -745,6 +824,21 @@ void FNiagaraSystemInstance::BindParameters() { Component->GetOverrideParameters().Bind(&InstanceParameters); + if (SystemSimulation->GetIsSolo()) + { + // If this simulation is solo than we can bind the instance parameters to the system simulation contexts so that + // the system and emitter scripts use the per-instance data interfaces. + Component->GetOverrideParameters().Bind(&SystemSimulation->GetSpawnExecutionContext().Parameters); + Component->GetOverrideParameters().Bind(&SystemSimulation->GetUpdateExecutionContext().Parameters); + } + else + { + // If this simulation is not solo than we have bind the source system parameters to the system simulation contexts so that + // the system and emitter scripts use the default shared data interfaces. + GetSystem()->GetExposedParameters().Bind(&SystemSimulation->GetSpawnExecutionContext().Parameters); + GetSystem()->GetExposedParameters().Bind(&SystemSimulation->GetSpawnExecutionContext().Parameters); + } + for (TSharedRef Simulation : Emitters) { Simulation->BindParameters(); @@ -755,6 +849,24 @@ void FNiagaraSystemInstance::UnbindParameters() { Component->GetOverrideParameters().Unbind(&InstanceParameters); + if (SystemSimulation.IsValid()) + { + if (SystemSimulation->GetIsSolo()) + { + Component->GetOverrideParameters().Unbind(&SystemSimulation->GetSpawnExecutionContext().Parameters); + Component->GetOverrideParameters().Unbind(&SystemSimulation->GetUpdateExecutionContext().Parameters); + } + else + { + UNiagaraSystem* System = GetSystem(); + if (System) + { + System->GetExposedParameters().Unbind(&SystemSimulation->GetSpawnExecutionContext().Parameters); + System->GetExposedParameters().Unbind(&SystemSimulation->GetSpawnExecutionContext().Parameters); + } + } + } + for (TSharedRef Simulation : Emitters) { Simulation->UnbindParameters(); @@ -813,6 +925,15 @@ void FNiagaraSystemInstance::InitDataInterfaces() CalcInstDataSize(InstanceParameters.GetDataInterfaces());//This probably should be a proper exec context. + if (SystemSimulation->GetIsSolo()) + { + CalcInstDataSize(SystemSimulation->GetSpawnExecutionContext().GetDataInterfaces()); + SystemSimulation->GetSpawnExecutionContext().DirtyDataInterfaces(); + + CalcInstDataSize(SystemSimulation->GetUpdateExecutionContext().GetDataInterfaces()); + SystemSimulation->GetUpdateExecutionContext().DirtyDataInterfaces(); + } + //Iterate over interfaces to get size for table and clear their interface bindings. for (TSharedRef Simulation : Emitters) { @@ -860,6 +981,15 @@ void FNiagaraSystemInstance::InitDataInterfaces() } } +bool FNiagaraSystemInstance::GetPerInstanceDataAndOffsets(void*& OutData, uint32& OutDataSize, TMap, int32>*& OutOffsets) +{ + OutData = DataInterfaceInstanceData.GetData(); + OutDataSize = DataInterfaceInstanceData.Num(); + OutOffsets = &DataInterfaceInstanceDataOffsets; + return DataInterfaceInstanceDataOffsets.Num() != 0; +} + + void FNiagaraSystemInstance::TickDataInterfaces(float DeltaSeconds, bool bPostSimulate) { if (!GetSystem() || !Component || IsDisabled()) @@ -902,8 +1032,8 @@ void FNiagaraSystemInstance::TickInstanceParameters(float DeltaSeconds) //TODO: Create helper binding objects to avoid the search in set parameter value. //Set System params. FTransform ComponentTrans = Component->GetComponentTransform(); - FVector OldPos = OwnerPositionParam.GetValue();// ComponentTrans.GetLocation(); FVector CurrPos = ComponentTrans.GetLocation(); + FVector OldPos = FMath::IsNearlyZero(Age) ? CurrPos : OwnerPositionParam.GetValue(); // The first frame the value in OwnerPositionParam is uninitialized memory, we need to make sure that we don't use it. OwnerPositionParam.SetValue(CurrPos); OwnerScaleParam.SetValue(ComponentTrans.GetScale3D()); OwnerVelocityParam.SetValue((CurrPos - OldPos) / DeltaSeconds); @@ -911,6 +1041,8 @@ void FNiagaraSystemInstance::TickInstanceParameters(float DeltaSeconds) OwnerYAxisParam.SetValue(ComponentTrans.GetRotation().GetAxisY()); OwnerZAxisParam.SetValue(ComponentTrans.GetRotation().GetAxisZ()); + OwnerRotationParam.SetValue(ComponentTrans.GetRotation()); + FMatrix Transform = ComponentTrans.ToMatrixWithScale(); FMatrix Inverse = Transform.Inverse(); FMatrix Transpose = Transform.GetTransposed(); @@ -971,6 +1103,7 @@ void FNiagaraSystemInstance::TickInstanceParameters(float DeltaSeconds) OwnerEngineRealtimeParam.SetValue(Age); } SystemAgeParam.SetValue(Age); + SystemTickCountParam.SetValue(TickCount); int32 NumAlive = 0; for (int32 i = 0; i < Emitters.Num(); i++) @@ -981,6 +1114,7 @@ void FNiagaraSystemInstance::TickInstanceParameters(float DeltaSeconds) NumAlive++; } ParameterNumParticleBindings[i].SetValue(NumParticles); + ParameterTotalSpawnedParticlesBindings[i].SetValue(Emitters[i]->GetTotalSpawnedParticles()); } SystemNumEmittersParam.SetValue(Emitters.Num()); SystemNumEmittersAliveParam.SetValue(NumAlive); @@ -1171,6 +1305,9 @@ void FNiagaraSystemInstance::PreSimulateTick(float DeltaSeconds) void FNiagaraSystemInstance::PostSimulateTick(float DeltaSeconds) { + SCOPE_CYCLE_COUNTER(STAT_NiagaraSystemInstanceTick); + SCOPE_CYCLE_COUNTER(STAT_NiagaraOverview_GT_CNC); + if (IsComplete() || !bHasTickingEmitters || GetSystem() == nullptr || Component == nullptr || DeltaSeconds < SMALL_NUMBER) { return; @@ -1197,6 +1334,7 @@ void FNiagaraSystemInstance::PostSimulateTick(float DeltaSeconds) } Age += DeltaSeconds; + TickCount += 1; } #if WITH_EDITORONLY_DATA diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemSimulation.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemSimulation.cpp index 7389e965138a..103798abed21 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemSimulation.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraSystemSimulation.cpp @@ -14,14 +14,14 @@ #include "NiagaraComponent.h" #include "NiagaraWorldManager.h" -DECLARE_CYCLE_STAT(TEXT("System Simulation"), STAT_NiagaraSystemSim, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System Pre Simulate"), STAT_NiagaraSystemSim_PreSimulate, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System Prepare For Simulate"), STAT_NiagaraSystemSim_PrepareForSimulate, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System Update"), STAT_NiagaraSystemSim_Update, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System Spawn"), STAT_NiagaraSystemSim_Spawn, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System Transfer Parameters"), STAT_NiagaraSystemSim_TransferParameters, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System Post Simulate"), STAT_NiagaraSystemSim_PostSimulate, STATGROUP_Niagara); -DECLARE_CYCLE_STAT(TEXT("System Mark Component Dirty"), STAT_NiagaraSystemSim_MarkComponentDirty, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Simulation [GT]"), STAT_NiagaraSystemSim, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Pre Simulate [GT]"), STAT_NiagaraSystemSim_PreSimulate, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Prepare For Simulate [GT]"), STAT_NiagaraSystemSim_PrepareForSimulate, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Sim Update [GT]"), STAT_NiagaraSystemSim_Update, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Sim Spawn [GT]"), STAT_NiagaraSystemSim_Spawn, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Sim Transfer Parameters [GT]"), STAT_NiagaraSystemSim_TransferParameters, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Post Simulate [GT]"), STAT_NiagaraSystemSim_PostSimulate, STATGROUP_Niagara); +DECLARE_CYCLE_STAT(TEXT("System Mark Component Dirty [GT]"), STAT_NiagaraSystemSim_MarkComponentDirty, STATGROUP_Niagara); static int32 GbDumpSystemData = 0; @@ -73,14 +73,15 @@ FNiagaraSystemSimulation::~FNiagaraSystemSimulation() Destroy(); } -bool FNiagaraSystemSimulation::Init(UNiagaraSystem* InSystem, UWorld* InWorld, FNiagaraSystemInstance* InSoloSystemInstance) +bool FNiagaraSystemSimulation::Init(UNiagaraSystem* InSystem, UWorld* InWorld, bool bInIsSolo) { UNiagaraSystem* System = InSystem; WeakSystem = System; - SoloSystemInstance = InSoloSystemInstance; World = InWorld; + bIsSolo = bInIsSolo; + FNiagaraWorldManager* WorldMan = FNiagaraWorldManager::Get(InWorld); check(WorldMan); @@ -162,6 +163,11 @@ bool FNiagaraSystemSimulation::Init(UNiagaraSystem* InSystem, UWorld* InWorld, F { EmitterSpawnInfoAccessors[EmitterIdx].Emplace(DataSet, FNiagaraVariable(FNiagaraTypeDefinition(FNiagaraSpawnInfo::StaticStruct()), AttrName)); } + + if (Emitter->bLimitDeltaTime) + { + MaxDeltaTime = MaxDeltaTime.IsSet() ? FMath::Min(MaxDeltaTime.GetValue(), Emitter->MaxDeltaTimePerTick) : Emitter->MaxDeltaTimePerTick; + } } SpawnDeltaTimeParam.Init(SpawnExecContext.Parameters, SYS_PARAM_ENGINE_DELTA_TIME); @@ -240,6 +246,10 @@ void FNiagaraSystemSimulation::TransferInstance(FNiagaraSystemSimulation* Source //Move the system direct to the new sim's SystemInst->SystemInstanceIndex = SystemInstances.Add(SystemInst); + if (SystemInst->SystemInstanceIndex == 0) + { + InitParameterDataSetBindings(SystemInst); + } check(NewDataIndex == SystemInst->SystemInstanceIndex); } @@ -274,6 +284,11 @@ bool FNiagaraSystemSimulation::Tick(float DeltaSeconds) return false; } + if (MaxDeltaTime.IsSet()) + { + DeltaSeconds = FMath::Clamp(DeltaSeconds, 0.0f, MaxDeltaTime.GetValue()); + } + UNiagaraScript* SystemSpawnScript = System->GetSystemSpawnScript(); UNiagaraScript* SystemUpdateScript = System->GetSystemUpdateScript(); #if WITH_EDITOR @@ -341,6 +356,11 @@ bool FNiagaraSystemSimulation::Tick(float DeltaSeconds) if (!Inst->IsComplete()) { Inst->SystemInstanceIndex = SystemInstances.Add(Inst); + if (Inst->SystemInstanceIndex == 0) + { + // When the first instance is added we need to initialize the parameter store to data set bindings. + InitParameterDataSetBindings(Inst); + } ++SpawnNum; } else @@ -392,8 +412,6 @@ bool FNiagaraSystemSimulation::Tick(float DeltaSeconds) } }; - InitBindings(SystemInstances[0]); - SpawnInstanceParameterDataSet.Allocate(NewNum); UpdateInstanceParameterDataSet.Allocate(NewNum); @@ -429,6 +447,8 @@ bool FNiagaraSystemSimulation::Tick(float DeltaSeconds) UpdateGlobalSystemCountScaleParam.SetValue(GlobalSystemCountScale); } + FNiagaraSystemInstance* SoloSystemInstance = bIsSolo && SystemInstances.Num() == 1 ? SystemInstances[0] : nullptr; + //TODO: JIRA - UE-60096 - Remove. //We're having to allocate and spawn before update here so we have to do needless copies. //Ideally this should be compiled directly into the script similarly to interpolated particle spawning. @@ -456,12 +476,13 @@ bool FNiagaraSystemSimulation::Tick(float DeltaSeconds) #if WITH_EDITORONLY_DATA if (SoloSystemInstance && SoloSystemInstance->ShouldCaptureThisFrame()) { - FNiagaraScriptDebuggerInfo* DebugInfo = SoloSystemInstance->GetActiveCaptureWrite(NAME_None, ENiagaraScriptUsage::SystemSpawnScript, FGuid()); + TSharedPtr DebugInfo = SoloSystemInstance->GetActiveCaptureWrite(NAME_None, ENiagaraScriptUsage::SystemSpawnScript, FGuid()); if (DebugInfo) { DataSet.Dump(DebugInfo->Frame, true, OrigNum, SpawnNum); //DebugInfo->Frame.Dump(true, 0, SpawnNum); DebugInfo->Parameters = SpawnExecContext.Parameters; + DebugInfo->bWritten = true; } } #endif @@ -524,12 +545,13 @@ bool FNiagaraSystemSimulation::Tick(float DeltaSeconds) #if WITH_EDITORONLY_DATA if (SoloSystemInstance && SoloSystemInstance->ShouldCaptureThisFrame()) { - FNiagaraScriptDebuggerInfo* DebugInfo = SoloSystemInstance->GetActiveCaptureWrite(NAME_None, ENiagaraScriptUsage::SystemUpdateScript, FGuid()); + TSharedPtr DebugInfo = SoloSystemInstance->GetActiveCaptureWrite(NAME_None, ENiagaraScriptUsage::SystemUpdateScript, FGuid()); if (DebugInfo) { DataSet.Dump(DebugInfo->Frame, true, 0, NewNum); //DebugInfo->Frame.Dump(true, 0, OrigNum); DebugInfo->Parameters = UpdateExecContext.Parameters; + DebugInfo->bWritten = true; } } #endif @@ -620,7 +642,7 @@ bool FNiagaraSystemSimulation::Tick(float DeltaSeconds) { SCOPE_CYCLE_COUNTER(STAT_NiagaraSystemSim_PostSimulate); - if (GbParallelSystemPostTick) + if (GbParallelSystemPostTick && FApp::ShouldUseThreadingForPerformance()) { ParallelFor(SystemInstances.Num(), [&](int32 SystemIndex) @@ -659,9 +681,9 @@ bool FNiagaraSystemSimulation::Tick(float DeltaSeconds) } #if WITH_EDITORONLY_DATA - if (SoloSystemInstance != nullptr) + if (bIsSolo && SystemInstances.Num() == 1) { - SoloSystemInstance->FinishCapture(); + SystemInstances[0]->FinishCapture(); } #endif @@ -837,20 +859,18 @@ void FNiagaraSystemSimulation::UnpauseInstance(FNiagaraSystemInstance* Instance) } } -void FNiagaraSystemSimulation::InitBindings(FNiagaraSystemInstance* SystemInst) +void FNiagaraSystemSimulation::InitParameterDataSetBindings(FNiagaraSystemInstance* SystemInst) { //Have to init here as we need an actual parameter store to pull the layout info from. //TODO: Pull the layout stuff out of each data set and store. So much duplicated data. //This assumes that all layouts for all emitters is the same. Which it should be. //Ideally we can store all this layout info in the systm/emitter assets so we can just generate this in Init() - if (DataSetToEmitterSpawnParameters.Num() == 0 && SystemInst != nullptr) + if (SystemInst != nullptr) { SpawnInstanceParameterToDataSetBinding.Init(SpawnInstanceParameterDataSet, SystemInst->GetInstanceParameters()); UpdateInstanceParameterToDataSetBinding.Init(UpdateInstanceParameterDataSet, SystemInst->GetInstanceParameters()); TArray>& Emitters = SystemInst->GetEmitters(); - check(DataSetToEmitterUpdateParameters.Num() == 0); - check(DataSetToEmitterEventParameters.Num() == 0); DataSetToEmitterSpawnParameters.SetNum(Emitters.Num()); DataSetToEmitterUpdateParameters.SetNum(Emitters.Num()); DataSetToEmitterEventParameters.SetNum(Emitters.Num()); @@ -872,13 +892,4 @@ void FNiagaraSystemSimulation::InitBindings(FNiagaraSystemInstance* SystemInst) } } } - - // If we have data interfaces (say user ones) that need to be pushed to the - // system spawn and update scripts, it needs to happen here. - if (SystemInst) - { - SystemInst->GetInstanceParameters().Bind(&SpawnExecContext.Parameters); - SystemInst->GetInstanceParameters().Bind(&UpdateExecContext.Parameters); - - } } diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraUserRedirectionParameterStore.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraUserRedirectionParameterStore.cpp new file mode 100644 index 000000000000..a8ffe87be972 --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraUserRedirectionParameterStore.cpp @@ -0,0 +1,113 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "NiagaraUserRedirectionParameterStore.h" +#include "NiagaraStats.h" +#include "NiagaraEmitterInstanceBatcher.h" +#include "NiagaraDataInterface.h" +#include "NiagaraSystemInstance.h" + +FNiagaraUserRedirectionParameterStore::FNiagaraUserRedirectionParameterStore() : FNiagaraParameterStore() +{ + +} + +FNiagaraUserRedirectionParameterStore::FNiagaraUserRedirectionParameterStore(const FNiagaraParameterStore& Other) +{ + *this = Other; +} + +FNiagaraUserRedirectionParameterStore& FNiagaraUserRedirectionParameterStore::operator=(const FNiagaraParameterStore& Other) +{ + Super::operator=(Other); + RecreateRedirections(); + return *this; +} + +bool FNiagaraUserRedirectionParameterStore::IsUserParameter(const FNiagaraVariable& InVar) const +{ + return InVar.GetName().ToString().StartsWith(TEXT("User.")); +} + +FNiagaraVariable FNiagaraUserRedirectionParameterStore::GetUserRedirection(const FNiagaraVariable & InVar) const +{ + if (!IsUserParameter(InVar)) + { + return InVar; + } + FNiagaraVariable SimpleVar = InVar; + FName DisplayName(*InVar.GetName().ToString().RightChop(5)); + SimpleVar.SetName(DisplayName); + return SimpleVar; +} + +void FNiagaraUserRedirectionParameterStore::RecreateRedirections() +{ + UserParameterRedirects.Reset(); + for (const TPair& ParamOffset : GetParameterOffsets()) + { + const FNiagaraVariable& Var = ParamOffset.Key; + if (IsUserParameter(Var)) + { + UserParameterRedirects.Add(GetUserRedirection(Var), Var); + } + } +} + +int32 FNiagaraUserRedirectionParameterStore::IndexOf(const FNiagaraVariable& Parameter) const +{ + const FNiagaraVariable* Redirection = UserParameterRedirects.Find(Parameter); + return Super::IndexOf(Redirection ? *Redirection : Parameter); +} + +bool FNiagaraUserRedirectionParameterStore::AddParameter(const FNiagaraVariable& Param, bool bInitialize /*= true*/, bool bTriggerRebind /*= true*/) +{ + if (IsUserParameter(Param)) + { + UserParameterRedirects.Add(GetUserRedirection(Param), Param); + } + return Super::AddParameter(Param, bInitialize, bTriggerRebind); +} + +bool FNiagaraUserRedirectionParameterStore::RemoveParameter(const FNiagaraVariable& InVar) +{ + const FNiagaraVariable* Redirection = UserParameterRedirects.Find(InVar); + const FNiagaraVariable& ToRemove = Redirection ? *Redirection : InVar; + bool Result = Super::RemoveParameter(ToRemove); + if (Result) + { + UserParameterRedirects.Remove(GetUserRedirection(ToRemove)); + } + return Result; +} + +void FNiagaraUserRedirectionParameterStore::InitFromSource(const FNiagaraParameterStore* SrcStore, bool bNotifyAsDirty) +{ + Super::InitFromSource(SrcStore, bNotifyAsDirty); + RecreateRedirections(); +} + +void FNiagaraUserRedirectionParameterStore::Empty(bool bClearBindings /*= true*/) +{ + Super::Empty(bClearBindings); + UserParameterRedirects.Empty(); +} + +void FNiagaraUserRedirectionParameterStore::Reset(bool bClearBindings /*= true*/) +{ + Super::Reset(bClearBindings); + UserParameterRedirects.Reset(); +} + +bool FNiagaraUserRedirectionParameterStore::SerializeFromMismatchedTag(const FPropertyTag & Tag, FStructuredArchive::FSlot Slot) +{ + static FName StoreDataName("NiagaraParameterStore"); + if (Tag.Type == NAME_StructProperty && Tag.StructName == StoreDataName) + { + FNiagaraParameterStore OldStore; + FNiagaraParameterStore::StaticStruct()->SerializeItem(Slot, &OldStore, nullptr); + *this = OldStore; + return true; + } + + return false; +} diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraWorldManager.cpp b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraWorldManager.cpp index e14f6195ac70..d45ad16e8940 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraWorldManager.cpp +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Private/NiagaraWorldManager.cpp @@ -12,6 +12,9 @@ #include "Misc/ConfigCacheIni.h" #include "NiagaraDataInterfaceSkeletalMesh.h" #include "EngineModule.h" +#include "NiagaraStats.h" + +DECLARE_CYCLE_STAT(TEXT("Niagara Manager Tick [GT]"), STAT_NiagaraWorldManTick, STATGROUP_Niagara); TGlobalResource GNiagaraViewDataManager; @@ -27,8 +30,11 @@ FNiagaraViewDataMgr::FNiagaraViewDataMgr() void FNiagaraViewDataMgr::Init() { IRendererModule& RendererModule = GetRendererModule(); + GNiagaraViewDataManager.PostOpaqueDelegate.BindRaw(&GNiagaraViewDataManager, &FNiagaraViewDataMgr::PostOpaqueRender); RendererModule.RegisterPostOpaqueRenderDelegate(GNiagaraViewDataManager.PostOpaqueDelegate); + + RendererModule.OnPreSceneRender().AddRaw(&GNiagaraViewDataManager, &FNiagaraViewDataMgr::OnPreSceneRenderCalled); } void FNiagaraViewDataMgr::Shutdown() @@ -149,7 +155,7 @@ TSharedRef FNiagaraWorldManager:: TSharedRef Sim = MakeShared(); SystemSimulations.Add(System, Sim); - Sim->Init(System, World); + Sim->Init(System, World, false); return Sim; } @@ -199,6 +205,9 @@ Going off this idea tbh } } */ + SCOPE_CYCLE_COUNTER(STAT_NiagaraWorldManTick); + SCOPE_CYCLE_COUNTER(STAT_NiagaraOverview_GT); + SkeletalMeshGeneratedData.TickGeneratedData(DeltaSeconds); //Tick our collections to push any changes to bound stores. diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraCommon.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraCommon.h index 15f74f78a049..8b076445ed24 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraCommon.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraCommon.h @@ -342,9 +342,6 @@ public: /** Would this data interface work on the target execution type? Only call this on the game thread.*/ bool CanExecuteOnTarget(ENiagaraSimTarget SimTarget) const; - /** Would this data interface force a system script to be solo? Only call this on the game thread.*/ - bool IsSystemSolo() const; - /** Note that this is the CDO for this type of data interface, as we often cannot guarantee that the same instance of the data interface we compiled with is the one the user ultimately executes. Only call this on the game thread.*/ UNiagaraDataInterface* GetDefaultDataInterface() const; }; @@ -511,6 +508,21 @@ struct FNiagaraVariableAttributeBinding FNiagaraVariable DefaultValueIfNonExistent; }; +USTRUCT() +struct FNiagaraVariableDataInterfaceBinding +{ + GENERATED_USTRUCT_BODY(); + + FNiagaraVariableDataInterfaceBinding() {} + FNiagaraVariableDataInterfaceBinding(const FNiagaraVariable& InVar) : BoundVariable(InVar) + { + check(InVar.IsDataInterface() == true); + } + + UPROPERTY() + FNiagaraVariable BoundVariable; +}; + namespace FNiagaraUtilities { diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponent.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponent.h index 20854d5b834b..c9511ae70a17 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponent.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraComponent.h @@ -8,7 +8,7 @@ #include "PrimitiveViewRelevance.h" #include "PrimitiveSceneProxy.h" #include "Components/PrimitiveComponent.h" -#include "NiagaraParameterStore.h" +#include "NiagaraUserRedirectionParameterStore.h" #include "NiagaraSystemInstance.h" #include "NiagaraComponent.generated.h" @@ -21,14 +21,15 @@ class UNiagaraParameterCollectionInstance; class FNiagaraSystemSimulation; // Called when the particle system is done -DECLARE_MULTICAST_DELEGATE_OneParam(FOnNiagaraSystemFinished, class UNiagaraComponent*); +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnNiagaraSystemFinished, class UNiagaraComponent*, PSystem); + /** * UNiagaraComponent is the primitive component for a Niagara System. * @see ANiagaraActor * @see UNiagaraSystem */ -UCLASS(ClassGroup = (Rendering, Common), hidecategories = Object, hidecategories = Physics, hidecategories = Collision, showcategories = Trigger, editinlinenew, meta = (BlueprintSpawnableComponent)) +UCLASS(ClassGroup = (Rendering, Common), hidecategories = Object, hidecategories = Physics, hidecategories = Collision, showcategories = Trigger, editinlinenew, meta = (BlueprintSpawnableComponent, DisplayName = "Niagara Particle System")) class NIAGARA_API UNiagaraComponent : public UPrimitiveComponent { GENERATED_UCLASS_BODY() @@ -47,7 +48,7 @@ private: Should expose anything in the "User" namespace. */ UPROPERTY(EditAnywhere, Category = Parameters) - FNiagaraParameterStore OverrideParameters; + FNiagaraUserRedirectionParameterStore OverrideParameters; #if WITH_EDITORONLY_DATA UPROPERTY() @@ -235,11 +236,11 @@ public: UFUNCTION(BlueprintCallable, Category = Niagara, meta = (DisplayName = "Get Niagara Emitter Positions")) TArray GetNiagaraParticlePositions_DebugOnly(const FString& InEmitterName); - /** Debug accessors for getting a float attribute array in blueprints. */ + /** Debug accessors for getting a float attribute array in blueprints. The attribute name should be without namespaces. For example for "Particles.Position", send "Position". */ UFUNCTION(BlueprintCallable, Category = Niagara, meta = (DisplayName = "Get Niagara Emitter Float Attrib")) TArray GetNiagaraParticleValues_DebugOnly(const FString& InEmitterName, const FString& InValueName); - /** Debug accessors for getting a FVector attribute array in blueprints. */ + /** Debug accessors for getting a FVector attribute array in blueprints. The attribute name should be without namespaces. For example for "Particles.Position", send "Position". */ UFUNCTION(BlueprintCallable, Category = Niagara, meta = (DisplayName = "Get Niagara Emitter Vec3 Attrib")) TArray GetNiagaraParticleValueVec3_DebugOnly(const FString& InEmitterName, const FString& InValueName); @@ -279,6 +280,7 @@ public: virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + void PostLoadNormalizeOverrideNames(); bool IsParameterValueOverriddenLocally(const FName& InParamName); void SetParameterValueOverriddenLocally(const FNiagaraVariable& InParam, bool bInOverridden, bool bRequiresSystemInstanceReset); @@ -287,11 +289,14 @@ public: FOnSynchronizedWithAssetParameters& OnSynchronizedWithAssetParameters() { return OnSynchronizedWithAssetParametersDelegate; } #endif - FNiagaraParameterStore& GetOverrideParameters() { return OverrideParameters; } + FNiagaraUserRedirectionParameterStore& GetOverrideParameters() { return OverrideParameters; } + + const FNiagaraParameterStore& GetOverrideParameters() const { return OverrideParameters; } //~ End UObject Interface. // Called when the particle system is done + UPROPERTY(BlueprintAssignable) FOnNiagaraSystemFinished OnSystemFinished; private: diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraLightRendererProperties.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraLightRendererProperties.h index a9da445ee5f1..96ba8945aa17 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraLightRendererProperties.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraLightRendererProperties.h @@ -31,21 +31,50 @@ public: virtual const TArray& GetOptionalAttributes() override; #endif // WITH_EDITORONLY_DATA + /** Whether to use physically based inverse squared falloff from the light. If unchecked, the value from the LightExponent binding will be used instead. */ UPROPERTY(EditAnywhere, Category = "Light Rendering") + uint32 bUseInverseSquaredFalloff : 1; + + /** + * Whether lights from this renderer should affect translucency. + * Use with caution - if enabled, create only a few particle lights at most, and the smaller they are, the less they will cost. + */ + UPROPERTY(EditAnywhere, Category = "Light Rendering") + uint32 bAffectsTranslucency : 1; + + /** By default, a light is spawned for each particle. Enable this to control the spawn-rate on a per-particle basis. */ + UPROPERTY(EditAnywhere, Category = "Light Rendering", meta = (InlineEditConditionToggle)) + uint32 bOverrideRenderingEnabled : 1; + + /** A factor used to scale each particle light radius */ + UPROPERTY(EditAnywhere, Category = "Light Rendering", meta = (UIMin = "0")) float RadiusScale; + /** A static color shift applied to each rendered light */ UPROPERTY(EditAnywhere, Category = "Light Rendering") FVector ColorAdd; + /** Which attribute should we use to check if light rendering should be enabled for a particle? */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Bindings", meta = (EditCondition = "bOverrideRenderingEnabled")) + FNiagaraVariableAttributeBinding LightRenderingEnabledBinding; + + /** Which attribute should we use for the light's exponent when inverse squared falloff is disabled? */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Bindings", meta = (EditCondition = "!bUseInverseSquaredFalloff")) + FNiagaraVariableAttributeBinding LightExponentBinding; + /** Which attribute should we use for position when generating lights?*/ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Light Rendering") + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Bindings") FNiagaraVariableAttributeBinding PositionBinding; /** Which attribute should we use for light color when generating lights?*/ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Light Rendering") + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Bindings") FNiagaraVariableAttributeBinding ColorBinding; /** Which attribute should we use for light radius when generating lights?*/ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Light Rendering") + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Bindings") FNiagaraVariableAttributeBinding RadiusBinding; + + /** Which attribute should we use for the intensity of the volumetric scattering from this light? This scales the light's intensity and color. */ + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = "Bindings") + FNiagaraVariableAttributeBinding VolumetricScatteringBinding; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraModule.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraModule.h index 9bff7a88f1f4..4821b3737237 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraModule.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraModule.h @@ -125,6 +125,7 @@ public: FORCEINLINE static const FNiagaraVariable& GetVar_Engine_Owner_YAxis() { return Engine_Owner_YAxis; } FORCEINLINE static const FNiagaraVariable& GetVar_Engine_Owner_ZAxis() { return Engine_Owner_ZAxis; } FORCEINLINE static const FNiagaraVariable& GetVar_Engine_Owner_Scale() { return Engine_Owner_Scale; } + FORCEINLINE static const FNiagaraVariable& GetVar_Engine_Owner_Rotation() { return Engine_Owner_Rotation; } FORCEINLINE static const FNiagaraVariable& GetVar_Engine_Owner_SystemLocalToWorld() { return Engine_Owner_SystemLocalToWorld; } FORCEINLINE static const FNiagaraVariable& GetVar_Engine_Owner_SystemWorldToLocal() { return Engine_Owner_SystemWorldToLocal; } @@ -140,6 +141,8 @@ public: FORCEINLINE static const FNiagaraVariable& GetVar_Engine_ExecutionCount() { return Engine_ExecutionCount; } FORCEINLINE static const FNiagaraVariable& GetVar_Engine_Emitter_NumParticles() { return Engine_Emitter_NumParticles; } + FORCEINLINE static const FNiagaraVariable& GetVar_Engine_Emitter_TotalSpawnedParticles() { return Engine_Emitter_TotalSpawnedParticles; } + FORCEINLINE static const FNiagaraVariable& GetVar_Engine_System_TickCount() { return Engine_System_TickCount; } FORCEINLINE static const FNiagaraVariable& GetVar_Engine_System_NumEmittersAlive() { return Engine_System_NumEmittersAlive; } FORCEINLINE static const FNiagaraVariable& GetVar_Engine_System_NumEmitters() { return Engine_System_NumEmitters; } FORCEINLINE static const FNiagaraVariable& GetVar_Engine_NumSystemInstances() { return Engine_NumSystemInstances; } @@ -150,11 +153,14 @@ public: FORCEINLINE static const FNiagaraVariable& GetVar_Engine_System_Age() { return Engine_System_Age; } FORCEINLINE static const FNiagaraVariable& GetVar_Emitter_Age() { return Emitter_Age; } FORCEINLINE static const FNiagaraVariable& GetVar_Emitter_LocalSpace() { return Emitter_LocalSpace; } + FORCEINLINE static const FNiagaraVariable& GetVar_Emitter_Determinism() { return Emitter_Determinism; } + FORCEINLINE static const FNiagaraVariable& GetVar_Emitter_RandomSeed() { return Emitter_RandomSeed; } FORCEINLINE static const FNiagaraVariable& GetVar_Emitter_SpawnRate() { return Emitter_SpawnRate; } FORCEINLINE static const FNiagaraVariable& GetVar_Emitter_SpawnInterval() { return Emitter_SpawnInterval; } FORCEINLINE static const FNiagaraVariable& GetVar_Emitter_InterpSpawnStartDt() { return Emitter_InterpSpawnStartDt; } FORCEINLINE static const FNiagaraVariable& GetVar_Emitter_SpawnGroup() { return Emitter_SpawnGroup; } + FORCEINLINE static const FNiagaraVariable& GetVar_Particles_UniqueID() { return Particles_UniqueID; } FORCEINLINE static const FNiagaraVariable& GetVar_Particles_ID() { return Particles_ID; } FORCEINLINE static const FNiagaraVariable& GetVar_Particles_Position() { return Particles_Position; } FORCEINLINE static const FNiagaraVariable& GetVar_Particles_Velocity() { return Particles_Velocity; } @@ -176,6 +182,9 @@ public: FORCEINLINE static const FNiagaraVariable& GetVar_Particles_CameraOffset() { return Particles_CameraOffset; } FORCEINLINE static const FNiagaraVariable& GetVar_Particles_MaterialRandom() { return Particles_MaterialRandom; } FORCEINLINE static const FNiagaraVariable& GetVar_Particles_LightRadius() { return Particles_LightRadius; } + FORCEINLINE static const FNiagaraVariable& GetVar_Particles_LightExponent() { return Particles_LightExponent; } + FORCEINLINE static const FNiagaraVariable& GetVar_Particles_LightEnabled() { return Particles_LightEnabled; } + FORCEINLINE static const FNiagaraVariable& GetVar_Particles_LightVolumetricScattering() { return Particles_LightVolumetricScattering; } FORCEINLINE static const FNiagaraVariable& GetVar_Particles_RibbonID() { return Particles_RibbonID; } FORCEINLINE static const FNiagaraVariable& GetVar_Particles_RibbonWidth() { return Particles_RibbonWidth; } FORCEINLINE static const FNiagaraVariable& GetVar_Particles_RibbonTwist() { return Particles_RibbonTwist; } @@ -212,6 +221,7 @@ private: static FNiagaraVariable Engine_Owner_YAxis; static FNiagaraVariable Engine_Owner_ZAxis; static FNiagaraVariable Engine_Owner_Scale; + static FNiagaraVariable Engine_Owner_Rotation; static FNiagaraVariable Engine_Owner_SystemLocalToWorld; static FNiagaraVariable Engine_Owner_SystemWorldToLocal; @@ -227,6 +237,8 @@ private: static FNiagaraVariable Engine_ExecutionCount; static FNiagaraVariable Engine_Emitter_NumParticles; + static FNiagaraVariable Engine_Emitter_TotalSpawnedParticles; + static FNiagaraVariable Engine_System_TickCount; static FNiagaraVariable Engine_System_NumEmittersAlive; static FNiagaraVariable Engine_System_NumEmitters; static FNiagaraVariable Engine_NumSystemInstances; @@ -237,11 +249,14 @@ private: static FNiagaraVariable Engine_System_Age; static FNiagaraVariable Emitter_Age; static FNiagaraVariable Emitter_LocalSpace; + static FNiagaraVariable Emitter_Determinism; + static FNiagaraVariable Emitter_RandomSeed; static FNiagaraVariable Emitter_SpawnRate; static FNiagaraVariable Emitter_SpawnInterval; static FNiagaraVariable Emitter_InterpSpawnStartDt; static FNiagaraVariable Emitter_SpawnGroup; + static FNiagaraVariable Particles_UniqueID; static FNiagaraVariable Particles_ID; static FNiagaraVariable Particles_Position; static FNiagaraVariable Particles_Velocity; @@ -263,6 +278,9 @@ private: static FNiagaraVariable Particles_CameraOffset; static FNiagaraVariable Particles_MaterialRandom; static FNiagaraVariable Particles_LightRadius; + static FNiagaraVariable Particles_LightExponent; + static FNiagaraVariable Particles_LightEnabled; + static FNiagaraVariable Particles_LightVolumetricScattering; static FNiagaraVariable Particles_RibbonID; static FNiagaraVariable Particles_RibbonWidth; static FNiagaraVariable Particles_RibbonTwist; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraParameterStore.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraParameterStore.h index 60e53361710b..b7847ab55b7e 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraParameterStore.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraParameterStore.h @@ -119,7 +119,7 @@ public: FNiagaraParameterStore(const FNiagaraParameterStore& Other); FNiagaraParameterStore& operator=(const FNiagaraParameterStore& Other); - ~FNiagaraParameterStore(); + virtual ~FNiagaraParameterStore(); #if WITH_EDITORONLY_DATA UPROPERTY() @@ -152,33 +152,33 @@ public: /** Unbinds this store from all stores it's being driven by. */ void UnbindFromSourceStores(); - bool VerifyBinding(const FNiagaraParameterStore* InDestStore)const; + bool VerifyBinding(const FNiagaraParameterStore* InDestStore) const; - void CheckForNaNs()const; + void CheckForNaNs() const; /** Adds the passed parameter to this store. Does nothing if this parameter is already present. Returns true if we added a new parameter. */ - bool AddParameter(const FNiagaraVariable& Param, bool bInitialize=true, bool bTriggerRebind = true); + virtual bool AddParameter(const FNiagaraVariable& Param, bool bInitialize=true, bool bTriggerRebind = true); /** Removes the passed parameter if it exists in the store. */ - bool RemoveParameter(const FNiagaraVariable& Param); + virtual bool RemoveParameter(const FNiagaraVariable& Param); /** Renames the passed parameter. */ void RenameParameter(const FNiagaraVariable& Param, FName NewName); /** Removes all parameters from this store and releases any data. */ - void Empty(bool bClearBindings = true); + virtual void Empty(bool bClearBindings = true); /** Removes all parameters from this store but does't change memory allocations. */ - void Reset(bool bClearBindings = true); + virtual void Reset(bool bClearBindings = true); FORCEINLINE TArray& GetSourceParameterStores() { return SourceStores; } - FORCEINLINE const TMap& GetParameterOffests()const { return ParameterOffsets; } + FORCEINLINE const TMap& GetParameterOffsets()const { return ParameterOffsets; } /** Get the list of FNiagaraVariables referenced by this store. Note that the values will be stale and are not to be trusted directly. Get the Values using the offset specified by IndexOf or GetParameterValue.*/ - FORCEINLINE void GetParameters(TArray& OutParametres) const { return ParameterOffsets.GenerateKeyArray(OutParametres); } + FORCEINLINE void GetParameters(TArray& OutParameters) const { return ParameterOffsets.GenerateKeyArray(OutParameters); } FORCEINLINE int32 GetNumParameters() const { return ParameterOffsets.Num(); } FORCEINLINE const TArray& GetDataInterfaces()const { return DataInterfaces; } @@ -187,10 +187,10 @@ public: FORCEINLINE void SetParameterDataArray(const TArray& InParameterDataArray); // Called to initially set up the parameter store to *exactly* match the input store (other than any bindings and the internal name of it). - void InitFromSource(const FNiagaraParameterStore* SrcStore, bool bNotifyAsDirty); + virtual void InitFromSource(const FNiagaraParameterStore* SrcStore, bool bNotifyAsDirty); /** Gets the index of the passed parameter. If it is a data interface, this is an offset into the data interface table, otherwise a byte offset into he parameter data buffer. */ - int32 IndexOf(const FNiagaraVariable& Parameter)const + virtual int32 IndexOf(const FNiagaraVariable& Parameter) const { const int32* Off = ParameterOffsets.Find(Parameter); if ( Off ) @@ -263,7 +263,7 @@ public: /** Returns the associated FNiagaraVariable for the passed data interface if it exists in the store. Null if not.*/ const FNiagaraVariable* FindVariable(UNiagaraDataInterface* Interface)const; - FORCEINLINE_DEBUGGABLE const int32* FindParameterOffset(const FNiagaraVariable& Parameter) const + FORCEINLINE_DEBUGGABLE virtual const int32* FindParameterOffset(const FNiagaraVariable& Parameter) const { return ParameterOffsets.Find(Parameter); } @@ -468,7 +468,7 @@ FORCEINLINE_DEBUGGABLE void FNiagaraParameterStoreBinding::BindParameters(FNiaga { InterfaceBindings.Reset(); ParameterBindings.Reset(); - for (const TPair& ParamOffsetPair : DestStore->GetParameterOffests()) + for (const TPair& ParamOffsetPair : DestStore->GetParameterOffsets()) { const FNiagaraVariable& Parameter = ParamOffsetPair.Key; int32 DestOffset = ParamOffsetPair.Value; @@ -505,7 +505,7 @@ FORCEINLINE_DEBUGGABLE bool FNiagaraParameterStoreBinding::VerifyBinding(const F bool bBindingValid = true; #if WITH_EDITORONLY_DATA TArray> MissingParameterNames; - for (const TPair& ParamOffsetPair : DestStore->GetParameterOffests()) + for (const TPair& ParamOffsetPair : DestStore->GetParameterOffsets()) { const FNiagaraVariable& Parameter = ParamOffsetPair.Key; int32 DestOffset = ParamOffsetPair.Value; @@ -578,7 +578,7 @@ FORCEINLINE_DEBUGGABLE void FNiagaraParameterStoreBinding::Dump(const FNiagaraPa ensure(Binding.DestOffset != -1); FNiagaraVariable Param; bool bFound = false; - for (const TPair& ParamOffsetPair : DestStore->GetParameterOffests()) + for (const TPair& ParamOffsetPair : DestStore->GetParameterOffsets()) { if (ParamOffsetPair.Value == Binding.DestOffset && !ParamOffsetPair.Key.IsDataInterface()) { @@ -610,7 +610,7 @@ FORCEINLINE_DEBUGGABLE void FNiagaraParameterStoreBinding::Dump(const FNiagaraPa ensure(Binding.DestOffset != -1); FNiagaraVariable Param; bool bFound = false; - for (const TPair& ParamOffsetPair : DestStore->GetParameterOffests()) + for (const TPair& ParamOffsetPair : DestStore->GetParameterOffsets()) { if (ParamOffsetPair.Value == Binding.DestOffset && ParamOffsetPair.Key.IsDataInterface()) { diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraScriptExecutionParameterStore.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraScriptExecutionParameterStore.h index c5d1dcec3719..75347c157142 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraScriptExecutionParameterStore.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraScriptExecutionParameterStore.h @@ -33,6 +33,8 @@ public: FNiagaraScriptExecutionParameterStore(const FNiagaraParameterStore& Other); FNiagaraScriptExecutionParameterStore& operator=(const FNiagaraParameterStore& Other); + virtual ~FNiagaraScriptExecutionParameterStore() = default; + //TODO: These function can probably go away entirely when we replace the FNiagaraParameters and DataInterface info in the script with an FNiagaraParameterStore. //Special care with prev params and internal params will have to be taken there. /** Call this init function if you are using a Niagara parameter store within a UNiagaraScript.*/ @@ -42,7 +44,7 @@ public: void AddScriptParams(UNiagaraScript* Script, ENiagaraSimTarget SimTarget, bool bTriggerRebind); void CopyCurrToPrev(); - bool AddParameter(const FNiagaraVariable& Param, bool bInitInterfaces = true, bool bTriggerRebind = true) + virtual bool AddParameter(const FNiagaraVariable& Param, bool bInitInterfaces = true, bool bTriggerRebind = true) override { if (FNiagaraParameterStore::AddParameter(Param, bInitInterfaces, bTriggerRebind)) { @@ -52,7 +54,7 @@ public: return false; } - bool RemoveParameter(FNiagaraVariable& Param) + virtual bool RemoveParameter(const FNiagaraVariable& Param) override { check(0);//Not allowed to remove parameters from an execution store as it will adjust the table layout mess up the return false; @@ -63,7 +65,7 @@ public: check(0);//Can't rename parameters for an execution store. } - void Empty(bool bClearBindings=true) + virtual void Empty(bool bClearBindings=true) override { FNiagaraParameterStore::Empty(bClearBindings); PaddingInfo.Empty(); diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemInstance.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemInstance.h index 2b70898611fb..a7e09cb472e7 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemInstance.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemInstance.h @@ -54,7 +54,7 @@ public: void Cleanup(); /** Initializes this System instance to simulate the supplied System. */ - void Init(UNiagaraSystem* InSystem, bool bInForceSolo=false); + void Init(bool bInForceSolo=false); void Activate(EResetMode InResetMode = EResetMode::ResetAll); void Deactivate(bool bImmediate = false); @@ -173,7 +173,7 @@ public: Manually advances this system's simulation by the specified number of ticks and tick delta. To be advanced in this way a system must be in solo mode or moved into solo mode which will add additional overhead. */ - void AdvanceSimulation(int32 TickCount, float TickDeltaSeconds); + void AdvanceSimulation(int32 TickCountToSimulate, float TickDeltaSeconds); #if WITH_EDITORONLY_DATA /** Request that this simulation capture a frame. Cannot capture if disabled or already completed.*/ @@ -192,13 +192,15 @@ public: bool ShouldCaptureThisFrame() const; /** Only call from within the script execution states. Value is nullptr if not capturing a frame.*/ - FNiagaraScriptDebuggerInfo* GetActiveCaptureWrite(const FName& InHandleName, ENiagaraScriptUsage InUsage, const FGuid& InUsageId); + TSharedPtr GetActiveCaptureWrite(const FName& InHandleName, ENiagaraScriptUsage InUsage, const FGuid& InUsageId); #endif /** Dumps all of this systems info to the log. */ void Dump()const; + bool GetPerInstanceDataAndOffsets(void*& OutData, uint32& OutDataSize, TMap, int32>*& OutOffsets); + private: /** Builds the emitter simulations. */ @@ -229,6 +231,9 @@ private: /** The age of the System instance. */ float Age; + /** The tick count of the System instance. */ + int32 TickCount; + TMap ExternalEvents; TArray< TSharedRef > Emitters; @@ -267,6 +272,8 @@ private: FNiagaraParameterDirectBinding OwnerYAxisParam; FNiagaraParameterDirectBinding OwnerZAxisParam; + FNiagaraParameterDirectBinding OwnerRotationParam; + FNiagaraParameterDirectBinding OwnerTransformParam; FNiagaraParameterDirectBinding OwnerInverseParam; FNiagaraParameterDirectBinding OwnerTransposeParam; @@ -279,6 +286,7 @@ private: FNiagaraParameterDirectBinding OwnerEngineTimeParam; FNiagaraParameterDirectBinding OwnerEngineRealtimeParam; FNiagaraParameterDirectBinding SystemAgeParam; + FNiagaraParameterDirectBinding SystemTickCountParam; FNiagaraParameterDirectBinding OwnerMinDistanceToCameraParam; FNiagaraParameterDirectBinding SystemNumEmittersParam; @@ -289,6 +297,7 @@ private: FNiagaraParameterDirectBinding OwnerExecutionStateParam; TArray> ParameterNumParticleBindings; + TArray> ParameterTotalSpawnedParticlesBindings; /** Indicates whether this instance must update itself rather than being batched up as most instances are. */ uint32 bSolo : 1; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemSimulation.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemSimulation.h index b7861b4e4ea8..ffe23f6b470e 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemSimulation.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraSystemSimulation.h @@ -121,7 +121,7 @@ class FNiagaraSystemSimulation { public: ~FNiagaraSystemSimulation(); - bool Init(UNiagaraSystem* InSystem, UWorld* InWorld, FNiagaraSystemInstance* InSoloSystemInstance=nullptr); + bool Init(UNiagaraSystem* InSystem, UWorld* InWorld, bool bInIsSolo); void Destroy(); bool Tick(float DeltaSeconds); @@ -141,14 +141,17 @@ public: void TransferInstance(FNiagaraSystemSimulation* SourceSimulation, FNiagaraSystemInstance* SystemInst); void DumpInstance(const FNiagaraSystemInstance* Inst)const; + + bool GetIsSolo() const { return bIsSolo; } + + FNiagaraScriptExecutionContext& GetSpawnExecutionContext() { return SpawnExecContext; } + FNiagaraScriptExecutionContext& GetUpdateExecutionContext() { return UpdateExecContext; } + protected: /** System of instances being simulated. We use a weak object ptr here because once the last referencing object goes away this system may be come invalid at runtime. */ TWeakObjectPtr WeakSystem; - /** The parent system instance if this simulation is */ - FNiagaraSystemInstance* SoloSystemInstance; - /** World this system simulation belongs to. */ UWorld* World; @@ -206,7 +209,7 @@ protected: TArray>> EmitterSpawnInfoAccessors; - void InitBindings(FNiagaraSystemInstance* SystemInst); + void InitParameterDataSetBindings(FNiagaraSystemInstance* SystemInst); FNiagaraDataSetAccessor SystemExecutionStateAccessor; TArray> EmitterExecutionStateAccessors; @@ -215,4 +218,8 @@ protected: /** A parameter store which contains the data interfaces parameters which were defined by the scripts. */ FNiagaraParameterStore ScriptDefinedDataInterfaceParameters; + + bool bIsSolo; + + TOptional MaxDeltaTime; }; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraTypes.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraTypes.h index 8dd4e67c859d..91b2476600f0 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraTypes.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraTypes.h @@ -892,3 +892,21 @@ FORCEINLINE uint32 GetTypeHash(const FNiagaraVariable& Var) { return HashCombine(GetTypeHash(Var.GetType()), GetTypeHash(Var.GetName())); } + +template<> +inline bool FNiagaraVariable::GetValue() const +{ + check(TypeDef == FNiagaraTypeDefinition::GetBoolDef()); + check(IsDataAllocated()); + FNiagaraBool* BoolStruct = (FNiagaraBool*)GetData(); + return BoolStruct->GetValue(); +} + +template<> +inline void FNiagaraVariable::SetValue(const bool& Data) +{ + check(TypeDef == FNiagaraTypeDefinition::GetBoolDef()); + AllocateData(); + FNiagaraBool* BoolStruct = (FNiagaraBool*)GetData(); + BoolStruct->SetValue(Data); +} \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraUserRedirectionParameterStore.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraUserRedirectionParameterStore.h new file mode 100644 index 000000000000..a20466f24fc1 --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraUserRedirectionParameterStore.h @@ -0,0 +1,66 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "NiagaraCommon.h" +#include "NiagaraDataInterface.h" +#include "NiagaraParameterStore.h" +#include "NiagaraUserRedirectionParameterStore.generated.h" + +/** +* Extension of the base parameter store to allow the user in the editor to use variable names without +* the "User." namespace prefix. The names without the prefix just redirect to the original variables, it is just done +* for better usability. +*/ +USTRUCT() +struct FNiagaraUserRedirectionParameterStore : public FNiagaraParameterStore +{ + GENERATED_USTRUCT_BODY() +public: + FNiagaraUserRedirectionParameterStore(); + FNiagaraUserRedirectionParameterStore(const FNiagaraParameterStore& Other); + FNiagaraUserRedirectionParameterStore& operator=(const FNiagaraParameterStore& Other); + + virtual ~FNiagaraUserRedirectionParameterStore() = default; + + void RecreateRedirections(); + + /** Get the list of FNiagaraVariables that are exposed to the user. Note that the values will be stale and are not to be trusted directly. Get the Values using the offset specified by IndexOf or GetParameterValue.*/ + FORCEINLINE void GetUserParameters(TArray& OutParameters) const { return UserParameterRedirects.GenerateKeyArray(OutParameters); } + + // ~ Begin FNiagaraParameterStore overrides + FORCEINLINE_DEBUGGABLE virtual const int32* FindParameterOffset(const FNiagaraVariable& Parameter) const override + { + const FNiagaraVariable* Redirection = UserParameterRedirects.Find(Parameter); + return FNiagaraParameterStore::FindParameterOffset(Redirection ? *Redirection : Parameter); + } + virtual int32 IndexOf(const FNiagaraVariable& Parameter) const override; + virtual bool AddParameter(const FNiagaraVariable& Param, bool bInitialize = true, bool bTriggerRebind = true) override; + virtual bool RemoveParameter(const FNiagaraVariable& InVar) override; + virtual void InitFromSource(const FNiagaraParameterStore* SrcStore, bool bNotifyAsDirty) override; + virtual void Empty(bool bClearBindings = true) override; + virtual void Reset(bool bClearBindings = true) override; + // ~ End FNiagaraParameterStore overrides + + /** Used to upgrade a serialized FNiagaraParameterStore property to our own struct */ + bool SerializeFromMismatchedTag(const struct FPropertyTag& Tag, FStructuredArchive::FSlot Slot); + +private: + + /** Map from the variables with shortened display names to the original variables with the full namespace */ + UPROPERTY() + TMap UserParameterRedirects; + + bool IsUserParameter(const FNiagaraVariable& InVar) const; + + FNiagaraVariable GetUserRedirection(const FNiagaraVariable& InVar) const; +}; + +template<> +struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 +{ + enum + { + WithStructuredSerializeFromMismatchedTag = true, + }; +}; diff --git a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraWorldManager.h b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraWorldManager.h index 2f94e2fca60e..989b8421c02c 100644 --- a/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraWorldManager.h +++ b/Engine/Plugins/FX/Niagara/Source/Niagara/Public/NiagaraWorldManager.h @@ -9,7 +9,7 @@ #include "NiagaraDataSet.h" #include "NiagaraScriptExecutionContext.h" #include "NiagaraSystemSimulation.h" - +#include "GlobalDistanceFieldParameters.h" #include "NiagaraDataInterfaceSkeletalMesh.h" class UWorld; @@ -32,22 +32,39 @@ public: ViewUniformBuffer = Params.ViewUniformBuffer; SceneNormalTexture = Params.NormalTexture; SceneTexturesUniformParams = Params.SceneTexturesUniformParams; + GlobalDistanceFieldParams = Params.GlobalDistanceFieldParams; + + PreSceneRenderValues = FPreSceneRenderValues(); + } + + void OnPreSceneRenderCalled(FPreSceneRenderValues& OutValues) const + { + OutValues.bUsesGlobalDistanceField |= PreSceneRenderValues.bUsesGlobalDistanceField; + } + + void SetGlobalDistanceFieldUsage() + { + PreSceneRenderValues.bUsesGlobalDistanceField = true; } FTexture2DRHIParamRef GetSceneDepthTexture() { return SceneDepthTexture; } FTexture2DRHIParamRef GetSceneNormalTexture() { return SceneNormalTexture; } FUniformBufferRHIParamRef GetViewUniformBuffer() { return ViewUniformBuffer; } TUniformBufferRef GetSceneTextureUniformParameters() { return SceneTexturesUniformParams; } + const FGlobalDistanceFieldParameterData* GetGlobalDistanceFieldParameters() { return GlobalDistanceFieldParams; } virtual void InitDynamicRHI() override; virtual void ReleaseDynamicRHI() override; + private: FTexture2DRHIParamRef SceneDepthTexture; FTexture2DRHIParamRef SceneNormalTexture; FUniformBufferRHIParamRef ViewUniformBuffer; + FPreSceneRenderValues PreSceneRenderValues; TUniformBufferRef SceneTexturesUniformParams; + const FGlobalDistanceFieldParameterData* GlobalDistanceFieldParams; FPostOpaqueRenderDelegate PostOpaqueDelegate; }; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Private/NiagaraCustomVersion.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Private/NiagaraCustomVersion.cpp index 14110124d900..cc27cca92e7d 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Private/NiagaraCustomVersion.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Private/NiagaraCustomVersion.cpp @@ -8,4 +8,4 @@ const FGuid FNiagaraCustomVersion::GUID(0xFCF57AFA, 0x50764283, 0xB9A9E658, 0xFF // Register the custom version with core FCustomVersionRegistration GRegisterNiagaraCustomVersion(FNiagaraCustomVersion::GUID, FNiagaraCustomVersion::LatestVersion, TEXT("NiagaraVer")); -const FGuid FNiagaraCustomVersion::LatestScriptCompileVersion(0x124E242E, 0xC5334EB6, 0xB88A6679, 0x582DE2EE); \ No newline at end of file +const FGuid FNiagaraCustomVersion::LatestScriptCompileVersion(0x0BE17832, 0xB1DE4421, 0x95243884, 0xBD77C7B3); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraDataInterfaceBase.h b/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraDataInterfaceBase.h index 19b3899fc501..b19a80f76dec 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraDataInterfaceBase.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraCore/Public/NiagaraDataInterfaceBase.h @@ -21,7 +21,9 @@ struct FNiagaraDataInterfaceParametersCS virtual ~FNiagaraDataInterfaceParametersCS() {} virtual void Bind(const FNiagaraDataInterfaceParamRef& ParamRef, const class FShaderParameterMap& ParameterMap) {} virtual void Serialize(FArchive& Ar) { } - virtual void Set(FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface) const {} + virtual void Set(FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface, void* PerInstanceData) const {} + virtual void Unset(FRHICommandList& RHICmdList, FNiagaraShader* Shader, class UNiagaraDataInterface* DataInterface, void* PerInstanceData) const {} + }; ////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/AssetTypeActions/AssetTypeActions_NiagaraEmitter.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/AssetTypeActions/AssetTypeActions_NiagaraEmitter.cpp index 70e7cc687bc2..ef8b314ff34a 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/AssetTypeActions/AssetTypeActions_NiagaraEmitter.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/AssetTypeActions/AssetTypeActions_NiagaraEmitter.cpp @@ -112,6 +112,12 @@ void FAssetTypeActions_NiagaraEmitter::ExecuteNewNiagaraSystem(TArrayRebuildEmitterNodes(); + // Ensure the new System is compiled + if (!Emitter->AreAllScriptAndSourcesSynchronized()) + { + System->RequestCompile(true); + } + ObjectsToSync.Add(NewAsset); } } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraComponentDetails.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraComponentDetails.cpp index 544bd39e8040..0246326f90d8 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraComponentDetails.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Customizations/NiagaraComponentDetails.cpp @@ -46,6 +46,7 @@ #include "INiagaraEditorTypeUtilities.h" #include "Widgets/Layout/SBox.h" #include "UObject/WeakObjectPtr.h" +#include "NiagaraUserRedirectionParameterStore.h" #define LOCTEXT_NAMESPACE "NiagaraComponentDetails" class FNiagaraComponentNodeBuilder : public IDetailCustomNodeBuilder @@ -55,8 +56,10 @@ public: { Component = InComponent; Component->OnSynchronizedWithAssetParameters().AddRaw(this, &FNiagaraComponentNodeBuilder::ComponentSynchronizedWithAssetParameters); - OriginalScripts.Add(SourceSpawn); - OriginalScripts.Add(SourceUpdate); + if (SourceSpawn) + OriginalScripts.Add(SourceSpawn); + if (SourceUpdate) + OriginalScripts.Add(SourceUpdate); //UE_LOG(LogNiagaraEditor, Log, TEXT("FNiagaraComponentNodeBuilder %p Component %p"), this, Component.Get()); } @@ -88,8 +91,8 @@ public: { check(Component.IsValid()); TArray Parameters; - FNiagaraParameterStore& ParamStore = Component->GetOverrideParameters(); - ParamStore.GetParameters(Parameters); + FNiagaraUserRedirectionParameterStore& ParamStore = Component->GetOverrideParameters(); + ParamStore.GetUserParameters(Parameters); FNiagaraEditorModule& NiagaraEditorModule = FModuleManager::GetModuleChecked("NiagaraEditor"); @@ -233,7 +236,7 @@ private: { check(Component.IsValid()); Component->GetOverrideParameters().OnParameterChange(); - Component->SetParameterValueOverriddenLocally(Var, true, false); + Component->SetParameterValueOverriddenLocally(Var, true, true); } void OnDataInterfaceChanged(FNiagaraVariable Var) @@ -254,7 +257,7 @@ private: check(Component.IsValid()); FScopedTransaction ScopedTransaction(LOCTEXT("ResetParameterValue", "Reset parameter value to system defaults.")); Component->Modify(); - Component->SetParameterValueOverriddenLocally(Parameter, false, false); + Component->SetParameterValueOverriddenLocally(Parameter, false, true); return FReply::Handled(); } @@ -360,6 +363,11 @@ void FNiagaraComponentDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuil IDetailCategoryBuilder& InputParamCategory = DetailBuilder.EditCategory(ParamCategoryName, LOCTEXT("ParamCategoryName", "Override Parameters")); InputParamCategory.AddCustomBuilder(MakeShared(Component.Get(), ScriptSpawn, ScriptUpdate)); } + else + { + IDetailCategoryBuilder& InputParamCategory = DetailBuilder.EditCategory(ParamCategoryName, LOCTEXT("ParamCategoryName", "Override Parameters")); + InputParamCategory.AddCustomBuilder(MakeShared(Component.Get(), nullptr, nullptr)); + } } } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/EdGraphSchema_Niagara.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/EdGraphSchema_Niagara.cpp index 924ea211f937..e1f1c91a6234 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/EdGraphSchema_Niagara.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/EdGraphSchema_Niagara.cpp @@ -42,6 +42,7 @@ #include "Modules/ModuleManager.h" #include "AssetRegistryModule.h" +#include "NiagaraNodeSimTargetSelector.h" #define LOCTEXT_NAMESPACE "NiagaraSchema" @@ -482,7 +483,7 @@ TArray > UEdGraphSchema_Niagara::GetGra { { const FText MenuDescFmt = LOCTEXT("AddEventReadFmt", "Add {0} Event Read"); - const FText MenuDesc = FText::Format(MenuDescFmt, Type.GetStruct()->GetDisplayNameText()); + const FText MenuDesc = FText::Format(MenuDescFmt, Type.GetNameText()); TSharedPtr Action = AddNewNodeAction(NewActions, MenuCat, MenuDesc, *MenuDesc.ToString(), FText::GetEmpty()); @@ -492,7 +493,7 @@ TArray > UEdGraphSchema_Niagara::GetGra } { const FText MenuDescFmt = LOCTEXT("AddEventWriteFmt", "Add {0} Event Write"); - const FText MenuDesc = FText::Format(MenuDescFmt, Type.GetStruct()->GetDisplayNameText()); + const FText MenuDesc = FText::Format(MenuDescFmt, Type.GetNameText()); TSharedPtr Action = AddNewNodeAction(NewActions, MenuCat, MenuDesc, *MenuDesc.ToString(), FText::GetEmpty()); @@ -570,11 +571,8 @@ TArray > UEdGraphSchema_Niagara::GetGra FText DescFmt = LOCTEXT("NiagaraMakeBreakFmt", "{0}"); auto MakeBreakType = [&](FNiagaraTypeDefinition Type, bool bMake) { - FText DisplayName = Type.GetStruct()->GetDisplayNameText(); - if (Type.GetEnum()) - { - DisplayName = FText::FromString(Type.GetEnum()->GetName()); - } + FText DisplayName = Type.GetNameText(); + FText Desc = FText::Format(DescFmt, DisplayName); TSharedPtr Action = AddNewNodeAction(NewActions, bMake ? MakeCat : BreakCat, Desc, *Type.GetStruct()->GetName(), FText::GetEmpty()); UNiagaraNodeConvert* ConvertNode = NewObject(OwnerOfTemporaries); @@ -832,7 +830,7 @@ TArray > UEdGraphSchema_Niagara::GetGra } } - const FText MenuDesc = FText::Format(MenuDescFmt, Type.GetStruct()->GetDisplayNameText()); + const FText MenuDesc = FText::Format(MenuDescFmt, Type.GetNameText()); TSharedPtr InputAction = AddNewNodeAction(NewActions, MenuCat, MenuDesc, *MenuDesc.ToString(), FText::GetEmpty()); UNiagaraNodeInput* InputNode = NewObject(OwnerOfTemporaries); FNiagaraEditorUtilities::InitializeParameterInputNode(*InputNode, Type, NiagaraGraph); @@ -854,7 +852,7 @@ TArray > UEdGraphSchema_Niagara::GetGra MenuCat = LOCTEXT("AddRIParameterCat", "Add Rapid Iteration Param"); } - const FText MenuDesc = FText::Format(MenuDescFmt, Type.GetStruct()->GetDisplayNameText()); + const FText MenuDesc = FText::Format(MenuDescFmt, Type.GetNameText()); TSharedPtr InputAction = AddNewNodeAction(NewActions, MenuCat, MenuDesc, *MenuDesc.ToString(), FText::GetEmpty()); UNiagaraNodeInput* InputNode = NewObject(OwnerOfTemporaries); FNiagaraEditorUtilities::InitializeParameterInputNode(*InputNode, Type, NiagaraGraph); @@ -866,7 +864,7 @@ TArray > UEdGraphSchema_Niagara::GetGra if (PinType != FNiagaraTypeDefinition::GetGenericNumericDef()) { //For correctly typed pins, offer the correct type at the top level. - const FText MenuDesc = FText::Format(MenuDescFmt, PinType.GetStruct()->GetDisplayNameText()); + const FText MenuDesc = FText::Format(MenuDescFmt, PinType.GetNameText()); TSharedPtr InputAction = AddNewNodeAction(NewActions, FText::GetEmpty(), MenuDesc, *MenuDesc.ToString(), FText::GetEmpty()); UNiagaraNodeInput* InputNode = NewObject(OwnerOfTemporaries); FNiagaraEditorUtilities::InitializeParameterInputNode(*InputNode, PinType, NiagaraGraph); @@ -905,6 +903,15 @@ TArray > UEdGraphSchema_Niagara::GetGra Action->NodeTemplate = Node; } + // Add simulation target selector node + { + const FText UtilMenuCat = LOCTEXT("NiagaraSimTargetSelectorMenuCat", "Util"); + const FText SimTargetSelectorMenuDesc = LOCTEXT("NiagaraSimTargetSelectorMenuDesc", "Select By Simulation Target"); + TSharedPtr Action = AddNewNodeAction(NewActions, UtilMenuCat, SimTargetSelectorMenuDesc, TEXT("Select By Simulation Target"), FText::GetEmpty()); + UNiagaraNodeSimTargetSelector* Node = NewObject(OwnerOfTemporaries); + Action->NodeTemplate = Node; + } + return NewActions; } @@ -931,63 +938,74 @@ const FPinConnectionResponse UEdGraphSchema_Niagara::CanCreateConnection(const U return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Directions are not compatible")); } - // Check for compatible type pins. - if (PinA->PinType.PinCategory == PinCategoryType && - PinB->PinType.PinCategory == PinCategoryType && - PinA->PinType != PinB->PinType) + if (PinA->PinType.PinCategory != TEXT("wildcard") && PinB->PinType.PinCategory != TEXT("wildcard")) { - FNiagaraTypeDefinition PinTypeA = PinToTypeDefinition(PinA); - FNiagaraTypeDefinition PinTypeB = PinToTypeDefinition(PinB); - if (FNiagaraTypeDefinition::TypesAreAssignable(PinTypeA, PinTypeB) == false) + // Check for compatible type pins. + if (PinA->PinType.PinCategory == PinCategoryType && + PinB->PinType.PinCategory == PinCategoryType && + PinA->PinType != PinB->PinType) { - //Do some limiting on auto conversions here? - if (PinTypeA.GetClass()) + FNiagaraTypeDefinition PinTypeA = PinToTypeDefinition(PinA); + FNiagaraTypeDefinition PinTypeB = PinToTypeDefinition(PinB); + + if (PinTypeA == FNiagaraTypeDefinition::GetParameterMapDef() || PinTypeB == FNiagaraTypeDefinition::GetParameterMapDef()) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Types are not compatible")); } - else + + else if (FNiagaraTypeDefinition::TypesAreAssignable(PinTypeA, PinTypeB) == false) { - return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, FString::Printf(TEXT("Convert %s to %s"), *(PinToTypeDefinition(PinA).GetNameText().ToString()), *(PinToTypeDefinition(PinB).GetNameText().ToString()))); + //Do some limiting on auto conversions here? + if (PinTypeA.GetClass()) + { + return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Types are not compatible")); + } + else + { + return FPinConnectionResponse(CONNECT_RESPONSE_MAKE_WITH_CONVERSION_NODE, FString::Printf(TEXT("Convert %s to %s"), *(PinToTypeDefinition(PinA).GetNameText().ToString()), *(PinToTypeDefinition(PinB).GetNameText().ToString()))); + } } } - } - // Check for compatible misc pins - if (PinA->PinType.PinCategory == PinCategoryMisc || - PinB->PinType.PinCategory == PinCategoryMisc) - { - // TODO: This shouldn't be handled explicitly here. - bool PinAIsConvertAddAndPinBIsNonGenericType = - PinA->PinType.PinCategory == PinCategoryMisc && PinA->PinType.PinSubCategory == UNiagaraNodeWithDynamicPins::AddPinSubCategory && - PinB->PinType.PinCategory == PinCategoryType && PinToTypeDefinition(PinB) != FNiagaraTypeDefinition::GetGenericNumericDef(); - - bool PinBIsConvertAddAndPinAIsNonGenericType = - PinB->PinType.PinCategory == PinCategoryMisc && PinB->PinType.PinSubCategory == UNiagaraNodeWithDynamicPins::AddPinSubCategory && - PinA->PinType.PinCategory == PinCategoryType && PinToTypeDefinition(PinA) != FNiagaraTypeDefinition::GetGenericNumericDef(); - - if(PinAIsConvertAddAndPinBIsNonGenericType == false && PinBIsConvertAddAndPinAIsNonGenericType == false) + // Check for compatible misc pins + if (PinA->PinType.PinCategory == PinCategoryMisc || + PinB->PinType.PinCategory == PinCategoryMisc) { - return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Types are not compatible")); + // TODO: This shouldn't be handled explicitly here. + bool PinAIsConvertAddAndPinBIsNonGenericType = + PinA->PinType.PinCategory == PinCategoryMisc && PinA->PinType.PinSubCategory == UNiagaraNodeWithDynamicPins::AddPinSubCategory && + PinB->PinType.PinCategory == PinCategoryType && PinToTypeDefinition(PinB) != FNiagaraTypeDefinition::GetGenericNumericDef() && + PinToTypeDefinition(PinB) != FNiagaraTypeDefinition::GetParameterMapDef(); + + bool PinBIsConvertAddAndPinAIsNonGenericType = + PinB->PinType.PinCategory == PinCategoryMisc && PinB->PinType.PinSubCategory == UNiagaraNodeWithDynamicPins::AddPinSubCategory && + PinA->PinType.PinCategory == PinCategoryType && PinToTypeDefinition(PinA) != FNiagaraTypeDefinition::GetGenericNumericDef() && + PinToTypeDefinition(PinA) != FNiagaraTypeDefinition::GetParameterMapDef(); + + if (PinAIsConvertAddAndPinBIsNonGenericType == false && PinBIsConvertAddAndPinAIsNonGenericType == false) + { + return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Types are not compatible")); + } } - } - if (PinA->PinType.PinCategory == PinCategoryClass || PinB->PinType.PinCategory == PinCategoryClass) - { - FNiagaraTypeDefinition AType = PinToTypeDefinition(PinA); - FNiagaraTypeDefinition BType = PinToTypeDefinition(PinB); - if (AType != BType) + if (PinA->PinType.PinCategory == PinCategoryClass || PinB->PinType.PinCategory == PinCategoryClass) { - return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Types are not compatible")); + FNiagaraTypeDefinition AType = PinToTypeDefinition(PinA); + FNiagaraTypeDefinition BType = PinToTypeDefinition(PinB); + if (AType != BType) + { + return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Types are not compatible")); + } } - } - if (PinA->PinType.PinCategory == PinCategoryEnum || PinB->PinType.PinCategory == PinCategoryEnum) - { - FNiagaraTypeDefinition PinTypeA = PinToTypeDefinition(PinA); - FNiagaraTypeDefinition PinTypeB = PinToTypeDefinition(PinB); - if (FNiagaraTypeDefinition::TypesAreAssignable(PinTypeA, PinTypeB) == false) + if (PinA->PinType.PinCategory == PinCategoryEnum || PinB->PinType.PinCategory == PinCategoryEnum) { - return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Types are not compatible")); + FNiagaraTypeDefinition PinTypeA = PinToTypeDefinition(PinA); + FNiagaraTypeDefinition PinTypeB = PinToTypeDefinition(PinB); + if (FNiagaraTypeDefinition::TypesAreAssignable(PinTypeA, PinTypeB) == false) + { + return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("Types are not compatible")); + } } } @@ -1270,7 +1288,11 @@ bool UEdGraphSchema_Niagara::TryGetPinDefaultValueFromNiagaraVariable(const FNia FNiagaraTypeDefinition UEdGraphSchema_Niagara::PinToTypeDefinition(const UEdGraphPin* Pin) const { - if (Pin->PinType.PinCategory == PinCategoryType && Pin->PinType.PinSubCategoryObject != nullptr) + if (Pin == nullptr) + { + return FNiagaraTypeDefinition(); + } + if (Pin->PinType.PinCategory == PinCategoryType && Pin->PinType.PinSubCategoryObject.IsValid()) { UScriptStruct* Struct = Cast(Pin->PinType.PinSubCategoryObject.Get()); if (Struct == nullptr) @@ -1554,6 +1576,19 @@ void UEdGraphSchema_Niagara::ToggleNodeEnabledState(UNiagaraNode* InNode) const } } +void UEdGraphSchema_Niagara::RefreshNode(UNiagaraNode* InNode) const +{ + if (InNode != nullptr) + { + const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "NiagaraEditorRefreshNode", "Refresh Node")); + InNode->Modify(); + if (InNode->RefreshFromExternalChanges()) + { + InNode->MarkNodeRequiresSynchronization(__FUNCTION__, true); + } + } +} + bool UEdGraphSchema_Niagara::CanPromoteSinglePinToParameter(const UEdGraphPin* SourcePin) { const UNiagaraGraph* NiagaraGraph = Cast(SourcePin->GetOwningNode()->GetGraph()); @@ -1648,6 +1683,9 @@ void UEdGraphSchema_Niagara::GetContextMenuActions(const UEdGraph* CurrentGraph, MenuBuilder->BeginSection("EdGraphSchema_NiagaraNodeActions", LOCTEXT("NodeActionsMenuHeader", "Node Actions")); MenuBuilder->AddMenuEntry(LOCTEXT("ToggleEnabledState", "Toggle Enabled State"), LOCTEXT("ToggleEnabledStateTooltip", "Toggle this node between Enbled (default) and Disabled (skipped from compilation)."), FSlateIcon(), FUIAction(FExecuteAction::CreateUObject((UEdGraphSchema_Niagara*const)this, &UEdGraphSchema_Niagara::ToggleNodeEnabledState, const_cast(Node)))); + MenuBuilder->AddMenuEntry(LOCTEXT("RefreshNode", "Refresh Node"), LOCTEXT("RefreshNodeTooltip", "Refresh this node."), FSlateIcon(), + FUIAction(FExecuteAction::CreateUObject((UEdGraphSchema_Niagara*const)this, &UEdGraphSchema_Niagara::RefreshNode, const_cast(Node)))); + MenuBuilder->EndSection(); } @@ -1656,7 +1694,7 @@ void UEdGraphSchema_Niagara::GetContextMenuActions(const UEdGraph* CurrentGraph, FNiagaraConnectionDrawingPolicy::FNiagaraConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, FSlateWindowElementList& InDrawElements, UEdGraph* InGraph) : FConnectionDrawingPolicy(InBackLayerID, InFrontLayerID, InZoomFactor, InClippingRect, InDrawElements) - , Graph(InGraph) + , Graph(CastChecked(InGraph)) { ArrowImage = nullptr; ArrowRadius = FVector2D::ZeroVector; @@ -1672,8 +1710,20 @@ void FNiagaraConnectionDrawingPolicy::DetermineWiringStyle(UEdGraphPin* OutputPi if (Graph) { - const UEdGraphSchema* Schema = Graph->GetSchema(); - Params.WireColor = Schema->GetPinTypeColor(OutputPin->PinType); + const UEdGraphSchema_Niagara* NSchema = Cast(Graph->GetSchema()); + if (NSchema && OutputPin) + { + Params.WireColor = NSchema->GetPinTypeColor(OutputPin->PinType); + if (NSchema->PinToTypeDefinition(OutputPin) == FNiagaraTypeDefinition::GetGenericNumericDef()) + { + FNiagaraTypeDefinition NewDef = Graph->GetCachedNumericConversion(OutputPin); + if (NewDef.IsValid()) + { + FEdGraphPinType NewPinType = NSchema->TypeDefinitionToPinType(NewDef); + Params.WireColor = NSchema->GetPinTypeColor(NewPinType); + } + } + } } } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraCompiler.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraCompiler.cpp index 3d1121d89865..a872e9dda057 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraCompiler.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraCompiler.cpp @@ -392,6 +392,31 @@ void FNiagaraCompileRequestData::FinishPrecompile(UNiagaraScriptSource* ScriptSo } } + // Generate CDO's for data interfaces that are passed in to function or dynamic input scripts compiled standalone as we do not have a history + if (InUsage == ENiagaraScriptUsage::Function || InUsage == ENiagaraScriptUsage::DynamicInput) + { + for (const auto ReferencedGraph : ClonedGraphs) + { + TArray InputNodes; + TArray InputVariables; + ReferencedGraph->FindInputNodes(InputNodes); + for (const auto InputNode : InputNodes) + { + InputVariables.Add(&InputNode->Input); + } + + for (const auto InputVariable : InputVariables) + { + if (InputVariable->IsDataInterface()) + { + UClass* Class = const_cast(InputVariable->GetType().GetClass()); + UObject* Obj = DuplicateObject(Class->GetDefaultObject(true), GetTransientPackage()); + CDOs.Add(Class, Obj); + } + } + } + } + } } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorCommon.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorCommon.cpp index 9b07dc51555d..1ff0afce1e31 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorCommon.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorCommon.cpp @@ -8,6 +8,7 @@ #include "ActorFactoryNiagara.h" #include "NiagaraActor.h" #include "NiagaraComponent.h" +#include "Misc/StringFormatter.h" #define LOCTEXT_NAMESPACE "NiagaraEditor" @@ -35,6 +36,30 @@ void FNiagaraOpInfo::BuildName(FString InName, FString InCategory) Name = FName(*(InCategory + TEXT("::") + InName)); } +bool FNiagaraOpInfo::CreateHlslForAddedInputs(int32 InputCount, FString & HlslResult) const +{ + if (!bSupportsAddedInputs || AddedInputFormatting.IsEmpty() || InputCount < 2) + { + return false; + } + + FString Result = TEXT("{0}"); + FString KeyA = TEXT("A"); + FString KeyB = TEXT("B"); + TMap FormatArgs; + FormatArgs.Add(KeyA, FStringFormatArg(Result)); + FormatArgs.Add(KeyB, FStringFormatArg(Result)); + for (int32 i = 1; i < InputCount; i++) + { + FormatArgs[KeyB] = FStringFormatArg(TEXT("{") + FString::FromInt(i) + TEXT("}")); + Result = FString::Format(*AddedInputFormatting, FormatArgs); + FormatArgs[KeyA] = FStringFormatArg(Result); + } + + HlslResult = Result; + return true; +} + BEGIN_FUNCTION_BUILD_OPTIMIZATION void FNiagaraOpInfo::Init() { @@ -87,6 +112,9 @@ void FNiagaraOpInfo::Init() Op->Inputs.Add(FNiagaraOpInOutInfo(B, Type, BText, BText, DefaultStr_Zero)); Op->Outputs.Add(FNiagaraOpInOutInfo(Result, Type, ResultText, ResultText, DefaultStr_Zero, TEXT("{0} + {1}"))); Op->BuildName(TEXT("Add"), CategoryName); + Op->bSupportsAddedInputs = true; + Op->AddedInputTypeRestrictions.Add(Type); + Op->AddedInputFormatting = TEXT("{A} + {B}"); OpInfoMap.Add(Op->Name) = Idx; Idx = OpInfos.AddDefaulted(); @@ -99,6 +127,9 @@ void FNiagaraOpInfo::Init() Op->Inputs.Add(FNiagaraOpInOutInfo(B, Type, BText, BText, DefaultStr_Zero)); Op->Outputs.Add(FNiagaraOpInOutInfo(Result, Type, ResultText, ResultText, DefaultStr_Zero, TEXT("{0} - {1}"))); Op->BuildName(TEXT("Subtract"), CategoryName); + Op->bSupportsAddedInputs = true; + Op->AddedInputTypeRestrictions.Add(Type); + Op->AddedInputFormatting = TEXT("{A} - {B}"); OpInfoMap.Add(Op->Name) = Idx; Idx = OpInfos.AddDefaulted(); @@ -111,6 +142,9 @@ void FNiagaraOpInfo::Init() Op->Inputs.Add(FNiagaraOpInOutInfo(B, Type, BText, BText, DefaultStr_One)); Op->Outputs.Add(FNiagaraOpInOutInfo(Result, Type, ResultText, ResultText, DefaultStr_One, TEXT("{0} * {1}"))); Op->BuildName(TEXT("Mul"), CategoryName); + Op->bSupportsAddedInputs = true; + Op->AddedInputTypeRestrictions.Add(Type); + Op->AddedInputFormatting = TEXT("{A} * {B}"); OpInfoMap.Add(Op->Name) = Idx; Idx = OpInfos.AddDefaulted(); @@ -153,13 +187,24 @@ void FNiagaraOpInfo::Init() Idx = OpInfos.AddDefaulted(); Op = &OpInfos[Idx]; Op->Category = CategoryText; - Op->FriendlyName = NSLOCTEXT("NiagaraOpInfo", "Reciprocal Name", "Reciprocal"); - Op->Description = NSLOCTEXT("NiagaraOpInfo", "Reciprocal Desc", "Result = 1 / A"); + Op->FriendlyName = NSLOCTEXT("NiagaraOpInfo", "Reciprocal Fast Name", "Reciprocal Fast"); + Op->Description = NSLOCTEXT("NiagaraOpInfo", "Reciprocal Fast Desc", "12-bits of accuracy, but faster. Result = 1 / A using Newton/Raphson approximation."); Op->Inputs.Add(FNiagaraOpInOutInfo(A, Type, AText, AText, DefaultStr_One)); Op->Outputs.Add(FNiagaraOpInOutInfo(Result, Type, ResultText, ResultText, DefaultStr_One, TEXT("rcp({0})"))); + Op->BuildName(TEXT("RcpFast"), CategoryName); + OpInfoMap.Add(Op->Name) = Idx; + + Idx = OpInfos.AddDefaulted(); + Op = &OpInfos[Idx]; + Op->Category = CategoryText; + Op->FriendlyName = NSLOCTEXT("NiagaraOpInfo", "Reciprocal Name", "Reciprocal"); + Op->Description = NSLOCTEXT("NiagaraOpInfo", "Reciprocal Desc", "More accurate than Reciprocal Fast. Result = 1 / A"); + Op->Inputs.Add(FNiagaraOpInOutInfo(A, Type, AText, AText, DefaultStr_One)); + Op->Outputs.Add(FNiagaraOpInOutInfo(Result, Type, ResultText, ResultText, DefaultStr_One, TEXT("Reciprocal({0})"))); Op->BuildName(TEXT("Rcp"), CategoryName); OpInfoMap.Add(Op->Name) = Idx; + Idx = OpInfos.AddDefaulted(); Op = &OpInfos[Idx]; Op->Category = CategoryText; @@ -574,15 +619,27 @@ void FNiagaraOpInfo::Init() Idx = OpInfos.AddDefaulted(); Op = &OpInfos[Idx]; Op->Category = CategoryText; - Op->FriendlyName = NSLOCTEXT("NiagaraOpInfo", "Fmod Name", "FMod"); + Op->FriendlyName = NSLOCTEXT("NiagaraOpInfo", "Fmod Name", "Modulo"); Op->Description = NSLOCTEXT("NiagaraOpInfo", "Fmod Desc", "Result = A % B"); Op->Keywords = FText::FromString(TEXT("%")); Op->Inputs.Add(FNiagaraOpInOutInfo(A, Type, AText, AText, DefaultStr_One)); Op->Inputs.Add(FNiagaraOpInOutInfo(B, Type, BText, BText, DefaultStr_One)); - Op->Outputs.Add(FNiagaraOpInOutInfo(Result, Type, ResultText, ResultText, DefaultStr_One, TEXT("Modulo({0}, {1})"))); + Op->Outputs.Add(FNiagaraOpInOutInfo(Result, Type, ResultText, ResultText, DefaultStr_One, TEXT("ModuloPrecise({0}, {1})"))); Op->BuildName(TEXT("FMod"), CategoryName); OpInfoMap.Add(Op->Name) = Idx; + Idx = OpInfos.AddDefaulted(); + Op = &OpInfos[Idx]; + Op->Category = CategoryText; + Op->FriendlyName = NSLOCTEXT("NiagaraOpInfo", "Fmod Name Fast", "Modulo Fast"); + Op->Description = NSLOCTEXT("NiagaraOpInfo", "Fmod Desc Fast", "Result = A % B. May be less precise than regular FMod."); + Op->Keywords = FText::FromString(TEXT("%")); + Op->Inputs.Add(FNiagaraOpInOutInfo(A, Type, AText, AText, DefaultStr_One)); + Op->Inputs.Add(FNiagaraOpInOutInfo(B, Type, BText, BText, DefaultStr_One)); + Op->Outputs.Add(FNiagaraOpInOutInfo(Result, Type, ResultText, ResultText, DefaultStr_One, TEXT("Modulo({0}, {1})"))); + Op->BuildName(TEXT("FModFast"), CategoryName); + OpInfoMap.Add(Op->Name) = Idx; + Idx = OpInfos.AddDefaulted(); Op = &OpInfos[Idx]; Op->Category = CategoryText; @@ -624,6 +681,9 @@ void FNiagaraOpInfo::Init() Op->Inputs.Add(FNiagaraOpInOutInfo(B, Type, BText, BText, DefaultStr_One)); Op->Outputs.Add(FNiagaraOpInOutInfo(Result, Type, ResultText, ResultText, DefaultStr_One, TEXT("min({0},{1})"))); Op->BuildName(TEXT("Min"), CategoryName); + Op->bSupportsAddedInputs = true; + Op->AddedInputTypeRestrictions.Add(Type); + Op->AddedInputFormatting = TEXT("min({A}, {B})"); OpInfoMap.Add(Op->Name) = Idx; Idx = OpInfos.AddDefaulted(); @@ -635,6 +695,9 @@ void FNiagaraOpInfo::Init() Op->Inputs.Add(FNiagaraOpInOutInfo(B, Type, BText, BText, DefaultStr_One)); Op->Outputs.Add(FNiagaraOpInOutInfo(Result, Type, ResultText, ResultText, DefaultStr_One, TEXT("max({0},{1})"))); Op->BuildName(TEXT("Max"), CategoryName); + Op->bSupportsAddedInputs = true; + Op->AddedInputTypeRestrictions.Add(Type); + Op->AddedInputFormatting = TEXT("max({A}, {B})"); OpInfoMap.Add(Op->Name) = Idx; Idx = OpInfos.AddDefaulted(); @@ -712,17 +775,41 @@ void FNiagaraOpInfo::Init() Op->BuildName(TEXT("Length"), CategoryName); OpInfoMap.Add(Op->Name) = Idx; - //Temporarily here. Rand will be reworked shortly. + // Non-deterministic random number generation. Calls FRandomStream on the CPU. Idx = OpInfos.AddDefaulted(); Op = &OpInfos[Idx]; Op->Category = CategoryText; Op->FriendlyName = NSLOCTEXT("NiagaraOpInfo", "Rand Name", "Random"); - Op->Description = NSLOCTEXT("NiagaraOpInfo", "Rand Desc", "Returns a random value between 0 and A."); + Op->Description = NSLOCTEXT("NiagaraOpInfo", "Rand Desc", "Returns a non-deterministic random value between 0 and A."); Op->Inputs.Add(FNiagaraOpInOutInfo(A, Type, AText, AText, DefaultStr_One)); Op->Outputs.Add(FNiagaraOpInOutInfo(Result, Type, ResultText, ResultText, DefaultStr_One, TEXT("rand({0})"))); Op->BuildName(TEXT("Rand"), CategoryName); OpInfoMap.Add(Op->Name) = Idx; + + // Deterministic/seeded random number generation. + static FName SeedName1(TEXT("Seed 1")); + static FName SeedName2(TEXT("Seed 2")); + static FName SeedName3(TEXT("Seed 3")); + static FText SeedText1 = NSLOCTEXT("NiagaraOpInfo", "Seed1 Desc", "Seed 1"); + static FText SeedText2 = NSLOCTEXT("NiagaraOpInfo", "Seed2 Desc", "Seed 2"); + static FText SeedText3 = NSLOCTEXT("NiagaraOpInfo", "Seed3 Desc", "Seed 3"); + FString DefaultSeed_Zero(TEXT("0")); + FNiagaraTypeDefinition SeedType = FNiagaraTypeDefinition::GetIntDef(); + + Idx = OpInfos.AddDefaulted(); + Op = &OpInfos[Idx]; + Op->Category = CategoryText; + Op->FriendlyName = NSLOCTEXT("NiagaraOpInfo", "Seeded Rand Name", "Seeded Random"); + Op->Description = NSLOCTEXT("NiagaraOpInfo", "Seeded Rand Desc", "Returns a deterministic random value between 0 and A."); + Op->Inputs.Add(FNiagaraOpInOutInfo(A, Type, AText, AText, DefaultStr_One)); + Op->Inputs.Add(FNiagaraOpInOutInfo(SeedName1, SeedType, SeedText1, SeedText1, DefaultSeed_Zero)); + Op->Inputs.Add(FNiagaraOpInOutInfo(SeedName2, SeedType, SeedText2, SeedText2, DefaultSeed_Zero)); + Op->Inputs.Add(FNiagaraOpInOutInfo(SeedName3, SeedType, SeedText3, SeedText3, DefaultSeed_Zero)); + Op->Outputs.Add(FNiagaraOpInOutInfo(Result, Type, ResultText, ResultText, DefaultStr_One, TEXT("rand({0}, {1}, {2}, {3})"))); + Op->BuildName(TEXT("SeededRand"), CategoryName); + OpInfoMap.Add(Op->Name) = Idx; + //Comparison ops Idx = OpInfos.AddDefaulted(); Op = &OpInfos[Idx]; @@ -816,6 +903,9 @@ void FNiagaraOpInfo::Init() Op->Inputs.Add(FNiagaraOpInOutInfo(B, IntType, BText, BText, Default_IntOne)); Op->Outputs.Add(FNiagaraOpInOutInfo(Result, IntType, ResultText, ResultText, Default_IntOne, TEXT("{0} & {1}"))); Op->BuildName(TEXT("BitAnd"), IntCategoryName); + Op->bSupportsAddedInputs = true; + Op->AddedInputTypeRestrictions.Add(IntType); + Op->AddedInputFormatting = TEXT("{A} & {B}"); OpInfoMap.Add(Op->Name) = Idx; Idx = OpInfos.AddDefaulted(); @@ -828,6 +918,9 @@ void FNiagaraOpInfo::Init() Op->Inputs.Add(FNiagaraOpInOutInfo(B, IntType, BText, BText, Default_IntOne)); Op->Outputs.Add(FNiagaraOpInOutInfo(Result, IntType, ResultText, ResultText, Default_IntOne, TEXT("{0} | {1}"))); Op->BuildName(TEXT("BitOr"), IntCategoryName); + Op->bSupportsAddedInputs = true; + Op->AddedInputTypeRestrictions.Add(IntType); + Op->AddedInputFormatting = TEXT("{A} | {B}"); OpInfoMap.Add(Op->Name) = Idx; Idx = OpInfos.AddDefaulted(); @@ -840,6 +933,9 @@ void FNiagaraOpInfo::Init() Op->Inputs.Add(FNiagaraOpInOutInfo(B, IntType, BText, BText, Default_IntOne)); Op->Outputs.Add(FNiagaraOpInOutInfo(Result, IntType, ResultText, ResultText, Default_IntOne, TEXT("{0} ^ {1}"))); Op->BuildName(TEXT("BitXOr"), IntCategoryName); + Op->bSupportsAddedInputs = true; + Op->AddedInputTypeRestrictions.Add(IntType); + Op->AddedInputFormatting = TEXT("{A} ^ {B}"); OpInfoMap.Add(Op->Name) = Idx; Idx = OpInfos.AddDefaulted(); @@ -853,6 +949,35 @@ void FNiagaraOpInfo::Init() Op->BuildName(TEXT("BitNot"), IntCategoryName); OpInfoMap.Add(Op->Name) = Idx; + Idx = OpInfos.AddDefaulted(); + Op = &OpInfos[Idx]; + Op->Category = IntCategory; + Op->FriendlyName = NSLOCTEXT("NiagaraOpInfo", "BitLShift Name", "Bitwise Left Shift"); + Op->Description = NSLOCTEXT("NiagaraOpInfo", "BitLShift Desc", "Shifts A left by B bits, padding with zeroes on the right. B should be between 0 and 31 or there will be undefined behavior."); + Op->Keywords = FText::FromString(TEXT("<<")); + Op->Inputs.Add(FNiagaraOpInOutInfo(A, IntType, AText, AText, Default_IntOne)); + Op->Inputs.Add(FNiagaraOpInOutInfo(B, IntType, BText, BText, Default_IntOne)); + Op->Outputs.Add(FNiagaraOpInOutInfo(Result, IntType, ResultText, ResultText, Default_IntOne, TEXT("{0} << {1}"))); + Op->BuildName(TEXT("BitLShift"), IntCategoryName); + Op->bSupportsAddedInputs = true; + Op->AddedInputTypeRestrictions.Add(IntType); + Op->AddedInputFormatting = TEXT("{A} << {B}"); + OpInfoMap.Add(Op->Name) = Idx; + + Idx = OpInfos.AddDefaulted(); + Op = &OpInfos[Idx]; + Op->Category = IntCategory; + Op->FriendlyName = NSLOCTEXT("NiagaraOpInfo", "BitRShift Name", "Bitwise Right Shift"); + Op->Description = NSLOCTEXT("NiagaraOpInfo", "BitRShift Desc", "Shifts A right by B bits, taking the sign bit and propagating it to fill in on left (i.e. negative numbers fill with 1's, positive fill with 0's. B should be between 0 and 31 or there will be undefined behavior."); + Op->Keywords = FText::FromString(TEXT(">>")); + Op->Inputs.Add(FNiagaraOpInOutInfo(A, IntType, AText, AText, Default_IntOne)); + Op->Inputs.Add(FNiagaraOpInOutInfo(B, IntType, BText, BText, Default_IntOne)); + Op->Outputs.Add(FNiagaraOpInOutInfo(Result, IntType, ResultText, ResultText, Default_IntOne, TEXT("{0} >> {1}"))); + Op->BuildName(TEXT("BitRShift"), IntCategoryName); + Op->bSupportsAddedInputs = true; + Op->AddedInputTypeRestrictions.Add(IntType); + Op->AddedInputFormatting = TEXT("{A} >> {B}"); + OpInfoMap.Add(Op->Name) = Idx; ////////////////////////////////////////////////////////////////////////// // Boolean Only Ops @@ -872,6 +997,9 @@ void FNiagaraOpInfo::Init() Op->Inputs.Add(FNiagaraOpInOutInfo(B, BoolType, BText, BText, Default_BoolOne)); Op->Outputs.Add(FNiagaraOpInOutInfo(Result, BoolType, ResultText, ResultText, Default_BoolOne, TEXT("{0} && {1}"))); Op->BuildName(TEXT("LogicAnd"), BoolCategoryName); + Op->bSupportsAddedInputs = true; + Op->AddedInputTypeRestrictions.Add(BoolType); + Op->AddedInputFormatting = TEXT("{A} && {B}"); OpInfoMap.Add(Op->Name) = Idx; Idx = OpInfos.AddDefaulted(); @@ -884,6 +1012,9 @@ void FNiagaraOpInfo::Init() Op->Inputs.Add(FNiagaraOpInOutInfo(B, BoolType, BText, BText, Default_BoolOne)); Op->Outputs.Add(FNiagaraOpInOutInfo(Result, BoolType, ResultText, ResultText, Default_BoolOne, TEXT("{0} || {1}"))); Op->BuildName(TEXT("LogicOr"), BoolCategoryName); + Op->bSupportsAddedInputs = true; + Op->AddedInputTypeRestrictions.Add(BoolType); + Op->AddedInputFormatting = TEXT("{A} || {B}"); OpInfoMap.Add(Op->Name) = Idx; Idx = OpInfos.AddDefaulted(); @@ -986,6 +1117,9 @@ void FNiagaraOpInfo::Init() Op->Inputs.Add(FNiagaraOpInOutInfo(B, MatrixType, BText, BText, Default_MatrixOne)); Op->Outputs.Add(FNiagaraOpInOutInfo(Result, MatrixType, ResultText, ResultText, Default_MatrixOne, TEXT("{0} * {1}"))); Op->BuildName(TEXT("MatrixMultiply"), MatrixCategoryName); + Op->bSupportsAddedInputs = true; + Op->AddedInputTypeRestrictions.Add(MatrixType); + Op->AddedInputFormatting = TEXT("{A} * {B}"); OpInfoMap.Add(Op->Name) = Idx; Idx = OpInfos.AddDefaulted(); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorModule.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorModule.cpp index 52c98f15ae45..41127363b68d 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorModule.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorModule.cpp @@ -132,11 +132,26 @@ public: { if (InPin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryType) { - const UScriptStruct* Struct = CastChecked(InPin->PinType.PinSubCategoryObject.Get()); - const FCreateGraphPin* CreateGraphPin = TypeToCreatePinDelegateMap.Find(Struct); - if (CreateGraphPin != nullptr) + if (InPin->PinType.PinSubCategoryObject != nullptr && InPin->PinType.PinSubCategoryObject->IsA()) { - return (*CreateGraphPin).Execute(InPin); + const UScriptStruct* Struct = CastChecked(InPin->PinType.PinSubCategoryObject.Get()); + const FCreateGraphPin* CreateGraphPin = TypeToCreatePinDelegateMap.Find(Struct); + if (CreateGraphPin != nullptr) + { + return (*CreateGraphPin).Execute(InPin); + } + // Otherwise, fall back to the generic pin for Niagara types. Previous iterations put out an error here, but this + // was not correct as the above list is just overrides from the default renamable pin, usually numeric types with their own custom + // editors for default values. Things like the parameter map can safely just fall through to the end condition and create a + // generic renamable pin. + } + else + { + UE_LOG(LogNiagaraEditor, Error, TEXT("Pin type is invalid! Pin Name '%s' Owning Node '%s'. Turning into standard int definition!"), *InPin->PinName.ToString(), + *InPin->GetOwningNode()->GetName()); + InPin->PinType.PinSubCategoryObject = MakeWeakObjectPtr(const_cast(FNiagaraTypeDefinition::GetIntStruct())); + InPin->DefaultValue.Empty(); + return CreatePin(InPin); } } else if (InPin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryEnum) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorUtilities.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorUtilities.cpp index 06b6e366e609..e7c3e1b66e9b 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorUtilities.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEditorUtilities.cpp @@ -89,7 +89,10 @@ void FNiagaraEditorUtilities::InitializeParameterInputNode(UNiagaraNodeInput& In if (Type.GetScriptStruct() != nullptr) { ResetVariableToDefaultValue(InputNode.Input); - InputNode.SetDataInterface(nullptr); + if (InputNode.GetDataInterface() != nullptr) + { + InputNode.SetDataInterface(nullptr); + } } else { @@ -608,56 +611,7 @@ void TraverseGraphFromOutputDepthFirst(const UEdGraphSchema_Niagara* Schema, UNi void FixUpNumericPinsVisitor(const UEdGraphSchema_Niagara* Schema, UNiagaraNode* Node) { - // Fix up numeric input pins and keep track of numeric types to decide the output type. - TArray InputTypes; - TArray InputPins; - Node->GetInputPins(InputPins); - for (UEdGraphPin* InputPin : InputPins) - { - if (InputPin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryType) - { - FNiagaraTypeDefinition InputPinType = Schema->PinToTypeDefinition(InputPin); - - // If the input pin is the generic numeric type set it to the type of the linked output pin which should have been processed already. - if (InputPinType == FNiagaraTypeDefinition::GetGenericNumericDef() && InputPin->LinkedTo.Num() == 1) - { - UEdGraphPin* InputPinLinkedPin = UNiagaraNode::TraceOutputPin(InputPin->LinkedTo[0]); - FNiagaraTypeDefinition InputPinLinkedPinType = Schema->PinToTypeDefinition(InputPinLinkedPin); - if (InputPinLinkedPinType.IsValid()) - { - // Only update the input pin type if the linked pin type is valid. - InputPin->PinType = Schema->TypeDefinitionToPinType(InputPinLinkedPinType); - InputPinType = InputPinLinkedPinType; - } - } - - if (InputPinType == FNiagaraTypeDefinition::GetGenericNumericDef()) - { - UE_LOG(LogNiagaraEditor, Error, TEXT("Unable to deduce type for numeric input pin. Node: %s Pin: %s"), *Node->GetName(), *InputPin->GetName()); - } - - InputTypes.Add(InputPinType); - } - } - - // Fix up numeric outputs based on the inputs. - if (InputTypes.Num() > 0 && Node->GetNumericOutputTypeSelectionMode() != ENiagaraNumericOutputTypeSelectionMode::None) - { - FNiagaraTypeDefinition OutputNumericType = FNiagaraTypeDefinition::GetNumericOutputType(InputTypes, Node->GetNumericOutputTypeSelectionMode()); - if (OutputNumericType != FNiagaraTypeDefinition::GetGenericNumericDef()) - { - TArray OutputPins; - Node->GetOutputPins(OutputPins); - for (UEdGraphPin* OutputPin : OutputPins) - { - FNiagaraTypeDefinition OutputPinType = Schema->PinToTypeDefinition(OutputPin); - if (OutputPinType == FNiagaraTypeDefinition::GetGenericNumericDef()) - { - OutputPin->PinType = Schema->TypeDefinitionToPinType(OutputNumericType); - } - } - } - } + Node->ResolveNumerics(Schema, true, nullptr); } void FNiagaraEditorUtilities::FixUpNumericPins(const UEdGraphSchema_Niagara* Schema, UNiagaraNode* Node) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEmitterFactoryNew.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEmitterFactoryNew.cpp index 0d1be150cfaa..c3992e62346b 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEmitterFactoryNew.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraEmitterFactoryNew.cpp @@ -82,7 +82,7 @@ bool UNiagaraEmitterFactoryNew::ConfigureProperties() UNiagaraNodeFunctionCall* AddModuleFromAssetPath(FString AssetPath, UNiagaraNodeOutput& TargetOutputNode) { - FStringAssetReference AssetRef(AssetPath); + FSoftObjectPath AssetRef(AssetPath); UNiagaraScript* AssetScript = Cast(AssetRef.TryLoad()); FAssetData ScriptAssetData(AssetScript); if (ScriptAssetData.IsValid()) @@ -196,6 +196,7 @@ UObject* UNiagaraEmitterFactoryNew::FactoryCreateNew(UClass* Class, UObject* InP FNiagaraStackGraphUtilities::RelayoutGraph(*Source->NodeGraph); NewEmitter->bInterpolatedSpawning = true; + NewEmitter->bDeterminism = false; // NOTE: Default to non-determinism NewEmitter->SpawnScriptProps.Script->SetUsage(ENiagaraScriptUsage::ParticleSpawnScriptInterpolated); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraGraph.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraGraph.cpp index 5044cd37ef29..7d0daf9c97dd 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraGraph.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraGraph.cpp @@ -59,6 +59,7 @@ FNiagaraGraphScriptUsageInfo::FNiagaraGraphScriptUsageInfo() : UsageType(ENiagar UNiagaraGraph::UNiagaraGraph(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) + , bNeedNumericCacheRebuilt(true) , bFindParametersAllowed(true) , bIsRenamingParameter(false) { @@ -96,6 +97,7 @@ void UNiagaraGraph::NotifyGraphChanged() { FindParameters(); Super::NotifyGraphChanged(); + InvalidateNumericCache(); } void UNiagaraGraph::PostLoad() @@ -724,6 +726,25 @@ void UNiagaraGraph::SubsumeExternalDependencies(TMap& } } +FNiagaraTypeDefinition UNiagaraGraph::GetCachedNumericConversion(class UEdGraphPin* InPin) +{ + if (bNeedNumericCacheRebuilt) + { + RebuildNumericCache(); + } + + FNiagaraTypeDefinition ReturnDef; + if (InPin && InPin->PinId.IsValid()) + { + FNiagaraTypeDefinition* FoundDef = CachedNumericConversions.Find(TPair(InPin->PinId, InPin->GetOwningNode())); + if (FoundDef) + { + ReturnDef = *FoundDef; + } + } + return ReturnDef; +} + void UNiagaraGraph::RebuildCachedData(bool bForce) { // If the graph hasn't changed since last rebuild, then do nothing. @@ -875,8 +896,72 @@ void UNiagaraGraph::RebuildCachedData(bool bForce) CachedUsageInfo = NewUsageCache; LastBuiltTraversalDataChangeId = ChangeId; + RebuildNumericCache(); } +const class UEdGraphSchema_Niagara* UNiagaraGraph::GetNiagaraSchema() const +{ + return Cast(GetSchema()); +} + +void UNiagaraGraph::RebuildNumericCache() +{ + CachedNumericConversions.Empty(); + TMap VisitedNodes; + for (UEdGraphNode* Node : Nodes) + { + ResolveNumerics(VisitedNodes, Node); + } + bNeedNumericCacheRebuilt = false; +} + +void UNiagaraGraph::InvalidateNumericCache() +{ + bNeedNumericCacheRebuilt = true; + CachedNumericConversions.Empty(); +} + +FString UNiagaraGraph::GetFunctionAliasByContext(const FNiagaraGraphFunctionAliasContext& FunctionAliasContext) +{ + FString FunctionAlias; + for (UEdGraphNode* Node : Nodes) + { + UNiagaraNode* NiagaraNode = Cast(Node); + if (NiagaraNode != nullptr) + { + NiagaraNode->AppendFunctionAliasForContext(FunctionAliasContext, FunctionAlias); + } + } + return FunctionAlias; +} + +void UNiagaraGraph::ResolveNumerics(TMap& VisitedNodes, UEdGraphNode* Node) +{ + UNiagaraNode* NiagaraNode = Cast(Node); + if (NiagaraNode) + { + TArray InputPins; + NiagaraNode->GetInputPins(InputPins); + for (int32 i = 0; i < InputPins.Num(); i++) + { + if (InputPins[i]) + { + UNiagaraNode* FoundNode = Cast(InputPins[i]->GetOwningNode()); + if (!FoundNode || VisitedNodes.Contains(FoundNode)) + { + continue; + } + VisitedNodes.Add(FoundNode, true); + ResolveNumerics(VisitedNodes, FoundNode); + } + } + + NiagaraNode->ResolveNumerics(GetNiagaraSchema(), false, &CachedNumericConversions); + + } +} + + void UNiagaraGraph::SynchronizeInternalCacheWithGraph(UNiagaraGraph* Other) { // Force us to rebuild the cache, note that this builds traversals and everything else, keeping it in sync if nothing changed from the current version. diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.cpp index 1561d8c08ac7..7a1c254f3fe5 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.cpp @@ -8,6 +8,7 @@ #include "UObject/UObjectHash.h" #include "NiagaraNode.h" #include "NiagaraNodeFunctionCall.h" +#include "NiagaraNodeIf.h" #include "NiagaraNodeInput.h" #include "NiagaraNodeOutput.h" #include "NiagaraNodeReadDataSet.h" @@ -263,7 +264,6 @@ void FHlslNiagaraTranslator::GenerateFunctionSignature(ENiagaraScriptUsage Scrip TArray InputVars; TArray InputsNodes; - // Only handle nodes connected to the correct output node in the event of multiple output nodes in the graph. { SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_Module_NiagaraHLSLTranslator_GenerateFunctionSignature_FindInputNodes); InputsNodes.Reserve(100); @@ -271,13 +271,17 @@ void FHlslNiagaraTranslator::GenerateFunctionSignature(ENiagaraScriptUsage Scrip Options.bSort = true; Options.bFilterDuplicates = true; Options.bIncludeTranslatorConstants = false; - Options.bFilterByScriptUsage = true; + // If we're compiling the emitter function we need to filter to the correct usage so that we only get inputs associated with the emitter call, but if we're compiling any other kind of function call we need all inputs + // since the function call nodes themselves will have been generated with pins for all inputs and since we match the input nodes here to the inputs passed in by index, the two collections must match otherwise we fail + // to compile a graph that would otherwise work correctly. + Options.bFilterByScriptUsage = ScriptUsage == ENiagaraScriptUsage::EmitterSpawnScript || ScriptUsage == ENiagaraScriptUsage::EmitterUpdateScript; Options.TargetScriptUsage = ScriptUsage; FuncGraph->FindInputNodes(InputsNodes, Options); if (Inputs.Num() != InputsNodes.Num()) { - const_cast(this)->Error(FText::Format(LOCTEXT("GenerateFunctionSignatureFail", "Generating function signature for {0} failed. The function graph is invalid."), FText::FromString(InFullName)), nullptr, nullptr); + const_cast(this)->Error(FText::Format(LOCTEXT("GenerateFunctionSignatureFail", "Generating function signature for {0} failed. The function call is providing a different number of inputs than the function graph supplies."), + FText::FromString(InFullName)), nullptr, nullptr); return; } } @@ -287,20 +291,38 @@ void FHlslNiagaraTranslator::GenerateFunctionSignature(ENiagaraScriptUsage Scrip InName.Reserve(100 * InputsNodes.Num()); InputVars.Reserve(InputsNodes.Num()); + TArray ConstantInputIndicesToRemove; for (int32 i = 0; i < InputsNodes.Num(); ++i) { //Only add to the signature if the caller has provided it, otherwise we use a local default. if (Inputs[i] != INDEX_NONE) { - InputVars.Add(InputsNodes[i]->Input); - if (bHadNumericInputs) + FNiagaraVariable LiteralConstant = InputsNodes[i]->Input; + if (GetLiteralConstantVariable(LiteralConstant)) { - InName += TEXT("_In"); - InName += InputsNodes[i]->Input.GetType().GetName(); + checkf(LiteralConstant.GetType() == FNiagaraTypeDefinition::GetBoolDef(), TEXT("Only boolean types are currently supported for literal constants.")); + FString LiteralConstantAlias = LiteralConstant.GetName().ToString() + TEXT("_") + (LiteralConstant.GetValue() ? TEXT("true") : TEXT("false")); + InName += TEXT("_") + GetSanitizedSymbolName(LiteralConstantAlias.Replace(TEXT("."), TEXT("_"))); + ConstantInputIndicesToRemove.Add(i); + } + else + { + InputVars.Add(InputsNodes[i]->Input); + if (bHadNumericInputs) + { + InName += TEXT("_In"); + InName += InputsNodes[i]->Input.GetType().GetName(); + } } } } + // Remove the inputs which will be handled by inline constants + for (int32 i = ConstantInputIndicesToRemove.Num() - 1; i >= 0; i--) + { + Inputs.RemoveAt(ConstantInputIndicesToRemove[i]); + } + //Now actually remove the missing inputs so they match the signature. Inputs.Remove(INDEX_NONE); } @@ -350,7 +372,10 @@ void FHlslNiagaraTranslator::GenerateFunctionSignature(ENiagaraScriptUsage Scrip } else { - OutSig = FNiagaraFunctionSignature(*InName, InputVars, OutputVars, *InFullName, true, false); + FNiagaraGraphFunctionAliasContext FunctionAliasContext; + FunctionAliasContext.CompileUsage = GetCurrentUsage(); + FString SignatureName = InName + FuncGraph->GetFunctionAliasByContext(FunctionAliasContext); + OutSig = FNiagaraFunctionSignature(*SignatureName, InputVars, OutputVars, *InFullName, true, false); } } @@ -697,10 +722,16 @@ const FNiagaraTranslateResults &FHlslNiagaraTranslator::Translate(const FNiagara return TranslateResults; } + bool bNeedsPersistentIDs = CompileOptions.AdditionalDefines.Contains(TEXT("RequiresPersistentIDs")); + if (bNeedsPersistentIDs && CompilationTarget == ENiagaraSimTarget::GPUComputeSim) { + Error(LOCTEXT("GPUPersistentIDFail", "GPU particles do not support persistent IDs. Change to a CPU simulation or disable persistent IDs."), nullptr, nullptr); + return TranslateResults; + } + TranslationStages.Empty(); ActiveStageIdx = 0; - bool bHasInterpolatedSpawn = InCompileOptions.AdditionalDefines.Contains(TEXT("InterpolatedSpawn")); + bool bHasInterpolatedSpawn = CompileOptions.AdditionalDefines.Contains(TEXT("InterpolatedSpawn")); ParamMapHistories.Empty(); ParamMapSetVariablesToChunks.Empty(); @@ -771,67 +802,88 @@ const FNiagaraTranslateResults &FHlslNiagaraTranslator::Translate(const FNiagara // Get all the parameter map histories traced to this graph from output nodes. We'll revisit this shortly in order to build out just the ones we care about for this translation. OtherOutputParamMapHistories = CompileData->GetPrecomputedHistories(); - for (FNiagaraParameterMapHistory& FoundHistory : OtherOutputParamMapHistories) + + if (ParamMapHistories.Num() == 1 && OtherOutputParamMapHistories.Num() == 1 && ( CompileOptions.TargetUsage == ENiagaraScriptUsage::Function || CompileOptions.TargetUsage == ENiagaraScriptUsage::DynamicInput ) ) { - const UNiagaraNodeOutput* HistoryOutputNode = FoundHistory.GetFinalOutputNode(); - if (HistoryOutputNode != nullptr && !ShouldConsiderTargetParameterMap(HistoryOutputNode->GetUsage())) + ParamMapHistories[0] = (OtherOutputParamMapHistories[0]); + + TArray Entries; + Entries.AddZeroed(OtherOutputParamMapHistories[0].Variables.Num()); + for (int32 i = 0; i < Entries.Num(); i++) { - continue; + Entries[i] = INDEX_NONE; } - - // Now see if we want to use any of these specifically.. - for (int32 ParamMapIdx = 0; ParamMapIdx < TranslationStages.Num(); ParamMapIdx++) + ParamMapSetVariablesToChunks[0] = (Entries); + } + else + { + for (FNiagaraParameterMapHistory& FoundHistory : OtherOutputParamMapHistories) { - UNiagaraNodeOutput* TargetOutputNode = TranslationStages[ParamMapIdx].OutputNode; - if (FoundHistory.GetFinalOutputNode() == TargetOutputNode) + const UNiagaraNodeOutput* HistoryOutputNode = FoundHistory.GetFinalOutputNode(); + if (HistoryOutputNode != nullptr && !ShouldConsiderTargetParameterMap(HistoryOutputNode->GetUsage())) { - bool bNeedsPersistentIDs = InCompileOptions.AdditionalDefines.Contains(TEXT("RequiresPersistentIDs")); - if (bNeedsPersistentIDs) + continue; + } + + // Now see if we want to use any of these specifically.. + for (int32 ParamMapIdx = 0; ParamMapIdx < TranslationStages.Num(); ParamMapIdx++) + { + UNiagaraNodeOutput* TargetOutputNode = TranslationStages[ParamMapIdx].OutputNode; + if (FoundHistory.GetFinalOutputNode() == TargetOutputNode) { - //TODO: Setup alias for current level to decouple from "Particles". Would we ever want emitter or system persistent IDs? - FNiagaraVariable Var = FNiagaraVariable(FNiagaraTypeDefinition::GetIDDef(), TEXT("Particles.ID")); - FoundHistory.AddVariable(Var, Var, nullptr); + if(bNeedsPersistentIDs) + { + //TODO: Setup alias for current level to decouple from "Particles". Would we ever want emitter or system persistent IDs? + FNiagaraVariable Var = FNiagaraVariable(FNiagaraTypeDefinition::GetIDDef(), TEXT("Particles.ID")); + FoundHistory.AddVariable(Var, Var, nullptr); + } + { + // NOTE(mv): This will explicitly expose Particles.UniqueID to the HLSL code regardless of whether it is exposed in a script or not. + // This is necessary as the script needs to know about it even when no scripts reference it. + FNiagaraVariable Var = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Particles.UniqueID")); + FoundHistory.AddVariable(Var, Var, nullptr); + } + + if (RequiresInterpolation()) + { + FNiagaraVariable Var = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Interpolation.InterpSpawn_Index")); + FoundHistory.AddVariable(Var, Var, nullptr); + + Var = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Interpolation.InterpSpawn_SpawnTime")); + FoundHistory.AddVariable(Var, Var, nullptr); + + Var = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Interpolation.InterpSpawn_UpdateTime")); + FoundHistory.AddVariable(Var, Var, nullptr); + + Var = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Interpolation.InterpSpawn_InvSpawnTime")); + FoundHistory.AddVariable(Var, Var, nullptr); + + Var = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Interpolation.InterpSpawn_InvUpdateTime")); + FoundHistory.AddVariable(Var, Var, nullptr); + + Var = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Interpolation.SpawnInterp")); + FoundHistory.AddVariable(Var, Var, nullptr); + + Var = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Interpolation.Emitter_SpawnInterval")); + FoundHistory.AddVariable(Var, Var, nullptr); + + Var = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Interpolation.Emitter_InterpSpawnStartDt")); + FoundHistory.AddVariable(Var, Var, nullptr); + + Var = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Interpolation.Emitter_SpawnGroup")); + FoundHistory.AddVariable(Var, Var, nullptr); + } + + ParamMapHistories[ParamMapIdx] = (FoundHistory); + + TArray Entries; + Entries.AddZeroed(FoundHistory.Variables.Num()); + for (int32 i = 0; i < Entries.Num(); i++) + { + Entries[i] = INDEX_NONE; + } + ParamMapSetVariablesToChunks[ParamMapIdx] = (Entries); } - - if (RequiresInterpolation()) - { - FNiagaraVariable Var = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Interpolation.InterpSpawn_Index")); - FoundHistory.AddVariable(Var, Var, nullptr); - - Var = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Interpolation.InterpSpawn_SpawnTime")); - FoundHistory.AddVariable(Var, Var, nullptr); - - Var = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Interpolation.InterpSpawn_UpdateTime")); - FoundHistory.AddVariable(Var, Var, nullptr); - - Var = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Interpolation.InterpSpawn_InvSpawnTime")); - FoundHistory.AddVariable(Var, Var, nullptr); - - Var = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Interpolation.InterpSpawn_InvUpdateTime")); - FoundHistory.AddVariable(Var, Var, nullptr); - - Var = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Interpolation.SpawnInterp")); - FoundHistory.AddVariable(Var, Var, nullptr); - - Var = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Interpolation.Emitter_SpawnInterval")); - FoundHistory.AddVariable(Var, Var, nullptr); - - Var = FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Interpolation.Emitter_InterpSpawnStartDt")); - FoundHistory.AddVariable(Var, Var, nullptr); - - Var = FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Interpolation.Emitter_SpawnGroup")); - FoundHistory.AddVariable(Var, Var, nullptr); - } - - ParamMapHistories[ParamMapIdx] = (FoundHistory); - - TArray Entries; - Entries.AddZeroed(FoundHistory.Variables.Num()); - for (int32 i = 0; i < Entries.Num(); i++) - { - Entries[i] = INDEX_NONE; - } - ParamMapSetVariablesToChunks[ParamMapIdx] = (Entries); } } } @@ -844,6 +896,11 @@ const FNiagaraTranslateResults &FHlslNiagaraTranslator::Translate(const FNiagara CompilationOutput.ScriptData.ParameterCollectionPaths.AddUnique(FSoftObjectPath(Collection).ToString()); } } + ENiagaraScriptUsage Usage = CompileOptions.TargetUsage; + if (Usage != ENiagaraScriptUsage::SystemSpawnScript && Usage != ENiagaraScriptUsage::SystemUpdateScript) + { + ValidateParticleIDUsage(); + } //Create main scope pin cache. PinToCodeChunks.AddDefaulted(1); @@ -935,18 +992,28 @@ const FNiagaraTranslateResults &FHlslNiagaraTranslator::Translate(const FNiagara //Generate function definitions FString FunctionDefinitionString = GetFunctionDefinitions(); FunctionDefinitionString += TEXT("\n"); - - if (TranslationStages.Num() > 1 && RequiresInterpolation()) { - //ensure the interpolated spawn constants are part of the parameter set. - int32 OutputIdx = 0; - ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_ENGINE_DELTA_TIME, nullptr, 0, OutputIdx, nullptr); - ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_ENGINE_INV_DELTA_TIME, nullptr, 0, OutputIdx, nullptr); - ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_ENGINE_EXEC_COUNT, nullptr, 0, OutputIdx, nullptr); - ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_EMITTER_SPAWNRATE, nullptr, 0, OutputIdx, nullptr); - ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_EMITTER_SPAWN_INTERVAL, nullptr, 0, OutputIdx, nullptr); - ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_EMITTER_INTERP_SPAWN_START_DT, nullptr, 0, OutputIdx, nullptr); - ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_EMITTER_SPAWN_GROUP, nullptr, 0, OutputIdx, nullptr); + if (TranslationStages.Num() > 1 && RequiresInterpolation()) + { + int32 OutputIdx = 0; + //ensure the interpolated spawn constants are part of the parameter set. + ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_ENGINE_DELTA_TIME, nullptr, 0, OutputIdx, nullptr); + ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_ENGINE_INV_DELTA_TIME, nullptr, 0, OutputIdx, nullptr); + ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_ENGINE_EXEC_COUNT, nullptr, 0, OutputIdx, nullptr); + ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_EMITTER_SPAWNRATE, nullptr, 0, OutputIdx, nullptr); + ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_EMITTER_SPAWN_INTERVAL, nullptr, 0, OutputIdx, nullptr); + ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_EMITTER_INTERP_SPAWN_START_DT, nullptr, 0, OutputIdx, nullptr); + ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_EMITTER_SPAWN_GROUP, nullptr, 0, OutputIdx, nullptr); + } + + if (TranslationStages.Num() > 0) + { + int32 OutputIdx = 0; + // NOTE(mv): This will explicitly expose Engine.Emitter.TotalSpawnedParticles to the HLSL code regardless of whether it is exposed in a script or not. + ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_ENGINE_EMITTER_TOTAL_SPAWNED_PARTICLES, nullptr, 0, OutputIdx, nullptr); + ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_EMITTER_RANDOM_SEED, nullptr, 0, OutputIdx, nullptr); + //ParameterMapRegisterExternalConstantNamespaceVariable(SYS_PARAM_EMITTER_DETERMINISM, nullptr, 0, OutputIdx, nullptr); + } } // Generate the Parameter Map HLSL definitions. We don't add to the final HLSL output here. We just build up the strings and tables @@ -956,7 +1023,12 @@ const FNiagaraTranslateResults &FHlslNiagaraTranslator::Translate(const FNiagara for (FNiagaraTypeDefinition Type : StructsToDefine) { - HlslOutput += BuildHLSLStructDecl(Type); + FText ErrorMessage; + HlslOutput += BuildHLSLStructDecl(Type, ErrorMessage); + if (ErrorMessage.IsEmpty() == false) + { + Error(ErrorMessage, nullptr, nullptr); + } } //Declare parameters. @@ -1192,7 +1264,14 @@ const FNiagaraTranslateResults &FHlslNiagaraTranslator::Translate(const FNiagara // We may have created some transient data interfaces. This cleans up the ones that we created. CompilationOutput.ScriptData.DIParamInfo = DIParamInfo; - CompilationOutput.ScriptData.bReadsAttributeData = InstanceRead.Variables.Num() != 0; + if (InstanceRead.Variables.Num() == 1 && InstanceRead.Variables[0].GetName() == TEXT("Particles.UniqueID")) { + // NOTE(mv): Explicitly allow reading from Particles.UniqueID, as it is an engine managed variable and + // is written to before Simulate() in the SpawnScript... + // TODO(mv): Also allow Particles.ID for the same reasons? + CompilationOutput.ScriptData.bReadsAttributeData = false; + } else { + CompilationOutput.ScriptData.bReadsAttributeData = InstanceRead.Variables.Num() != 0; + } TranslateResults.OutputHLSL = HlslOutput; } @@ -1639,6 +1718,27 @@ void FHlslNiagaraTranslator::DefineMain(FString &OutHlslOutput, OutHlslOutput += FString::Printf(TEXT("\t%s.Particles.ID.Index = TempIDIndex;\n\t%s.Particles.ID.AcquireTag = TempIDTag;\n"), *MapName, *MapName); } + { + // Manually write to Particles.UniqueID on spawn, and deliberately place it at the top of SimulateMain to make sure it's initialized in the right order + + // NOTE(mv): These relies on Particles.UniqueID and Engine.Emitter.TotalSpawnedParticles both being explicitly added to the parameter histories in + // FHlslNiagaraTranslator::Translate. + + // NOTE(mv): This relies on Particles.UniqueID being excluded from being default initialized. + // This happens in FNiagaraParameterMapHistory::ShouldIgnoreVariableDefault + if (UNiagaraScript::IsParticleSpawnScript(CompileOptions.TargetUsage)) + { + FString MapName = UNiagaraScript::IsInterpolatedParticleSpawnScript(CompileOptions.TargetUsage) ? TEXT("Context.MapSpawn") : TEXT("Context.Map"); + OutHlslOutput += FString::Printf(TEXT("\t%s.Particles.UniqueID = Engine_Emitter_TotalSpawnedParticles + ExecIndex();\n"), *MapName); + } + else if (UNiagaraScript::IsGPUScript(CompileOptions.TargetUsage)) + { + // NOTE(mv): The GPU script only have one file, so we need to make sure we only apply this in the spawn phase. + // + OutHlslOutput += TEXT("\tif (Phase == 0) \n\t{\n\t\tContext.MapSpawn.Particles.UniqueID = Engine_Emitter_TotalSpawnedParticles + ExecIndex();\n\t}\n"); + } + } + // Fill in the defaults for parameters. for (int32 i = 0; i < MainPreSimulateChunks.Num(); ++i) { @@ -1701,7 +1801,7 @@ void FHlslNiagaraTranslator::DefineMain(FString &OutHlslOutput, } OutHlslOutput += FString::Printf(TEXT("\t\tSimulate%s(Context);\n"), TranslationStages.Num() > 1 ? *TranslationStages[StageIdx].PassNamespace : TEXT("")); - + if (StageIdx + 1 < TranslationStages.Num() && TranslationStages[StageIdx + 1].bCopyPreviousParams) { OutHlslOutput += TEXT("\t\t//Begin Transfer of Attributes!\n"); @@ -1720,6 +1820,7 @@ void FHlslNiagaraTranslator::DefineMain(FString &OutHlslOutput, OutHlslOutput += TEXT("\t\t//End Transfer of Attributes!\n\n"); } + // Either go on to the next phase, or write to the final output context. if (StageIdx + 1 < TranslationStages.Num() && TranslationStages[StageIdx + 1].bInterpolatePreviousParams) { @@ -2324,7 +2425,7 @@ int32 FHlslNiagaraTranslator::AddUniformChunk(FString SymbolName, const FNiagara Chunk.SymbolName = GetSanitizedSymbolName(SymbolName); Chunk.Type = Type; - if (CompileOptions.TargetUsage == ENiagaraScriptUsage::ParticleGPUComputeScript) + if (UNiagaraScript::IsGPUScript(CompileOptions.TargetUsage)) { if (Type == FNiagaraTypeDefinition::GetVec2Def()) { @@ -2462,7 +2563,9 @@ bool FHlslNiagaraTranslator::ShouldInterpolateParameter(const FNiagaraVariable& Parameter == SYS_PARAM_EMITTER_SPAWNRATE || Parameter == SYS_PARAM_EMITTER_SPAWN_INTERVAL || Parameter == SYS_PARAM_EMITTER_INTERP_SPAWN_START_DT || - Parameter == SYS_PARAM_EMITTER_SPAWN_GROUP ) + Parameter == SYS_PARAM_ENGINE_EMITTER_TOTAL_SPAWNED_PARTICLES || + Parameter == SYS_PARAM_EMITTER_RANDOM_SEED || + Parameter == SYS_PARAM_ENGINE_SYSTEM_TICK_COUNT) { return false; } @@ -2554,7 +2657,9 @@ int32 FHlslNiagaraTranslator::GetParameter(const FNiagaraVariable& Parameter) } int32 FuncParam = INDEX_NONE; - if (GetFunctionParameter(Parameter, FuncParam)) + const FNiagaraVariable* FoundKnownVariable = FNiagaraConstants::GetKnownConstant(Parameter.GetName(), false); + + if (FoundKnownVariable == nullptr && GetFunctionParameter(Parameter, FuncParam)) { if (FuncParam != INDEX_NONE) { @@ -2568,6 +2673,16 @@ int32 FHlslNiagaraTranslator::GetParameter(const FNiagaraVariable& Parameter) } } + if (FoundKnownVariable != nullptr) + { + FNiagaraVariable Var = *FoundKnownVariable; + //Some special variables can be replaced directly with constants which allows for extra optimization in the compiler. + if (GetLiteralConstantVariable(Var)) + { + return GetConstant(Var); + } + } + // We don't pass in the input node here (really there could be multiple nodes for the same parameter) // so we have to match up the input parameter map variable value through the pre-traversal histories // so that we know which parameter map we are referencing. @@ -2647,7 +2762,18 @@ int32 FHlslNiagaraTranslator::GetConstant(const FNiagaraVariable& Constant) return INDEX_NONE; } - FString ConstantStr = GenerateConstantString(Constant); + FString ConstantStr; + FNiagaraVariable LiteralConstant = Constant; + if (GetLiteralConstantVariable(LiteralConstant)) + { + checkf(LiteralConstant.GetType() == FNiagaraTypeDefinition::GetBoolDef(), TEXT("Only boolean types are currently supported for literal constants.")); + ConstantStr = LiteralConstant.GetValue() ? TEXT("true") : TEXT("false"); + } + else + { + ConstantStr = GenerateConstantString(Constant); + } + if (ConstantStr.IsEmpty()) { return INDEX_NONE; @@ -2921,15 +3047,15 @@ int32 FHlslNiagaraTranslator::GetAttribute(const FNiagaraVariable& Attribute) Error(FText::Format(LOCTEXT("GetConstantFail", "Cannot handle type {0}! Variable: {1}"), Attribute.GetType().GetNameText(), FText::FromName(Attribute.GetName())), nullptr, nullptr); } - if (TranslationStages.Num() > 1 && UNiagaraScript::IsParticleSpawnScript(TranslationStages[0].ScriptUsage)) + if (TranslationStages.Num() > 1 && UNiagaraScript::IsParticleSpawnScript(TranslationStages[0].ScriptUsage) && (Attribute.GetName() != TEXT("Particles.UniqueID"))) { if (ActiveStageIdx > 0) { //This is a special case where we allow the grabbing of attributes in the update section of an interpolated spawn script. //But we return the results of the previously ran spawn script. FString ParameterMapInstanceName = GetParameterMapInstanceName(0); - FNiagaraVariable NamespacedVar = FNiagaraParameterMapHistory::BasicAttributeToNamespacedAttribute(Attribute); + FNiagaraVariable NamespacedVar = Attribute; FString SymbolName = *(ParameterMapInstanceName + TEXT(".") + GetSanitizedSymbolName(NamespacedVar.GetName().ToString())); return AddSourceChunk(SymbolName, Attribute.GetType()); } @@ -2941,9 +3067,13 @@ int32 FHlslNiagaraTranslator::GetAttribute(const FNiagaraVariable& Attribute) } else { - CompilationOutput.ScriptData.DataUsage.bReadsAttributeData = true; + // NOTE(mv): Explicitly allow reading from Particles.UniqueID, as it is an engine managed variable and + // is written to before Simulate() in the SpawnScript... + // TODO(mv): Also allow Particles.ID for the same reasons? + CompilationOutput.ScriptData.DataUsage.bReadsAttributeData |= (Attribute.GetName() != TEXT("Particles.UniqueID")); + int32 Chunk = INDEX_NONE; - if (!ParameterMapRegisterUniformAttributeVariable(Attribute, nullptr, 0, Chunk)) + if (!ParameterMapRegisterNamespaceAttributeVariable(Attribute, nullptr, 0, Chunk)) { Error(FText::Format(LOCTEXT("AttrReadError", "Cannot read attribute {0} {1}."), Attribute.GetType().GetNameText(), FText::FromString(*Attribute.GetName().ToString())), nullptr, nullptr); return INDEX_NONE; @@ -3146,7 +3276,7 @@ bool FHlslNiagaraTranslator::RequiresInterpolation() const return false; } -bool FHlslNiagaraTranslator::GetLiteralConstantVariable(FNiagaraVariable& OutVar) +bool FHlslNiagaraTranslator::GetLiteralConstantVariable(FNiagaraVariable& OutVar) const { if (FNiagaraParameterMapHistory::IsInNamespace(OutVar, PARAM_MAP_EMITTER_STR) || FNiagaraParameterMapHistory::IsInNamespace(OutVar, PARAM_MAP_SYSTEM_STR)) { @@ -3157,6 +3287,12 @@ bool FHlslNiagaraTranslator::GetLiteralConstantVariable(FNiagaraVariable& OutVar OutVar.SetValue(bEmitterLocalSpace ? FNiagaraBool(true) : FNiagaraBool(false)); return true; } + if (OutVar == FNiagaraVariable(FNiagaraTypeDefinition::GetBoolDef(), TEXT("Emitter.Determinism"))) + { + bool bEmitterDeterminism = CompileOptions.AdditionalDefines.Contains(ResolvedVar.GetName().ToString()); + OutVar.SetValue(bEmitterDeterminism ? FNiagaraBool(true) : FNiagaraBool(false)); + return true; + } } return false; } @@ -3295,6 +3431,26 @@ bool FHlslNiagaraTranslator::ParameterMapRegisterUniformAttributeVariable(const return false; } +void FHlslNiagaraTranslator::ValidateParticleIDUsage() +{ + if (CompileOptions.AdditionalDefines.Contains(TEXT("RequiresPersistentIDs"))) + { + // persistent IDs are active and can be safely used as inputs + return; + } + FName particleIDName(TEXT("Particles.ID")); + for (FNiagaraParameterMapHistory& History : ParamMapHistories) + { + for (const FNiagaraVariable& Variable : History.Variables) + { + if (Variable.GetName() == particleIDName) + { + Error(LOCTEXT("PersistentIDActivationFail", "Before the Particles.ID parameter can be used, the 'Requires persistent IDs' option has to be activated in the emitter properties. Note that this comes with additional memory and CPU costs."), nullptr, nullptr); + } + } + } +} + bool FHlslNiagaraTranslator::ParameterMapRegisterNamespaceAttributeVariable(const FNiagaraVariable& InVariable, UNiagaraNode* InNode, int32 InParamMapHistoryIdx, int32& Output) { FString VarName = InVariable.GetName().ToString(); @@ -3977,9 +4133,7 @@ void FHlslNiagaraTranslator::Operation(class UNiagaraNodeOp* Operation, TArrayOpName); - - //EnterStatsScope(FNiagaraStatScope(*Operation->GetFullName(), OpInfo->FriendlyName)); - + TArray OutputPins; Operation->GetOutputPins(OutputPins); for (int32 OutputIndex = 0; OutputIndex < OutputPins.Num(); OutputIndex++) @@ -3994,11 +4148,22 @@ void FHlslNiagaraTranslator::Operation(class UNiagaraNodeOp* Operation, TArrayOutputs[OutputIndex]; - check(!IOInfo.HlslSnippet.IsEmpty()); - Outputs.Add(AddBodyChunk(GetUniqueSymbolName(IOInfo.Name), IOInfo.HlslSnippet, OutputType, Inputs)); + FString OutputHlsl; + if (OpInfo->bSupportsAddedInputs) + { + if (!OpInfo->CreateHlslForAddedInputs(Inputs.Num(), OutputHlsl)) + { + FText PinNameText = OutputPin->PinFriendlyName.IsEmpty() ? FText::FromName(OutputPin->PinName) : OutputPin->PinFriendlyName; + Error(FText::Format(LOCTEXT("AggregateInputFailTypePin", "Cannot create hlsl output for type {0}! Output Pin: {1}"), OutputType.GetNameText(), PinNameText), Operation, OutputPin); + OutputHlsl = IOInfo.HlslSnippet; + } + } + else { + OutputHlsl = IOInfo.HlslSnippet; + } + check(!OutputHlsl.IsEmpty()); + Outputs.Add(AddBodyChunk(GetUniqueSymbolName(IOInfo.Name), OutputHlsl, OutputType, Inputs)); } - - //ExitStatsScope(); } void FHlslNiagaraTranslator::FunctionCall(UNiagaraNodeFunctionCall* FunctionNode, TArray& Inputs, TArray& Outputs) @@ -4388,9 +4553,10 @@ void FHlslNiagaraTranslator::RegisterFunctionCall(ENiagaraScriptUsage ScriptUsag UNiagaraNodeOutput* FuncOutput = SourceGraph->FindOutputNode(ScriptUsage); check(FuncOutput); - // Go ahead and insert any defaulted values into the parameter map here at the top level. - if (ActiveHistoryForFunctionCalls.InTopLevelFunctionCall(CompileOptions.TargetUsage) && ActiveHistoryForFunctionCalls.GetModuleAlias() != nullptr) + if (ActiveHistoryForFunctionCalls.GetModuleAlias() != nullptr) { + bool bIsInTopLevelFunction = ActiveHistoryForFunctionCalls.InTopLevelFunctionCall(CompileOptions.TargetUsage); + UEdGraphPin* ParamMapPin = nullptr; for (UEdGraphPin* Pin : CallInputs) { @@ -4417,20 +4583,35 @@ void FHlslNiagaraTranslator::RegisterFunctionCall(ENiagaraScriptUsage ScriptUsag for (uint32 VarIdx = History.MapNodeVariableMetaData[FoundIdx].Key; VarIdx < History.MapNodeVariableMetaData[FoundIdx].Value; VarIdx++) { + if (History.PerVariableReadHistory[VarIdx].Num() == 0) + { + // We don't need to worry about defaults if the variable is only written to. + continue; + } + const FNiagaraVariable& Var = History.Variables[VarIdx]; const FNiagaraVariable& AliasedVar = History.VariablesWithOriginalAliasesIntact[VarIdx]; - int32 LastSetChunkIdx = ParamMapSetVariablesToChunks[ActiveStageIdx][VarIdx]; - if (LastSetChunkIdx == INDEX_NONE) + bool bIsAliased = Var.GetName() != AliasedVar.GetName(); + + // For non aliased values we resolve the defaults once at the top level since it's impossible to know which context they were actually used in, but + // for aliased values we check to see if they're used in the current context by resolving the alias and checking against the current resolved variable + // name since aliased values can only be resolved for reading in the correct context. + bool bIsValidForCurrentCallingContext = (bIsInTopLevelFunction && bIsAliased == false) || (bIsAliased && ActiveHistoryForFunctionCalls.ResolveAliases(AliasedVar).GetName() == Var.GetName()); + if (bIsValidForCurrentCallingContext) { - const UEdGraphPin* DefaultPin = History.GetDefaultValuePin(VarIdx); - HandleParameterRead(ActiveStageIdx, AliasedVar, DefaultPin, ParamNode, LastSetChunkIdx); - - // If this variable was in the pending defaults list, go ahead and remove it - // as we added it before first use... - if (DeferredVariablesMissingDefault.Contains(Var)) + int32 LastSetChunkIdx = ParamMapSetVariablesToChunks[ActiveStageIdx][VarIdx]; + if (LastSetChunkIdx == INDEX_NONE) { - DeferredVariablesMissingDefault.Remove(Var); - UniqueVarToChunk.Add(Var, LastSetChunkIdx); + const UEdGraphPin* DefaultPin = History.GetDefaultValuePin(VarIdx); + HandleParameterRead(ActiveStageIdx, AliasedVar, DefaultPin, ParamNode, LastSetChunkIdx); + + // If this variable was in the pending defaults list, go ahead and remove it + // as we added it before first use... + if (DeferredVariablesMissingDefault.Contains(Var)) + { + DeferredVariablesMissingDefault.Remove(Var); + UniqueVarToChunk.Add(Var, LastSetChunkIdx); + } } } } @@ -4580,7 +4761,7 @@ void FHlslNiagaraTranslator::RegisterFunctionCall(ENiagaraScriptUsage ScriptUsag return; } - if (Info.UserPtrIdx != INDEX_NONE) + if (Info.UserPtrIdx != INDEX_NONE && CompilationTarget != ENiagaraSimTarget::GPUComputeSim) { //This interface requires per instance data via a user ptr so place the index to it at the end of the inputs. Inputs.Add(AddSourceChunk(LexToString(Info.UserPtrIdx), FNiagaraTypeDefinition::GetIntDef(), false)); @@ -5006,7 +5187,7 @@ void FHlslNiagaraTranslator::Convert(class UNiagaraNodeConvert* Convert, TArray } } -void FHlslNiagaraTranslator::If(TArray& Vars, int32 Condition, TArray& PathA, TArray& PathB, TArray& Outputs) +void FHlslNiagaraTranslator::If(UNiagaraNodeIf* IfNode, TArray& Vars, int32 Condition, TArray& PathA, TArray& PathB, TArray& Outputs) { SCOPE_CYCLE_COUNTER(STAT_NiagaraEditor_HlslTranslator_If); @@ -5016,10 +5197,19 @@ void FHlslNiagaraTranslator::If(TArray& Vars, int32 Condition, TArray OutSymbols; OutSymbols.Reserve(Vars.Num()); + int32 PinIdx = 1; for (FNiagaraVariable& Var : Vars) { + FNiagaraTypeDefinition Type = Schema->PinToTypeDefinition(IfNode->GetInputPin(PinIdx++)); + if (!AddStructToDefinitionSet(Type)) + { + FText OutErrorMessage = FText::Format(LOCTEXT("UnknownNumeric", "Variable in If node uses invalid type. Var: {0} Type: {1}"), + FText::FromName(Var.GetName()), Type.GetNameText()); + + Error(OutErrorMessage, IfNode, nullptr); + } OutSymbols.Add(GetUniqueSymbolName(*(Var.GetName().ToString() + TEXT("_IfResult")))); - Outputs.Add(AddBodyChunk(OutSymbols.Last(), TEXT(""), Var.GetType(), true)); + Outputs.Add(AddBodyChunk(OutSymbols.Last(), TEXT(""), Type, true)); } AddBodyChunk(TEXT(""), TEXT("if({0})\n\t{"), FNiagaraTypeDefinition::GetFloatDef(), Condition, false, false); for (int32 i = 0; i < NumVars; ++i) @@ -5159,9 +5349,9 @@ void FHlslNiagaraTranslator::Error(FText ErrorText, const UNiagaraNode* Node, co } } } - if (Pin && Pin->PinFriendlyName.ToString().Len() > 0) + if (Pin) { - NodePinStr += TEXT(" Pin: ") + Pin->PinFriendlyName.ToString(); + NodePinStr += TEXT(" Pin: ") + (Pin->PinFriendlyName.ToString().Len() > 0 ? Pin->PinFriendlyName.ToString() : Pin->GetName()); NodePinSuffix = TEXT(" - "); } @@ -5388,14 +5578,17 @@ FString FHlslNiagaraTranslator::GetPropertyHlslTypeName(const UProperty* Propert const UEnumProperty* EnumProp = Cast(Property); return "int"; } + else if (Property->IsA(UBoolProperty::StaticClass())) + { + return "bool"; + } else { - check(false); // unknown type - return TEXT("UnknownType"); + return TEXT(""); } } -FString FHlslNiagaraTranslator::BuildHLSLStructDecl(FNiagaraTypeDefinition Type) +FString FHlslNiagaraTranslator::BuildHLSLStructDecl(FNiagaraTypeDefinition Type, FText& OutErrorMessage) { if (!IsBuiltInHlslType(Type)) { @@ -5405,7 +5598,14 @@ FString FHlslNiagaraTranslator::BuildHLSLStructDecl(FNiagaraTypeDefinition Type) for (TFieldIterator PropertyIt(Type.GetStruct(), EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt) { UProperty* Property = *PropertyIt; - Decl += TEXT("\t") + GetPropertyHlslTypeName(Property) + TEXT(" ") + Property->GetName() + TEXT(";\n"); + FString PropertyTypeName = GetPropertyHlslTypeName(Property); + if (PropertyTypeName.IsEmpty()) + { + OutErrorMessage = FText::Format(LOCTEXT("UnknownPropertyTypeErrorFormat", "Failed to build hlsl struct declaration for type {0}. Property {1} has an unsuported type {2}."), + FText::FromString(Type.GetName()), Property->GetDisplayNameText(), FText::FromString(Property->GetClass()->GetName())); + return TEXT(""); + } + Decl += TEXT("\t") + PropertyTypeName + TEXT(" ") + Property->GetName() + TEXT(";\n"); } Decl += "};\n\n"; return Decl; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.h index 1ff3ef90e28b..9f2e32d6b200 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraHlslTranslator.h @@ -304,7 +304,7 @@ protected: /** Map of Pins to compiled code chunks. Allows easy reuse of previously compiled pins. - A stack so that we can track pin reuse within function calls but not have cached pins cross talk with subsequent calls to the same funciton. + A stack so that we can track pin reuse within function calls but not have cached pins cross talk with subsequent calls to the same function. */ TArray> PinToCodeChunks; @@ -446,7 +446,7 @@ public: virtual void FunctionCall(UNiagaraNodeFunctionCall* FunctionNode, TArray& Inputs, TArray& Outputs); virtual void Convert(class UNiagaraNodeConvert* Convert, TArray & Inputs, TArray& Outputs); - virtual void If(TArray& Vars, int32 Condition, TArray& PathA, TArray& PathB, TArray& Outputs); + virtual void If(class UNiagaraNodeIf* IfNode, TArray& Vars, int32 Condition, TArray& PathA, TArray& PathB, TArray& Outputs); virtual void Error(FText ErrorText, const UNiagaraNode* Node, const UEdGraphPin* Pin); virtual void Warning(FText WarningText, const UNiagaraNode* Node, const UEdGraphPin* Pin); @@ -457,11 +457,15 @@ public: virtual ENiagaraScriptUsage GetTargetUsage() const; FGuid GetTargetUsageId() const; virtual ENiagaraScriptUsage GetCurrentUsage() const; + virtual ENiagaraSimTarget GetSimulationTarget() const + { + return CompilationTarget; + } static bool IsBuiltInHlslType(FNiagaraTypeDefinition Type); static FString GetStructHlslTypeName(FNiagaraTypeDefinition Type); static FString GetPropertyHlslTypeName(const UProperty* Property); - static FString BuildHLSLStructDecl(FNiagaraTypeDefinition Type); + static FString BuildHLSLStructDecl(FNiagaraTypeDefinition Type, FText& OutErrorMessage); static FString GetHlslDefaultForType(FNiagaraTypeDefinition Type); static bool IsHlslBuiltinVector(FNiagaraTypeDefinition Type); static TArray ConditionPropertyPath(const FNiagaraTypeDefinition& Type, const TArray& InPath); @@ -523,6 +527,8 @@ private: // Register an attribute in its namespaced form bool ParameterMapRegisterUniformAttributeVariable(const FNiagaraVariable& InVariable, UNiagaraNode* InNode, int32 InParamMapHistoryIdx, int32& Output); + // Checks that the Partices.ID parameter is only used if persistent IDs are active + void ValidateParticleIDUsage(); bool ValidateTypePins(UNiagaraNode* NodeToValidate); void GenerateFunctionSignature(ENiagaraScriptUsage ScriptUsage, FString InName, const FString& InFullName, UNiagaraGraph* FuncGraph, TArray& Inputs, bool bHadNumericInputs, bool bHasParameterMapParameters, FNiagaraFunctionSignature& OutSig)const; @@ -536,7 +542,7 @@ private: bool RequiresInterpolation() const; /** If OutVar can be replaced by a literal constant, it's data is initialized with the correct value and we return true. Returns false otherwise. */ - bool GetLiteralConstantVariable(FNiagaraVariable& OutVar); + bool GetLiteralConstantVariable(FNiagaraVariable& OutVar) const; /** Map of symbol names to count of times it's been used. Used for generating unique symbol names. */ TMap SymbolCounts; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNode.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNode.cpp index 2cb3aafa00aa..b7d04bce83d1 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNode.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNode.cpp @@ -421,6 +421,102 @@ UEdGraphPin* UNiagaraNode::GetPinByPersistentGuid(const FGuid& InPersistentGuid) return nullptr; } +void UNiagaraNode::NumericResolutionByPins(const UEdGraphSchema_Niagara* Schema, TArray& InputPins, TArray& OutputPins, + bool bFixInline, TMap, FNiagaraTypeDefinition>* PinCache) +{ + TArray InputTypes; + TArray OutputTypes; + + TArray NonNumericInputs; + for (UEdGraphPin* InputPin : InputPins) + { + FNiagaraTypeDefinition InputPinType = Schema->PinToTypeDefinition(InputPin); + if (InputPin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryType) + { + + // If the input pin is the generic numeric type set it to the type of the linked output pin which should have been processed already. + if (InputPinType == FNiagaraTypeDefinition::GetGenericNumericDef() && InputPin->LinkedTo.Num() == 1) + { + UEdGraphPin* InputPinLinkedPin = UNiagaraNode::TraceOutputPin(InputPin->LinkedTo[0]); + FNiagaraTypeDefinition InputPinLinkedPinType = Schema->PinToTypeDefinition(InputPinLinkedPin); + if (InputPinLinkedPinType.IsValid()) + { + if (InputPinLinkedPinType == FNiagaraTypeDefinition::GetGenericNumericDef() && PinCache) + { + FNiagaraTypeDefinition* FoundDef = PinCache->Find(TPair(InputPinLinkedPin->PinId, InputPinLinkedPin->GetOwningNode())); + if (FoundDef && FoundDef->IsValid()) + { + InputPinLinkedPinType = *FoundDef; + } + } + + // Only update the input pin type if the linked pin type is valid. + FEdGraphPinType PinType = Schema->TypeDefinitionToPinType(InputPinLinkedPinType); + if (bFixInline) + { + InputPin->PinType = PinType; + } + InputPinType = InputPinLinkedPinType; + } + } + + if (InputPinType != FNiagaraTypeDefinition::GetGenericNumericDef()) + { + NonNumericInputs.Add(InputPinType); + } + } + InputTypes.Add(InputPinType); + } + + // Fix up numeric outputs based on the inputs. + for (UEdGraphPin* OutputPin : OutputPins) + { + FNiagaraTypeDefinition OutputPinType = Schema->PinToTypeDefinition(OutputPin); + if (NonNumericInputs.Num() > 0 && GetNumericOutputTypeSelectionMode() != ENiagaraNumericOutputTypeSelectionMode::None) + { + FNiagaraTypeDefinition OutputNumericType = FNiagaraTypeDefinition::GetNumericOutputType(NonNumericInputs, GetNumericOutputTypeSelectionMode()); + if (OutputNumericType != FNiagaraTypeDefinition::GetGenericNumericDef()) + { + if (OutputPinType == FNiagaraTypeDefinition::GetGenericNumericDef()) + { + FEdGraphPinType PinType = Schema->TypeDefinitionToPinType(OutputNumericType); + if (bFixInline) + { + OutputPin->PinType = PinType; + } + OutputPinType = OutputNumericType; + } + } + } + OutputTypes.Add(OutputPinType); + } + + + if (PinCache) + { + int32 j; + for (j = 0; j < InputPins.Num(); j++) + { + PinCache->Add(TPair(InputPins[j]->PinId, InputPins[j]->GetOwningNode()), InputTypes[j]); + } + for (j = 0; j < OutputPins.Num(); j++) + { + PinCache->Add(TPair(OutputPins[j]->PinId, OutputPins[j]->GetOwningNode()), OutputTypes[j]); + } + } +} + + +void UNiagaraNode::ResolveNumerics(const UEdGraphSchema_Niagara* Schema, bool bSetInline, TMap, FNiagaraTypeDefinition>* PinCache) +{ + // Fix up numeric input pins and keep track of numeric types to decide the output type. + TArray InputPins; + GetInputPins(InputPins); + TArray OutputPins; + GetOutputPins(OutputPins); + NumericResolutionByPins(Schema, InputPins, OutputPins, bSetInline, PinCache); +} + ENiagaraNumericOutputTypeSelectionMode UNiagaraNode::GetNumericOutputTypeSelectionMode() const { return ENiagaraNumericOutputTypeSelectionMode::None; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeCustomHlsl.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeCustomHlsl.cpp index 5078dd662f75..c0c292543594 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeCustomHlsl.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeCustomHlsl.cpp @@ -63,7 +63,7 @@ bool UNiagaraNodeCustomHlsl::GetTokens(TArray& OutTokens) const return false; } - FString InputVars = TEXT(";/*+-)(?:, \t\n"); + FString InputVars = TEXT(";/*+-)(?:, []\t\n"); int32 LastValidIdx = INDEX_NONE; bool bComment = false; int32 TargetLength = HlslData.Len(); @@ -158,7 +158,7 @@ void UNiagaraNodeCustomHlsl::InitAsCustomHlslDynamicInput(const FNiagaraTypeDefi Modify(); ReallocatePins(); RequestNewTypedPin(EGPD_Input, FNiagaraTypeDefinition::GetParameterMapDef(), FName("Map")); - RequestNewTypedPin(EGPD_Output, OutputType, FName("Output")); + RequestNewTypedPin(EGPD_Output, OutputType, FName("CustomHLSLOutput")); ScriptUsage = ENiagaraScriptUsage::DynamicInput; } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeFunctionCall.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeFunctionCall.cpp index d0f5ffa4d5bb..01e1aa545994 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeFunctionCall.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeFunctionCall.cpp @@ -194,6 +194,17 @@ void UNiagaraNodeFunctionCall::AllocateDefaultPins() { ComputeNodeName(); } + +} + +// Returns true if this node is deprecated +bool UNiagaraNodeFunctionCall::IsDeprecated() const +{ + if (FunctionScript) + { + return FunctionScript->bDeprecated; + } + return false; } FText UNiagaraNodeFunctionCall::GetNodeTitle(ENodeTitleType::Type TitleType) const @@ -316,6 +327,21 @@ void UNiagaraNodeFunctionCall::Compile(class FHlslNiagaraTranslator* Translator, UNiagaraGraph* CallerGraph = GetNiagaraGraph(); if (FunctionScript) { + if (FunctionScript->bDeprecated && IsNodeEnabled()) + { + if (FunctionScript->DeprecationRecommendation) + { + Translator->Warning(FText::Format(LOCTEXT("DeprecationErrorFmtRecommendation", "Function call \"{0}\" is deprecated. Please use {1} instead."), FText::FromString(FunctionDisplayName), + FText::FromString(FunctionScript->DeprecationRecommendation->GetPathName())), + this, nullptr); + } + else + { + Translator->Warning(FText::Format(LOCTEXT("DeprecationErrorFmtUnknown", "Function call \"{0}\" is deprecated. No recommendation was provided."), FText::FromString(FunctionDisplayName)), + this, nullptr); + } + } + TArray CallerInputPins; GetInputPins(CallerInputPins); @@ -480,6 +506,22 @@ bool UNiagaraNodeFunctionCall::RefreshFromExternalChanges() bReload = true; } } + + if (FunctionScript->bDeprecated) + { + bHasCompilerMessage = true; + ErrorType = EMessageSeverity::Warning; + FFormatNamedArguments Args; + Args.Add(TEXT("NodeName"), GetNodeTitle(ENodeTitleType::ListView)); + Args.Add(TEXT("Recommendation"), FunctionScript->DeprecationRecommendation != nullptr ? FText::FromString(FunctionScript->DeprecationRecommendation->GetPathName()) : LOCTEXT("UnspecifiedName","Unspecified")); + ErrorMsg = FText::Format(LOCTEXT("DeprecationWarning", "{NodeName} is deprecated. Suggested replacement: {Recommendation}"), Args).ToString(); + } + else + { + bHasCompilerMessage = false; + ErrorMsg = FString(); + } + } else { @@ -528,7 +570,7 @@ void UNiagaraNodeFunctionCall::GatherExternalDependencyIDs(ENiagaraScriptUsage I // We don't know which graph type we're referencing, so we try them all... may need to replace this with something faster in the future. if (FunctionGraph) { - for (int32 i = (int32)ENiagaraScriptUsage::Function; i <= (int32)ENiagaraScriptUsage::Module; i++) + for (int32 i = (int32)ENiagaraScriptUsage::Function; i <= (int32)ENiagaraScriptUsage::DynamicInput; i++) { FGuid FoundGuid = FunctionGraph->GetCompileID((ENiagaraScriptUsage)i, FGuid(0, 0, 0, 0)); if (FoundGuid.IsValid()) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeIf.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeIf.cpp index 4e3401f87238..68d186a38825 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeIf.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeIf.cpp @@ -74,7 +74,9 @@ void UNiagaraNodeIf::PostLoad() bool UNiagaraNodeIf::AllowNiagaraTypeForAddPin(const FNiagaraTypeDefinition& InType) { - return Super::AllowNiagaraTypeForAddPin(InType) && InType != FNiagaraTypeDefinition::GetParameterMapDef(); + // Explicitly allow Numeric types, and explicitly disallow ParameterMap types + + return (Super::AllowNiagaraTypeForAddPin(InType) || InType == FNiagaraTypeDefinition::GetGenericNumericDef()) && InType != FNiagaraTypeDefinition::GetParameterMapDef(); } void UNiagaraNodeIf::AllocateDefaultPins() @@ -137,7 +139,29 @@ void UNiagaraNodeIf::Compile(class FHlslNiagaraTranslator* Translator, TArrayCompilePin(Pins[PinIdx++])); } - Translator->If(OutputVars, Condition, PathA, PathB, Outputs); + Translator->If(this, OutputVars, Condition, PathA, PathB, Outputs); +} + +ENiagaraNumericOutputTypeSelectionMode UNiagaraNodeIf::GetNumericOutputTypeSelectionMode() const +{ + return ENiagaraNumericOutputTypeSelectionMode::Largest; +} + + +void UNiagaraNodeIf::ResolveNumerics(const UEdGraphSchema_Niagara* Schema, bool bSetInline, TMap, FNiagaraTypeDefinition>* PinCache) +{ + int32 VarStartIdx = 1; + for (int32 i = 0; i < OutputVars.Num(); ++i) + { + // Fix up numeric input pins and keep track of numeric types to decide the output type. + TArray InputPins; + TArray OutputPins; + + InputPins.Add(Pins[i + VarStartIdx]); + InputPins.Add(Pins[i + VarStartIdx + OutputVars.Num()]); + OutputPins.Add(Pins[i + VarStartIdx + 2 * OutputVars.Num()]); + NumericResolutionByPins(Schema, InputPins, OutputPins, bSetInline, PinCache); + } } bool UNiagaraNodeIf::RefreshFromExternalChanges() diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeIf.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeIf.h index a9e7b229b891..6360ceab28fd 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeIf.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeIf.h @@ -13,16 +13,16 @@ class UNiagaraNodeIf : public UNiagaraNodeWithDynamicPins public: /** Outputs of this branch. */ - UPROPERTY(EditAnywhere, Category=If) + UPROPERTY() TArray OutputVars; - UPROPERTY(EditAnywhere, Category = If) + UPROPERTY() TArray OutputVarGuids; - UPROPERTY(EditAnywhere, Category = If) + UPROPERTY() TArray InputAVarGuids; - UPROPERTY(EditAnywhere, Category = If) + UPROPERTY() TArray InputBVarGuids; //~ Begin UObject Interface @@ -39,6 +39,8 @@ public: //~ Begin UNiagaraNode Interface virtual void Compile(class FHlslNiagaraTranslator* Translator, TArray& Outputs) override; virtual bool RefreshFromExternalChanges() override; + virtual ENiagaraNumericOutputTypeSelectionMode GetNumericOutputTypeSelectionMode() const; + virtual void ResolveNumerics(const UEdGraphSchema_Niagara* Schema, bool bSetInline, TMap, FNiagaraTypeDefinition>* PinCache); //~ End UNiagaraNode Interface protected: @@ -49,7 +51,7 @@ protected: //~ Begin EdGraphNode Interface virtual void OnPinRemoved(UEdGraphPin* PinToRemove) override; //~ End EdGraphNode Interface - + //~ Begin UNiagaraNodeWithDynamicPins Interface virtual void OnNewTypedPinAdded(UEdGraphPin* NewPin) override; virtual void OnPinRenamed(UEdGraphPin* RenamedPin, const FString& OldName) override; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeOp.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeOp.cpp index 97f559785580..fed968aac1b1 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeOp.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeOp.cpp @@ -20,6 +20,7 @@ void UNiagaraNodeOp::AllocateDefaultPins() const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); check(OpInfo); + // Create input pins from the op for (int32 SrcIndex = 0; SrcIndex < OpInfo->Inputs.Num(); ++SrcIndex) { const FNiagaraOpInOutInfo& InOutInfo = OpInfo->Inputs[SrcIndex]; @@ -32,7 +33,19 @@ void UNiagaraNodeOp::AllocateDefaultPins() Pin->AutogeneratedDefaultValue = InOutInfo.Default; Pin->PinToolTip = InOutInfo.Description.ToString(); } + + // Restore pins added by the user + for (int32 SrcIndex = 0; SrcIndex < AddedPins.Num(); ++SrcIndex) + { + FAddedPinData& OldPinData = AddedPins[SrcIndex]; + UEdGraphPin* Pin = CreatePin(EGPD_Input, OldPinData.PinType, OldPinData.PinName); + check(Pin); + Pin->bDefaultValueIsIgnored = false; + Pin->bDefaultValueIsReadOnly = false; + Pin->bNotConnectable = false; + } + // Create output pins from the op for (int32 OutIdx = 0; OutIdx < OpInfo->Outputs.Num(); ++OutIdx) { const FNiagaraOpInOutInfo& InOutInfo = OpInfo->Outputs[OutIdx]; @@ -40,6 +53,11 @@ void UNiagaraNodeOp::AllocateDefaultPins() check(Pin); Pin->PinToolTip = InOutInfo.Description.ToString(); } + + if (OpInfo->bSupportsAddedInputs) + { + CreateAddPin(EGPD_Input); + } } FText UNiagaraNodeOp::GetNodeTitle(ENodeTitleType::Type TitleType) const @@ -77,7 +95,7 @@ void UNiagaraNodeOp::Compile(class FHlslNiagaraTranslator* Translator, TArrayInputs.Num(); + int32 NumInputs = OpInfo->bSupportsAddedInputs ? Pins.Num() : OpInfo->Inputs.Num(); int32 NumOutputs = OpInfo->Outputs.Num(); TArray Inputs; @@ -85,7 +103,10 @@ void UNiagaraNodeOp::Compile(class FHlslNiagaraTranslator* Translator, TArrayDirection == EGPD_Input); + if (Pin->Direction != EGPD_Input || IsAddPin(Pin)) + { + continue; + } int32 CompiledInput = Translator->CompilePin(Pin); if (CompiledInput == INDEX_NONE) { @@ -141,6 +162,25 @@ void UNiagaraNodeOp::PostLoad() UE_LOG(LogNiagaraEditor, Log, TEXT("OpNode: Converted %s to %s, Package: %s"), *OriginalOpName.ToString(), *OpName.ToString(), *GetOutermost()->GetName()); } + + if (AllowDynamicPins()) + { + bool HasAddPin = false; + for (UEdGraphPin* Pin : Pins) + { + if (IsAddPin(Pin)) + { + HasAddPin = true; + break; + } + } + if (!HasAddPin) + { + // This adds the "plus" pin to nodes that were created before the feature was added + ReallocatePins(); + UE_LOG(LogNiagaraEditor, Log, TEXT("OpNode %s, Package %s: reallocated existing pins to add new extension pin"), *OpName.ToString(), *GetOutermost()->GetName()); + } + } } bool UNiagaraNodeOp::RefreshFromExternalChanges() @@ -157,4 +197,109 @@ ENiagaraNumericOutputTypeSelectionMode UNiagaraNodeOp::GetNumericOutputTypeSelec return OpInfo->NumericOuputTypeSelectionMode; } +bool UNiagaraNodeOp::AllowNiagaraTypeForAddPin(const FNiagaraTypeDefinition& InType) +{ + const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); + if (!OpInfo) + { + return false; + } + if (OpInfo->AddedInputTypeRestrictions.Num() == 0) + { + return true; + } + return OpInfo->AddedInputTypeRestrictions.Contains(InType); +} + +void UNiagaraNodeOp::OnPinRemoved(UEdGraphPin* PinToRemove) +{ + auto FindPredicate = [=](const FAddedPinData& PinData) + { + return PinData.PinName == PinToRemove->PinName && PinData.PinType == PinToRemove->PinType; + }; + AddedPins.RemoveAll(FindPredicate); + ReallocatePins(); +} + +bool UNiagaraNodeOp::AllowDynamicPins() const +{ + const FNiagaraOpInfo* OpInfo = FNiagaraOpInfo::GetOpInfo(OpName); + return OpInfo && OpInfo->bSupportsAddedInputs; +} + +void UNiagaraNodeOp::OnNewTypedPinAdded(UEdGraphPin* NewPin) +{ + FName UniqueName = GetUniqueAdditionalPinName(); + + FAddedPinData PinData; + PinData.PinType = NewPin->PinType; + PinData.PinName = UniqueName; + NewPin->PinName = UniqueName; + AddedPins.Add(PinData); +} + +void UNiagaraNodeOp::OnPinRenamed(UEdGraphPin* RenamedPin, const FString& OldName) +{ + TSet ExistingNames; + for (const FAddedPinData& PinData : AddedPins) + { + ExistingNames.Add(PinData.PinName); + } + FName UniqueName = FNiagaraUtilities::GetUniqueName(*RenamedPin->PinName.ToString(), ExistingNames); + FName OldPinName(*OldName); + + for (FAddedPinData& PinData : AddedPins) + { + if (PinData.PinName == OldPinName && PinData.PinType == RenamedPin->PinType) + { + PinData.PinName = UniqueName; + RenamedPin->PinName = UniqueName; + break; + } + } +} + +bool UNiagaraNodeOp::CanRemovePin(const UEdGraphPin* Pin) const +{ + if (!Pin || Pin->Direction != EGPD_Input || !Super::CanRemovePin(Pin)) + { + return false; + } + + // check if the pin was added by the user, only those can be removed + for (int32 SrcIndex = 0; SrcIndex < AddedPins.Num(); ++SrcIndex) + { + const FAddedPinData& PinData = AddedPins[SrcIndex]; + if (Pin->PinType == PinData.PinType && Pin->PinName == PinData.PinName) + { + return true; + } + } + return false; +} + +FName UNiagaraNodeOp::GetUniqueAdditionalPinName() const +{ + // count existing input pins + FString Name; + TSet ExistingNames; + for (UEdGraphPin* Pin : Pins) + { + if (Pin->Direction == EEdGraphPinDirection::EGPD_Input && !IsAddPin(Pin)) + { + ExistingNames.Add(Pin->PinName); + } + } + + // create a new name based on the existing pins (A, B, ... AA, AB, ...) + static FString Alphabet = TEXT("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + for (int32 Remaining = ExistingNames.Num(); Remaining > 0; Remaining = Remaining / 26) + { + int32 CharIndex = Remaining < 26 ? Remaining - 1 : Remaining % 26; + Name = FString() + (*Alphabet)[CharIndex] + Name; + } + + return FNiagaraUtilities::GetUniqueName(*Name, ExistingNames); +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapBase.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapBase.cpp index 8b374a38e127..de26a0facea3 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapBase.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeParameterMapBase.cpp @@ -91,7 +91,8 @@ TArray UNiagaraNodeParameterMapBase::GetParameterMa bool UNiagaraNodeParameterMapBase::AllowNiagaraTypeForAddPin(const FNiagaraTypeDefinition& InType) { - return InType != FNiagaraTypeDefinition::GetGenericNumericDef(); + return InType != FNiagaraTypeDefinition::GetGenericNumericDef() && + InType != FNiagaraTypeDefinition::GetParameterMapDef(); } FText UNiagaraNodeParameterMapBase::GetPinDescriptionText(UEdGraphPin* Pin) const diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeReroute.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeReroute.cpp index ecb26c40a7dd..6f70424fe2d5 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeReroute.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeReroute.cpp @@ -24,6 +24,12 @@ void UNiagaraNodeReroute::PostEditChangeProperty(FPropertyChangedEvent& Property } } + +ENiagaraNumericOutputTypeSelectionMode UNiagaraNodeReroute::GetNumericOutputTypeSelectionMode() const +{ + return ENiagaraNumericOutputTypeSelectionMode::Largest; +} + void UNiagaraNodeReroute::PostLoad() { Super::PostLoad(); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeReroute.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeReroute.h index 976070c2a1ad..7485217322c3 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeReroute.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeReroute.h @@ -34,6 +34,7 @@ public: virtual bool RefreshFromExternalChanges() override; virtual void PinConnectionListChanged(UEdGraphPin* Pin) override; void BuildParameterMapHistory(FNiagaraParameterMapHistoryBuilder& OutHistory, bool bRecursive) override; + virtual ENiagaraNumericOutputTypeSelectionMode GetNumericOutputTypeSelectionMode() const; /** Trace to an output pin that is not a reroute node output pin. If the reroute nodes resulted in a dead end and no output pin was found return nullptr.*/ virtual UEdGraphPin* GetTracedOutputPin(UEdGraphPin* LocallyOwnedOutputPin) const override; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeSimTargetSelector.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeSimTargetSelector.cpp new file mode 100644 index 000000000000..71ec0d8dc384 --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeSimTargetSelector.cpp @@ -0,0 +1,122 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "NiagaraNodeSimTargetSelector.h" +#include "NiagaraEditorUtilities.h" +#include "NiagaraHlslTranslator.h" + +#define LOCTEXT_NAMESPACE "NiagaraNodeSimTargetSelector" + +UNiagaraNodeSimTargetSelector::UNiagaraNodeSimTargetSelector(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void UNiagaraNodeSimTargetSelector::AllocateDefaultPins() +{ + const UEdGraphSchema_Niagara* Schema = GetDefault(); + + // create all the cpu input pins + for (FNiagaraVariable& Var : OutputVars) + { + FEdGraphPinType PinType = Schema->TypeDefinitionToPinType(Var.GetType()); + CreatePin(EGPD_Input, PinType, *(Var.GetName().ToString() + TEXT(" if CPU VM"))); + } + + // create all the gpu input pins + for (FNiagaraVariable& Var : OutputVars) + { + FEdGraphPinType PinType = Schema->TypeDefinitionToPinType(Var.GetType()); + CreatePin(EGPD_Input, PinType, *(Var.GetName().ToString() + TEXT(" if GPU Shader"))); + } + + // create the output pins + for (int32 Index = 0; Index < OutputVars.Num(); Index++) + { + const FNiagaraVariable& Var = OutputVars[Index]; + UEdGraphPin* NewPin = CreatePin(EGPD_Output, Schema->TypeDefinitionToPinType(Var.GetType()), Var.GetName()); + NewPin->PersistentGuid = OutputVarGuids[Index]; + } + + CreateAddPin(EGPD_Output); +} + +void UNiagaraNodeSimTargetSelector::InsertInputPinsFor(const FNiagaraVariable& Var) +{ + const UEdGraphSchema_Niagara* Schema = GetDefault(); + + TArray OldPins(Pins); + Pins.Reset(Pins.Num() + 2); + + // Create the inputs for each simulation target. + for (int64 i = 0; i < 2; i++) + { + // Add the previous input pins + for (int32 k = 0; k < OutputVars.Num() - 1; k++) + { + Pins.Add(OldPins[k]); + } + OldPins.RemoveAt(0, OutputVars.Num() - 1); + + // Add the new input pin + const FString PathSuffix = i == 0 ? TEXT(" if CPU VM") : TEXT(" if GPU Shader"); + CreatePin(EGPD_Input, Schema->TypeDefinitionToPinType(Var.GetType()), *(Var.GetName().ToString() + PathSuffix)); + } + + // Move the rest of the old pins over + Pins.Append(OldPins); +} + +void UNiagaraNodeSimTargetSelector::Compile(class FHlslNiagaraTranslator* Translator, TArray& Outputs) +{ + TArray InputPins; + GetInputPins(InputPins); + TArray OutputPins; + GetOutputPins(OutputPins); + + ENiagaraSimTarget SimulationTarget = Translator->GetSimulationTarget(); + int32 VarIdx; + if (SimulationTarget == ENiagaraSimTarget::CPUSim) + { + VarIdx = 0; + } + else if (SimulationTarget == ENiagaraSimTarget::GPUComputeSim) + { + VarIdx = InputPins.Num() / 2; + } + else + { + Translator->Error(LOCTEXT("InvalidSimTarget", "Unknown simulation target"), this, nullptr); + return; + } + + Outputs.SetNumUninitialized(OutputPins.Num()); + for (int32 i = 0; i < OutputVars.Num(); i++) + { + int32 InputIdx = Translator->CompilePin(InputPins[VarIdx + i]); + Outputs[i] = InputIdx; + } + check(this->IsAddPin(OutputPins[OutputPins.Num() - 1])); + Outputs[OutputPins.Num() - 1] = INDEX_NONE; +} + +UEdGraphPin* UNiagaraNodeSimTargetSelector::GetPassThroughPin(const UEdGraphPin* LocallyOwnedOutputPin, ENiagaraScriptUsage MasterUsage) const +{ + return nullptr; +} + +void UNiagaraNodeSimTargetSelector::BuildParameterMapHistory(FNiagaraParameterMapHistoryBuilder& OutHistory, bool bRecursive) +{ + UNiagaraNode::BuildParameterMapHistory(OutHistory, bRecursive); +} + +FText UNiagaraNodeSimTargetSelector::GetTooltipText() const +{ + return LOCTEXT("SimTargetSelectorDesc", "If the simulation target matches, then the traversal will follow that path."); +} + +FText UNiagaraNodeSimTargetSelector::GetNodeTitle(ENodeTitleType::Type TitleType) const +{ + return LOCTEXT("SimTargetSelectorTitle", "Select by Simulation Target"); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeSimTargetSelector.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeSimTargetSelector.h new file mode 100644 index 000000000000..1b3a3c98e3e5 --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeSimTargetSelector.h @@ -0,0 +1,30 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "EdGraphSchema_Niagara.h" +#include "NiagaraNodeUsageSelector.h" +#include "NiagaraNodeSimTargetSelector.generated.h" + +UCLASS(MinimalAPI) +class UNiagaraNodeSimTargetSelector: public UNiagaraNodeUsageSelector +{ + GENERATED_UCLASS_BODY() + +public: + //~ Begin EdGraphNode Interface + virtual void AllocateDefaultPins() override; + virtual FText GetTooltipText() const override; + virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; + //~ End EdGraphNode Interface + + //~ Begin UNiagaraNode Interface + virtual void Compile(class FHlslNiagaraTranslator* Translator, TArray& Outputs) override; + void BuildParameterMapHistory(FNiagaraParameterMapHistoryBuilder& OutHistory, bool bRecursive) override; + virtual UEdGraphPin* GetPassThroughPin(const UEdGraphPin* LocallyOwnedOutputPin, ENiagaraScriptUsage MasterUsage) const override; + //~ End UNiagaraNode Interface + +protected: + //~ Begin UNiagaraNodeUsageSelector Interface + virtual void InsertInputPinsFor(const FNiagaraVariable& Var) override; + //~ End UNiagaraNodeUsageSelector Interface +}; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeUsageSelector.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeUsageSelector.cpp index cc406cb55051..3b3eee5c3e51 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeUsageSelector.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeUsageSelector.cpp @@ -32,10 +32,37 @@ bool UNiagaraNodeUsageSelector::AllowNiagaraTypeForAddPin(const FNiagaraTypeDefi return Super::AllowNiagaraTypeForAddPin(InType) && InType != FNiagaraTypeDefinition::GetParameterMapDef(); } +void UNiagaraNodeUsageSelector::InsertInputPinsFor(const FNiagaraVariable& Var) +{ + const UEdGraphSchema_Niagara* Schema = GetDefault(); + UEnum* ENiagaraScriptGroupEnum = FindObject(ANY_PACKAGE, TEXT("ENiagaraScriptGroup"), true); + int64 GroupCount = (int64)ENiagaraScriptGroup::Max; + + TArray OldPins(Pins); + Pins.Reset(Pins.Num() + GroupCount); + + // Create the inputs for each path. + for (int64 i = 0; i < GroupCount; i++) + { + // Add the previous input pins + for (int32 k = 0; k < OutputVars.Num() - 1; k++) + { + Pins.Add(OldPins[k]); + } + OldPins.RemoveAt(0, OutputVars.Num() - 1); + + // Add the new input pin + const FString PathSuffix = ENiagaraScriptGroupEnum ? (FString::Printf(TEXT(" if %s"), *ENiagaraScriptGroupEnum->GetNameStringByValue((int64)i))) : TEXT("Error Unknown!"); + CreatePin(EGPD_Input, Schema->TypeDefinitionToPinType(Var.GetType()), *(Var.GetName().ToString() + PathSuffix)); + } + + // Move the rest of the old pins over + Pins.Append(OldPins); +} + void UNiagaraNodeUsageSelector::AllocateDefaultPins() { const UEdGraphSchema_Niagara* Schema = GetDefault(); - UEnum* ENiagaraScriptGroupEnum = FindObject(ANY_PACKAGE, TEXT("ENiagaraScriptGroup"), true); //Create the inputs for each path. @@ -137,6 +164,33 @@ UEdGraphPin* UNiagaraNodeUsageSelector::GetPassThroughPin(const UEdGraphPin* Loc return nullptr; } +void UNiagaraNodeUsageSelector::AppendFunctionAliasForContext(const FNiagaraGraphFunctionAliasContext& InFunctionAliasContext, FString& InOutFunctionAlias) +{ + FString UsageString; + switch (InFunctionAliasContext.CompileUsage) + { + case ENiagaraScriptUsage::SystemSpawnScript: + case ENiagaraScriptUsage::SystemUpdateScript: + UsageString = "System"; + break; + case ENiagaraScriptUsage::EmitterSpawnScript: + case ENiagaraScriptUsage::EmitterUpdateScript: + UsageString = "Emitter"; + break; + case ENiagaraScriptUsage::ParticleSpawnScript: + case ENiagaraScriptUsage::ParticleUpdateScript: + case ENiagaraScriptUsage::ParticleEventScript: + case ENiagaraScriptUsage::ParticleGPUComputeScript: + UsageString = "Particle"; + break; + } + + if (UsageString.IsEmpty() == false) + { + InOutFunctionAlias += "_" + UsageString; + } +} + void UNiagaraNodeUsageSelector::BuildParameterMapHistory(FNiagaraParameterMapHistoryBuilder& OutHistory, bool bRecursive) { const UEdGraphSchema_Niagara* Schema = CastChecked(GetSchema()); @@ -208,14 +262,17 @@ void UNiagaraNodeUsageSelector::OnNewTypedPinAdded(UEdGraphPin* NewPin) { OutputNames.Add(Output.GetName()); } - FName OutputName = FNiagaraUtilities::GetUniqueName(*OutputType.GetNameText().ToString(), OutputNames); - + FName OutputName = FNiagaraUtilities::GetUniqueName(*NewPin->GetName(), OutputNames); + NewPin->PinName = OutputName; FGuid Guid = AddOutput(OutputType, OutputName); // Update the pin's data too so that it's connection is maintained after reallocating. NewPin->PersistentGuid = Guid; - ReallocatePins(); + // We cannot just reallocate the pins here, because that invalidates all pins of this node (including + // the NewPin parameter). If the calling method tries to access the provided new pin afterwards, it + // runs into a nullptr error (e.g. when called by drag and drop). + InsertInputPinsFor(OutputVars.Last()); } void UNiagaraNodeUsageSelector::OnPinRenamed(UEdGraphPin* RenamedPin, const FString& OldName) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeUsageSelector.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeUsageSelector.h index a003d0ffa42c..e58e07ab44ae 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeUsageSelector.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeUsageSelector.h @@ -11,10 +11,10 @@ class UNiagaraNodeUsageSelector: public UNiagaraNodeWithDynamicPins GENERATED_UCLASS_BODY() public: - UPROPERTY(EditAnywhere, Category=If) + UPROPERTY() TArray OutputVars; - UPROPERTY(EditAnywhere, Category=If) + UPROPERTY() TArray OutputVarGuids; //~ Begin UObject Interface @@ -40,6 +40,8 @@ public: FGuid AddOutput(FNiagaraTypeDefinition Type, const FName& Name); virtual UEdGraphPin* GetPassThroughPin(const UEdGraphPin* LocallyOwnedOutputPin, ENiagaraScriptUsage MasterUsage) const override; + virtual void AppendFunctionAliasForContext(const FNiagaraGraphFunctionAliasContext& InFunctionAliasContext, FString& InOutFunctionAlias) override; + protected: //~ Begin EdGraphNode Interface virtual void OnPinRemoved(UEdGraphPin* PinToRemove) override; @@ -53,4 +55,6 @@ protected: virtual bool CanMovePin(const UEdGraphPin* Pin) const override { return false; } virtual bool AllowNiagaraTypeForAddPin(const FNiagaraTypeDefinition& InType) override; //~ End UNiagaraNodeWithDynamicPins Interface + + virtual void InsertInputPinsFor(const FNiagaraVariable& Var); }; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraParameterMapHistory.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraParameterMapHistory.cpp index bc65d83cbbd1..2646f7747399 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraParameterMapHistory.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraParameterMapHistory.cpp @@ -673,8 +673,12 @@ UNiagaraParameterCollection* FNiagaraParameterMapHistory::IsParameterCollectionP bool FNiagaraParameterMapHistory::ShouldIgnoreVariableDefault(const FNiagaraVariable& Var)const { - //For now just skip default for ID but maybe other cases/reasons? - return Var == FNiagaraVariable(FNiagaraTypeDefinition::GetIDDef(), TEXT("Particles.ID")); + // NOTE(mv): Used for variables that are explicitly assigned to (on spawn) and should not be default initialized + // These are explicitly written to in NiagaraHlslTranslator::DefineMain + bool bShouldBeIgnored = false; + bShouldBeIgnored |= (Var == FNiagaraVariable(FNiagaraTypeDefinition::GetIDDef(), TEXT("Particles.ID"))); + bShouldBeIgnored |= (Var == FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Particles.UniqueID"))); + return bShouldBeIgnored; } FNiagaraParameterMapHistoryBuilder::FNiagaraParameterMapHistoryBuilder() @@ -1050,7 +1054,7 @@ bool FNiagaraParameterMapHistoryBuilder::IsInEncounteredEmitterNamespace(FNiagar /** * Use the current alias map to resolve any aliases in this input variable name. */ -FNiagaraVariable FNiagaraParameterMapHistoryBuilder::ResolveAliases(const FNiagaraVariable& InVar) +FNiagaraVariable FNiagaraParameterMapHistoryBuilder::ResolveAliases(const FNiagaraVariable& InVar) const { return FNiagaraParameterMapHistory::ResolveAliases(InVar, AliasMap, TEXT(".")); } @@ -1341,7 +1345,8 @@ int32 FNiagaraParameterMapHistoryBuilder::HandleExternalVariableRead(int32 Param } else { - UE_LOG(LogNiagaraEditor, Log, TEXT("Could not resolve variable: %s"), *Name.ToString()); + // This is overly spammy and doesn't provide useful info. Disabling for now. + //UE_LOG(LogNiagaraEditor, Log, TEXT("Could not resolve variable: %s"), *Name.ToString()); } } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptFactory.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptFactory.cpp index 963860e0b4f2..2f33a4bd2e10 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptFactory.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptFactory.cpp @@ -41,7 +41,7 @@ UObject* UNiagaraScriptFactoryNew::FactoryCreateNew(UClass* Class, UObject* InPa check(Settings); UNiagaraScript* NewScript; - const FStringAssetReference& SettingDefaultScript = GetDefaultScriptFromSettings(Settings); + const FSoftObjectPath& SettingDefaultScript = GetDefaultScriptFromSettings(Settings); if (UNiagaraScript* Default = Cast(SettingDefaultScript.TryLoad())) { NewScript = Cast(StaticDuplicateObject(Default, InParent, Name, Flags, Class)); @@ -58,7 +58,7 @@ UObject* UNiagaraScriptFactoryNew::FactoryCreateNew(UClass* Class, UObject* InPa return NewScript; } -const FStringAssetReference& UNiagaraScriptFactoryNew::GetDefaultScriptFromSettings(const UNiagaraEditorSettings* Settings) +const FSoftObjectPath& UNiagaraScriptFactoryNew::GetDefaultScriptFromSettings(const UNiagaraEditorSettings* Settings) { switch (GetScriptUsage()) { diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptSource.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptSource.cpp index 5eaa602b5e96..c921f8fa8e46 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptSource.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraScriptSource.cpp @@ -153,7 +153,7 @@ void UNiagaraScriptSource::PostLoadFromEmitter(UNiagaraEmitter& OwningEmitter) bool UNiagaraScriptSource::AddModuleIfMissing(FString ModulePath, ENiagaraScriptUsage Usage, bool& bOutFoundModule) { - FStringAssetReference SystemUpdateScriptRef(ModulePath); + FSoftObjectPath SystemUpdateScriptRef(ModulePath); FAssetData ModuleScriptAsset; ModuleScriptAsset.ObjectPath = SystemUpdateScriptRef.GetAssetPathName(); bOutFoundModule = false; @@ -185,6 +185,13 @@ void InitializeNewRapidIterationParametersForNode(const UEdGraphSchema_Niagara* for (const UEdGraphPin* FunctionInputPin : FunctionInputPins) { FNiagaraTypeDefinition InputType = Schema->PinToTypeDefinition(FunctionInputPin); + if (InputType.IsValid() == false) + { + UE_LOG(LogNiagaraEditor, Error, TEXT("Invalid input type found while attempting initialize new rapid iteration parameters. Function Node: %s %s Input Name: %s"), + *FunctionCallNode->GetPathName(), *FunctionCallNode->GetFunctionName(), *FunctionInputPin->GetName()); + continue; + } + if (FNiagaraStackGraphUtilities::IsRapidIterationType(InputType)) { FNiagaraParameterHandle AliasedFunctionInputHandle = FNiagaraParameterHandle::CreateAliasedModuleParameterHandle(FNiagaraParameterHandle(FunctionInputPin->PinName), FunctionCallNode); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraStackEditorData.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraStackEditorData.cpp index b36eaaa74ee4..46b90ae5f2ce 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraStackEditorData.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraStackEditorData.cpp @@ -27,6 +27,20 @@ void UNiagaraStackEditorData::SetStackEntryIsExpanded(const FString& StackEntryK } } +bool UNiagaraStackEditorData::GetStackEntryWasExpandedPreSearch(const FString& StackEntryKey, bool bWasExpandedPreSearchDefault) const +{ + const bool* bWasExpandedPreSearchPtr = StackEntryKeyToPreSearchExpandedMap.Find(StackEntryKey); + return bWasExpandedPreSearchPtr != nullptr ? *bWasExpandedPreSearchPtr : bWasExpandedPreSearchDefault; +} + +void UNiagaraStackEditorData::SetStackEntryWasExpandedPreSearch(const FString& StackEntryKey, bool bWasExpandedPreSearch) +{ + if (ensureMsgf(StackEntryKey.IsEmpty() == false, TEXT("Can not set the pre-search expanded state with an empty key"))) + { + StackEntryKeyToPreSearchExpandedMap.FindOrAdd(StackEntryKey) = bWasExpandedPreSearch; + } +} + bool UNiagaraStackEditorData::GetStackItemShowAdvanced(const FString& StackEntryKey, bool bShowAdvancedDefault) const { const bool* bShowAdvancedPtr = StackItemKeyToShowAdvancedMap.Find(StackEntryKey); @@ -61,6 +75,16 @@ void UNiagaraStackEditorData::SetShowOutputs(bool bInShowOutputs) bShowOutputs = bInShowOutputs; } +bool UNiagaraStackEditorData::GetShowLinkedInputs() const +{ + return bShowLinkedInputs; +} + +void UNiagaraStackEditorData::SetShowLinkedInputs(bool bInShowLinkedInputs) +{ + bShowLinkedInputs = bInShowLinkedInputs; +} + double UNiagaraStackEditorData::GetLastScrollPosition() const { return LastScrollPosition; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraSystemEditorData.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraSystemEditorData.cpp index 7d32f195b84a..dad03465249a 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraSystemEditorData.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraSystemEditorData.cpp @@ -56,7 +56,7 @@ UNiagaraSystemEditorData::UNiagaraSystemEditorData(const FObjectInitializer& Obj { RootFolder = ObjectInitializer.CreateDefaultSubobject(this, TEXT("RootFolder")); StackEditorData = ObjectInitializer.CreateDefaultSubobject(this, TEXT("StackEditorData")); - OwnerTransform.SetLocation(FVector(0.0f, 0.0f, 100.0f)); + OwnerTransform.SetLocation(FVector(0.0f, 0.0f, 0.0f)); PlaybackRangeMin = 0; PlaybackRangeMax = 10; } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraSystemFactoryNew.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraSystemFactoryNew.cpp index 7b5115d667db..157798b46b37 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraSystemFactoryNew.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraSystemFactoryNew.cpp @@ -162,7 +162,7 @@ void UNiagaraSystemFactoryNew::InitializeSystem(UNiagaraSystem* System, bool bCr if (bCreateDefaultNodes) { - FStringAssetReference SystemUpdateScriptRef(TEXT("/Niagara/Modules/System/SystemLifeCycle.SystemLifeCycle")); + FSoftObjectPath SystemUpdateScriptRef(TEXT("/Niagara/Modules/System/SystemLifeCycle.SystemLifeCycle")); UNiagaraScript* Script = Cast(SystemUpdateScriptRef.TryLoad()); FAssetData ModuleScriptAsset(Script); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraScriptToolkit.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraScriptToolkit.cpp index 9eeb467b4f65..3e139feda511 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraScriptToolkit.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraScriptToolkit.cpp @@ -51,7 +51,7 @@ DECLARE_CYCLE_STAT(TEXT("Niagara - ScriptToolkit - OnApply"), STAT_NiagaraEditor static TAutoConsoleVariable CVarDevDetails( TEXT("fx.DevDetailsPanels"), - 0, + 1, TEXT("Whether to enable the development details panels inside Niagara.")); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraSystemToolkit.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraSystemToolkit.cpp index 9482153f1f2e..32bfd01cce98 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraSystemToolkit.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Toolkits/NiagaraSystemToolkit.cpp @@ -998,6 +998,7 @@ void FNiagaraSystemToolkit::UpdateOriginalEmitter() // overwrite the original script in place by constructing a new one with the same name Emitter = (UNiagaraEmitter*)StaticDuplicateObject(EditableEmitter, Emitter->GetOuter(), Emitter->GetFName(), RF_AllFlags, Emitter->GetClass()); + Emitter->PostEditChange(); TArray EmitterScripts; Emitter->GetScripts(EmitterScripts, false); @@ -1036,6 +1037,7 @@ void FNiagaraSystemToolkit::UpdateOriginalEmitter() { Emitter->MarkPackageDirty(); Emitter->ThumbnailImage = (UTexture2D*)StaticDuplicateObject(EditableEmitter->ThumbnailImage, Emitter); + Emitter->PostEditChange(); bEmitterThumbnailUpdated = false; } } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraCollectionParameterViewModel.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraCollectionParameterViewModel.cpp index b8482d94b623..732e58122674 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraCollectionParameterViewModel.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraCollectionParameterViewModel.cpp @@ -33,7 +33,7 @@ FName FNiagaraCollectionParameterViewModel::GetName() const FText FNiagaraCollectionParameterViewModel::GetTypeDisplayName() const { - return FText::Format(LOCTEXT("TypeTextFormat", "Type: {0}"), Parameter.GetType().GetStruct()->GetDisplayNameText()); + return FText::Format(LOCTEXT("TypeTextFormat", "Type: {0}"), Parameter.GetType().GetNameText()); } void FNiagaraCollectionParameterViewModel::NameTextComitted(const FText& Name, ETextCommit::Type CommitInfo) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraScriptParameterViewModel.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraScriptParameterViewModel.cpp index fc3e9904840e..2f85750a4165 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraScriptParameterViewModel.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraScriptParameterViewModel.cpp @@ -73,7 +73,7 @@ FText FNiagaraScriptParameterViewModel::GetTypeDisplayName() const { return FText(); } - return FText::Format(LOCTEXT("TypeTextFormat", "Type: {0}"), GraphVariable->GetType().GetStruct()->GetDisplayNameText()); + return FText::Format(LOCTEXT("TypeTextFormat", "Type: {0}"), GraphVariable->GetType().GetNameText()); } void FNiagaraScriptParameterViewModel::NameTextComitted(const FText& Name, ETextCommit::Type CommitInfo) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraSystemViewModel.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraSystemViewModel.cpp index 207b69bd468c..bd19db78babf 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraSystemViewModel.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/NiagaraSystemViewModel.cpp @@ -983,7 +983,9 @@ void FNiagaraSystemViewModel::ReInitializeSystemInstances() { if (Sequencer.IsValid() && Sequencer->GetPlaybackStatus() == EMovieScenePlayerStatus::Playing) { + Sequencer->SetPlaybackStatus(EMovieScenePlayerStatus::Stopped); Sequencer->SetGlobalTime(0); + Sequencer->SetPlaybackStatus(EMovieScenePlayerStatus::Playing); } for (TObjectIterator ComponentIt; ComponentIt; ++ComponentIt) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEntry.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEntry.cpp index 70831de04645..877cc48f893f 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEntry.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackEntry.cpp @@ -115,9 +115,16 @@ const TArray& UNiagaraStackEntry::FStackIssu return Fixes; } +void UNiagaraStackEntry::FStackIssue::InsertFix(int32 InsertionIdx, const UNiagaraStackEntry::FStackIssueFix& Fix) +{ + Fixes.Insert(Fix, InsertionIdx); +} + + UNiagaraStackEntry::UNiagaraStackEntry() : IndentLevel(0) , bIsFinalized(false) + , bIsSearchResult(false) { } @@ -230,14 +237,14 @@ bool UNiagaraStackEntry::GetShouldShowInStack() const return true; } -void UNiagaraStackEntry::GetFilteredChildren(TArray& OutFilteredChildren) +void UNiagaraStackEntry::GetFilteredChildren(TArray& OutFilteredChildren) const { OutFilteredChildren.Append(ErrorChildren); for (UNiagaraStackEntry* Child : Children) { bool bPassesFilter = true; - for(const FOnFilterChild& ChildFilter : ChildFilters) - { + for (const FOnFilterChild& ChildFilter : ChildFilters) + { if (ChildFilter.Execute(*Child) == false) { bPassesFilter = false; @@ -252,12 +259,6 @@ void UNiagaraStackEntry::GetFilteredChildren(TArray& OutFil } } -void UNiagaraStackEntry::GetUnfilteredChildren(TArray& OutUnfilteredChildren) -{ - OutUnfilteredChildren.Append(ErrorChildren); - OutUnfilteredChildren.Append(Children); -} - void UNiagaraStackEntry::GetUnfilteredChildren(TArray& OutUnfilteredChildren) const { OutUnfilteredChildren.Append(ErrorChildren); @@ -319,7 +320,6 @@ int32 UNiagaraStackEntry::GetIndentLevel() const void UNiagaraStackEntry::GetSearchItems(TArray& SearchItems) const { SearchItems.Add({FName("DisplayName"), GetDisplayName()}); - GetAdditionalSearchItemsInternal(SearchItems); } UObject* UNiagaraStackEntry::GetExternalAsset() const @@ -372,8 +372,14 @@ void UNiagaraStackEntry::SetOnRequestDrop(FOnRequestDrop InOnRequestDrop) OnRequestDropDelegate = InOnRequestDrop; } -void UNiagaraStackEntry::GetAdditionalSearchItemsInternal(TArray& SearchItems) const +const bool UNiagaraStackEntry::GetIsSearchResult() const { + return bIsSearchResult; +} + +void UNiagaraStackEntry::SetIsSearchResult(bool bInIsSearchResult) +{ + bIsSearchResult = bInIsSearchResult; } TOptional UNiagaraStackEntry::CanDropInternal(const TArray& DraggedEntries) @@ -517,7 +523,11 @@ void UNiagaraStackEntry::RefreshStackErrorChildren() void UNiagaraStackEntry::IssueModified() { - RefreshChildren(); + if (bIsFinalized == false) + { + // Fixing an issue may have caused this entry to be deleted and finalized, so don't refresh in that case. + RefreshChildren(); + } } void UNiagaraStackEntry::BeginDestroy() diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInput.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInput.cpp index b1263bf34087..7d8ce89d37cd 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInput.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackFunctionInput.cpp @@ -50,6 +50,7 @@ UNiagaraStackFunctionInput::UNiagaraStackFunctionInput() , bUpdatingLocalValueDirectly(false) , bShowEditConditionInline(false) , bIsInlineEditConditionToggle(false) + , bIsDynamicInputScriptReassignmentPending(false) { } @@ -309,21 +310,71 @@ void UNiagaraStackFunctionInput::RefreshChildrenInternal(const TArray(CurrentChildren, - [=](UNiagaraStackFunctionInputCollection* CurrentFunctionInputEntry) - { - return CurrentFunctionInputEntry->GetInputFunctionCallNode() == InputValues.DynamicNode.Get() && - CurrentFunctionInputEntry->GetModuleNode() == OwningModuleNode.Get(); - }); + if (InputValues.DynamicNode->FunctionScript != nullptr) + { + UNiagaraStackFunctionInputCollection* DynamicInputEntry = FindCurrentChildOfTypeByPredicate(CurrentChildren, + [=](UNiagaraStackFunctionInputCollection* CurrentFunctionInputEntry) + { + return CurrentFunctionInputEntry->GetInputFunctionCallNode() == InputValues.DynamicNode.Get() && + CurrentFunctionInputEntry->GetModuleNode() == OwningModuleNode.Get(); + }); - if (DynamicInputEntry == nullptr) - { - DynamicInputEntry = NewObject(this); - DynamicInputEntry->Initialize(CreateDefaultChildRequiredData(), *OwningModuleNode, *InputValues.DynamicNode.Get(), GetOwnerStackItemEditorDataKey()); - DynamicInputEntry->SetShouldShowInStack(false); + if (DynamicInputEntry == nullptr) + { + DynamicInputEntry = NewObject(this); + DynamicInputEntry->Initialize(CreateDefaultChildRequiredData(), *OwningModuleNode, *InputValues.DynamicNode.Get(), GetOwnerStackItemEditorDataKey()); + DynamicInputEntry->SetShouldShowInStack(false); + } + if (InputValues.DynamicNode->FunctionScript != nullptr && InputValues.DynamicNode->FunctionScript->bDeprecated) + { + FText LongMessage = InputValues.DynamicNode->FunctionScript->DeprecationRecommendation != nullptr ? + FText::Format(LOCTEXT("ModuleScriptDeprecationLong", "The script asset for the assigned module {0} has been deprecated. Suggested replacement: {1}"), FText::FromString(InputValues.DynamicNode->GetName()), FText::FromString(InputValues.DynamicNode->FunctionScript->DeprecationRecommendation->GetPathName())) : + FText::Format(LOCTEXT("ModuleScriptDeprecationUnknownLong", "The script asset for the assigned module {0} has been deprecated."), FText::FromString(InputValues.DynamicNode->GetName())); + + int32 AddIdx = NewIssues.Add(FStackIssue( + EStackIssueSeverity::Warning, + LOCTEXT("ModuleScriptDeprecationShort", "Deprecated module"), + LongMessage, + GetStackEditorDataKey(), + false, + { + FStackIssueFix( + LOCTEXT("SelectNewDynamicInputScriptFix", "Select a new dynamic input script"), + FStackIssueFixDelegate::CreateLambda([this]() { this->bIsDynamicInputScriptReassignmentPending = true; })), + FStackIssueFix( + LOCTEXT("ResetFix", "Reset this input to it's default value"), + FStackIssueFixDelegate::CreateLambda([this]() { this->Reset(); })) + })); + + if (InputValues.DynamicNode->FunctionScript->DeprecationRecommendation != nullptr) + { + NewIssues[AddIdx].InsertFix(0, + FStackIssueFix( + LOCTEXT("SelectNewModuleScriptFixUseRecommended", "Use recommended replacement"), + FStackIssueFixDelegate::CreateLambda([this]() { ReassignDynamicInputScript(InputValues.DynamicNode->FunctionScript->DeprecationRecommendation); }))); + } + } + + + NewChildren.Add(DynamicInputEntry); + } + else + { + NewIssues.Add(FStackIssue( + EStackIssueSeverity::Error, + LOCTEXT("DynamicInputScriptMissingShort", "Missing dynamic input script"), + FText::Format(LOCTEXT("DynamicInputScriptMissingLong", "The script asset for the assigned dynamic input {0} is missing."), FText::FromString(InputValues.DynamicNode->GetFunctionName())), + GetStackEditorDataKey(), + false, + { + FStackIssueFix( + LOCTEXT("SelectNewDynamicInputScriptFix", "Select a new dynamic input script"), + FStackIssueFixDelegate::CreateLambda([this]() { this->bIsDynamicInputScriptReassignmentPending = true; })), + FStackIssueFix( + LOCTEXT("ResetFix", "Reset this input to it's default value"), + FStackIssueFixDelegate::CreateLambda([this]() { this->Reset(); })) + })); } - - NewChildren.Add(DynamicInputEntry); } if (InputValues.Mode == EValueMode::Data && InputValues.DataObjects.GetValueObject() != nullptr) @@ -751,6 +802,26 @@ void UNiagaraStackFunctionInput::GetAvailableParameterHandles(TArray("AssetRegistry"); + TArray CollectionAssets; + AssetRegistryModule.Get().GetAssetsByClass(UNiagaraParameterCollection::StaticClass()->GetFName(), CollectionAssets); + + for (FAssetData& CollectionAsset : CollectionAssets) + { + UNiagaraParameterCollection* Collection = CastChecked(CollectionAsset.GetAsset()); + if (Collection) + { + for (const FNiagaraVariable& CollectionParam : Collection->GetParameters()) + { + if (CollectionParam.GetType() == InputType) + { + AvailableParameterHandles.AddUnique(FNiagaraParameterHandle(CollectionParam.GetName())); + } + } + } + } } UNiagaraNodeFunctionCall* UNiagaraStackFunctionInput::GetDynamicInputNode() const @@ -1285,6 +1356,8 @@ void UNiagaraStackFunctionInput::RenameInput(FName NewName) { if (OwningAssignmentNode.IsValid() && InputParameterHandlePath.Num() == 1 && InputParameterHandle.GetName() != NewName) { + FScopedTransaction ScopedTransaction(LOCTEXT("RenameInput", "Rename this function's input.")); + FInputValues OldInputValues = InputValues; UEdGraphPin* OriginalOverridePin = GetOverridePin(); @@ -1301,10 +1374,17 @@ void UNiagaraStackFunctionInput::RenameInput(FName NewName) check(FoundIdx != INDEX_NONE); FNiagaraParameterHandle TargetHandle(OwningAssignmentNode->GetAssignmentTargetName(FoundIdx)); + OwningAssignmentNode->Modify(); + if (OwningAssignmentNode->FunctionScript != nullptr) + { + OwningAssignmentNode->FunctionScript->Modify(); + OwningAssignmentNode->FunctionScript->GetSource()->Modify(); + } if (OwningAssignmentNode->SetAssignmentTargetName(FoundIdx, NewName)) { OwningAssignmentNode->RefreshFromExternalChanges(); } + InputParameterHandle = FNiagaraParameterHandle(InputParameterHandle.GetNamespace(), NewName); InputParameterHandlePath.Empty(1); InputParameterHandlePath.Add(InputParameterHandle); @@ -1318,6 +1398,7 @@ void UNiagaraStackFunctionInput::RenameInput(FName NewName) for (TWeakObjectPtr Script : AffectedScripts) { + Script->Modify(); Script->RapidIterationParameters.RenameParameter(OldRapidIterationParameter, *RapidIterationParameter.GetName().ToString()); } @@ -1438,6 +1519,42 @@ bool UNiagaraStackFunctionInput::GetIsInlineEditConditionToggle() const return bIsInlineEditConditionToggle; } +bool UNiagaraStackFunctionInput::GetIsDynamicInputScriptReassignmentPending() const +{ + return bIsDynamicInputScriptReassignmentPending; +} + +void UNiagaraStackFunctionInput::SetIsDynamicInputScriptReassignmentPending(bool bIsPending) +{ + bIsDynamicInputScriptReassignmentPending = bIsPending; +} + +void UNiagaraStackFunctionInput::ReassignDynamicInputScript(UNiagaraScript* DynamicInputScript) +{ + if (ensureMsgf(InputValues.Mode == EValueMode::Dynamic && InputValues.DynamicNode != nullptr && InputValues.DynamicNode->GetClass() == UNiagaraNodeFunctionCall::StaticClass(), + TEXT("Can not reassign the dynamic input script when tne input doesn't have a valid dynamic input."))) + { + FScopedTransaction ScopedTransaction(LOCTEXT("ReassignDynamicInputTransaction", "Reassign dynamic input script")); + InputValues.DynamicNode->Modify(); + InputValues.DynamicNode->FunctionScript = DynamicInputScript; + InputValues.DynamicNode->MarkNodeRequiresSynchronization(TEXT("Dynamic input script reassigned."), true); + RefreshChildren(); + } +} + +bool UNiagaraStackFunctionInput::GetShouldPassFilterForVisibleCondition() const +{ + return GetHasVisibleCondition() == false || GetVisibleConditionEnabled(); +} + +void UNiagaraStackFunctionInput::GetSearchItems(TArray& SearchItems) const +{ + if (GetShouldPassFilterForVisibleCondition() && GetIsInlineEditConditionToggle() == false) + { + SearchItems.Add({ FName("DisplayName"), GetDisplayName() }); + } +} + void UNiagaraStackFunctionInput::OnGraphChanged(const struct FEdGraphEditAction& InAction) { if (bUpdatingGraphDirectly == false) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackGraphUtilities.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackGraphUtilities.cpp index 61b5b2766b54..fa93f2b3e95d 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackGraphUtilities.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackGraphUtilities.cpp @@ -1168,6 +1168,7 @@ void GetFunctionNamesForOutputNode(UNiagaraNodeOutput& OutputNode, TArray FNiagaraStackGraphUtilities::GetNamespaceForScriptUsage(ENiagar case ENiagaraScriptUsage::ParticleSpawnScript: case ENiagaraScriptUsage::ParticleSpawnScriptInterpolated: case ENiagaraScriptUsage::ParticleUpdateScript: + case ENiagaraScriptUsage::ParticleEventScript: return FNiagaraParameterHandle::ParticleAttributeNamespace; case ENiagaraScriptUsage::EmitterSpawnScript: case ENiagaraScriptUsage::EmitterUpdateScript: @@ -1505,15 +1507,20 @@ bool TryGetStackFunctionInputValue(UNiagaraScript& OwningScript, const UEdGraphP } else if (InputPin.LinkedTo.Num() == 1) { - if (InputPin.LinkedTo[0]->GetOwningNode()->IsA()) + const UEdGraphSchema_Niagara* NiagaraSchema = GetDefault(); + UEdGraphNode* PreviousOwningNode = InputPin.LinkedTo[0]->GetOwningNode(); + + + if (PreviousOwningNode->IsA()) { OutStackFunctionInputValue.LinkedValue = InputPin.LinkedTo[0]->GetFName(); } - else if (InputPin.LinkedTo[0]->GetOwningNode()->IsA()) + else if (PreviousOwningNode->IsA()) { OutStackFunctionInputValue.DataValue = CastChecked(InputPin.LinkedTo[0]->GetOwningNode())->GetDataInterface(); } - else if (InputPin.LinkedTo[0]->GetOwningNode()->IsA()) + else if (PreviousOwningNode->IsA() && + FNiagaraStackGraphUtilities::GetParameterMapInputPin(*(static_cast(PreviousOwningNode))) != nullptr) { UNiagaraNodeFunctionCall* DynamicInputFunctionCall = CastChecked(InputPin.LinkedTo[0]->GetOwningNode()); OutStackFunctionInputValue.DynamicValue = DynamicInputFunctionCall; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackInputCategory.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackInputCategory.cpp index 8be293fc0bdc..e2b9c6854351 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackInputCategory.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackInputCategory.cpp @@ -84,7 +84,7 @@ void UNiagaraStackInputCategory::SetShouldShowInStack(bool bInShouldShowInStack) bool UNiagaraStackInputCategory::FilterForVisibleCondition(const UNiagaraStackEntry& Child) const { const UNiagaraStackFunctionInput* StackFunctionInputChild = Cast(&Child); - return StackFunctionInputChild == nullptr || StackFunctionInputChild->GetHasVisibleCondition() == false || StackFunctionInputChild->GetVisibleConditionEnabled(); + return StackFunctionInputChild == nullptr || StackFunctionInputChild->GetShouldPassFilterForVisibleCondition(); } bool UNiagaraStackInputCategory::FilterForIsInlineEditConditionToggle(const UNiagaraStackEntry& Child) const diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackItem.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackItem.cpp index 9411aadf1370..43a4d0134074 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackItem.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackItem.cpp @@ -195,7 +195,7 @@ void UNiagaraStackItemContent::SetIsAdvanced(bool bInIsAdvanced) bool UNiagaraStackItemContent::FilterAdvancedChildren(const UNiagaraStackEntry& Child) const { const UNiagaraStackItemContent* ItemContent = Cast(&Child); - if (ItemContent == nullptr || ItemContent->GetIsAdvanced() == false) + if (ItemContent == nullptr || ItemContent->GetIsAdvanced() == false || ItemContent->GetIsSearchResult()) { return true; } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItem.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItem.cpp index 8049f8ac8d20..e612c7bdf880 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItem.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItem.cpp @@ -5,6 +5,7 @@ #include "ViewModels/NiagaraSystemViewModel.h" #include "ViewModels/NiagaraEmitterHandleViewModel.h" #include "ViewModels/NiagaraEmitterViewModel.h" +#include "ViewModels/Stack/NiagaraStackModuleItemLinkedInputCollection.h" #include "ViewModels/Stack/NiagaraStackFunctionInputCollection.h" #include "ViewModels/Stack/NiagaraStackFunctionInput.h" #include "ViewModels/Stack/NiagaraStackInputCategory.h" @@ -46,66 +47,51 @@ TArray UsagePriority = { // Ordered such as the highest pri ENiagaraScriptUsage::SystemUpdateScript, ENiagaraScriptUsage::SystemSpawnScript }; -UNiagaraNodeOutput* GetOutputNodeForModuleDependency(ENiagaraScriptUsage DependantUsage, UNiagaraScript* DependencyScript, UNiagaraSystem& System, UNiagaraEmitter* Emitter, FNiagaraModuleDependency Dependency) +UNiagaraNodeOutput* GetOutputNodeForModuleDependency(ENiagaraScriptUsage DependantUsage, UNiagaraScript* DependencyScript, UNiagaraSystem& System, const FNiagaraEmitterHandle* EmitterHandle, FNiagaraModuleDependency Dependency) { UNiagaraNodeOutput* TargetOutputNode = nullptr; if (DependencyScript) { UNiagaraScript* OutputScript = nullptr; TArray SupportedUsages = UNiagaraScript::GetSupportedUsageContextsForBitmask(DependencyScript->ModuleUsageBitmask); - ENiagaraScriptUsage ScriptUsage = SupportedUsages[0]; - int32 ClosestDistance = MAX_int32; - int32 DependantIndex = UsagePriority.IndexOfByPredicate( - [&](const ENiagaraScriptUsage CurrentUsage) + if (Dependency.ScriptConstraint == ENiagaraModuleDependencyScriptConstraint::AllScripts) { - return UNiagaraScript::IsEquivalentUsage(DependantUsage, CurrentUsage); - }); - - for (ENiagaraScriptUsage PossibleUsage : SupportedUsages) - { - int32 PossibleIndex = UsagePriority.IndexOfByPredicate( + int32 ClosestDistance = MAX_int32; + int32 DependantIndex = UsagePriority.IndexOfByPredicate( [&](const ENiagaraScriptUsage CurrentUsage) + { + return UNiagaraScript::IsEquivalentUsage(DependantUsage, CurrentUsage); + }); + + for (ENiagaraScriptUsage PossibleUsage : SupportedUsages) + { + int32 PossibleIndex = UsagePriority.IndexOfByPredicate( + [&](const ENiagaraScriptUsage CurrentUsage) { return UNiagaraScript::IsEquivalentUsage(PossibleUsage, CurrentUsage); }); - if (PossibleIndex == INDEX_NONE) - { - // This usage isn't in the execution flow so check the next one. - continue; - } + if (PossibleIndex == INDEX_NONE) + { + // This usage isn't in the execution flow so check the next one. + continue; + } - int32 Distance = PossibleIndex - DependantIndex; - bool bCorrectOrder = (Dependency.Type == ENiagaraModuleDependencyType::PreDependency && Distance >= 0) || (Dependency.Type == ENiagaraModuleDependencyType::PostDependency && Distance <= 0); - if ((FMath::Abs(Distance) < ClosestDistance) && bCorrectOrder) + int32 Distance = PossibleIndex - DependantIndex; + bool bCorrectOrder = (Dependency.Type == ENiagaraModuleDependencyType::PreDependency && Distance >= 0) || (Dependency.Type == ENiagaraModuleDependencyType::PostDependency && Distance <= 0); + if ((FMath::Abs(Distance) < ClosestDistance) && bCorrectOrder) + { + ClosestDistance = Distance; + OutputScript = FNiagaraEditorUtilities::GetScriptFromSystem(System, EmitterHandle->GetId(), PossibleUsage, FGuid()); + } + } + } + else if (Dependency.ScriptConstraint == ENiagaraModuleDependencyScriptConstraint::SameScript) + { + if (SupportedUsages.Contains(DependantUsage)) { - ClosestDistance = Distance; - ScriptUsage = PossibleUsage; - if (UNiagaraScript::IsEquivalentUsage(ScriptUsage, ENiagaraScriptUsage::SystemSpawnScript)) - { - OutputScript = System.GetSystemSpawnScript(); - } - else if (UNiagaraScript::IsEquivalentUsage(ScriptUsage, ENiagaraScriptUsage::SystemUpdateScript)) - { - OutputScript = System.GetSystemUpdateScript(); - } - else if (UNiagaraScript::IsEquivalentUsage(ScriptUsage, ENiagaraScriptUsage::EmitterSpawnScript)) - { - OutputScript = Emitter->EmitterSpawnScriptProps.Script; - } - else if (UNiagaraScript::IsEquivalentUsage(ScriptUsage, ENiagaraScriptUsage::EmitterUpdateScript)) - { - OutputScript = Emitter->EmitterUpdateScriptProps.Script; - } - else if (UNiagaraScript::IsEquivalentUsage(ScriptUsage, ENiagaraScriptUsage::ParticleSpawnScript)) - { - OutputScript = Emitter->SpawnScriptProps.Script; - } - else if (UNiagaraScript::IsEquivalentUsage(ScriptUsage, ENiagaraScriptUsage::ParticleUpdateScript)) - { - OutputScript = Emitter->UpdateScriptProps.Script; - } + OutputScript = FNiagaraEditorUtilities::GetScriptFromSystem(System, EmitterHandle->GetId(), DependantUsage, FGuid()); } } @@ -121,6 +107,7 @@ UNiagaraStackModuleItem::UNiagaraStackModuleItem() : FunctionCallNode(nullptr) , bCanRefresh(false) , InputCollection(nullptr) + , bIsModuleScriptReassignmentPending(false) { } @@ -142,7 +129,13 @@ void UNiagaraStackModuleItem::Initialize(FRequiredEntryData InRequiredEntryData, GroupAddUtilities = InGroupAddUtilities; FunctionCallNode = &InFunctionCallNode; OutputNode = FNiagaraStackGraphUtilities::GetEmitterOutputNodeForStackNode(*FunctionCallNode); - AddChildFilter(FOnFilterChild::CreateUObject(this, &UNiagaraStackModuleItem::FilterOutputCollection)); + + // We do not need to include child filters for NiagaraNodeAssignments as they do not display their output or linked input collections + if (!FunctionCallNode->IsA()) + { + AddChildFilter(FOnFilterChild::CreateUObject(this, &UNiagaraStackModuleItem::FilterOutputCollection)); + AddChildFilter(FOnFilterChild::CreateUObject(this, &UNiagaraStackModuleItem::FilterLinkedInputCollection)); + } // Update bCanMoveAndDelete if (GetSystemViewModel()->GetEditMode() == ENiagaraSystemViewModelEditMode::EmitterAsset) @@ -228,23 +221,43 @@ void UNiagaraStackModuleItem::RefreshChildrenInternal(const TArrayInitialize(CreateDefaultChildRequiredData(), *FunctionCallNode, *FunctionCallNode, GetStackEditorDataKey()); } - InputCollection->SetShouldShowInStack(GetStackEditorData().GetShowOutputs()); - - if (OutputCollection == nullptr) + // NiagaraNodeAssignments should not display OutputCollection and LinkedInputCollection as they effectively handle this through their InputCollection + if (!FunctionCallNode->IsA()) { - OutputCollection = NewObject(this); - OutputCollection->Initialize(CreateDefaultChildRequiredData(), *FunctionCallNode); + + if (LinkedInputCollection == nullptr) + { + LinkedInputCollection = NewObject(this); + LinkedInputCollection->Initialize(CreateDefaultChildRequiredData(), *FunctionCallNode); + LinkedInputCollection->AddChildFilter(FOnFilterChild::CreateUObject(this, &UNiagaraStackModuleItem::FilterLinkedInputCollectionChild)); + } + + if (OutputCollection == nullptr) + { + OutputCollection = NewObject(this); + OutputCollection->Initialize(CreateDefaultChildRequiredData(), *FunctionCallNode); + OutputCollection->AddChildFilter(FOnFilterChild::CreateUObject(this, &UNiagaraStackModuleItem::FilterOutputCollectionChild)); + } + + InputCollection->SetShouldShowInStack(GetStackEditorData().GetShowOutputs() || GetStackEditorData().GetShowLinkedInputs()); + + NewChildren.Add(LinkedInputCollection); + NewChildren.Add(InputCollection); + NewChildren.Add(OutputCollection); + } + else + { + // We do not show the expander arrow for InputCollections of NiagaraNodeAssignments as they only have this one collection + InputCollection->SetShouldShowInStack(false); - NewChildren.Add(InputCollection); - NewChildren.Add(OutputCollection); - - RefreshIsEnabled(); - - Super::RefreshChildrenInternal(CurrentChildren, NewChildren, NewIssues); - - RefreshIssues(NewIssues); + NewChildren.Add(InputCollection); + } } + + RefreshIsEnabled(); + Super::RefreshChildrenInternal(CurrentChildren, NewChildren, NewIssues); + RefreshIssues(NewIssues); } void UNiagaraStackModuleItem::RefreshIssues(TArray& NewIssues) @@ -256,12 +269,58 @@ void UNiagaraStackModuleItem::RefreshIssues(TArray& NewIssues) } if (FunctionCallNode != nullptr) { - if (!FunctionCallNode->ScriptIsValid()) + if (FunctionCallNode->FunctionScript != nullptr && FunctionCallNode->FunctionScript->bDeprecated) + { + FText LongMessage = FunctionCallNode->FunctionScript->DeprecationRecommendation != nullptr ? + FText::Format(LOCTEXT("ModuleScriptDeprecationLong", "The script asset for the assigned module {0} has been deprecated. Suggested replacement: {1}"), FText::FromString(FunctionCallNode->GetFunctionName()),FText::FromString(FunctionCallNode->FunctionScript->DeprecationRecommendation->GetPathName())) : + FText::Format(LOCTEXT("ModuleScriptDeprecationUnknownLong", "The script asset for the assigned module {0} has been deprecated."), FText::FromString(FunctionCallNode->GetFunctionName())); + + int32 AddIdx = NewIssues.Add(FStackIssue( + EStackIssueSeverity::Warning, + LOCTEXT("ModuleScriptDeprecationShort", "Deprecated module"), + LongMessage, + GetStackEditorDataKey(), + false, + { + FStackIssueFix( + LOCTEXT("SelectNewModuleScriptFix", "Select a new module script"), + FStackIssueFixDelegate::CreateLambda([this]() { this->bIsModuleScriptReassignmentPending = true; })), + FStackIssueFix( + LOCTEXT("DeleteFix", "Delete this module"), + FStackIssueFixDelegate::CreateLambda([this]() { this->Delete(); })) + })); + + if (FunctionCallNode->FunctionScript->DeprecationRecommendation != nullptr) + { + NewIssues[AddIdx].InsertFix(0, + FStackIssueFix( + LOCTEXT("SelectNewModuleScriptFixUseRecommended", "Use recommended replacement"), + FStackIssueFixDelegate::CreateLambda([this]() { ReassignModuleScript(FunctionCallNode->FunctionScript->DeprecationRecommendation); }))); + } + } + if(FunctionCallNode->FunctionScript == nullptr && FunctionCallNode->GetClass() == UNiagaraNodeFunctionCall::StaticClass()) + { + NewIssues.Add(FStackIssue( + EStackIssueSeverity::Error, + LOCTEXT("ModuleScriptMissingShort", "Missing module script"), + FText::Format(LOCTEXT("ModuleScriptMissingLong", "The script asset for the assigned module {0} is missing."), FText::FromString(FunctionCallNode->GetFunctionName())), + GetStackEditorDataKey(), + false, + { + FStackIssueFix( + LOCTEXT("SelectNewModuleScriptFix", "Select a new module script"), + FStackIssueFixDelegate::CreateLambda([this]() { this->bIsModuleScriptReassignmentPending = true; })), + FStackIssueFix( + LOCTEXT("DeleteFix", "Delete this module"), + FStackIssueFixDelegate::CreateLambda([this]() { this->Delete(); })) + })); + } + else if (!FunctionCallNode->ScriptIsValid()) { FStackIssue InvalidScriptError( EStackIssueSeverity::Error, LOCTEXT("InvalidModuleScriptErrorSummary", "Invalid module script."), - LOCTEXT("InvalidModuleScriptError", "The script this module is supposed to execute is missing or invalid for other reasons. If it depends on an external script that no longer exists there will be load errors in the log."), + LOCTEXT("InvalidModuleScriptError", "The script this module is supposed to execute is missing or invalid for other reasons."), GetStackEditorDataKey(), false); @@ -371,6 +430,15 @@ void UNiagaraStackModuleItem::RefreshIssues(TArray& NewIssues) : ((Dependency.Type == ENiagaraModuleDependencyType::PreDependency && Distance < 0) || (Dependency.Type == ENiagaraModuleDependencyType::PostDependency && Distance > 0)); + bool bSameScriptDependencyConstraint = Dependency.ScriptConstraint == ENiagaraModuleDependencyScriptConstraint::SameScript; + bool bEquivalentScriptUsage = UNiagaraScript::IsEquivalentUsage(OutputNode->GetUsage(), ModuleData.Usage); + + // If the dependency is for modules in the same script, the two modules are only incorrectly ordered if they share equivalent script usages + if (bSameScriptDependencyConstraint) + { + bIncorrectOrder = bEquivalentScriptUsage && bIncorrectOrder; + } + if (bIncorrectOrder) { DisorderedDependencies.Add(ModuleData); @@ -379,7 +447,8 @@ void UNiagaraStackModuleItem::RefreshIssues(TArray& NewIssues) { DisabledDependencies.Add(FunctionNode); } - else + else if (Dependency.ScriptConstraint == ENiagaraModuleDependencyScriptConstraint::AllScripts || + (bSameScriptDependencyConstraint && bEquivalentScriptUsage && OutputNode->GetUsageId() == ModuleData.UsageId)) { bDependencyMet = true; break; @@ -439,6 +508,17 @@ void UNiagaraStackModuleItem::RefreshIssues(TArray& NewIssues) FNiagaraStackGraphUtilities::GetScriptAssetsByDependencyProvided(ENiagaraScriptUsage::Module, Dependency.Id, ModuleAssets); for (FAssetData ModuleAsset : ModuleAssets) { + UNiagaraScript* DependencyScript = Cast(ModuleAsset.GetAsset()); + if (Dependency.ScriptConstraint == ENiagaraModuleDependencyScriptConstraint::SameScript) + { + TArray SupportedUsages = UNiagaraScript::GetSupportedUsageContextsForBitmask(DependencyScript->ModuleUsageBitmask); + if (SupportedUsages.Contains(OutputNode->GetUsage()) == false) + { + // If the dependency requires the provider be in the same script and the usage of this module doesn't support that usage, skip it. + continue; + } + } + FText FixDescription = FText::Format(LOCTEXT("AddDependency", "Add new dependency module {0}"), FText::FromName(ModuleAsset.AssetName)); UNiagaraStackEntry::FStackIssueFix Fix( FixDescription, @@ -447,25 +527,26 @@ void UNiagaraStackModuleItem::RefreshIssues(TArray& NewIssues) FScopedTransaction ScopedTransaction(FixDescription); UNiagaraNodeFunctionCall* NewModuleNode = nullptr; int32 TargetIndex = 0; - UNiagaraScript* DependencyScript = Cast(ModuleAsset.GetAsset()); checkf(DependencyScript != nullptr, TEXT("Add module action failed")); // Determine the output node for the group where the added dependency module belongs UNiagaraNodeOutput* TargetOutputNode = nullptr; for (int i = ModuleIndex; i < SystemModuleData.Num() && i >= 0; i = Dependency.Type == ENiagaraModuleDependencyType::PostDependency ? i + 1 : i - 1) // moving up or down depending on type - // starting at current module, which is a dependant + // starting at current module, which is a dependent { - auto FoundRequirement = SystemModuleData[i].ModuleNode->FunctionScript->RequiredDependencies.FindByPredicate( - [&](FNiagaraModuleDependency CurrentDependency) + bool bRequiredDependencyFound = SystemModuleData[i].ModuleNode->FunctionScript->RequiredDependencies.ContainsByPredicate( + [&Dependency](const FNiagaraModuleDependency& RequiredDependency) { - return CurrentDependency.Id == Dependency.Id; + return RequiredDependency.Id == Dependency.Id; }); - if (FoundRequirement) // check for multiple dependendants along the way, and stop adjacent to the last one + if (bRequiredDependencyFound) // check for multiple dependents along the way, and stop adjacent to the last one { ENiagaraScriptUsage DependencyUsage = SystemModuleData[i].Usage; - TargetOutputNode = GetOutputNodeForModuleDependency(DependencyUsage, DependencyScript, GetSystemViewModel()->GetSystem(), GetEmitterViewModel()->GetEmitter(), Dependency); - if (TargetOutputNode != nullptr) + const FNiagaraEmitterHandle* EmitterHandle = FNiagaraEditorUtilities::GetEmitterHandleForEmitter(GetSystemViewModel()->GetSystem(), *GetEmitterViewModel()->GetEmitter()); + UNiagaraNodeOutput* FoundTargetOutputNode = GetOutputNodeForModuleDependency(DependencyUsage, DependencyScript, GetSystemViewModel()->GetSystem(), EmitterHandle, Dependency); + if (FoundTargetOutputNode != nullptr) { - auto CurrentOutputNode = FNiagaraStackGraphUtilities::GetEmitterOutputNodeForStackNode(*SystemModuleData[i].ModuleNode); + TargetOutputNode = FoundTargetOutputNode; + UNiagaraNodeOutput* CurrentOutputNode = FNiagaraStackGraphUtilities::GetEmitterOutputNodeForStackNode(*SystemModuleData[i].ModuleNode); if (TargetOutputNode == CurrentOutputNode) { TargetIndex = Dependency.Type == ENiagaraModuleDependencyType::PostDependency ? SystemModuleData[i].Index + 1 : SystemModuleData[i].Index; @@ -557,9 +638,56 @@ void UNiagaraStackModuleItem::RefreshIssues(TArray& NewIssues) bool UNiagaraStackModuleItem::FilterOutputCollection(const UNiagaraStackEntry& Child) const { - if (Child.IsA() && GetStackEditorData().GetShowOutputs() == false) + if (Child.IsA()) { - return false; + TArray FilteredChildren; + Child.GetFilteredChildren(FilteredChildren); + if (FilteredChildren.Num() != 0) + { + return true; + } + else if (GetStackEditorData().GetShowOutputs() == false) + { + return false; + } + } + return true; +} + +bool UNiagaraStackModuleItem::FilterOutputCollectionChild(const UNiagaraStackEntry& Child) const +{ + // Filter to only show search result matches inside collapsed collection + if (GetStackEditorData().GetShowOutputs() == false) + { + return Child.GetIsSearchResult(); + } + return true; +} + +bool UNiagaraStackModuleItem::FilterLinkedInputCollection(const UNiagaraStackEntry& Child) const +{ + if (Child.IsA()) + { + TArray FilteredChildren; + Child.GetFilteredChildren(FilteredChildren); + if (FilteredChildren.Num() != 0) + { + return true; + } + else if (GetStackEditorData().GetShowLinkedInputs() == false && Child.GetShouldShowInStack()) + { + return false; + } + } + return true; +} + +bool UNiagaraStackModuleItem::FilterLinkedInputCollectionChild(const UNiagaraStackEntry& Child) const +{ + // Filter to only show search result matches inside collapsed collection + if (GetStackEditorData().GetShowLinkedInputs() == false) + { + return Child.GetIsSearchResult(); } return true; } @@ -683,6 +811,29 @@ void UNiagaraStackModuleItem::AddInput(FNiagaraVariable InputParameter) } } +bool UNiagaraStackModuleItem::GetIsModuleScriptReassignmentPending() const +{ + return bIsModuleScriptReassignmentPending; +} + +void UNiagaraStackModuleItem::SetIsModuleScriptReassignmentPending(bool bIsPending) +{ + bIsModuleScriptReassignmentPending = bIsPending; +} + +void UNiagaraStackModuleItem::ReassignModuleScript(UNiagaraScript* ModuleScript) +{ + if (ensureMsgf(FunctionCallNode != nullptr && FunctionCallNode->GetClass() == UNiagaraNodeFunctionCall::StaticClass(), + TEXT("Can not reassign the module script when the module isn't a valid function call module."))) + { + FScopedTransaction ScopedTransaction(LOCTEXT("ReassignModuleTransaction", "Reassign module script")); + FunctionCallNode->Modify(); + FunctionCallNode->FunctionScript = ModuleScript; + FunctionCallNode->MarkNodeRequiresSynchronization(TEXT("Module script reassigned."), true); + RefreshChildren(); + } +} + UObject* UNiagaraStackModuleItem::GetExternalAsset() const { if (GetModuleNode().FunctionScript != nullptr && GetModuleNode().FunctionScript->IsAsset()) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItemLinkedInputCollection.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItemLinkedInputCollection.cpp new file mode 100644 index 000000000000..5e71d876e07c --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackModuleItemLinkedInputCollection.cpp @@ -0,0 +1,92 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#include "ViewModels/Stack/NiagaraStackModuleItemLinkedInputCollection.h" +#include "ViewModels/Stack/NiagaraStackModuleItemOutput.h" +#include "NiagaraNodeFunctionCall.h" +#include "NiagaraNodeParameterMapGet.h" +#include "NiagaraEmitterEditorData.h" +#include "ViewModels/Stack/NiagaraStackGraphUtilities.h" + +#include "EdGraph/EdGraphPin.h" + +UNiagaraStackModuleItemLinkedInputCollection::UNiagaraStackModuleItemLinkedInputCollection() + : FunctionCallNode(nullptr) +{ +} + +void UNiagaraStackModuleItemLinkedInputCollection::Initialize(FRequiredEntryData InRequiredEntryData, UNiagaraNodeFunctionCall& InFunctionCallNode) +{ + checkf(FunctionCallNode == nullptr, TEXT("Can not set the node more than once.")); + FString LinkedInputCollectionStackEditorDataKey = FString::Printf(TEXT("%s-LinkedInputs"), *InFunctionCallNode.NodeGuid.ToString(EGuidFormats::DigitsWithHyphens)); + Super::Initialize(InRequiredEntryData, LinkedInputCollectionStackEditorDataKey); + FunctionCallNode = &InFunctionCallNode; +} + +FText UNiagaraStackModuleItemLinkedInputCollection::GetDisplayName() const +{ + return NSLOCTEXT("StackModuleItemLinkedInputCollection", "LinkedInputsLabel", "Linked Script Inputs"); +} + +bool UNiagaraStackModuleItemLinkedInputCollection::IsExpandedByDefault() const +{ + return false; +} + +bool UNiagaraStackModuleItemLinkedInputCollection::GetIsEnabled() const +{ + return FunctionCallNode->GetDesiredEnabledState() == ENodeEnabledState::Enabled; +} + +UNiagaraStackEntry::EStackRowStyle UNiagaraStackModuleItemLinkedInputCollection::GetStackRowStyle() const +{ + return EStackRowStyle::ItemContent; +} + +bool UNiagaraStackModuleItemLinkedInputCollection::GetShouldShowInStack() const +{ + TArray UnfilteredChildren; + GetUnfilteredChildren(UnfilteredChildren); + if (UnfilteredChildren.Num() != 0) + { + return true; + } + return false; +} + +void UNiagaraStackModuleItemLinkedInputCollection::RefreshChildrenInternal(const TArray& CurrentChildren, TArray& NewChildren, TArray& NewIssues) +{ + UEdGraphPin* OutputParameterMapPin = FNiagaraStackGraphUtilities::GetParameterMapOutputPin(*FunctionCallNode); + if (ensureMsgf(OutputParameterMapPin != nullptr, TEXT("Invalid Stack Graph - Function call node has no output pin."))) + { + FNiagaraParameterMapHistoryBuilder Builder; + Builder.SetIgnoreDisabled(false); + FunctionCallNode->BuildParameterMapHistory(Builder, false); + + if (ensureMsgf(Builder.Histories.Num() == 1, TEXT("Invalid Stack Graph - Function call node has invalid history count!"))) + { + for (int32 i = 0; i < Builder.Histories[0].Variables.Num(); i++) + { + FNiagaraVariable& Variable = Builder.Histories[0].Variables[i]; + TArray& WriteHistory = Builder.Histories[0].PerVariableWriteHistory[i]; + + for (const UEdGraphPin* WritePin : WriteHistory) + { + if (Cast(WritePin->GetOwningNode()) != nullptr) + { + UNiagaraStackModuleItemOutput* Output = FindCurrentChildOfTypeByPredicate(CurrentChildren, + [&](UNiagaraStackModuleItemOutput* CurrentOutput) { return CurrentOutput->GetOutputParameterHandle().GetParameterHandleString() == Variable.GetName(); }); + + if (Output == nullptr) + { + Output = NewObject(this); + Output->Initialize(CreateDefaultChildRequiredData(), *FunctionCallNode, Variable.GetName(), Variable.GetType()); + } + + NewChildren.Add(Output); + break; + } + } + } + } + } +} diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackPropertyRow.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackPropertyRow.cpp index 80624053f2f7..0ede587fa6c8 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackPropertyRow.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackPropertyRow.cpp @@ -52,13 +52,14 @@ void UNiagaraStackPropertyRow::RefreshChildrenInternal(const TArray& SearchItems) const +void UNiagaraStackPropertyRow::GetSearchItems(TArray& SearchItems) const { + SearchItems.Add({ FName("DisplayName"), GetDisplayName() }); + TArray NodeFilterStrings; DetailTreeNode->GetFilterStrings(NodeFilterStrings); for (FString& FilterString : NodeFilterStrings) { SearchItems.Add({ "PropertyRowFilterString", FText::FromString(FilterString) }); } -} \ No newline at end of file +} diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackScriptItemGroup.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackScriptItemGroup.cpp index 1c5e86785be2..f508ce37a1ae 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackScriptItemGroup.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackScriptItemGroup.cpp @@ -397,7 +397,7 @@ void UNiagaraStackScriptItemGroup::RefreshIssues(TArray& NewIssues) // The factor ensures this, but older assets may not have it or it may have been removed accidentally. // For now, treat this as an error and allow them to resolve. FString ModulePath = TEXT("/Niagara/Modules/System/SystemLifeCycle.SystemLifeCycle"); - FStringAssetReference SystemUpdateScriptRef(ModulePath); + FSoftObjectPath SystemUpdateScriptRef(ModulePath); FAssetData ModuleScriptAsset; ModuleScriptAsset.ObjectPath = SystemUpdateScriptRef.GetAssetPathName(); @@ -643,13 +643,6 @@ void UNiagaraStackScriptItemGroup::OnScriptGraphChanged(const struct FEdGraphEdi void UNiagaraStackScriptItemGroup::AddParameterModuleToStack(const UNiagaraStackModuleSpacer* InModuleSpacer, const FNiagaraVariable &InVariable) { - TArray Vars; - Vars.Add(InVariable); - TArray DefaultVals; - DefaultVals.Add(FNiagaraConstants::GetAttributeDefaultValue(InVariable)); - - UNiagaraNodeOutput* OutputNode = GetScriptOutputNode(); - int32 TargetIndex = INDEX_NONE; UNiagaraStackModuleItem** TargetModuleItemPtr = StackSpacerToModuleItemMap.Find(FObjectKey(InModuleSpacer)); if (*TargetModuleItemPtr != nullptr) @@ -658,8 +651,8 @@ void UNiagaraStackScriptItemGroup::AddParameterModuleToStack(const UNiagaraStack TargetIndex = TargetModuleItem->GetModuleIndex(); } - FNiagaraStackGraphUtilities::AddParameterModuleToStack(Vars, *OutputNode, TargetIndex, DefaultVals); - RefreshChildren(); + TSharedRef AddAction = FScriptGroupAddAction::CreateExistingParameterModuleAction(InVariable); + AddUtilities->ExecuteAddAction(AddAction, TargetIndex); } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackViewModel.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackViewModel.cpp index b425c85fcc51..f8e30be7651e 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackViewModel.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/ViewModels/Stack/NiagaraStackViewModel.cpp @@ -111,9 +111,10 @@ void UNiagaraStackViewModel::Tick() void UNiagaraStackViewModel::OnSearchTextChanged(const FText& SearchText) { - if (RootEntry) + if (RootEntry && !CurrentSearchText.EqualTo(SearchText)) { CurrentSearchText = SearchText; + RestoreStackEntryExpansionPreSearch(); // postpone searching until next tick; protects against crashes from the GC // also this can be triggered by multiple events, so better wait bRestartSearch = true; @@ -225,6 +226,10 @@ void UNiagaraStackViewModel::SearchTick() if (bRestartSearch) { // clear the search results + for (auto SearchResult : GetCurrentSearchResults()) + { + SearchResult.GetEntry()->SetIsSearchResult(false); + } CurrentSearchResults.Empty(); CurrentFocusedSearchMatchIndex = -1; // generates ItemsToSearch, these will be processed on tick, in batches @@ -232,11 +237,18 @@ void UNiagaraStackViewModel::SearchTick() { GenerateTraversalEntries(RootEntry, TArray(), ItemsToSearch); } + // we need to call the SearchCompletedDelegate to go through SynchronizeTreeExpansion in SNiagaraStack so that when exiting search we return the stack expansion to the state it was before searching + else + { + SearchCompletedDelegate.Broadcast(); + } bRestartSearch = false; } - else if (IsSearching()) + + if (IsSearching()) { - double SearchStartTime= FPlatformTime::Seconds(); + UNiagaraStackEditorData& EditorData = GetSystemViewModel()->GetEditorData().GetStackEditorData(); + double SearchStartTime = FPlatformTime::Seconds(); double CurrentSearchLoopTime = SearchStartTime; // process at least one item, but don't go over MaxSearchTime for the rest while (ItemsToSearch.Num() > 0 && CurrentSearchLoopTime - SearchStartTime < MaxSearchTime) @@ -250,10 +262,16 @@ void UNiagaraStackViewModel::SearchTick() TSet MatchedKeys; for (UNiagaraStackEntry::FStackSearchItem SearchItem : SearchItems) { + if (!ItemsToSearch[0].GetEntry()->GetStackEditorDataKey().IsEmpty()) + { + EditorData.SetStackEntryWasExpandedPreSearch(ItemsToSearch[0].GetEntry()->GetStackEditorDataKey(), ItemsToSearch[0].GetEntry()->GetIsExpanded()); + } + if (ItemMatchesSearchCriteria(SearchItem)) { if (MatchedKeys.Contains(SearchItem.Key) == false) { + EntryToProcess->SetIsSearchResult(true); CurrentSearchResults.Add({ ItemsToSearch[0].EntryPath, SearchItem }); MatchedKeys.Add(SearchItem.Key); } @@ -274,7 +292,7 @@ void UNiagaraStackViewModel::GenerateTraversalEntries(UNiagaraStackEntry* Root, TArray& TraversedArray) { TArray Children; - Root->GetFilteredChildren(Children); + Root->GetUnfilteredChildren(Children); ParentChain.Add(Root); TraversedArray.Add(FSearchWorkItem{ParentChain}); for (auto Child : Children) @@ -309,6 +327,28 @@ void UNiagaraStackViewModel::GeneratePathForEntry(UNiagaraStackEntry* Root, UNia } } +void UNiagaraStackViewModel::RestoreStackEntryExpansionPreSearch() +{ + UNiagaraStackEditorData& StackEditorData = SystemViewModel->GetEditorData().GetStackEditorData(); + GenerateTraversalEntries(RootEntry, TArray(), ItemsToRestoreExpansionState); + + while (ItemsToRestoreExpansionState.Num() != 0) + { + UNiagaraStackEntry* EntryToProcess = ItemsToRestoreExpansionState[0].GetEntry(); + if (EntryToProcess->GetIsSearchResult() && !EntryToProcess->IsA()) + { + for (auto EntryToExpand : ItemsToRestoreExpansionState[0].EntryPath) + { + if (!EntryToExpand->IsA()) + { + EntryToExpand->SetIsExpanded(StackEditorData.GetStackEntryWasExpandedPreSearch(EntryToExpand->GetStackEditorDataKey(), false)); + } + } + } + ItemsToRestoreExpansionState.RemoveAtSwap(0); + } +} + TArray& UNiagaraStackViewModel::GetRootEntries() { return RootEntries; @@ -368,6 +408,29 @@ void UNiagaraStackViewModel::SetShowOutputs(bool bInShowOutputs) } } +bool UNiagaraStackViewModel::GetShowLinkedInputs() const +{ + if (SystemViewModel.IsValid() && EmitterHandleViewModel.IsValid()) + { + return SystemViewModel->GetEditorData().GetStackEditorData().GetShowLinkedInputs() || + EmitterHandleViewModel->GetEmitterViewModel()->GetEditorData().GetStackEditorData().GetShowLinkedInputs(); + } + return false; +} + +void UNiagaraStackViewModel::SetShowLinkedInputs(bool bInShowLinkedInputs) +{ + if (SystemViewModel.IsValid() && EmitterHandleViewModel.IsValid()) + { + SystemViewModel->GetOrCreateEditorData().GetStackEditorData().SetShowLinkedInputs(bInShowLinkedInputs); + EmitterHandleViewModel->GetEmitterViewModel()->GetOrCreateEditorData().GetStackEditorData().SetShowLinkedInputs(bInShowLinkedInputs); + OnSearchTextChanged(CurrentSearchText); + + // Showing linked inputs changes indenting so a full refresh is needed. + RootEntry->RefreshChildren(); + } +} + double UNiagaraStackViewModel::GetLastScrollPosition() const { if (EmitterHandleViewModel.IsValid()) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.cpp index f3b2cc17a977..51f43e8aeecf 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.cpp @@ -280,6 +280,11 @@ void SNiagaraParameterMapView::AddParameter(FNiagaraVariable NewVariable) } } +bool SNiagaraParameterMapView::AllowMakeType(const FNiagaraTypeDefinition& InType) const +{ + return InType != FNiagaraTypeDefinition::GetParameterMapDef(); +} + void SNiagaraParameterMapView::OnFilterTextChanged(const FText& InFilterText) { GraphActionMenu->GenerateFilteredItems(false); @@ -496,6 +501,7 @@ TSharedRef SNiagaraParameterMapView::OnGetParameterMenu(const NiagaraPa { TSharedRef MenuWidget = SNew(SNiagaraAddParameterMenu, Graphs) .OnAddParameter(this, &SNiagaraParameterMapView::AddParameter) + .OnAllowMakeType(this, &SNiagaraParameterMapView::AllowMakeType) .Section(InSection) .ShowNamespaceCategory(false) .ShowGraphParameters(false) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.h index f029834b89d9..347cfb506ba5 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraParameterMapView.h @@ -93,6 +93,9 @@ public: static TSharedRef CreateCustomActionExpander(const struct FCustomExpanderData& ActionMenuData); private: + /** Function to bind to SNiagaraAddParameterMenus to filter types we allow creating */ + bool AllowMakeType(const FNiagaraTypeDefinition& InType) const; + /** Callback when the filter is changed, forces the action tree(s) to filter */ void OnFilterTextChanged(const FText& InFilterText); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp index 4909ae56aa58..9c446a43bb43 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.cpp @@ -265,6 +265,12 @@ void SNiagaraSpreadsheetView::Construct(const FArguments& InArgs, TSharedRefFloatStartOffset; float* Src = CaptureData[(int32)TabState].DataSet.PrevData().GetInstancePtrFloat(CompBufferOffset, RowIndex); - CSVOutput += FString::Printf(TEXT("%3.3f"), Src[0]); + CSVOutput += FString::Printf(TEXT("%3.9f"), Src[0]); } else { @@ -1423,6 +1438,10 @@ FReply SNiagaraSpreadsheetView::OnCaptureRequestPressed() CaptureData[i].TargetUsage = ENiagaraScriptUsage::SystemUpdateScript; CaptureData[i].TargetUsageId = FGuid(); break; + case UIPerParticleGPU: + CaptureData[i].TargetUsage = ENiagaraScriptUsage::ParticleGPUComputeScript; + CaptureData[i].TargetUsageId = FGuid(); + break; default: CaptureData[i].TargetUsage = ENiagaraScriptUsage::Function; CaptureData[i].TargetUsageId = FGuid(); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.h index b726f4775626..84d8320b9c42 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/Widgets/SNiagaraSpreadsheetView.h @@ -54,6 +54,7 @@ protected: UIPerParticleEvent1, UIPerParticleEvent2, UIPerParticleEvent3, + UIPerParticleGPU, UISystemUpdate, UIMax }; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/EdGraphSchema_Niagara.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/EdGraphSchema_Niagara.h index b0e1a665832c..83223d92fb4c 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/EdGraphSchema_Niagara.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/EdGraphSchema_Niagara.h @@ -85,6 +85,7 @@ class NIAGARAEDITOR_API UEdGraphSchema_Niagara : public UEdGraphSchema void PromoteSinglePinToParameter(UEdGraphPin* SourcePin); static bool CanPromoteSinglePinToParameter(const UEdGraphPin* SourcePin); void ToggleNodeEnabledState(class UNiagaraNode* InNode) const; + void RefreshNode(UNiagaraNode* InNode) const; /** * Creates a niagara variable using the name, type, and default value stored on an ed graph pin. @@ -136,6 +137,6 @@ public: virtual void DetermineWiringStyle(UEdGraphPin* OutputPin, UEdGraphPin* InputPin, /*inout*/ FConnectionParams& Params) override; private: - UEdGraph* Graph; + class UNiagaraGraph* Graph; }; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorCommon.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorCommon.h index 7f6ddfff1449..6b568be5f74e 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorCommon.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorCommon.h @@ -12,7 +12,7 @@ class FNiagaraOpInfo { public: FNiagaraOpInfo() - : Keywords(FText()), NumericOuputTypeSelectionMode(ENiagaraNumericOutputTypeSelectionMode::Largest) + : Keywords(FText()), NumericOuputTypeSelectionMode(ENiagaraNumericOutputTypeSelectionMode::Largest), bSupportsAddedInputs(false) {} FName Name; @@ -24,6 +24,21 @@ public: TArray Inputs; TArray Outputs; + /** If true then this operation supports a variable number of inputs */ + bool bSupportsAddedInputs; + + /** + * The format that can generate the hlsl for the given number of inputs. + * Used the placeholder {A} and {B} to chain the inputs together. + */ + FString AddedInputFormatting; + + /** + * If added inputs are enabled then this filters the available pin types shown to the user. + * If empty then all the default niagara types are shown. + */ + TArray AddedInputTypeRestrictions; + static TMap OpInfoMap; static TArray OpInfos; @@ -32,5 +47,7 @@ public: static const TArray& GetOpInfoArray(); void BuildName(FString InName, FString InCategory); + + bool CreateHlslForAddedInputs(int32 InputCount, FString& HlslResult) const; }; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorSettings.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorSettings.h index 352167a52f08..4f3f907611ec 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorSettings.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraEditorSettings.h @@ -39,15 +39,15 @@ public: /** Niagara script to duplicate as the base of all new script assets created. */ UPROPERTY(config, EditAnywhere, Category = Niagara) - FStringAssetReference DefaultDynamicInputScript; + FSoftObjectPath DefaultDynamicInputScript; /** Niagara script to duplicate as the base of all new script assets created. */ UPROPERTY(config, EditAnywhere, Category = Niagara) - FStringAssetReference DefaultFunctionScript; + FSoftObjectPath DefaultFunctionScript; /** Niagara script to duplicate as the base of all new script assets created. */ UPROPERTY(config, EditAnywhere, Category = Niagara) - FStringAssetReference DefaultModuleScript; + FSoftObjectPath DefaultModuleScript; /** Shortcut key bindings that if held down while doing a mouse click, will spawn the specified type of Niagara node.*/ UPROPERTY(config, EditAnywhere, Category = Niagara) diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraGraph.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraGraph.h index 1df93c8361a9..95150578a895 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraGraph.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraGraph.h @@ -75,6 +75,10 @@ public: TArray Traversal; }; +struct FNiagaraGraphFunctionAliasContext +{ + ENiagaraScriptUsage CompileUsage; +}; UCLASS(MinimalAPI) class UNiagaraGraph : public UEdGraph @@ -231,8 +235,20 @@ class UNiagaraGraph : public UEdGraph /** Remove a listener for OnGraphNeedsRecompile events */ void RemoveOnGraphNeedsRecompileHandler(FDelegateHandle Handle); + FNiagaraTypeDefinition GetCachedNumericConversion(class UEdGraphPin* InPin); + + const class UEdGraphSchema_Niagara* GetNiagaraSchema() const; + + void InvalidateNumericCache(); + + FString GetFunctionAliasByContext(const FNiagaraGraphFunctionAliasContext& FunctionAliasContext); + protected: void RebuildCachedData(bool bForce = false); + void RebuildNumericCache(); + bool bNeedNumericCacheRebuilt; + TMap, FNiagaraTypeDefinition> CachedNumericConversions; + void ResolveNumerics(TMap& VisitedNodes, UEdGraphNode* Node); private: virtual void NotifyGraphChanged(const FEdGraphEditAction& InAction) override; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNode.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNode.h index ab9744fd33a1..7096fffb00f2 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNode.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNode.h @@ -13,6 +13,7 @@ class UEdGraphPin; class INiagaraCompiler; +struct FNiagaraGraphFunctionAliasContext; UCLASS() class NIAGARAEDITOR_API UNiagaraNode : public UEdGraphNode @@ -65,6 +66,7 @@ public: UEdGraphPin* GetOutputPin(int32 OutputIndex) const; void GetOutputPins(TArray& OutOutputPins) const; UEdGraphPin* GetPinByPersistentGuid(const FGuid& InGuid) const; + virtual void ResolveNumerics(const UEdGraphSchema_Niagara* Schema, bool bSetInline, TMap, FNiagaraTypeDefinition>* PinCache); /** Apply any node-specific logic to determine if it is safe to add this node to the graph. This is meant to be called only in the Editor before placing the node.*/ virtual bool CanAddToGraph(UNiagaraGraph* TargetGraph, FString& OutErrorMsg) const; @@ -114,10 +116,16 @@ public: void ForceChangeId(const FGuid& InId, bool bRaiseGraphNeedsRecompile); FOnNodeVisualsChanged& OnVisualsChanged(); + + virtual void AppendFunctionAliasForContext(const FNiagaraGraphFunctionAliasContext& InFunctionAliasContext, FString& InOutFunctionAlias) { }; + protected: virtual int32 CompileInputPin(class FHlslNiagaraTranslator *Translator, UEdGraphPin* Pin); virtual bool IsValidPinToCompile(UEdGraphPin* Pin) const { return true; }; + void NumericResolutionByPins(const UEdGraphSchema_Niagara* Schema, TArray& InputPins, TArray& OutputPins, + bool bFixInline, TMap, FNiagaraTypeDefinition>* PinCache); + /** Route input parameter map to output parameter map if it exists. Note that before calling this function, the input pins should have been visited already.*/ virtual void RouteParameterMapAroundMe(FNiagaraParameterMapHistoryBuilder& OutHistory, bool bRecursive); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeAssignment.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeAssignment.h index 919fe1c9b91c..b1aa08903aaa 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeAssignment.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeAssignment.h @@ -59,10 +59,10 @@ protected: UPROPERTY() FString AssignmentDefaultValue_DEPRECATED; - UPROPERTY(EditAnywhere, Category = Assignment) + UPROPERTY() TArray AssignmentTargets; - UPROPERTY(EditAnywhere, Category = Assignment) + UPROPERTY() TArray AssignmentDefaultValues; UPROPERTY() diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeCustomHlsl.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeCustomHlsl.h index cd7066e322d9..0870de8dbb50 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeCustomHlsl.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeCustomHlsl.h @@ -20,7 +20,7 @@ public: UPROPERTY(EditAnywhere, Category = "Function", meta = (MultiLine = true)) FString CustomHlsl; - UPROPERTY(EditAnywhere, Category = "Function") + UPROPERTY() ENiagaraScriptUsage ScriptUsage; virtual TSharedPtr CreateVisualWidget() override; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeDataSetBase.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeDataSetBase.h index ee41774c311a..aa493d2382bc 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeDataSetBase.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeDataSetBase.h @@ -16,10 +16,10 @@ class UNiagaraNodeDataSetBase : public UNiagaraNode public: - UPROPERTY(EditAnywhere, Category = DataSet) + UPROPERTY() FNiagaraDataSetID DataSet; - UPROPERTY(EditAnywhere, Category = Variables) + UPROPERTY() TArray Variables; UPROPERTY() diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeFunctionCall.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeFunctionCall.h index 40163e5a217f..d01b9598fb82 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeFunctionCall.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeFunctionCall.h @@ -59,6 +59,9 @@ public: virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; virtual FText GetTooltipText() const override; virtual FLinearColor GetNodeTitleColor() const override; + + /** Returns true if this node is deprecated */ + virtual bool IsDeprecated() const override; //~ End EdGraphNode Interface bool FindAutoBoundInput(UNiagaraNodeInput* InputNode, UEdGraphPin* PinToAutoBind, FNiagaraVariable& OutFoundVar, ENiagaraInputNodeUsage& OutNodeUsage); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeInput.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeInput.h index 7931bc2b6128..a4c1364a3b51 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeInput.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeInput.h @@ -49,10 +49,10 @@ class UNiagaraNodeInput : public UNiagaraNode GENERATED_UCLASS_BODY() public: - UPROPERTY(EditAnywhere, Category = Input) + UPROPERTY(EditAnywhere, Category = Input) FNiagaraVariable Input; - UPROPERTY(VisibleAnywhere, Category = Input) + UPROPERTY(EditAnywhere, Category = Input) ENiagaraInputNodeUsage Usage; /** Controls where this input is relative to others in the calling node. */ diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeOp.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeOp.h index 933cb52bc779..58797a417600 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeOp.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeOp.h @@ -9,14 +9,29 @@ #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SOverlay.h" #include "NiagaraNode.h" +#include "NiagaraNodeWithDynamicPins.h" #include "NiagaraNodeOp.generated.h" class SGraphNode; class SGraphPin; class SVerticalBox; +USTRUCT() +struct FAddedPinData +{ + GENERATED_BODY() + + /** The data type of the added pin */ + UPROPERTY() + FEdGraphPinType PinType; + + /** The name type of the added pin */ + UPROPERTY() + FName PinName; +}; + UCLASS(MinimalAPI) -class UNiagaraNodeOp : public UNiagaraNode +class UNiagaraNodeOp : public UNiagaraNodeWithDynamicPins { GENERATED_UCLASS_BODY() @@ -26,6 +41,9 @@ public: UPROPERTY() FName OpName; + UPROPERTY() + TArray AddedPins; + //~ Begin UObject interface virtual void PostLoad() override; //~ End UObject interface @@ -42,6 +60,26 @@ public: virtual bool RefreshFromExternalChanges() override; virtual ENiagaraNumericOutputTypeSelectionMode GetNumericOutputTypeSelectionMode() const override; //~ End UNiagaraNode Interface + + //~ Begin UNiagaraNodeWithDynamicPins Interface + virtual bool AllowNiagaraTypeForAddPin(const FNiagaraTypeDefinition& InType) override; + //~ End UNiagaraNodeWithDynamicPins Interface + +protected: + //~ Begin EdGraphNode Interface + virtual void OnPinRemoved(UEdGraphPin* PinToRemove) override; + //~ End EdGraphNode Interface + + //~ Begin UNiagaraNodeWithDynamicPins Interface + virtual bool AllowDynamicPins() const override; + virtual bool CanMovePin(const UEdGraphPin* Pin) const override { return false; } + virtual void OnNewTypedPinAdded(UEdGraphPin* NewPin) override; + virtual void OnPinRenamed(UEdGraphPin* RenamedPin, const FString& OldName) override; + virtual bool CanRemovePin(const UEdGraphPin* Pin) const override; + //~ End UNiagaraNodeWithDynamicPins Interface + +private: + FName GetUniqueAdditionalPinName() const; }; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeOutput.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeOutput.h index 3bd63d3f4175..71f8f1182cb3 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeOutput.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeOutput.h @@ -15,7 +15,7 @@ class UNiagaraNodeOutput : public UNiagaraNode { GENERATED_UCLASS_BODY() - UPROPERTY(EditAnywhere, Category = Output) + UPROPERTY(VisibleAnywhere, Category = Output) TArray Outputs; UPROPERTY() diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeWithDynamicPins.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeWithDynamicPins.h index fbe0f81fee23..e56b23b1762a 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeWithDynamicPins.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraNodeWithDynamicPins.h @@ -15,7 +15,7 @@ class SNiagaraGraphPinAdd; DECLARE_DELEGATE_OneParam(FOnAddParameter, const FNiagaraVariable&); /** A base node for niagara nodes with pins which can be dynamically added and removed by the user. */ -UCLASS() +UCLASS(Abstract) class UNiagaraNodeWithDynamicPins : public UNiagaraNode { public: diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraParameterMapHistory.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraParameterMapHistory.h index 8a40ecd181aa..1b4637fbd369 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraParameterMapHistory.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraParameterMapHistory.h @@ -270,7 +270,7 @@ public: /** * Use the current alias map to resolve any aliases in this input variable name. */ - FNiagaraVariable ResolveAliases(const FNiagaraVariable& InVar); + FNiagaraVariable ResolveAliases(const FNiagaraVariable& InVar) const; /** * Has RegisterNodeVisitation been called yet on the owning node of this pin? diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraScriptFactoryNew.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraScriptFactoryNew.h index abb23e72508a..b87d6713cd5a 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraScriptFactoryNew.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraScriptFactoryNew.h @@ -25,7 +25,7 @@ public: protected: /** Gets the script asset to create a new one from that based on the script usage type. */ - const FStringAssetReference& GetDefaultScriptFromSettings(const class UNiagaraEditorSettings* Settings); + const FSoftObjectPath& GetDefaultScriptFromSettings(const class UNiagaraEditorSettings* Settings); /** Give child factory classes the ability to define which asset action it represents. */ virtual FName GetAssetTypeActionName() const { return NAME_None; }; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraStackEditorData.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraStackEditorData.h index 4dbb9e7b5fae..9634e63f5f4b 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraStackEditorData.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/NiagaraStackEditorData.h @@ -39,6 +39,20 @@ public: */ void SetStackEntryIsExpanded(const FString& StackEntryKey, bool bIsExpanded); + /* + * Gets whether or not a stack entry was Expanded before triggering a stack search. + * @param bWasExpandedPreSearchDefault The default value to to return if the pre-search expanded state hasn't been set for the stack entry. + * @param StackEntryKey a unique key for the entry. + */ + bool GetStackEntryWasExpandedPreSearch(const FString& StackEntryKey, bool bWasExpandedPreSearchDefault) const; + + /* + * Sets whether or not a stack entry was Expanded before a stack search was triggered. + * @param StackEntryKey A unique key for the entry. + * @param bWasExpandedPreSearch Whether or not the entry was expanded pre-search. + */ + void SetStackEntryWasExpandedPreSearch(const FString& StackEntryKey, bool bWasExpandedPreSearch); + /* * Gets whether or not a stack item is showing advanced items. * @param StackItemKey A unique key for the entry. @@ -65,6 +79,12 @@ public: /* Sets whether or not item outputs should be shown in the stack. */ void SetShowOutputs(bool bInShowOutputs); + /* Gets whether or not item linked script inputs should be shown in the stack. */ + bool GetShowLinkedInputs() const; + + /* Sets whether or not item linked script inputs should be shown in the stack. */ + void SetShowLinkedInputs(bool bInShowLinkedInputs); + /* Gets the last scroll position for the associated stack. */ double GetLastScrollPosition() const; @@ -89,6 +109,9 @@ private: UPROPERTY() TMap StackEntryKeyToExpandedMap; + UPROPERTY() + TMap StackEntryKeyToPreSearchExpandedMap; + UPROPERTY() TMap StackItemKeyToShowAdvancedMap; @@ -98,6 +121,9 @@ private: UPROPERTY() bool bShowOutputs; + UPROPERTY() + bool bShowLinkedInputs; + UPROPERTY() double LastScrollPosition; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackEntry.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackEntry.h index 5bac714c8c5c..8c251f2486d7 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackEntry.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackEntry.h @@ -149,6 +149,8 @@ public: const TArray& GetFixes() const; + void InsertFix(int32 InsertionIdx, const FStackIssueFix& Fix); + private: EStackIssueSeverity Severity; FText ShortDescription; @@ -195,9 +197,7 @@ public: virtual bool GetShouldShowInStack() const; - void GetFilteredChildren(TArray& OutFilteredChildren); - - void GetUnfilteredChildren(TArray& OutUnfilteredChildren); + void GetFilteredChildren(TArray& OutFilteredChildren) const; void GetUnfilteredChildren(TArray& OutUnfilteredChildren) const; @@ -232,7 +232,7 @@ public: return nullptr; } - void GetSearchItems(TArray& SearchItems) const; + virtual void GetSearchItems(TArray& SearchItems) const; virtual UObject* GetExternalAsset() const; @@ -246,6 +246,10 @@ public: void SetOnRequestDrop(FOnRequestDrop InOnRequestCanDrop); + const bool GetIsSearchResult() const; + + void SetIsSearchResult(bool bInIsSearchResult); + protected: virtual void BeginDestroy() override; @@ -257,8 +261,6 @@ protected: virtual int32 GetChildIndentLevel() const; - virtual void GetAdditionalSearchItemsInternal(TArray& SearchItems) const; - virtual TOptional CanDropInternal(const TArray& DraggedEntries); virtual TOptional DropInternal(const TArray& DraggedEntries); @@ -325,4 +327,6 @@ private: TArray StackIssues; bool bIsFinalized; + + bool bIsSearchResult; }; \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInput.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInput.h index 955a9f283613..d079f3c41316 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInput.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackFunctionInput.h @@ -198,6 +198,22 @@ public: /** Gets whether or not this input is used as an edit condition for another input and should be hidden. */ bool GetIsInlineEditConditionToggle() const; + /** Gets whether or not a dynamic input script reassignment is pending. This can happen when trying to fix dynamic inputs which are missing their scripts. */ + bool GetIsDynamicInputScriptReassignmentPending() const; + + /** Gets whether or not a dynamic input script reassignment should be be pending. */ + void SetIsDynamicInputScriptReassignmentPending(bool bIsPending); + + /** Reassigns the function script for the current dynamic input without resetting the sub-inputs. */ + void ReassignDynamicInputScript(UNiagaraScript* DynamicInputScript); + + /** Gets whether or not this input is filtered from search results and appearing in stack due to visibility metadata*/ + bool GetShouldPassFilterForVisibleCondition() const; + +public: + //~ UNiagaraStackEntry interface + virtual void GetSearchItems(TArray& SearchItems) const override; + protected: //~ UNiagaraStackEntry interface virtual void FinalizeInternal() override; @@ -431,4 +447,7 @@ private: /** Whether or not this input is an edit condition toggle. */ bool bIsInlineEditConditionToggle; + + /** Whether or not the dynamic input for this input has a function script reassignment pending due to a request to fix a missing script. */ + bool bIsDynamicInputScriptReassignmentPending; }; \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackGraphUtilities.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackGraphUtilities.h index 7fdca2646b6d..f4178c33e58f 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackGraphUtilities.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackGraphUtilities.h @@ -128,7 +128,7 @@ namespace FNiagaraStackGraphUtilities void GetNewParameterAvailableTypes(TArray& OutAvailableTypes); - void GetScriptAssetsByUsage(ENiagaraScriptUsage AssetUsage, ENiagaraScriptUsage TargetUsage, TArray& OutAssets); + NIAGARAEDITOR_API void GetScriptAssetsByUsage(ENiagaraScriptUsage AssetUsage, ENiagaraScriptUsage TargetUsage, TArray& OutAssets); void GetScriptAssetsByDependencyProvided(ENiagaraScriptUsage AssetUsage, FName DependencyName, TArray& OutAssets); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackModuleItem.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackModuleItem.h index d7ee316c5593..842423be7b16 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackModuleItem.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackModuleItem.h @@ -7,8 +7,10 @@ #include "NiagaraStackModuleItem.generated.h" class UNiagaraNodeFunctionCall; +class UNiagaraStackModuleItemLinkedInputCollection; class UNiagaraStackFunctionInputCollection; class UNiagaraStackModuleItemOutputCollection; +class UNiagaraScript; class INiagaraStackItemGroupAddUtilities; struct FAssetData; @@ -55,11 +57,23 @@ public: void AddInput(FNiagaraVariable InputParameter); + /** Gets whether or not a module script reassignment is pending. This can happen when trying to fix modules which are missing their scripts. */ + bool GetIsModuleScriptReassignmentPending() const; + + /** Gets whether or not a module script reassignment should be be pending. */ + void SetIsModuleScriptReassignmentPending(bool bIsPending); + + /** Reassigns the function script for the module without resetting the inputs. */ + void ReassignModuleScript(UNiagaraScript* ModuleScript); + protected: virtual void RefreshChildrenInternal(const TArray& CurrentChildren, TArray& NewChildren, TArray& NewIssues) override; private: bool FilterOutputCollection(const UNiagaraStackEntry& Child) const; + bool FilterOutputCollectionChild(const UNiagaraStackEntry& Child) const; + bool FilterLinkedInputCollection(const UNiagaraStackEntry& Child) const; + bool FilterLinkedInputCollectionChild(const UNiagaraStackEntry& Child) const; void RefreshIssues(TArray& NewIssues); private: @@ -72,6 +86,9 @@ private: bool bIsEnabled; bool bCanRefresh; + UPROPERTY() + UNiagaraStackModuleItemLinkedInputCollection* LinkedInputCollection; + UPROPERTY() UNiagaraStackFunctionInputCollection* InputCollection; @@ -79,4 +96,6 @@ private: UNiagaraStackModuleItemOutputCollection* OutputCollection; INiagaraStackItemGroupAddUtilities* GroupAddUtilities; + + bool bIsModuleScriptReassignmentPending; }; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackModuleItemLinkedInputCollection.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackModuleItemLinkedInputCollection.h new file mode 100644 index 000000000000..e95b80861ce2 --- /dev/null +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackModuleItemLinkedInputCollection.h @@ -0,0 +1,32 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "ViewModels/Stack/NiagaraStackEntry.h" +#include "NiagaraStackModuleItemLinkedInputCollection.generated.h" + +class UNiagaraNodeFunctionCall; + +UCLASS() +class NIAGARAEDITOR_API UNiagaraStackModuleItemLinkedInputCollection : public UNiagaraStackEntry +{ + GENERATED_BODY() + +public: + UNiagaraStackModuleItemLinkedInputCollection(); + + void Initialize(FRequiredEntryData InRequiredEntryData, UNiagaraNodeFunctionCall& InFunctionCallNode); + + //~ UNiagaraStackEntry interface + virtual FText GetDisplayName() const override; + virtual bool IsExpandedByDefault() const override; + virtual bool GetIsEnabled() const override; + virtual EStackRowStyle GetStackRowStyle() const override; + virtual bool GetShouldShowInStack() const override; + +protected: + virtual void RefreshChildrenInternal(const TArray& CurrentChildren, TArray& NewChildren, TArray& NewIssues) override; + +private: + UNiagaraNodeFunctionCall* FunctionCallNode; +}; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackPropertyRow.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackPropertyRow.h index 9707ebe339c2..f6f9aa437b6d 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackPropertyRow.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackPropertyRow.h @@ -23,7 +23,7 @@ public: protected: virtual void RefreshChildrenInternal(const TArray& CurrentChildren, TArray& NewChildren, TArray& NewIssues) override; - virtual void GetAdditionalSearchItemsInternal(TArray& SearchItems) const override; + virtual void GetSearchItems(TArray& SearchItems) const override; private: TSharedPtr DetailTreeNode; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackViewModel.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackViewModel.h index 4f425d2ca349..6f7383a89840 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackViewModel.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Public/ViewModels/Stack/NiagaraStackViewModel.h @@ -52,6 +52,8 @@ public: bool GetShowOutputs() const; void SetShowOutputs(bool bInShowOutputs); + bool GetShowLinkedInputs() const; + void SetShowLinkedInputs(bool bInShowLinkedInputs); double GetLastScrollPosition() const; void SetLastScrollPosition(double InLastScrollPosition); @@ -110,6 +112,7 @@ private: TArray& TraversedArray); bool ItemMatchesSearchCriteria(UNiagaraStackEntry::FStackSearchItem SearchItem); void GeneratePathForEntry(UNiagaraStackEntry* Root, UNiagaraStackEntry* Entry, TArray CurrentPath, TArray& EntryPath); + void RestoreStackEntryExpansionPreSearch(); private: TSharedPtr EmitterHandleViewModel; @@ -127,6 +130,7 @@ private: int CurrentFocusedSearchMatchIndex; FOnSearchCompleted SearchCompletedDelegate; TArray ItemsToSearch; + TArray ItemsToRestoreExpansionState; TArray CurrentSearchResults; static const double MaxSearchTime; bool bRestartSearch; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.cpp index 8749e18e47be..afe0873d1e17 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.cpp @@ -14,6 +14,7 @@ #include "ViewModels/Stack/NiagaraStackViewModel.h" #include "ViewModels/Stack/NiagaraStackSpacer.h" #include "ViewModels/Stack/NiagaraStackModuleItemOutputCollection.h" +#include "ViewModels/Stack/NiagaraStackModuleItemLinkedInputCollection.h" #include "ViewModels/Stack/NiagaraStackModuleItemOutput.h" #include "ViewModels/Stack/NiagaraStackErrorItem.h" #include "ViewModels/Stack/NiagaraStackInputCategory.h" @@ -53,6 +54,7 @@ #include "ScopedTransaction.h" #include "Widgets/Layout/SWrapBox.h" #include "Stack/SNiagaraStackSpacer.h" +#include "ViewModels/Stack/NiagaraStackRoot.h" /** Contains data for a socket drag and drop operation in the StackEntry node. */ class FNiagaraStackEntryDragDropOp : public FDecoratedDragDropOp @@ -220,10 +222,10 @@ void SNiagaraStack::Construct(const FArguments& InArgs, UNiagaraStackViewModel* }; HeaderBox->SetOnMouseButtonUp(FPointerEventHandler::CreateLambda(OnHeaderMouseButtonUp)); - PrimeTreeExpansion(); + SynchronizeTreeExpansion(); } -void SNiagaraStack::PrimeTreeExpansion() +void SNiagaraStack::SynchronizeTreeExpansion() { TArray EntriesToProcess(StackViewModel->GetRootEntries()); while (EntriesToProcess.Num() > 0) @@ -413,33 +415,73 @@ FReply SNiagaraStack::PinButtonPressed() void SNiagaraStack::OnSearchTextChanged(const FText& SearchText) { - if (StackViewModel->GetCurrentSearchText().CompareTo(SearchText) != 0) - { - if (SearchExpandTimer.IsValid()) - { - UnRegisterActiveTimer(SearchExpandTimer.ToSharedRef()); - } - // restore expansion state of previous search - for (auto SearchResult : StackViewModel->GetCurrentSearchResults()) - { - for (UNiagaraStackEntry* ParentalUnit : SearchResult.EntryPath) - { - StackTree->SetItemExpansion(ParentalUnit, ParentalUnit->GetIsExpanded()); - } - } - bNeedsJumpToNextOccurence = true; - StackViewModel->OnSearchTextChanged(SearchText); - } + bNeedsJumpToNextOccurence = true; + StackViewModel->OnSearchTextChanged(SearchText); } FReply SNiagaraStack::ScrollToNextMatch() { + + const int NextMatchIndex = StackViewModel->GetCurrentFocusedMatchIndex() + 1; + TArray CurrentSearchResults = StackViewModel->GetCurrentSearchResults(); + if (CurrentSearchResults.Num() != 0) + { + if (NextMatchIndex < CurrentSearchResults.Num()) + { + for (auto SearchResultEntry : CurrentSearchResults[NextMatchIndex].EntryPath) + { + if (!SearchResultEntry->IsA()) + { + SearchResultEntry->SetIsExpanded(true); + } + } + } + else + { + for (auto SearchResultEntry : CurrentSearchResults[0].EntryPath) + { + if (!SearchResultEntry->IsA()) + { + SearchResultEntry->SetIsExpanded(true); + } + } + } + SynchronizeTreeExpansion(); + } + AddSearchScrollOffset(1); return FReply::Handled(); } FReply SNiagaraStack::ScrollToPreviousMatch() { + const int PreviousMatchIndex = StackViewModel->GetCurrentFocusedMatchIndex() - 1; + TArray CurrentSearchResults = StackViewModel->GetCurrentSearchResults(); + if (CurrentSearchResults.Num() != 0) + { + if (PreviousMatchIndex > 0) + { + for (auto SearchResultEntry : CurrentSearchResults[PreviousMatchIndex].EntryPath) + { + if (!SearchResultEntry->IsA()) + { + SearchResultEntry->SetIsExpanded(true); + } + } + } + else + { + for (auto SearchResultEntry : CurrentSearchResults.Last().EntryPath) + { + if (!SearchResultEntry->IsA()) + { + SearchResultEntry->SetIsExpanded(true); + } + } + } + SynchronizeTreeExpansion(); + } + // move current match to the previous one in the StackTree, wrap around AddSearchScrollOffset(-1); return FReply::Handled(); @@ -589,9 +631,19 @@ TSharedRef SNiagaraStack::GetViewOptionsMenu() const FGetActionCheckState::CreateLambda([=]() { return StackViewModel->GetShowAllAdvanced() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })), NAME_None, EUserInterfaceActionType::Check); + MenuBuilder.AddMenuEntry( + LOCTEXT("ShowLinkedInputsLabel", "Show Linked Script Inputs"), + LOCTEXT("ShowLinkedInputsToolTip", "Whether or not to show internal module linked inputs in the stack."), + FSlateIcon(), + FUIAction( + FExecuteAction::CreateLambda([=]() { StackViewModel->SetShowLinkedInputs(!StackViewModel->GetShowLinkedInputs()); }), + FCanExecuteAction(), + FGetActionCheckState::CreateLambda([=]() { return StackViewModel->GetShowLinkedInputs() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })), + NAME_None, EUserInterfaceActionType::Check); + MenuBuilder.AddMenuEntry( LOCTEXT("ShowOutputsLabel", "Show Outputs"), - LOCTEXT("ShowOutputsToolTip", "Whether or now to show module outputs in the stack."), + LOCTEXT("ShowOutputsToolTip", "Whether or not to show module outputs in the stack."), FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=]() { StackViewModel->SetShowOutputs(!StackViewModel->GetShowOutputs()); }), @@ -664,12 +716,6 @@ FReply SNiagaraStack::OnRowAcceptDrop(const FDragDropEvent& InDragDropEvent, EIt } void SNiagaraStack::OnStackSearchComplete() -{ - // fire up timer to expand all parentchains!! - SearchExpandTimer = RegisterActiveTimer(0.7f, FWidgetActiveTimerDelegate::CreateSP(this, &SNiagaraStack::TriggerExpandSearchResults)); -} - -EActiveTimerReturnType SNiagaraStack::TriggerExpandSearchResults(double InCurrentTime, float InDeltaTime) { ExpandSearchResults(); if (bNeedsJumpToNextOccurence) @@ -677,7 +723,6 @@ EActiveTimerReturnType SNiagaraStack::TriggerExpandSearchResults(double InCurren ScrollToNextMatch(); bNeedsJumpToNextOccurence = false; } - return EActiveTimerReturnType::Stop; } void SNiagaraStack::ExpandSearchResults() @@ -686,22 +731,17 @@ void SNiagaraStack::ExpandSearchResults() { for (UNiagaraStackEntry* ParentalUnit : SearchResult.EntryPath) { - StackTree->SetItemExpansion(ParentalUnit, true); + if (!ParentalUnit->IsA()) // should not alter expansion state of root entries + { + ParentalUnit->SetIsExpanded(true); + } } } + SynchronizeTreeExpansion(); } void SNiagaraStack::OnSearchBoxTextCommitted(const FText& NewText, ETextCommit::Type CommitInfo) { - if (StackViewModel->GetCurrentSearchText().CompareTo(NewText) != 0) - { - if (SearchExpandTimer.IsValid()) - { - UnRegisterActiveTimer(SearchExpandTimer.ToSharedRef()); - ExpandSearchResults(); - SearchExpandTimer.Reset(); - } - } if (bNeedsJumpToNextOccurence || CommitInfo == ETextCommit::OnEnter) // hasn't been autojumped yet or we hit enter { AddSearchScrollOffset(+1); @@ -933,7 +973,8 @@ SNiagaraStack::FRowWidgets SNiagaraStack::ConstructNameAndValueWidgetsForItem(UN .HighlightText_UObject(StackViewModel, &UNiagaraStackViewModel::GetCurrentSearchText)); } else if (Item->IsA() || - Item->IsA()) + Item->IsA() || + Item->IsA()) { return FRowWidgets( SNew(STextBlock) @@ -1015,7 +1056,7 @@ void SNiagaraStack::OnContentColumnWidthChanged(float Width) void SNiagaraStack::StackStructureChanged() { - PrimeTreeExpansion(); + SynchronizeTreeExpansion(); StackTree->RequestTreeRefresh(); } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.h index a97ec765886a..7d56fee76215 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/SNiagaraStack.h @@ -46,7 +46,7 @@ private: TSharedPtr ValueWidget; }; - void PrimeTreeExpansion(); + void SynchronizeTreeExpansion(); TSharedRef OnGenerateRowForStackItem(UNiagaraStackEntry* Item, const TSharedRef& OwnerTable); @@ -96,7 +96,6 @@ private: FSlateColor GetTextColorForItem(UNiagaraStackEntry* Item) const; void AddSearchScrollOffset(int NumberOfSteps); void OnStackSearchComplete(); - EActiveTimerReturnType TriggerExpandSearchResults(double InCurrentTime, float InDeltaTime); void ExpandSearchResults(); bool IsEntryFocusedInSearch(UNiagaraStackEntry* Entry) const; @@ -139,7 +138,6 @@ private: // ~ search stuff TSharedPtr SearchBox; - TSharedPtr SearchExpandTimer; static const FText OccurencesFormat; bool bNeedsJumpToNextOccurence; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackFunctionInputValue.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackFunctionInputValue.cpp index 64d8a845282c..5d6a69dff499 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackFunctionInputValue.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackFunctionInputValue.cpp @@ -23,6 +23,8 @@ #include "IStructureDetailsView.h" #include "SDropTarget.h" #include "Modules/ModuleManager.h" +#include "AssetRegistryModule.h" +#include "NiagaraParameterCollection.h" #define LOCTEXT_NAMESPACE "NiagaraStackFunctionInputValue" @@ -95,6 +97,7 @@ void SNiagaraStackFunctionInputValue::Construct(const FArguments& InArgs, UNiaga SNew(STextBlock) .TextStyle(FNiagaraEditorStyle::Get(), "NiagaraEditor.ParameterText") .Text(this, &SNiagaraStackFunctionInputValue::GetLinkedValueHandleText) + .OnDoubleClicked(this, &SNiagaraStackFunctionInputValue::OnLinkedInputDoubleClicked) ] ] @@ -222,6 +225,15 @@ void SNiagaraStackFunctionInputValue::Construct(const FArguments& InArgs, UNiaga ]; } +void SNiagaraStackFunctionInputValue::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) +{ + if (FunctionInput->GetIsDynamicInputScriptReassignmentPending()) + { + FunctionInput->SetIsDynamicInputScriptReassignmentPending(false); + ShowReassignDynamicInputScriptMenu(); + } +} + bool SNiagaraStackFunctionInputValue::GetInputEnabled() const { return FunctionInput->GetHasEditCondition() == false || FunctionInput->GetEditConditionEnabled(); @@ -413,6 +425,29 @@ FReply SNiagaraStackFunctionInputValue::DynamicInputTextDoubleClicked() return FReply::Unhandled(); } +FReply SNiagaraStackFunctionInputValue::OnLinkedInputDoubleClicked() +{ + FString ParamCollection; + FString ParamName; + FunctionInput->GetLinkedValueHandle().GetName().ToString().Split(TEXT("."), &ParamCollection, &ParamName); + + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + TArray CollectionAssets; + AssetRegistryModule.Get().GetAssetsByClass(UNiagaraParameterCollection::StaticClass()->GetFName(), CollectionAssets); + + for (FAssetData& CollectionAsset : CollectionAssets) + { + UNiagaraParameterCollection* Collection = CastChecked(CollectionAsset.GetAsset()); + if (Collection && Collection->GetNamespace() == *ParamCollection) + { + FAssetEditorManager::Get().OpenEditorForAsset(Collection); + return FReply::Handled(); + } + } + + return FReply::Unhandled(); +} + TSharedRef SNiagaraStackFunctionInputValue::CreateCustomNiagaraFunctionInputActionExpander(const FCustomExpanderData& ActionMenuData) { return SNew(SNiagaraFunctionInputActionMenuExpander, ActionMenuData); @@ -497,6 +532,7 @@ void SNiagaraStackFunctionInputValue::CollectAllActions(FGraphActionListBuilderB TArray AvailableHandles; FunctionInput->GetAvailableParameterHandles(AvailableHandles); + TArray ParameterCollectionHandles; TArray UserHandles; TArray EngineHandles; TArray SystemHandles; @@ -505,7 +541,11 @@ void SNiagaraStackFunctionInputValue::CollectAllActions(FGraphActionListBuilderB TArray OtherHandles; for (const FNiagaraParameterHandle AvailableHandle : AvailableHandles) { - if (AvailableHandle.IsUserHandle()) + if (AvailableHandle.IsParameterCollectionHandle()) + { + ParameterCollectionHandles.Add(AvailableHandle); + } + else if (AvailableHandle.IsUserHandle()) { UserHandles.Add(AvailableHandle); } @@ -546,6 +586,7 @@ void SNiagaraStackFunctionInputValue::CollectAllActions(FGraphActionListBuilderB } }; + AddMenuItemsForHandleList(ParameterCollectionHandles, LOCTEXT("NPC", "Parameter Collections")); AddMenuItemsForHandleList(UserHandles, LOCTEXT("UserSection", "User Exposed")); AddMenuItemsForHandleList(EngineHandles, LOCTEXT("EngineSection", "Engine")); AddMenuItemsForHandleList(SystemHandles, LOCTEXT("SystemSection", "System")); @@ -766,4 +807,49 @@ bool SNiagaraStackFunctionInputValue::OnFunctionInputAllowDrop(TSharedPtrReassignDynamicInputScript(NewDynamicInputScript); +} + +void CollectDynamicInputActions(FGraphActionListBuilderBase& DynamicInputActions, UNiagaraStackFunctionInput* FunctionInput) +{ + const FText CategoryName = LOCTEXT("DynamicInputValueCategory", "Dynamic Inputs"); + TArray DynamicInputScripts; + FunctionInput->GetAvailableDynamicInputs(DynamicInputScripts); + for (UNiagaraScript* DynamicInputScript : DynamicInputScripts) + { + const FText DynamicInputText = FText::FromString(FName::NameToDisplayString(DynamicInputScript->GetName(), false)); + const FText Tooltip = FNiagaraEditorUtilities::FormatScriptAssetDescription(DynamicInputScript->Description, *DynamicInputScript->GetPathName()); + TSharedPtr DynamicInputAction(new FNiagaraMenuAction(CategoryName, DynamicInputText, Tooltip, 0, DynamicInputScript->Keywords, + FNiagaraMenuAction::FOnExecuteStackAction::CreateStatic(&ReassignDynamicInputScript, FunctionInput, DynamicInputScript))); + DynamicInputActions.AddAction(DynamicInputAction); + } +} + +void SNiagaraStackFunctionInputValue::ShowReassignDynamicInputScriptMenu() +{ + TSharedRef MenuWidget = SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + .Padding(5) + [ + SNew(SBox) + .WidthOverride(300) + .HeightOverride(400) + [ + SNew(SGraphActionMenu) + .OnActionSelected(this, &SNiagaraStackFunctionInputValue::OnActionSelected) + .OnCollectAllActions_Static(CollectDynamicInputActions, FunctionInput) + .AutoExpandActionMenu(true) + .ShowFilterTextBox(true) + .OnCreateCustomRowExpander_Static(&CreateCustomNiagaraFunctionInputActionExpander) + ] + ]; + + FGeometry ThisGeometry = GetCachedGeometry(); + bool bAutoAdjustForDpiScale = false; // Don't adjust for dpi scale because the push menu command is expecting an unscaled position. + FVector2D MenuPosition = FSlateApplication::Get().CalculatePopupWindowPosition(ThisGeometry.GetLayoutBoundingRect(), MenuWidget->GetDesiredSize(), bAutoAdjustForDpiScale); + FSlateApplication::Get().PushMenu(AsShared(), FWidgetPath(), MenuWidget, MenuPosition, FPopupTransitionEffect::ContextMenu); +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackFunctionInputValue.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackFunctionInputValue.h index b3163db60454..a9eb2020cee2 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackFunctionInputValue.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackFunctionInputValue.h @@ -25,6 +25,8 @@ public: void Construct(const FArguments& InArgs, UNiagaraStackFunctionInput* InFunctionInput); + virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; + private: bool GetInputEnabled() const; @@ -58,6 +60,7 @@ private: FText GetInvalidValueToolTipText() const; FReply DynamicInputTextDoubleClicked(); + FReply OnLinkedInputDoubleClicked(); class SNiagaraFunctionInputActionMenuExpander: public SExpanderArrow { @@ -130,6 +133,8 @@ private: bool OnFunctionInputAllowDrop(TSharedPtr DragDropOperation); + void ShowReassignDynamicInputScriptMenu(); + private: UNiagaraStackFunctionInput* FunctionInput; diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackModuleItem.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackModuleItem.cpp index e351fb6d84e4..4993fd189b2a 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackModuleItem.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackModuleItem.cpp @@ -23,6 +23,8 @@ #include "Toolkits/AssetEditorManager.h" #include "SDropTarget.h" #include "SNiagaraStackErrorButton.h" +#include "NiagaraEditorUtilities.h" +#include "SGraphActionMenu.h" #define LOCTEXT_NAMESPACE "NiagaraStackModuleItem" @@ -180,6 +182,15 @@ FReply SNiagaraStackModuleItem::OnMouseButtonDoubleClick(const FGeometry& InMyGe return FReply::Unhandled(); } +void SNiagaraStackModuleItem::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) +{ + if (ModuleItem->GetIsModuleScriptReassignmentPending()) + { + ModuleItem->SetIsModuleScriptReassignmentPending(false); + ShowReassignModuleScriptMenu(); + } +} + ECheckBoxState SNiagaraStackModuleItem::GetCheckState() const { return ModuleItem->GetIsEnabled() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; @@ -273,7 +284,8 @@ void SNiagaraStackModuleItem::ShowInsertModuleMenu(int32 InsertIndex) { TSharedRef MenuContent = SNew(SNiagaraStackItemGroupAddMenu, ModuleItem->GetGroupAddUtilities(), InsertIndex); FGeometry ThisGeometry = GetCachedGeometry(); - FVector2D MenuPosition = FSlateApplication::Get().CalculatePopupWindowPosition(ThisGeometry.GetLayoutBoundingRect(), MenuContent->GetDesiredSize()); + bool bAutoAdjustForDpiScale = false; // Don't adjust for dpi scale because the push menu command is expecting an unscaled position. + FVector2D MenuPosition = FSlateApplication::Get().CalculatePopupWindowPosition(ThisGeometry.GetLayoutBoundingRect(), MenuContent->GetDesiredSize(), bAutoAdjustForDpiScale); FSlateApplication::Get().PushMenu(AsShared(), FWidgetPath(), MenuContent, MenuPosition, FPopupTransitionEffect::ContextMenu); } @@ -315,4 +327,86 @@ FText SNiagaraStackModuleItem::GetErrorButtonTooltipText() const return FText::Format(LOCTEXT("ModuleIssuesTooltip", "This module has {0} issues, click to expand."), ModuleItem->GetRecursiveStackIssuesCount()); } +void ReassignModuleScript(UNiagaraStackModuleItem* ModuleItem, FAssetData NewModuleScriptAsset) +{ + UNiagaraScript* NewModuleScript = Cast(NewModuleScriptAsset.GetAsset()); + if (NewModuleScript != nullptr) + { + ModuleItem->ReassignModuleScript(NewModuleScript); + } +} + +void OnActionSelected(const TArray>& SelectedActions, ESelectInfo::Type InSelectionType) +{ + if (SelectedActions.Num() == 1 && (InSelectionType == ESelectInfo::OnKeyPress || InSelectionType == ESelectInfo::OnMouseClick)) + { + TSharedPtr Action = StaticCastSharedPtr(SelectedActions[0]); + if (Action.IsValid()) + { + FSlateApplication::Get().DismissAllMenus(); + Action->ExecuteAction(); + } + } +} + +void CollectModuleActions(FGraphActionListBuilderBase& ModuleActions, UNiagaraStackModuleItem* ModuleItem) +{ + TArray ModuleAssets; + FNiagaraStackGraphUtilities::GetScriptAssetsByUsage(ENiagaraScriptUsage::Module, ModuleItem->GetOutputNode()->GetUsage(), ModuleAssets); + for (const FAssetData& ModuleAsset : ModuleAssets) + { + FText Category; + ModuleAsset.GetTagValue(GET_MEMBER_NAME_CHECKED(UNiagaraScript, Category), Category); + if (Category.IsEmptyOrWhitespace()) + { + Category = LOCTEXT("ModuleNotCategorized", "Uncategorized Modules"); + } + + uint32 bDeprecatedModule; + ModuleAsset.GetTagValue(GET_MEMBER_NAME_CHECKED(UNiagaraScript, bDeprecated), bDeprecatedModule); + + if (bDeprecatedModule) + { + continue; + } + + FString DisplayNameString = FName::NameToDisplayString(ModuleAsset.AssetName.ToString(), false); + FText DisplayName = FText::FromString(DisplayNameString); + + FText AssetDescription; + ModuleAsset.GetTagValue(GET_MEMBER_NAME_CHECKED(UNiagaraScript, Description), AssetDescription); + FText Description = FNiagaraEditorUtilities::FormatScriptAssetDescription(AssetDescription, ModuleAsset.ObjectPath); + + FText Keywords; + ModuleAsset.GetTagValue(GET_MEMBER_NAME_CHECKED(UNiagaraScript, Keywords), Keywords); + + TSharedPtr ModuleAction(new FNiagaraMenuAction(Category, DisplayName, Description, 0, Keywords, + FNiagaraMenuAction::FOnExecuteStackAction::CreateStatic(&ReassignModuleScript, ModuleItem, ModuleAsset))); + ModuleActions.AddAction(ModuleAction); + } +} + +void SNiagaraStackModuleItem::ShowReassignModuleScriptMenu() +{ + TSharedRef MenuWidget = SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("Menu.Background")) + .Padding(5) + [ + SNew(SBox) + .WidthOverride(300) + .HeightOverride(400) + [ + SNew(SGraphActionMenu) + .OnActionSelected_Static(OnActionSelected) + .OnCollectAllActions_Static(CollectModuleActions, ModuleItem) + .ShowFilterTextBox(true) + ] + ]; + + FGeometry ThisGeometry = GetCachedGeometry(); + bool bAutoAdjustForDpiScale = false; // Don't adjust for dpi scale because the push menu command is expecting an unscaled position. + FVector2D MenuPosition = FSlateApplication::Get().CalculatePopupWindowPosition(ThisGeometry.GetLayoutBoundingRect(), MenuWidget->GetDesiredSize(), bAutoAdjustForDpiScale); + FSlateApplication::Get().PushMenu(AsShared(), FWidgetPath(), MenuWidget, MenuPosition, FPopupTransitionEffect::ContextMenu); +} + #undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackModuleItem.h b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackModuleItem.h index 9dd06e4b8370..89e1cd18831a 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackModuleItem.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraEditorWidgets/Private/Stack/SNiagaraStackModuleItem.h @@ -25,6 +25,7 @@ public: //~ SWidget interface virtual FReply OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent) override; + virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; private: ECheckBoxState GetCheckState() const; @@ -59,6 +60,8 @@ private: FText GetErrorButtonTooltipText() const; + void ShowReassignModuleScriptMenu(); + private: UNiagaraStackModuleItem* ModuleItem; }; \ No newline at end of file diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShader.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShader.cpp index fd1c6301c8c7..c719bdc3c0cf 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShader.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShader.cpp @@ -835,7 +835,8 @@ FShader* FNiagaraShaderMap::ProcessCompilationResultsForSingleJob(FShaderCommonC bCompiledSuccessfully = CurrentJob.bSucceeded; FNiagaraShader *NiagaraShader = static_cast(Shader); - check(Shader); + // UE-67395 - we had a case where we polluted the DDC with a shader containing no bytecode. + check(Shader && Shader->GetCode().Num() > 0); check(!HasShader(NiagaraShaderType, /* PermutationId = */ 0)); AddShader(NiagaraShaderType, /* PermutationId = */ 0, Shader); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderCompilationManager.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderCompilationManager.cpp index fafc87a99f35..afa99cc9281c 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderCompilationManager.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderCompilationManager.cpp @@ -13,7 +13,7 @@ DEFINE_LOG_CATEGORY_STATIC(LogNiagaraShaderCompiler, All, All); -static int32 GShowNiagaraShaderWarnings = 1; +static int32 GShowNiagaraShaderWarnings = 0; static FAutoConsoleVariableRef CVarShowNiagaraShaderWarnings( TEXT("niagara.ShowShaderCompilerWarnings"), GShowNiagaraShaderWarnings, @@ -104,6 +104,12 @@ void FNiagaraShaderCompilationManager::RunCompileJobs() UE_LOG(LogNiagaraShaderCompiler, Fatal, TEXT("Can't compile shaders for format %s, couldn't load compiler dll"), *Format.ToString()); } CA_ASSUME(Compiler != NULL); + + // Fast math breaks The ExecGrid layout script because floor(x/y) returns a bad value if x == y. Yay. + if(IsMetalPlatform((EShaderPlatform)CurrentJob.Input.Target.Platform)) + { + CurrentJob.Input.Environment.CompilerFlags.Add(CFLAG_NoFastMath); + } UE_LOG(LogNiagaraShaderCompiler, Log, TEXT("Compile Job processing... %s"), *CurrentJob.Input.DebugGroupName); diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderDerivedDataVersion.h b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderDerivedDataVersion.h index 8f4826ff58cb..e6495a08b0f9 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderDerivedDataVersion.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShaderDerivedDataVersion.h @@ -11,5 +11,5 @@ NiagaraShaderDerivedDataVersion.h: Shader derived data version for Niagara. // In case of merge conflicts with DDC versions, you MUST generate a new GUID and set this new // guid as version -#define NIAGARASHADERMAP_DERIVEDDATA_VER TEXT("9B98AE21EB7347A99AB0862D8B1EB8A6}") +#define NIAGARASHADERMAP_DERIVEDDATA_VER TEXT("183CB3659FE7414CB5DC9CB93D9A13D9}") diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShared.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShared.cpp index 5b173b62e5d3..3cf33aa97d46 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShared.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraShared.cpp @@ -359,7 +359,10 @@ void FNiagaraShaderScript::FinishCompilation() // Shouldn't have anything left to do... TArray ShaderMapIdsToFinish2; GetShaderMapIDsWithUnfinishedCompilation(ShaderMapIdsToFinish2); - ensure(ShaderMapIdsToFinish2.Num() == 0); + if (ShaderMapIdsToFinish2.Num() != 0) + { + UE_LOG(LogShaders, Warning, TEXT("Skipped multiple Niagara shader maps for compilation! May be indicative of no support for a given platform. Count: %d"), ShaderMapIdsToFinish2.Num()); + } } } diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Private/NiagaraRibbonVertexFactory.cpp b/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Private/NiagaraRibbonVertexFactory.cpp index d9b76765cb1d..22e4d9b6872d 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Private/NiagaraRibbonVertexFactory.cpp +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Private/NiagaraRibbonVertexFactory.cpp @@ -38,6 +38,7 @@ public: SortedIndices.Bind(ParameterMap, TEXT("SortedIndices")); SortedIndicesOffset.Bind(ParameterMap, TEXT("SortedIndicesOffset")); SegmentDistances.Bind(ParameterMap, TEXT("SegmentDistances")); + MultiRibbonIndices.Bind(ParameterMap, TEXT("MultiRibbonIndices")); PackedPerRibbonDataByIndex.Bind(ParameterMap, TEXT("PackedPerRibbonDataByIndex")); ensure(NiagaraParticleDataFloat.IsBound()); @@ -55,6 +56,7 @@ public: Ar << SortedIndices; Ar << SortedIndicesOffset; Ar << SegmentDistances; + Ar << MultiRibbonIndices; Ar << PackedPerRibbonDataByIndex; } @@ -70,6 +72,7 @@ public: SetSRVParameter(RHICmdList, VertexShaderRHI, SortedIndices, RibbonVF->GetSortedIndicesSRV()); SetSRVParameter(RHICmdList, VertexShaderRHI, SegmentDistances, RibbonVF->GetSegmentDistancesSRV()); + SetSRVParameter(RHICmdList, VertexShaderRHI, MultiRibbonIndices, RibbonVF->GetMultiRibbonIndicesSRV()); SetSRVParameter(RHICmdList, VertexShaderRHI, PackedPerRibbonDataByIndex, RibbonVF->GetPackedPerRibbonDataByIndexSRV()); SetShaderValue(RHICmdList, VertexShaderRHI, SortedIndicesOffset, RibbonVF->GetSortedIndicesOffset()); } @@ -81,6 +84,7 @@ private: FShaderResourceParameter SortedIndices; FShaderResourceParameter SegmentDistances; + FShaderResourceParameter MultiRibbonIndices; FShaderResourceParameter PackedPerRibbonDataByIndex; FShaderParameter SortedIndicesOffset; }; @@ -123,10 +127,6 @@ public: virtual void FillDeclElements(FVertexDeclarationElementList& Elements, int32& Offset) { - uint32 Stride = sizeof(FNiagaraRibbonVertex); - /** The stream to read the ribbon index from. */ - Elements.Add(FVertexElement(0, Offset, VET_UByte4, 0, Stride)); - Offset += sizeof(int32); } virtual void InitDynamicRHI() diff --git a/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Public/NiagaraRibbonVertexFactory.h b/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Public/NiagaraRibbonVertexFactory.h index 794887b1a146..31998e961dd2 100644 --- a/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Public/NiagaraRibbonVertexFactory.h +++ b/Engine/Plugins/FX/Niagara/Source/NiagaraVertexFactories/Public/NiagaraRibbonVertexFactory.h @@ -15,16 +15,6 @@ # class FMaterial; - - -// FNiagaraRibbonVertex -struct FNiagaraRibbonVertex -{ - /** The index of the ribbon for multi-ribbon emitters */ - uint32 RibbonIndex; -}; - - // FNiagaraRibbonVertexDynamicParameter struct FNiagaraRibbonVertexDynamicParameter { @@ -147,6 +137,12 @@ public: SegmentDistancesSRV = InSegmentDistancesSRV; } + + void SetMultiRibbonIndicesSRV(const FShaderResourceViewRHIRef& InMultiRibbonIndicesSRV) + { + MultiRibbonIndicesSRV = InMultiRibbonIndicesSRV; + } + void SetPackedPerRibbonDataByIndexSRV(const FShaderResourceViewRHIRef& InPackedPerRibbonDataByIndexSRV) { PackedPerRibbonDataByIndexSRV = InPackedPerRibbonDataByIndexSRV; @@ -182,6 +178,11 @@ public: return SegmentDistancesSRV; } + FORCEINLINE FShaderResourceViewRHIRef GetMultiRibbonIndicesSRV() + { + return MultiRibbonIndicesSRV; + } + FORCEINLINE FShaderResourceViewRHIRef GetPackedPerRibbonDataByIndexSRV() { return PackedPerRibbonDataByIndexSRV; @@ -225,6 +226,7 @@ private: FShaderResourceViewRHIRef SortedIndicesSRV; FShaderResourceViewRHIRef SegmentDistancesSRV; + FShaderResourceViewRHIRef MultiRibbonIndicesSRV; FShaderResourceViewRHIRef PackedPerRibbonDataByIndexSRV; uint32 SortedIndicesOffset; diff --git a/Engine/Plugins/Media/MediaIOFramework/Source/MediaIOEditor/Public/Widgets/SMediaPermutationsSelector.inl b/Engine/Plugins/Media/MediaIOFramework/Source/MediaIOEditor/Public/Widgets/SMediaPermutationsSelector.inl index 6479659436de..9b1790678a5e 100644 --- a/Engine/Plugins/Media/MediaIOFramework/Source/MediaIOEditor/Public/Widgets/SMediaPermutationsSelector.inl +++ b/Engine/Plugins/Media/MediaIOFramework/Source/MediaIOEditor/Public/Widgets/SMediaPermutationsSelector.inl @@ -22,7 +22,7 @@ ItemType SMediaPermutationsSelector::GetSelectedItem() co template void SMediaPermutationsSelector::Construct(const FArguments& InArgs) { - SWidget::Construct(InArgs._ToolTipText, InArgs._ToolTip, InArgs._Cursor, InArgs._IsEnabled, InArgs._Visibility, InArgs._RenderOpacity, InArgs._RenderTransform, InArgs._RenderTransformPivot, InArgs._Tag, InArgs._ForceVolatile, InArgs._Clipping, InArgs.MetaData); + SWidget::Construct(InArgs._ToolTipText, InArgs._ToolTip, InArgs._Cursor, InArgs._IsEnabled, InArgs._Visibility, InArgs._RenderOpacity, InArgs._RenderTransform, InArgs._RenderTransformPivot, InArgs._Tag, InArgs._ForceVolatile, InArgs._Clipping, InArgs._FlowDirectionPreference, InArgs.MetaData); PermutationsSource = InArgs._PermutationsSource; SelectedPermutationIndex = INDEX_NONE; diff --git a/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/User/SocialUser.cpp b/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/User/SocialUser.cpp index a810db23abdc..a137974547a0 100644 --- a/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/User/SocialUser.cpp +++ b/Engine/Plugins/Online/OnlineFramework/Source/Party/Private/User/SocialUser.cpp @@ -137,15 +137,18 @@ void USocialUser::InitLocalUser() IOnlineSubsystem* OSS = OwningToolkit.GetSocialOss(SubsystemType); check(OSS); - IOnlineIdentityPtr IdentityInterface = OSS->GetIdentityInterface(); - FUniqueNetIdRepl LocalUserSubsystemId = IdentityInterface ? IdentityInterface->GetUniquePlayerId(OwningToolkit.GetLocalUserNum()) : FUniqueNetIdRepl(); - if (ensure(LocalUserSubsystemId.IsValid())) + if (IOnlineIdentityPtr IdentityInterface = OSS->GetIdentityInterface()) { - SetSubsystemId(SubsystemType, LocalUserSubsystemId); - } - else - { - UE_LOG(LogParty, Error, TEXT("Local SocialUser unable to establish a valid UniqueId on subsystem [%s]"), ToString(SubsystemType)); + const int32 LocalUserNum = OwningToolkit.GetLocalUserNum(); + FUniqueNetIdRepl LocalUserSubsystemId = IdentityInterface->GetUniquePlayerId(LocalUserNum); + if (IdentityInterface->GetLoginStatus(LocalUserNum) == ELoginStatus::LoggedIn && ensure(LocalUserSubsystemId.IsValid())) + { + SetSubsystemId(SubsystemType, LocalUserSubsystemId); + } + else + { + UE_LOG(LogParty, Warning, TEXT("Local SocialUser unable to establish a valid UniqueId on subsystem [%s]"), ToString(SubsystemType)); + } } } diff --git a/Engine/Plugins/Online/OnlineSubsystem/Source/Private/OnlineError.cpp b/Engine/Plugins/Online/OnlineSubsystem/Source/Private/OnlineError.cpp index 3f085b63069f..ccf8be35e610 100644 --- a/Engine/Plugins/Online/OnlineSubsystem/Source/Private/OnlineError.cpp +++ b/Engine/Plugins/Online/OnlineSubsystem/Source/Private/OnlineError.cpp @@ -97,7 +97,7 @@ void FOnlineError::SetFromErrorCode(EOnlineErrorResult InResult, const FString& FOnlineError::FOnlineError(bool bSucceededIn) : bSucceeded(bSucceededIn) - , Result(bSucceededIn ? EOnlineErrorResult::Success : EOnlineErrorResult::Unknown) + , Result(EOnlineErrorResult::Unknown) { } diff --git a/Engine/Plugins/Online/OnlineSubsystem/Source/Private/OnlineSubsystemModule.cpp b/Engine/Plugins/Online/OnlineSubsystem/Source/Private/OnlineSubsystemModule.cpp index a179b4c1aef6..21e68a2caab3 100644 --- a/Engine/Plugins/Online/OnlineSubsystem/Source/Private/OnlineSubsystemModule.cpp +++ b/Engine/Plugins/Online/OnlineSubsystem/Source/Private/OnlineSubsystemModule.cpp @@ -303,6 +303,8 @@ IOnlineSubsystem* FOnlineSubsystemModule::GetOnlineSubsystem(const FName InSubsy if (OSSFactory != nullptr) { + UE_LOG_ONLINE(Verbose, TEXT("Creating online subsystem instance for: %s"), *InSubsystemName.ToString()); + IOnlineSubsystemPtr NewSubsystemInstance = (*OSSFactory)->CreateSubsystem(InstanceName); if (NewSubsystemInstance.IsValid()) { diff --git a/Engine/Plugins/Online/OnlineSubsystemFacebook/Source/Private/OnlineSubsystemFacebookModule.cpp b/Engine/Plugins/Online/OnlineSubsystemFacebook/Source/Private/OnlineSubsystemFacebookModule.cpp index 05044358d870..3ca75055f65d 100644 --- a/Engine/Plugins/Online/OnlineSubsystemFacebook/Source/Private/OnlineSubsystemFacebookModule.cpp +++ b/Engine/Plugins/Online/OnlineSubsystemFacebook/Source/Private/OnlineSubsystemFacebookModule.cpp @@ -20,6 +20,8 @@ public: FOnlineSubsystemFacebookPtr OnlineSub = MakeShared(InstanceName); if (OnlineSub->IsEnabled()) { + UE_LOG_ONLINE(Log, TEXT("Facebook API is being initialized.")); + if(!OnlineSub->Init()) { UE_LOG_ONLINE(Warning, TEXT("Facebook API failed to initialize!")); @@ -40,7 +42,7 @@ public: void FOnlineSubsystemFacebookModule::StartupModule() { - UE_LOG_ONLINE(Log, TEXT("Facebook Startup!")); + UE_LOG_ONLINE(Log, TEXT("Facebook Module Startup!")); FacebookFactory = new FOnlineFactoryFacebook(); @@ -51,7 +53,7 @@ void FOnlineSubsystemFacebookModule::StartupModule() void FOnlineSubsystemFacebookModule::ShutdownModule() { - UE_LOG_ONLINE(Log, TEXT("Facebook Shutdown!")); + UE_LOG_ONLINE(Log, TEXT("Facebook Module Shutdown!")); FOnlineSubsystemModule& OSS = FModuleManager::GetModuleChecked("OnlineSubsystem"); OSS.UnregisterPlatformService(FACEBOOK_SUBSYSTEM); diff --git a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Public/OnlineSubsystemUtils.h b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Public/OnlineSubsystemUtils.h index f36be05496d7..21fdcc817536 100644 --- a/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Public/OnlineSubsystemUtils.h +++ b/Engine/Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Public/OnlineSubsystemUtils.h @@ -224,7 +224,7 @@ namespace Online } /** - * Determine if the subsystem for a given interface is already loaded + * Determine if the subsystem module for a given interface is already loaded. Note that modules do not typically initialize the subsystem when the module is loaded. * * @param World the world to use for context * @param SubsystemName name of the requested online service @@ -249,7 +249,34 @@ namespace Online return IOnlineSubsystem::IsLoaded(SubsystemName); #endif } - + + /** + * Determine if the subsystem for a given interface has been instanced + * + * @param World the world to use for context + * @param SubsystemName name of the requested online service + * + * @return true if the subsystem has been instanced + */ + static bool DoesInstanceExist(const UWorld* World, const FName& SubsystemName = NAME_None) + { +#if UE_EDITOR // at present, multiple worlds are only possible in the editor + FName Identifier = SubsystemName; + if (World != NULL) + { + FWorldContext& CurrentContext = GEngine->GetWorldContextFromWorldChecked(World); + if (CurrentContext.WorldType == EWorldType::PIE) + { + Identifier = FName(*FString::Printf(TEXT("%s:%s"), SubsystemName != NAME_None ? *SubsystemName.ToString() : TEXT(""), *CurrentContext.ContextHandle.ToString())); + } + } + + return IOnlineSubsystem::DoesInstanceExist(SubsystemName); +#else + return IOnlineSubsystem::DoesInstanceExist(SubsystemName); +#endif + } + /** Reimplement all the interfaces of Online.h with support for UWorld accessors */ IMPLEMENT_GET_INTERFACE(Session); IMPLEMENT_GET_INTERFACE(Party); diff --git a/Engine/Plugins/Runtime/AR/Apple/AppleARKit/Source/AppleARKit/Private/AppleARKitSystem.cpp b/Engine/Plugins/Runtime/AR/Apple/AppleARKit/Source/AppleARKit/Private/AppleARKitSystem.cpp index 02a72d77eba4..1dc812753a61 100644 --- a/Engine/Plugins/Runtime/AR/Apple/AppleARKit/Source/AppleARKit/Private/AppleARKitSystem.cpp +++ b/Engine/Plugins/Runtime/AR/Apple/AppleARKit/Source/AppleARKit/Private/AppleARKitSystem.cpp @@ -1165,25 +1165,51 @@ void FAppleARKitSystem::SetDeviceOrientation( EScreenOrientation::Type InOrienta // So pick ANY ALLOWED default. // This only realy happens if the device is face down on something or // in another "useless" state for AR. + + // Note: the order in which this selection is done is important and must match that + // established in UEDeployIOS.cs and written into UISupportedInterfaceOrientations. + // IOSView preferredInterfaceOrientationForPresentation presumably also should match. + // + // However it would very likely be better to hook statusBarOrientation instead of deviceOrientation to update + // our orientation, in which case we would only need to handle unknown. if (!NewOrientation.IsSet()) { NewOrientation = PickAllowedDeviceOrientation(EScreenOrientation::Portrait); } - - if (!NewOrientation.IsSet()) - { - NewOrientation = PickAllowedDeviceOrientation(EScreenOrientation::LandscapeLeft); - } - + if (!NewOrientation.IsSet()) { NewOrientation = PickAllowedDeviceOrientation(EScreenOrientation::PortraitUpsideDown); } - - if (!NewOrientation.IsSet()) + +#if SUPPORTS_ARKIT_1_0 + const UIOSRuntimeSettings* IOSSettings = GetDefault(); + const bool bPreferLandscapeLeftHomeButton = IOSSettings->PreferredLandscapeOrientation == EIOSLandscapeOrientation::LandscapeLeft; +#else + const bool bPreferLandscapeLeftHomeButton = true; +#endif + if (bPreferLandscapeLeftHomeButton) { - NewOrientation = PickAllowedDeviceOrientation(EScreenOrientation::LandscapeRight); + if (!NewOrientation.IsSet()) + { + NewOrientation = PickAllowedDeviceOrientation(EScreenOrientation::LandscapeRight); + } + if (!NewOrientation.IsSet()) + { + NewOrientation = PickAllowedDeviceOrientation(EScreenOrientation::LandscapeLeft); + } + } + else + { + if (!NewOrientation.IsSet()) + { + NewOrientation = PickAllowedDeviceOrientation(EScreenOrientation::LandscapeLeft); + } + if (!NewOrientation.IsSet()) + { + NewOrientation = PickAllowedDeviceOrientation(EScreenOrientation::LandscapeRight); + } } check(NewOrientation.IsSet()); @@ -1196,6 +1222,28 @@ void FAppleARKitSystem::SetDeviceOrientation( EScreenOrientation::Type InOrienta } } +static EScreenOrientation::Type GetAppOrientation() +{ +#if PLATFORM_IOS && !PLATFORM_TVOS + // We want the orientation that the app is running with, not necessarily the orientation of the device right now. + UIInterfaceOrientation Orientation = [[UIApplication sharedApplication] statusBarOrientation]; +#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_8_0 + Orientation = [[IOSAppDelegate GetDelegate].IOSController interfaceOrientation]; +#endif + EScreenOrientation::Type ScreenOrientation = EScreenOrientation::Unknown; + switch (Orientation) + { + case UIInterfaceOrientationUnknown: return EScreenOrientation::Unknown; + case UIInterfaceOrientationPortrait: return EScreenOrientation::Portrait; + case UIInterfaceOrientationPortraitUpsideDown: return EScreenOrientation::PortraitUpsideDown; + case UIInterfaceOrientationLandscapeLeft: return EScreenOrientation::LandscapeRight; + case UIInterfaceOrientationLandscapeRight: return EScreenOrientation::LandscapeLeft; + default: check(false); return EScreenOrientation::Unknown; + } +#else + return static_cast(FPlatformMisc::GetDeviceOrientation()); +#endif +} PRAGMA_DISABLE_OPTIMIZATION bool FAppleARKitSystem::Run(UARSessionConfig* SessionConfig) @@ -1212,7 +1260,8 @@ bool FAppleARKitSystem::Run(UARSessionConfig* SessionConfig) // Make sure this is set at session start, because there are timing issues with using only the delegate approach if (DeviceOrientation == EScreenOrientation::Unknown) { - SetDeviceOrientation( static_cast(FPlatformMisc::GetDeviceOrientation()) ); + const EScreenOrientation::Type ScreenOrientation = GetAppOrientation(); + SetDeviceOrientation( ScreenOrientation ); } #if SUPPORTS_ARKIT_1_0 diff --git a/Engine/Plugins/Runtime/AndroidPermission/AndroidPermission.uplugin b/Engine/Plugins/Runtime/AndroidPermission/AndroidPermission.uplugin index a846c25062ad..a3fabb12c438 100644 --- a/Engine/Plugins/Runtime/AndroidPermission/AndroidPermission.uplugin +++ b/Engine/Plugins/Runtime/AndroidPermission/AndroidPermission.uplugin @@ -14,10 +14,6 @@ "CanContainContent": false, "IsBetaVersion": true, "Installed": false, - "SupportedTargetPlatforms": [ - "Android", - "Lumin" - ], "Modules": [ { "Name": "AndroidPermission", diff --git a/Engine/Plugins/Runtime/GameplayAbilities/GameplayAbilities.uplugin b/Engine/Plugins/Runtime/GameplayAbilities/GameplayAbilities.uplugin index b95542d12bd7..ef4d25aa4a32 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/GameplayAbilities.uplugin +++ b/Engine/Plugins/Runtime/GameplayAbilities/GameplayAbilities.uplugin @@ -3,7 +3,7 @@ "Version" : 1, "VersionName" : "0.1", "FriendlyName" : "Gameplay Abilities", - "Description" : "[UNSUPPORTED] Adds GameplayEffect and GameplayAbility classes to handle complicated gameplay interactions.", + "Description" : "Adds GameplayEffect and GameplayAbility classes to handle complicated gameplay interactions.", "Category" : "Gameplay", "CreatedBy" : "Epic Games, Inc.", "CreatedByURL" : "http://epicgames.com", diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemBlueprintLibrary.cpp b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemBlueprintLibrary.cpp index b75852fdd179..8c5b8b98f262 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemBlueprintLibrary.cpp +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemBlueprintLibrary.cpp @@ -681,6 +681,48 @@ bool UAbilitySystemBlueprintLibrary::DoesGameplayCueMeetTagRequirements(FGamepla && TargetTagReqs.RequirementsMet(Parameters.AggregatedSourceTags); } +FGameplayCueParameters UAbilitySystemBlueprintLibrary::MakeGameplayCueParameters(float NormalizedMagnitude, float RawMagnitude, FGameplayEffectContextHandle EffectContext, FGameplayTag MatchedTagName, FGameplayTag OriginalTag, FGameplayTagContainer AggregatedSourceTags, FGameplayTagContainer AggregatedTargetTags, FVector Location, FVector Normal, AActor* Instigator, AActor* EffectCauser, UObject* SourceObject, UPhysicalMaterial* PhysicalMaterial, int32 GameplayEffectLevel, int32 AbilityLevel, USceneComponent* TargetAttachComponent) +{ + FGameplayCueParameters Parameters; + Parameters.NormalizedMagnitude = NormalizedMagnitude; + Parameters.RawMagnitude = RawMagnitude; + Parameters.EffectContext = EffectContext; + Parameters.MatchedTagName = MatchedTagName; + Parameters.OriginalTag = OriginalTag; + Parameters.AggregatedSourceTags = AggregatedSourceTags; + Parameters.AggregatedTargetTags = AggregatedTargetTags; + Parameters.Location = Location; + Parameters.Normal = Normal; + Parameters.Instigator = Instigator; + Parameters.EffectCauser = EffectCauser; + Parameters.SourceObject = SourceObject; + Parameters.PhysicalMaterial = PhysicalMaterial; + Parameters.GameplayEffectLevel = GameplayEffectLevel; + Parameters.AbilityLevel = AbilityLevel; + Parameters.TargetAttachComponent = TargetAttachComponent; + return Parameters; +} + +void UAbilitySystemBlueprintLibrary::BreakGameplayCueParameters(const struct FGameplayCueParameters& Parameters, float& NormalizedMagnitude, float& RawMagnitude, FGameplayEffectContextHandle& EffectContext, FGameplayTag& MatchedTagName, FGameplayTag& OriginalTag, FGameplayTagContainer& AggregatedSourceTags, FGameplayTagContainer& AggregatedTargetTags, FVector& Location, FVector& Normal, AActor*& Instigator, AActor*& EffectCauser, UObject*& SourceObject, UPhysicalMaterial*& PhysicalMaterial, int32& GameplayEffectLevel, int32& AbilityLevel, USceneComponent*& TargetAttachComponent) +{ + NormalizedMagnitude = Parameters.NormalizedMagnitude; + RawMagnitude = Parameters.RawMagnitude; + EffectContext = Parameters.EffectContext; + MatchedTagName = Parameters.MatchedTagName; + OriginalTag = Parameters.OriginalTag; + AggregatedSourceTags = Parameters.AggregatedSourceTags; + AggregatedTargetTags = Parameters.AggregatedTargetTags; + Location = Parameters.Location; + Normal = Parameters.Normal; + Instigator = Parameters.Instigator.Get(); + EffectCauser = Parameters.EffectCauser.Get(); + SourceObject = const_cast(Parameters.SourceObject.Get()); + PhysicalMaterial = const_cast(Parameters.PhysicalMaterial.Get()); + GameplayEffectLevel = Parameters.GameplayEffectLevel; + AbilityLevel = Parameters.AbilityLevel; + TargetAttachComponent = Parameters.TargetAttachComponent.Get(); +} + // --------------------------------------------------------------------------------------- FGameplayEffectSpecHandle UAbilitySystemBlueprintLibrary::AssignSetByCallerMagnitude(FGameplayEffectSpecHandle SpecHandle, FName DataName, float Magnitude) diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cpp b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cpp index 97b748337e08..3b668c38c66e 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cpp +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/AbilitySystemComponent_Abilities.cpp @@ -30,7 +30,6 @@ #include "Abilities/GameplayAbilityTargetActor.h" #include "TickableAttributeSetInterface.h" #include "GameplayTagResponseTable.h" -#include "Engine/DemoNetDriver.h" #include "ProfilingDebugging/CsvProfiler.h" #define LOCTEXT_NAMESPACE "AbilitySystemComponent" @@ -2519,7 +2518,7 @@ void UAbilitySystemComponent::OnRep_ReplicatedAnimMontage() RepAnimMontageInfo.PlayRate = 1.f; } - const bool bIsPlayingReplay = World && World->DemoNetDriver && World->DemoNetDriver->IsPlaying(); + const bool bIsPlayingReplay = World && World->IsPlayingReplay(); const float MONTAGE_REP_POS_ERR_THRESH = bIsPlayingReplay ? CVarReplayMontageErrorThreshold.GetValueOnGameThread() : 0.1f; diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayCueManager.cpp b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayCueManager.cpp index 65c72780a782..1265cf242060 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayCueManager.cpp +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Private/GameplayCueManager.cpp @@ -438,6 +438,7 @@ AGameplayCueNotify_Actor* UGameplayCueManager::GetInstancedCueActor(AActor* Targ ABILITY_LOG(Warning, TEXT("Spawning GameplaycueActor: %s"), *CueClass->GetName()); } + SpawnParams.OverrideLevel = World->PersistentLevel; SpawnedCue = World->SpawnActor(CueClass, TargetActor->GetActorLocation(), TargetActor->GetActorRotation(), SpawnParams); } diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AbilitySystemBlueprintLibrary.h b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AbilitySystemBlueprintLibrary.h index 89efbc24ad1a..5e8ea4593830 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AbilitySystemBlueprintLibrary.h +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/AbilitySystemBlueprintLibrary.h @@ -270,6 +270,14 @@ class GAMEPLAYABILITIES_API UAbilitySystemBlueprintLibrary : public UBlueprintFu UFUNCTION(BlueprintPure, Category = "Ability|GameplayCue") static bool DoesGameplayCueMeetTagRequirements(FGameplayCueParameters Parameters, const FGameplayTagRequirements& SourceTagReqs, const FGameplayTagRequirements& TargetTagReqs); + /** Native make, to avoid having to deal with quantized vector types */ + UFUNCTION(BlueprintPure, Category = "Ability|GameplayCue", meta = (NativeMakeFunc, AdvancedDisplay=5, Location="0,0,0", Normal= "0,0,0", GameplayEffectLevel = "1", AbilityLevel = "1")) + static FGameplayCueParameters MakeGameplayCueParameters(float NormalizedMagnitude, float RawMagnitude, FGameplayEffectContextHandle EffectContext, FGameplayTag MatchedTagName, FGameplayTag OriginalTag, FGameplayTagContainer AggregatedSourceTags, FGameplayTagContainer AggregatedTargetTags, FVector Location, FVector Normal, AActor* Instigator, AActor* EffectCauser, UObject* SourceObject, UPhysicalMaterial* PhysicalMaterial, int32 GameplayEffectLevel, int32 AbilityLevel, USceneComponent* TargetAttachComponent); + + /** Native break, to avoid having to deal with quantized vector types */ + UFUNCTION(BlueprintPure, Category = "Ability|GameplayCue", meta = (NativeBreakFunc, AdvancedDisplay=6)) + static void BreakGameplayCueParameters(const struct FGameplayCueParameters& Parameters, float& NormalizedMagnitude, float& RawMagnitude, FGameplayEffectContextHandle& EffectContext, FGameplayTag& MatchedTagName, FGameplayTag& OriginalTag, FGameplayTagContainer& AggregatedSourceTags, FGameplayTagContainer& AggregatedTargetTags, FVector& Location, FVector& Normal, AActor*& Instigator, AActor*& EffectCauser, UObject*& SourceObject, UPhysicalMaterial*& PhysicalMaterial, int32& GameplayEffectLevel, int32& AbilityLevel, USceneComponent*& TargetAttachComponent); + // ------------------------------------------------------------------------------- // GameplayEffectSpec // ------------------------------------------------------------------------------- diff --git a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/GameplayEffectTypes.h b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/GameplayEffectTypes.h index c1cd0ad61a18..98c3e2468932 100644 --- a/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/GameplayEffectTypes.h +++ b/Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/GameplayEffectTypes.h @@ -918,7 +918,7 @@ struct FGameplayEffectRemovalInfo /** Metadata about a gameplay cue execution */ -USTRUCT(BlueprintType) +USTRUCT(BlueprintType, meta = (HasNativeBreak = "GameplayAbilities.AbilitySystemBlueprintLibrary.BreakGameplayCueParameters", HasNativeMake = "GameplayAbilities.AbilitySystemBlueprintLibrary.MakeGameplayCueParameters")) struct GAMEPLAYABILITIES_API FGameplayCueParameters { GENERATED_USTRUCT_BODY() diff --git a/Engine/Plugins/Runtime/TimeSynth/Source/TimeSynth/Classes/TimeSynthComponent.h b/Engine/Plugins/Runtime/TimeSynth/Source/TimeSynth/Classes/TimeSynthComponent.h index 02ed7f33e829..6d1718ce8ca2 100644 --- a/Engine/Plugins/Runtime/TimeSynth/Source/TimeSynth/Classes/TimeSynthComponent.h +++ b/Engine/Plugins/Runtime/TimeSynth/Source/TimeSynth/Classes/TimeSynthComponent.h @@ -565,7 +565,7 @@ private: void UpdateEnvelopeFollower(); void ShutdownPlayingClips(); - Audio::SpectrumAnalyzerSettings::EFFTSize GetFFTSize(ETimeSynthFFTSize InSize) const; + Audio::FSpectrumAnalyzerSettings::EFFTSize GetFFTSize(ETimeSynthFFTSize InSize) const; // Defines type for a volume group ID typedef uint32 VolumeGroupUniqueId; @@ -703,7 +703,7 @@ private: // Spectum analyzer to allow BP delegates to visualize music Audio::FSpectrumAnalyzer SpectrumAnalyzer; - Audio::SpectrumAnalyzerSettings::FSettings SpectrumAnalyzerSettings; + Audio::FSpectrumAnalyzerSettings SpectrumAnalyzerSettings; FThreadSafeCounter SpectrumAnalysisCounter; // Array of spectrum data, maps to FrequenciesToAnalyze UProperty diff --git a/Engine/Plugins/Runtime/TimeSynth/Source/TimeSynth/Private/TimeSynthComponent.cpp b/Engine/Plugins/Runtime/TimeSynth/Source/TimeSynth/Private/TimeSynthComponent.cpp index 20d313d3cee4..e59faf3c79cf 100644 --- a/Engine/Plugins/Runtime/TimeSynth/Source/TimeSynth/Private/TimeSynthComponent.cpp +++ b/Engine/Plugins/Runtime/TimeSynth/Source/TimeSynth/Private/TimeSynthComponent.cpp @@ -140,23 +140,23 @@ void UTimeSynthComponent::SetEnvelopeFollowerEnabled(bool bInIsEnabled) }); } -Audio::SpectrumAnalyzerSettings::EFFTSize UTimeSynthComponent::GetFFTSize(ETimeSynthFFTSize InSize) const +Audio::FSpectrumAnalyzerSettings::EFFTSize UTimeSynthComponent::GetFFTSize(ETimeSynthFFTSize InSize) const { switch (InSize) { - case ETimeSynthFFTSize::Min_64: return Audio::SpectrumAnalyzerSettings::EFFTSize::Min_64; - case ETimeSynthFFTSize::Small_256: return Audio::SpectrumAnalyzerSettings::EFFTSize::Small_256; - case ETimeSynthFFTSize::Medium_512: return Audio::SpectrumAnalyzerSettings::EFFTSize::Medium_512; - case ETimeSynthFFTSize::Large_1024: return Audio::SpectrumAnalyzerSettings::EFFTSize::Large_1024; + case ETimeSynthFFTSize::Min_64: return Audio::FSpectrumAnalyzerSettings::EFFTSize::Min_64; + case ETimeSynthFFTSize::Small_256: return Audio::FSpectrumAnalyzerSettings::EFFTSize::Small_256; + case ETimeSynthFFTSize::Medium_512: return Audio::FSpectrumAnalyzerSettings::EFFTSize::Medium_512; + case ETimeSynthFFTSize::Large_1024: return Audio::FSpectrumAnalyzerSettings::EFFTSize::Large_1024; break; } // return default - return Audio::SpectrumAnalyzerSettings::EFFTSize::Medium_512; + return Audio::FSpectrumAnalyzerSettings::EFFTSize::Medium_512; } void UTimeSynthComponent::SetFFTSize(ETimeSynthFFTSize InFFTSize) { - Audio::SpectrumAnalyzerSettings::EFFTSize NewFFTSize = GetFFTSize(InFFTSize); + Audio::FSpectrumAnalyzerSettings::EFFTSize NewFFTSize = GetFFTSize(InFFTSize); SynthCommand([this, NewFFTSize] { diff --git a/Engine/Shaders/Private/Common.ush b/Engine/Shaders/Private/Common.ush index 695f5ffd3246..d481923c736e 100644 --- a/Engine/Shaders/Private/Common.ush +++ b/Engine/Shaders/Private/Common.ush @@ -172,7 +172,7 @@ MaterialFloat4 TextureExternalSample(TextureExternal Tex, SamplerState Sampler, return Tex.Sample(Sampler, UV); #endif } -MaterialFloat4 TextureExternalSampleLevel(Texture2D Tex, SamplerState Sampler, float2 UV, MaterialFloat Mip) +MaterialFloat4 TextureExternalSampleLevel(TextureExternal Tex, SamplerState Sampler, float2 UV, MaterialFloat Mip) { return Tex.SampleLevel(Sampler, UV, Mip); } diff --git a/Engine/Shaders/Private/NiagaraEmitterInstanceShader.usf b/Engine/Shaders/Private/NiagaraEmitterInstanceShader.usf index f008dd426c2c..69912829a056 100644 --- a/Engine/Shaders/Private/NiagaraEmitterInstanceShader.usf +++ b/Engine/Shaders/Private/NiagaraEmitterInstanceShader.usf @@ -8,6 +8,7 @@ NiagaraSimulationShader.usf: #if GPU_SIMULATION #include "/Engine/Public/Platform.ush" #include "Common.ush" + #include "GlobalDistanceFieldShared.ush" #else const static float PI = 3.1415926535897932f; #endif @@ -15,6 +16,20 @@ NiagaraSimulationShader.usf: #include "Definitions.usf" +// Most of the vector implementations work this way. This helps us keep proper precision. +float4 ModuloPrecise(float4 x, float4 y){ return x - y * trunc(x/y); } +float3 ModuloPrecise(float3 x, float3 y){ return x - y * trunc(x/y); } +float2 ModuloPrecise(float2 x, float2 y){ return x - y * trunc(x/y);} +float ModuloPrecise(float x, float y){ return x - y * trunc(x/y); } +int ModuloPrecise(int x, int y){ return x - y * (x/y); } +int Modulo(int x, int y){ return x - y * (x/y); } + + +// using rcp is only 12 bits of precision, we should usually pay for perf +float4 Reciprocal(float4 x){ return 1.0f/x;} +float3 Reciprocal(float3 x){ return 1.0f/x; } +float2 Reciprocal(float2 x){ return 1.0f/x;} +float Reciprocal(float x){ return 1.0f/x; } /* ----------------------------------------------------------------- * GPU simulation utility functions @@ -35,77 +50,169 @@ NiagaraSimulationShader.usf: float2 Modulo(float2 x, float2 y){ return fmod(x,y); } float Modulo(float x, float y){ return fmod(x,y); } - - // 4D random number generator inspired by PCGs (permuted congruential generator) - // COPY FROM RANDOM.USH, which we can't easily include here due to a sizable dependency chain - uint4 NiagaraRand4DPCG32(int4 p) + // utility function used for scene depth calculations + float3 WorldPositionFromSceneDepth(float2 ScreenPosition, float SceneDepth) { - // taking a signed int then reinterpreting as unsigned gives good behavior for negatives - uint4 v = uint4(p); - - // Linear congruential step. - v = v * 1664525u + 1013904223u; - - // swapping low and high bits makes all 32 bits pretty good - v = v * (1u << 16u) + (v >> 16u); - - // final shuffle - v.x += v.y*v.w; - v.y += v.z*v.x; - v.z += v.x*v.y; - v.w += v.y*v.z; - v.x += v.y*v.w; - v.y += v.z*v.x; - v.z += v.x*v.y; - v.w += v.y*v.z; - - return v; + float4 HomogeneousWorldPosition = mul(float4(ScreenPosition * SceneDepth, SceneDepth, 1), View.ScreenToWorld); + return HomogeneousWorldPosition.xyz / HomogeneousWorldPosition.w; } +#endif +/* ---------------------------------------------------------------------------- + * Seeded/Deterministic random number generation functions + * + * This is a variant of NiagaraRand4DPCG32 from Random.ush. + * + * uint is not fully supported in the VM so we simply use ints and drop the + * top and bottom bit swap. This should be fine since signed overflow should + * produce the same results as unsigned overflow when comparing bit-by-bit on + * all relevant architectures. + * + * Warning: Only contains 24 bits of randomness, since we produce values in + * the unit interval. + * + * Note: Deterministic as long as Seed1, Seed2 and Seed3 are the same *and* as + * long as no Random or Seeded Random ops are inserted into or removed + * from the current script. + * + * TODO(mv): Might want to use a special case based on NiagaraRand3DPCG32 if + * only 3 or less components are needed, and we can drop one of the + * seeds, to reduce the operation count + * ---------------------------------------------------------------------------- + */ +// static counter that is incremented every time rand3 or rand4 is called. +static int RandomCounter = 0; - // using Marc's PCG random implementation, sequentially; initially seed with DispatchThreadID and tick counter, - // then repeat seeding with the result from the PRNG; should be deterministic - // TODO: use this in the VM for CPU sims as well - // - static uint4 RandomVal = 2308975; - float4 rand4() - { - RandomVal = NiagaraRand4DPCG32( int4(GDispatchThreadId+(RandomVal/573).xyz, RandomVal.x/921) + int4(EmitterTickCounter.xxxx)); - return float4(RandomVal.xyzw) / float4(0xFFFFFFFFu,0xFFFFFFFFu,0xFFFFFFFFu,0xFFFFFFFFu); - } +// Returns 4 random normalized floats based on 3 explicit integer seeds and one +// implicit integer seed. +float4 rand4(int Seed1, int Seed2, int Seed3) +{ + RandomCounter += 1; + + int4 v = int4(RandomCounter, Seed1, Seed2, Seed3) * 1664525 + 1013904223; + + v.x += v.y*v.w; + v.y += v.z*v.x; + v.z += v.x*v.y; + v.w += v.y*v.z; + v.x += v.y*v.w; + v.y += v.z*v.x; + v.z += v.x*v.y; + v.w += v.y*v.z; + + // NOTE(mv): We can use 24 bits of randomness, as all integers in [0, 2^24] + // are exactly representable in single precision floats. + // We use the upper 24 bits as they tend to be higher quality. + + // NOTE(mv): The divide can often be folded with the range scale in the rand functions + + return float4((v >> 8) & 0x00ffffff) / 16777216.0; // 0x01000000 == 16777216 + // return float4((v >> 8) & 0x00ffffff) * (1.0/16777216.0); // bugged, see UE-67738 +} + +// float3 specialization of the above: +// Returns 3 random normalized floats based on 3 explicit integer seeds and one +// implicit integer seed. The implicit seed takes the upper 16 bits of the third +// explicit seed, as this is often a global seed. +// The expression +// +// RandomCounter | (Seed3 << 16) +// +// should be optimized away at compile time, as the third seed is typically +// a global constant. +float3 rand3(int Seed1, int Seed2, int Seed3) +{ + RandomCounter += 1; + + int3 v = int3(Seed1, Seed2, RandomCounter | (Seed3 << 16)) * 1664525 + 1013904223; + + v.x += v.y*v.z; + v.y += v.z*v.x; + v.z += v.x*v.y; + v.x += v.y*v.z; + v.y += v.z*v.x; + v.z += v.x*v.y; + + return float3((v >> 8) & 0x00ffffff) / 16777216.0; // 0x01000000 == 16777216 +} + +// Cost using rand4: 6 imad, 1 itof, 1 ishr, 1 add, 2 mul +float rand(float x, int Seed1, int Seed2, int Seed3) +{ + return rand3(Seed1, Seed2, Seed3).x * x; +} + +// Cost using rand4: 7 imad, 1 itof, 1 ishr, 1 add, 2 mul +float2 rand(float2 x, int Seed1, int Seed2, int Seed3) +{ + return rand3(Seed1, Seed2, Seed3).xy * x; +} + +// Cost using rand4: 8 imad, 1 itof, 1 ishr, 1 add, 2 mul +float3 rand(float3 x, int Seed1, int Seed2, int Seed3) +{ + return rand3(Seed1, Seed2, Seed3).xyz * x; +} + +// Cost using rand4: 9 imad, 1 itof, 1 ishr, 1 and, 2 mul +float4 rand(float4 x, int Seed1, int Seed2, int Seed3) +{ + return rand4(Seed1, Seed2, Seed3).xyzw * x; +} + +// Cost using rand4: 6 imad, 2 itof, 1 ishr, 1 add, 2 mul, 1 ftoi +int rand(int x, int Seed1, int Seed2, int Seed3) +{ + // NOTE: Scaling a uniform float range provides better distribution of + // numbers than using %. + // NOTE: Inclusive! So [0, x] instead of [0, x) + return int(rand3(Seed1, Seed2, Seed3).x * (x+1)); +} + +/* ----------------------------------------------------------------- + * Un-seeded/Non-deterministic random number generation functions + * ----------------------------------------------------------------- + */ +#if GPU_SIMULATION + // NOTE(mv): This simply calls the deterministic random number functions + // from the Seeded RNG section, but uses non-deterministic seeds + // as input. In particular, GDispatchThreadId for the first seed + // and EmitterTickCounter for the second seed. Also note that + // EmitterTickCounter does *not* reset for each frame, which + // secures non-determinism across simulations. + + // TODO(mv): This could perhaps be optimized a little bit using slightly + // cheaper functions, but the difference is likely negligible. + // So for the sake of brevity and cleanliness, keep it like this. float rand(float x) { - return rand4().x * x; + return rand4(GDispatchThreadId.x, EmitterTickCounter, 0).x * x; } float2 rand(float2 x) { - float4 rnd = rand4(); - return float2(rnd.x, rnd.y) * x; + return rand4(GDispatchThreadId.x, EmitterTickCounter, 0).xy * x; } float3 rand(float3 x) { - float4 rnd = rand4(); - return float3(rnd.x, rnd.y, rnd.z) * x; + return rand4(GDispatchThreadId.x, EmitterTickCounter, 0).xyz * x; } float4 rand(float4 x) { - return rand4() * x; + return rand4(GDispatchThreadId.x, EmitterTickCounter, 0).xyzw * x; } + // Note: integer randoms are INCLUSIVE, i.e. includes both the upper and lower limit int rand(int x) { - RandomVal = NiagaraRand4DPCG32(int4(GDispatchThreadId + (RandomVal / 57843).xyz, 54121) + int4(EmitterTickCounter.xxxx)); - return RandomVal.x % x+1; + return int(rand4(GDispatchThreadId.x, EmitterTickCounter, 0).x * (x+1)); } - #else - - //Temporary random hacks + // Old unseeded, passthrough to FRandomStream float2 rand(float2 x) { return float2(rand(x.x), rand(x.y)); @@ -120,7 +227,8 @@ NiagaraSimulationShader.usf: { return float4(rand(x.x), rand(x.y), rand(x.z), rand(x.w)); } - + + // NOTE(mv): Where's rand(float)? Seems to work regardless.. int rand(int x); #endif diff --git a/Engine/Shaders/Private/NiagaraMeshVertexFactory.ush b/Engine/Shaders/Private/NiagaraMeshVertexFactory.ush index 6b95cabb4be1..689b0fee1b08 100644 --- a/Engine/Shaders/Private/NiagaraMeshVertexFactory.ush +++ b/Engine/Shaders/Private/NiagaraMeshVertexFactory.ush @@ -61,12 +61,12 @@ struct FVertexFactoryInterpolantsVSToPS float4 SubUV0AndTexCoord0 : TEXCOORD1; float4 SubUV1AndLerp : TEXCOORD2; #else - #if NUM_MATERIAL_TEXCOORDS + #if NUM_TEX_COORD_INTERPOLATORS #if (ES2_PROFILE || ES3_1_PROFILE) // Avoid dependent texture fetches, put all UVs in xy - float2 TexCoords[NUM_MATERIAL_TEXCOORDS] : TEXCOORD0; + float2 TexCoords[ NUM_TEX_COORD_INTERPOLATORS] : TEXCOORD0; #else - float4 TexCoords[(NUM_MATERIAL_TEXCOORDS+1)/2] : TEXCOORD0; + float4 TexCoords[( NUM_TEX_COORD_INTERPOLATORS+1)/2] : TEXCOORD0; #endif #endif #endif @@ -202,9 +202,9 @@ FMaterialPixelParameters GetMaterialPixelParameters(FVertexFactoryInterpolantsVS FMaterialPixelParameters Result = MakeInitializedMaterialPixelParameters(); #if USE_PARTICLE_SUBUVS - #if NUM_MATERIAL_TEXCOORDS + #if NUM_TEX_COORD_INTERPOLATORS UNROLL - for( int CoordinateIndex = 0; CoordinateIndex < NUM_MATERIAL_TEXCOORDS; CoordinateIndex++ ) + for( int CoordinateIndex = 0; CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS; CoordinateIndex++ ) { Result.TexCoords[CoordinateIndex] = Interpolants.SubUV0AndTexCoord0.zw; } @@ -212,20 +212,20 @@ FMaterialPixelParameters GetMaterialPixelParameters(FVertexFactoryInterpolantsVS Result.Particle.SubUVCoords[0] = Interpolants.SubUV0AndTexCoord0.xy; Result.Particle.SubUVCoords[1] = Interpolants.SubUV1AndLerp.xy; Result.Particle.SubUVLerp = Interpolants.SubUV1AndLerp.z; -#elif NUM_MATERIAL_TEXCOORDS +#elif NUM_TEX_COORD_INTERPOLATORS #if (ES2_PROFILE || ES3_1_PROFILE) UNROLL - for( int CoordinateIndex = 0; CoordinateIndex < NUM_MATERIAL_TEXCOORDS; CoordinateIndex++ ) + for( int CoordinateIndex = 0; CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS; CoordinateIndex++ ) { Result.TexCoords[CoordinateIndex] = Interpolants.TexCoords[CoordinateIndex].xy; } #else UNROLL - for(int CoordinateIndex = 0;CoordinateIndex < NUM_MATERIAL_TEXCOORDS;CoordinateIndex += 2) + for(int CoordinateIndex = 0;CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS;CoordinateIndex += 2) { Result.TexCoords[CoordinateIndex] = Interpolants.TexCoords[CoordinateIndex/2].xy; - if(CoordinateIndex + 1 < NUM_MATERIAL_TEXCOORDS) + if(CoordinateIndex + 1 < NUM_TEX_COORD_INTERPOLATORS) { Result.TexCoords[CoordinateIndex + 1] = Interpolants.TexCoords[CoordinateIndex/2].wz; } @@ -719,28 +719,28 @@ FVertexFactoryInterpolantsVSToPS VertexFactoryGetInterpolantsVSToPS(FVertexFacto Interpolants.SubUV1AndLerp.xy = Intermediates.SubUVCoords.zw; Interpolants.SubUV1AndLerp.zw = Intermediates.SubUVLerp.xx; - #if NUM_MATERIAL_TEXCOORDS - float2 CustomizedUVs[NUM_MATERIAL_TEXCOORDS]; + #if NUM_TEX_COORD_INTERPOLATORS + float2 CustomizedUVs[ NUM_TEX_COORD_INTERPOLATORS]; GetMaterialCustomizedUVs(VertexParameters, CustomizedUVs); Interpolants.SubUV0AndTexCoord0.zw = CustomizedUVs[0]; #else Interpolants.SubUV0AndTexCoord0.zw = 0; #endif -#elif NUM_MATERIAL_TEXCOORDS +#elif NUM_TEX_COORD_INTERPOLATORS #if (ES2_PROFILE || ES3_1_PROFILE) GetMaterialCustomizedUVs(VertexParameters, Interpolants.TexCoords); #else // Ensure the unused components of the last packed texture coordinate are initialized. - Interpolants.TexCoords[(NUM_MATERIAL_TEXCOORDS + 1) / 2 - 1] = 0; + Interpolants.TexCoords[( NUM_TEX_COORD_INTERPOLATORS + 1) / 2 - 1] = 0; - float2 CustomizedUVs[NUM_MATERIAL_TEXCOORDS]; + float2 CustomizedUVs[ NUM_TEX_COORD_INTERPOLATORS]; GetMaterialCustomizedUVs(VertexParameters, CustomizedUVs); UNROLL - for(int CoordinateIndex = 0;CoordinateIndex < NUM_MATERIAL_TEXCOORDS;CoordinateIndex += 2) + for(int CoordinateIndex = 0;CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS;CoordinateIndex += 2) { Interpolants.TexCoords[CoordinateIndex / 2].xy = CustomizedUVs[CoordinateIndex]; - if(CoordinateIndex + 1 < NUM_MATERIAL_TEXCOORDS) + if(CoordinateIndex + 1 < NUM_TEX_COORD_INTERPOLATORS) { Interpolants.TexCoords[CoordinateIndex / 2].wz = CustomizedUVs[CoordinateIndex + 1]; } @@ -834,12 +834,12 @@ float4 VertexFactoryGetPreviousWorldPosition(FVertexFactoryInput Input, FVertexF FMaterialTessellationParameters GetMaterialTessellationParameters(FVertexFactoryInterpolantsVSToDS Interpolants, float3 CameraLocalWorldPosition) { FMaterialTessellationParameters Result; - #if NUM_MATERIAL_TEXCOORDS + #if NUM_TEX_COORD_INTERPOLATORS UNROLL - for(int CoordinateIndex = 0;CoordinateIndex < NUM_MATERIAL_TEXCOORDS;CoordinateIndex += 2) + for(int CoordinateIndex = 0;CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS;CoordinateIndex += 2) { Result.TexCoords[CoordinateIndex] = Interpolants.InterpolantsVSToPS.TexCoords[CoordinateIndex/2].xy; - if(CoordinateIndex + 1 < NUM_MATERIAL_TEXCOORDS) + if(CoordinateIndex + 1 < NUM_TEX_COORD_INTERPOLATORS) { Result.TexCoords[CoordinateIndex + 1] = Interpolants.InterpolantsVSToPS.TexCoords[CoordinateIndex/2].wz; } @@ -881,8 +881,8 @@ float4 VertexFactoryGetPreviousWorldPosition(FVertexFactoryInput Input, FVertexF TESSELLATION_INTERPOLATE_MEMBER(InterpolantsVSToPS.ParticleColor); #endif -#if NUM_MATERIAL_TEXCOORDS - for (int i = 0; i < (NUM_MATERIAL_TEXCOORDS + 1) / 2; ++i) +#if NUM_TEX_COORD_INTERPOLATORS + for (int i = 0; i < ( NUM_TEX_COORD_INTERPOLATORS + 1) / 2; ++i) { TESSELLATION_INTERPOLATE_MEMBER(InterpolantsVSToPS.TexCoords[i]); } diff --git a/Engine/Shaders/Private/NiagaraRibbonVertexFactory.ush b/Engine/Shaders/Private/NiagaraRibbonVertexFactory.ush index 94d546c820be..22253c6bcfbb 100644 --- a/Engine/Shaders/Private/NiagaraRibbonVertexFactory.ush +++ b/Engine/Shaders/Private/NiagaraRibbonVertexFactory.ush @@ -149,14 +149,13 @@ Buffer SortedIndices; uint SortedIndicesOffset; Buffer SegmentDistances; +Buffer MultiRibbonIndices; Buffer PackedPerRibbonDataByIndex; struct FVertexFactoryInput { uint VertexId : SV_VertexID; - uint RibbonIndex : ATTRIBUTE0; - // Optional instance ID for vertex layered rendering #if FEATURE_LEVEL >= FEATURE_LEVEL_SM4 && ONEPASS_POINTLIGHT_SHADOW && USING_VERTEX_SHADER_LAYER uint InstanceId : SV_InstanceID; @@ -502,13 +501,10 @@ void GetTangents(FVertexFactoryInput Input, FVertexFactoryIntermediates Intermed float cosa = cos(Intermediates.Twist); Facing = Facing * cosa + cross(Facing, NormDir) * sin(Intermediates.Twist) + NormDir * dot(NormDir, Facing) * (1-cosa); - if( abs(dot(NormDir, Facing)) < 0.999f) + RightDir = SafeNormalize(cross(NormDir, Facing)); + if(!any(RightDir)) { - RightDir = SafeNormalize(cross(NormDir, Facing)); - } - else - { - RightDir = SafeNormalize(cross(NormDir, float3(0.0f, 0.0f, -1.0f))); + RightDir = SafeNormalize(cross(NormDir, float3(0.0f, 0.0f, 1.0f))); } Right = RightDir; @@ -553,7 +549,9 @@ FVertexFactoryIntermediates GetVertexFactoryIntermediates(FVertexFactoryInput In float2 UV1Offset; uint NumSegments; uint StartVertexId; - UnpackPerRibbonDataByIndex(Input.RibbonIndex, UV0Scale, UV0Offset, UV1Scale, UV1Offset, NumSegments, StartVertexId); + + uint RibbonIndex = MultiRibbonIndices[Input.VertexId / 2]; + UnpackPerRibbonDataByIndex(RibbonIndex, UV0Scale, UV0Offset, UV1Scale, UV1Offset, NumSegments, StartVertexId); uint StartParticleId = GetParticleIdByVertexId(StartVertexId); diff --git a/Engine/Shaders/Private/NiagaraSpriteVertexFactory.ush b/Engine/Shaders/Private/NiagaraSpriteVertexFactory.ush index 0c4a8b9db4ad..5d5874829775 100644 --- a/Engine/Shaders/Private/NiagaraSpriteVertexFactory.ush +++ b/Engine/Shaders/Private/NiagaraSpriteVertexFactory.ush @@ -75,11 +75,11 @@ struct FVertexFactoryInterpolantsVSToPS #endif #if NEEDS_PARTICLE_COLOR - float4 Color : TEXCOORD2; + float4 Color : TEXCOORD0; #endif -#if NUM_MATERIAL_TEXCOORDS - float4 TexCoords[(NUM_MATERIAL_TEXCOORDS + 1) / 2] : TEXCOORD3; +#if NUM_TEX_COORD_INTERPOLATORS + float4 TexCoords[(NUM_TEX_COORD_INTERPOLATORS + 1) / 2] : TEXCOORD1; #endif //Not sure this is actually being used now and it's awkward to slot in now we're supporting custom UVs so I'm just giving this its own interpolant. @@ -189,7 +189,7 @@ struct FVertexFactoryIntermediates #endif }; -#if NUM_MATERIAL_TEXCOORDS +#if NUM_TEX_COORD_INTERPOLATORS bool UVIndexUseZW(int UVIndex) { #if COMPILER_GLSL_ES2 @@ -225,13 +225,13 @@ FMaterialPixelParameters GetMaterialPixelParameters(FVertexFactoryInterpolantsVS // GetMaterialPixelParameters is responsible for fully initializing the result FMaterialPixelParameters Result = MakeInitializedMaterialPixelParameters(); -#if NUM_MATERIAL_TEXCOORDS +#if NUM_TEX_COORD_INTERPOLATORS UNROLL - for (int CoordinateIndex = 0; CoordinateIndex < NUM_MATERIAL_TEXCOORDS; CoordinateIndex++) + for (int CoordinateIndex = 0; CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS; CoordinateIndex++) { Result.TexCoords[CoordinateIndex] = GetUV(Interpolants, CoordinateIndex); } -#endif // NUM_MATERIAL_TEXCOORDS +#endif // NUM_TEX_COORD_INTERPOLATORS Result.VertexColor = 1; @@ -906,13 +906,13 @@ FVertexFactoryInterpolantsVSToPS VertexFactoryGetInterpolantsVSToPS(FVertexFacto // Initialize the whole struct to 0 Interpolants = (FVertexFactoryInterpolantsVSToPS)0; -#if NUM_MATERIAL_TEXCOORDS +#if NUM_TEX_COORD_INTERPOLATORS - float2 CustomizedUVs[NUM_MATERIAL_TEXCOORDS]; + float2 CustomizedUVs[NUM_TEX_COORD_INTERPOLATORS]; GetMaterialCustomizedUVs(VertexParameters, CustomizedUVs); UNROLL - for (int CoordinateIndex = 0; CoordinateIndex < NUM_MATERIAL_TEXCOORDS; CoordinateIndex++) + for (int CoordinateIndex = 0; CoordinateIndex < NUM_TEX_COORD_INTERPOLATORS; CoordinateIndex++) { SetUV(Interpolants, CoordinateIndex, CustomizedUVs[CoordinateIndex]); } @@ -1013,5 +1013,9 @@ uint VertexFactoryGetEyeIndex(uint InstanceId) float4 VertexFactoryGetTranslatedPrimitiveVolumeBounds(FVertexFactoryInterpolantsVSToPS Interpolants) { +#if USE_PARTICLE_POSITION + return Interpolants.ParticleTranslatedWorldPositionAndSize; +#else return 0; +#endif } diff --git a/Engine/Shaders/Private/VolumetricFogLightFunction.usf b/Engine/Shaders/Private/VolumetricFogLightFunction.usf index f3437db2b548..15be3734586a 100644 --- a/Engine/Shaders/Private/VolumetricFogLightFunction.usf +++ b/Engine/Shaders/Private/VolumetricFogLightFunction.usf @@ -42,7 +42,7 @@ void Main( float3 AbsoluteWorldPosition = SvPositionToWorld(SvPosition); */ - float2 ScreenPosition = (SvPosition * LightFunctionTexelSize - .5f) * float2(2, -2); + float2 ScreenPosition = (SvPosition.xy * LightFunctionTexelSize - .5f) * float2(2, -2); float3 AbsoluteWorldPosition = GetWorldPositionForLightFunctionTexturePosition(ScreenPosition); float4 LightVector = mul(float4(AbsoluteWorldPosition, 1), LightFunctionWorldToLight); diff --git a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendBase.cpp b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendBase.cpp index 001720fdfa66..65d7c38b1cf8 100644 --- a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendBase.cpp +++ b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendBase.cpp @@ -323,11 +323,11 @@ FString FBlueprintCompilerCppBackendBase::GenerateCodeFromClass(UClass* SourceCl auto CleanCppClassName = FEmitHelper::GetBaseFilename(SourceClass, NativizationOptions); auto CppClassName = FEmitHelper::GetCppName(SourceClass); - FGatherConvertedClassDependencies Dependencies(SourceClass, NativizationOptions); - FNativizationSummaryHelper::RegisterRequiredModules(NativizationOptions.PlatformName, Dependencies.RequiredModuleNames); - FEmitterLocalContext EmitterContext(Dependencies, NativizationOptions); + TSharedPtr Dependencies = FGatherConvertedClassDependencies::Get(SourceClass, NativizationOptions); + FNativizationSummaryHelper::RegisterRequiredModules(NativizationOptions.PlatformName, Dependencies->RequiredModuleNames); + FEmitterLocalContext EmitterContext(Dependencies.ToSharedRef().Get(), NativizationOptions); - UClass* OriginalSourceClass = Dependencies.FindOriginalClass(SourceClass); + UClass* OriginalSourceClass = Dependencies->FindOriginalClass(SourceClass); ensure(OriginalSourceClass != SourceClass); FNativizationSummaryHelper::RegisterClass(OriginalSourceClass); @@ -427,9 +427,7 @@ FString FBlueprintCompilerCppBackendBase::GenerateCodeFromClass(UClass* SourceCl { UBlueprintGeneratedClass* BPGC = CastChecked(EmitterContext.GetCurrentlyGeneratedClass()); UBlueprintGeneratedClass* ParentBPGC = Cast(BPGC->GetSuperClass()); - ParentDependencies = TSharedPtr( ParentBPGC - ? new FGatherConvertedClassDependencies(ParentBPGC, NativizationOptions) - : nullptr); + ParentDependencies = FGatherConvertedClassDependencies::Get(ParentBPGC, NativizationOptions); EmitterContext.Header.AddLine(FString::Printf(TEXT("%s(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());"), *CppClassName)); EmitterContext.Header.AddLine(TEXT("virtual void PostLoadSubobjects(FObjectInstancingGraph* OuterInstanceGraph) override;")); @@ -1041,9 +1039,9 @@ void FBlueprintCompilerCppBackendBase::GenerateCodeFromEnum(UUserDefinedEnum* So void FBlueprintCompilerCppBackendBase::GenerateCodeFromStruct(UUserDefinedStruct* SourceStruct, const FCompilerNativizationOptions& NativizationOptions, FString& OutHeaderCode, FString& OutCPPCode) { check(SourceStruct); - FGatherConvertedClassDependencies Dependencies(SourceStruct, NativizationOptions); - FNativizationSummaryHelper::RegisterRequiredModules(NativizationOptions.PlatformName, Dependencies.RequiredModuleNames); - FEmitterLocalContext EmitterContext(Dependencies, NativizationOptions); + TSharedPtr Dependencies = FGatherConvertedClassDependencies::Get(SourceStruct, NativizationOptions); + FNativizationSummaryHelper::RegisterRequiredModules(NativizationOptions.PlatformName, Dependencies->RequiredModuleNames); + FEmitterLocalContext EmitterContext(Dependencies.ToSharedRef().Get(), NativizationOptions); // use GetBaseFilename() so that we can coordinate #includes and filenames EmitFileBeginning(FEmitHelper::GetBaseFilename(SourceStruct, NativizationOptions), EmitterContext, true, true); { @@ -1088,9 +1086,9 @@ void FBlueprintCompilerCppBackendBase::GenerateCodeFromStruct(UUserDefinedStruct FString FBlueprintCompilerCppBackendBase::GenerateWrapperForClass(UClass* SourceClass, const FCompilerNativizationOptions& NativizationOptions) { - FGatherConvertedClassDependencies Dependencies(SourceClass, NativizationOptions); - FNativizationSummaryHelper::RegisterRequiredModules(NativizationOptions.PlatformName, Dependencies.RequiredModuleNames); - FEmitterLocalContext EmitterContext(Dependencies, NativizationOptions); + TSharedPtr Dependencies = FGatherConvertedClassDependencies::Get(SourceClass, NativizationOptions); + FNativizationSummaryHelper::RegisterRequiredModules(NativizationOptions.PlatformName, Dependencies->RequiredModuleNames); + FEmitterLocalContext EmitterContext(Dependencies.ToSharedRef().Get(), NativizationOptions); UBlueprintGeneratedClass* BPGC = Cast(SourceClass); @@ -1140,7 +1138,7 @@ FString FBlueprintCompilerCppBackendBase::GenerateWrapperForClass(UClass* Source break; } UBlueprintGeneratedClass* SuperBPGC = Cast(SuperClassToUse); - if (SuperBPGC && Dependencies.WillClassBeConverted(SuperBPGC)) + if (SuperBPGC && Dependencies->WillClassBeConverted(SuperBPGC)) { break; } @@ -1156,7 +1154,7 @@ FString FBlueprintCompilerCppBackendBase::GenerateWrapperForClass(UClass* Source } UBlueprintGeneratedClass* SuperBPGC = Cast(SuperClassToUse); - if (SuperBPGC && !Dependencies.WillClassBeConverted(SuperBPGC)) + if (SuperBPGC && !Dependencies->WillClassBeConverted(SuperBPGC)) { ParentStruct = FString::Printf(TEXT("FUnconvertedWrapper__%s"), *FEmitHelper::GetCppName(SuperBPGC)); EmitterContext.MarkUnconvertedClassAsNecessary(SuperBPGC); diff --git a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendGatherDependencies.cpp b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendGatherDependencies.cpp index ead556e7accd..8ff5fb7a37a2 100644 --- a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendGatherDependencies.cpp +++ b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendGatherDependencies.cpp @@ -72,6 +72,14 @@ struct FGatherConvertedClassDependenciesHelperBase : public FReferenceCollector } } + void AddAssetDependency(UObject* InAsset) + { + if (InAsset) + { + Dependencies.Assets.Add(InAsset); + } + } + void AddConvertedClassDependency(UBlueprintGeneratedClass* InBPGC) { if (InBPGC && !Dependencies.ConvertedClasses.Contains(InBPGC)) @@ -166,13 +174,13 @@ struct FFindAssetsToInclude : public FGatherConvertedClassDependenciesHelperBase else { // Add the class itself as a dependency. - Dependencies.Assets.AddUnique(OwnerClass); + AddAssetDependency(OwnerClass); if (ObjAsBPGC) { // For BPGC types, we also include the CDO as a dependency (since it will be serialized). // Note that if we get here, we already know from above that the BPGC is not being converted. - Dependencies.Assets.AddUnique(ObjAsBPGC->GetDefaultObject()); + AddAssetDependency(ObjAsBPGC->GetDefaultObject()); } } } @@ -186,19 +194,19 @@ struct FFindAssetsToInclude : public FGatherConvertedClassDependenciesHelperBase else { // Add the struct itself as a dependency. - Dependencies.Assets.AddUnique(OwnerStruct); + AddAssetDependency(OwnerStruct); } } else { // UFUNCTION, UENUM, etc. - Dependencies.Assets.AddUnique(Object); + AddAssetDependency(Object); } } else { // Include the asset as a dependency. - Dependencies.Assets.AddUnique(Object); + AddAssetDependency(Object); } // No need to traverse these objects any further, so we just return. @@ -317,7 +325,7 @@ struct FFindHeadersToInclude : public FGatherConvertedClassDependenciesHelperBas } { - auto ObjAsField = Cast(Object); + UField* ObjAsField = Cast(Object); if (!ObjAsField) { const bool bTransientObject = (Object->HasAnyFlags(RF_Transient) && !Object->IsIn(CurrentlyConvertedStruct)) || Object->IsIn(GetTransientPackage()); @@ -358,7 +366,7 @@ struct FFindHeadersToInclude : public FGatherConvertedClassDependenciesHelperBas return; } - auto OwnedByAnythingInHierarchy = [&]()->bool + auto OwnedByAnythingInHierarchy = [this, Object, CurrentlyConvertedStruct]()->bool { for (UStruct* IterStruct = CurrentlyConvertedStruct; IterStruct; IterStruct = IterStruct->GetSuperStruct()) { @@ -424,7 +432,7 @@ FGatherConvertedClassDependencies::FGatherConvertedClassDependencies(UStruct* In static const FBoolConfigValueHelper DontNativizeDataOnlyBP(TEXT("BlueprintNativizationSettings"), TEXT("bDontNativizeDataOnlyBP")); if (DontNativizeDataOnlyBP) { - auto RemoveFieldsFromDataOnlyBP = [&](TSet& FieldSet) + auto RemoveFieldsFromDataOnlyBP = [this](TSet& FieldSet) { TSet FieldsToAdd; for (auto Iter = FieldSet.CreateIterator(); Iter; ++Iter) @@ -447,7 +455,7 @@ FGatherConvertedClassDependencies::FGatherConvertedClassDependencies(UStruct* In { TSet ExcludedModules(NativizationOptions.ExcludedModules); - auto RemoveFieldsDependentOnExcludedModules = [&](TSet& FieldSet) + auto RemoveFieldsDependentOnExcludedModules = [&ExcludedModules, InStruct](TSet& FieldSet) { for (auto Iter = FieldSet.CreateIterator(); Iter; ++Iter) { @@ -463,9 +471,9 @@ FGatherConvertedClassDependencies::FGatherConvertedClassDependencies(UStruct* In RemoveFieldsDependentOnExcludedModules(IncludeInBody); } - auto GatherRequiredModules = [&](const TSet& Fields) + auto GatherRequiredModules = [this](const TSet& Fields) { - for (auto Field : Fields) + for (UField* Field : Fields) { const UPackage* Package = Field ? Field->GetOutermost() : nullptr; if (Package && Package->HasAnyPackageFlags(PKG_CompiledIn)) @@ -532,13 +540,13 @@ void FGatherConvertedClassDependencies::DependenciesForHeader() TArray NeededObjects; FReferenceFinder HeaderReferenceFinder(NeededObjects, nullptr, false, false, true, false); - auto ShouldIncludeHeaderFor = [&](UObject* InObj)->bool + auto ShouldIncludeHeaderFor = [this](UObject* InObj)->bool { if (InObj && (InObj->IsA() || InObj->IsA() || InObj->IsA()) && !InObj->HasAnyFlags(RF_ClassDefaultObject)) { - auto ObjAsBPGC = Cast(InObj); + UBlueprintGeneratedClass* ObjAsBPGC = Cast(InObj); const bool bWillBeConvetedAsBPGC = ObjAsBPGC && WillClassBeConverted(ObjAsBPGC); const bool bRemainAsUnconvertedBPGC = ObjAsBPGC && !bWillBeConvetedAsBPGC; if (!bRemainAsUnconvertedBPGC && (InObj->GetOutermost() != OriginalStruct->GetOutermost())) @@ -549,7 +557,7 @@ void FGatherConvertedClassDependencies::DependenciesForHeader() return false; }; - for (auto Obj : ObjectsToCheck) + for (UObject* Obj : ObjectsToCheck) { const UProperty* Property = Cast(Obj); if (const UArrayProperty* ArrayProperty = Cast(Property)) @@ -561,28 +569,28 @@ void FGatherConvertedClassDependencies::DependenciesForHeader() const bool bIsMemberVariable = OwnerProperty && (OwnerProperty->GetOuter() == OriginalStruct); if (bIsParam || bIsMemberVariable) { - if (auto SoftClassProperty = Cast(Property)) + if (const USoftClassProperty* SoftClassProperty = Cast(Property)) { DeclareInHeader.Add(GetFirstNativeOrConvertedClass(SoftClassProperty->MetaClass)); } - if (auto ClassProperty = Cast(Property)) + if (const UClassProperty* ClassProperty = Cast(Property)) { DeclareInHeader.Add(GetFirstNativeOrConvertedClass(ClassProperty->MetaClass)); } - if (auto ObjectProperty = Cast(Property)) + if (const UObjectPropertyBase* ObjectProperty = Cast(Property)) { DeclareInHeader.Add(GetFirstNativeOrConvertedClass(ObjectProperty->PropertyClass)); } - else if (auto InterfaceProperty = Cast(Property)) + else if (const UInterfaceProperty* InterfaceProperty = Cast(Property)) { IncludeInHeader.Add(InterfaceProperty->InterfaceClass); } - else if (auto DelegateProperty = Cast(Property)) + else if (const UDelegateProperty* DelegateProperty = Cast(Property)) { IncludeInHeader.Add(DelegateProperty->SignatureFunction ? DelegateProperty->SignatureFunction->GetOwnerStruct() : nullptr); } /* MC Delegate signatures are recreated in local scope anyway. - else if (auto MulticastDelegateProperty = Cast(Property)) + else if (const UMulticastDelegateProperty* MulticastDelegateProperty = Cast(Property)) { IncludeInHeader.Add(MulticastDelegateProperty->SignatureFunction ? MulticastDelegateProperty->SignatureFunction->GetOwnerClass() : nullptr); } @@ -608,12 +616,12 @@ void FGatherConvertedClassDependencies::DependenciesForHeader() } } - if (auto SuperStruct = OriginalStruct->GetSuperStruct()) + if (UStruct* SuperStruct = OriginalStruct->GetSuperStruct()) { IncludeInHeader.Add(SuperStruct); } - if (auto SourceClass = Cast(OriginalStruct)) + if (UClass* SourceClass = Cast(OriginalStruct)) { for (auto& ImplementedInterface : SourceClass->Interfaces) { @@ -621,7 +629,7 @@ void FGatherConvertedClassDependencies::DependenciesForHeader() } } - for (auto Obj : NeededObjects) + for (UObject* Obj : NeededObjects) { if (ShouldIncludeHeaderFor(Obj)) { @@ -684,9 +692,9 @@ TSet FGatherConvertedClassDependencies::AllDependencies() const All.Add(SuperClass); } - if (auto SourceClass = Cast(OriginalStruct)) + if (UClass* SourceClass = Cast(OriginalStruct)) { - for (auto& ImplementedInterface : SourceClass->Interfaces) + for (const FImplementedInterface& ImplementedInterface : SourceClass->Interfaces) { UBlueprintGeneratedClass* InterfaceClass = Cast(ImplementedInterface.Class); if (ImplementedInterface.Class && (!InterfaceClass || WillClassBeConverted(InterfaceClass))) @@ -695,11 +703,18 @@ TSet FGatherConvertedClassDependencies::AllDependencies() const } } } + + TSet NestedAssets; + GatherAssetsReferencedByConvertedTypes(NestedAssets); for (auto It : Assets) { All.Add(It); } + for (auto It : NestedAssets) + { + All.Add(It); + } for (auto It : ConvertedClasses) { All.Add(It); @@ -715,6 +730,24 @@ TSet FGatherConvertedClassDependencies::AllDependencies() const return All; } +TMap> FGatherConvertedClassDependencies::CachedConvertedClassDependencies; + +TSharedPtr FGatherConvertedClassDependencies::Get(UStruct* InStruct, const FCompilerNativizationOptions& InNativizationOptions) +{ + TSharedPtr ConvertedClassDependenciesPtr; + if (InStruct) + { + ConvertedClassDependenciesPtr = CachedConvertedClassDependencies.FindOrAdd(InStruct); + if (!ConvertedClassDependenciesPtr.IsValid()) + { + ConvertedClassDependenciesPtr = MakeShared(InStruct, InNativizationOptions); + check(ConvertedClassDependenciesPtr.IsValid()); + } + } + + return ConvertedClassDependenciesPtr; +} + class FArchiveReferencesInStructInstance : public FArchive { public: @@ -745,6 +778,39 @@ public: } }; +void FGatherConvertedClassDependencies::GatherAssetsReferencedByConvertedTypes(TSet& Dependencies) const +{ + TSet VisitedTypes = { GetActualStruct() }; + + TArray ConvertedTypeStack; + ConvertedTypeStack.Append(ConvertedStructs.Array()); + ConvertedTypeStack.Append(ConvertedClasses.Array()); + + while(ConvertedTypeStack.Num() > 0) + { + UStruct* ConvertedType = ConvertedTypeStack.Pop(); + TSharedPtr ConvertedTypeDependenciesPtr = Get(ConvertedType, NativizationOptions); + + VisitedTypes.Add(ConvertedType); + Dependencies.Append(ConvertedTypeDependenciesPtr->Assets); + + for (UUserDefinedStruct* ConvertedStruct : ConvertedTypeDependenciesPtr->ConvertedStructs) + { + if (!VisitedTypes.Contains(ConvertedStruct)) + { + ConvertedTypeStack.Push(ConvertedStruct); + } + } + + for (UBlueprintGeneratedClass* ConvertedClass : ConvertedTypeDependenciesPtr->ConvertedClasses) + { + if (!VisitedTypes.Contains(ConvertedClass)) + { + ConvertedTypeStack.Push(ConvertedClass); + } + } + } +} void FGatherConvertedClassDependencies::GatherAssetsReferencedByUDSDefaultValue(TSet& Dependencies, UUserDefinedStruct* Struct) { diff --git a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendUtils.cpp b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendUtils.cpp index a4afa58d8fe0..2be819d2a485 100644 --- a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendUtils.cpp +++ b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendUtils.cpp @@ -200,7 +200,7 @@ FString FEmitterLocalContext::FindGloballyMappedObject(const UObject* Object, co if (bTryUsedAssetsList) { int32 AssetIndex = UsedObjectInCurrentClass.IndexOfByKey(Object); - if (INDEX_NONE == AssetIndex && Dependencies.Assets.Contains(Object)) + if (INDEX_NONE == AssetIndex && Dependencies.Assets.Contains(const_cast(Object))) { AssetIndex = UsedObjectInCurrentClass.Add(Object); } @@ -1381,15 +1381,16 @@ FString FEmitHelper::LiteralTerm(FEmitterLocalContext& EmitterContext, const FLi MetaClass = MetaClass ? MetaClass : UObject::StaticClass(); const FString ObjTypeStr = FEmitHelper::GetCppName(EmitterContext.GetFirstNativeOrConvertedClass(MetaClass)); + const bool bAssetSubclassOf = (UEdGraphSchema_K2::PC_SoftClass == Type.PinCategory); + const FString TermTypeStr = bAssetSubclassOf ? TEXT("TSoftClassPtr") : TEXT("TSoftObjectPtr"); + + FString TermValueStr; if (!CustomValue.IsEmpty()) { - const bool bAssetSubclassOf = (UEdGraphSchema_K2::PC_SoftClass == Type.PinCategory); - return FString::Printf(TEXT("%s<%s>(FSoftObjectPath(TEXT(\"%s\")))") - , bAssetSubclassOf ? TEXT("TSoftClassPtr") : TEXT("TSoftObjectPtr") - , *ObjTypeStr - , *(CustomValue.ReplaceCharWithEscapedChar())); + TermValueStr = FString::Printf(TEXT("FSoftObjectPath(TEXT(\"%s\"))"), *(CustomValue.ReplaceCharWithEscapedChar())); } - return FString::Printf(TEXT("((%s*)nullptr)"), *ObjTypeStr); + + return FString::Printf(TEXT("%s<%s>(%s)"), *TermTypeStr, *ObjTypeStr, *TermValueStr); } else if (UEdGraphSchema_K2::PC_Object == Type.PinCategory) { diff --git a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendValueHelper.cpp b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendValueHelper.cpp index ea0a131ad08c..3cc5c97a0e81 100644 --- a/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendValueHelper.cpp +++ b/Engine/Source/Developer/BlueprintCompilerCppBackend/Private/BlueprintCompilerCppBackendValueHelper.cpp @@ -152,7 +152,9 @@ void FEmitDefaultValueHelper::GenerateUserStructConstructor(const UUserDefinedSt FUserStructOnScopeIgnoreDefaults RawDefaultStructOnScope(Struct); for (auto Property : TFieldRange(Struct)) { - OuterGenerate(Context, Property, TEXT(""), StructData.GetStructMemory(), RawDefaultStructOnScope.GetStructMemory(), EPropertyAccessOperator::None); + // Since UDS types are converted to native USTRUCT, all POD fields must be initialized in the ctor, just as with "regular" native USTRUCT types. + const bool bForceInit = Property->HasAnyPropertyFlags(CPF_IsPlainOldData); + OuterGenerate(Context, Property, TEXT(""), StructData.GetStructMemory(), bForceInit ? nullptr : RawDefaultStructOnScope.GetStructMemory(), EPropertyAccessOperator::None); } } Context.Body.DecreaseIndent(); @@ -1157,7 +1159,7 @@ struct FFakeImportTableHelper UClass* SourceClass = Cast(SourceStruct); if (ensure(SourceStruct) && ensure(!SourceClass || OriginalClass)) { - auto GatherDependencies = [&](UStruct* InStruct) + auto GatherDependencies = [this](UStruct* InStruct) { SerializeBeforeSerializeStructDependencies.Add(InStruct->GetSuperStruct()); @@ -1215,7 +1217,7 @@ struct FFakeImportTableHelper GatherDependencies(OriginalClass); } - auto GetClassesOfSubobjects = [&](TMap& SubobjectsMap) + auto GetClassesOfSubobjects = [this, &Context](TMap& SubobjectsMap) { TArray Subobjects; SubobjectsMap.GetKeys(Subobjects); @@ -1223,8 +1225,21 @@ struct FFakeImportTableHelper { if (Subobject) { - SerializeBeforeSerializeStructDependencies.Add(Subobject->GetClass()); - SerializeBeforeCreateCDODependencies.Add(Subobject->GetClass()->GetDefaultObject()); + UClass* SubobjectClass = Subobject->GetClass(); + SerializeBeforeSerializeStructDependencies.Add(SubobjectClass); + SerializeBeforeCreateCDODependencies.Add(SubobjectClass->GetDefaultObject()); + + // This ensures that any nested asset dependencies will be serialized before attempting to instance a subobject that's a converted type when constructing the CDO. + if (UBlueprintGeneratedClass* SubobjectClassAsBPGC = Cast(SubobjectClass)) + { + if (Context.Dependencies.ConvertedClasses.Contains(SubobjectClassAsBPGC)) + { + TSharedPtr SubobjectClassDependencies = FGatherConvertedClassDependencies::Get(SubobjectClassAsBPGC, Context.Dependencies.NativizationOptions); + + SerializeBeforeCreateCDODependencies.Append(SubobjectClassDependencies->Assets); + SubobjectClassDependencies->GatherAssetsReferencedByConvertedTypes(SerializeBeforeCreateCDODependencies); + } + } } } }; @@ -1256,7 +1271,7 @@ struct FFakeImportTableHelper //everything was created for class CompactDataRef.CDODependency.bCreateBeforeCreateDependency = false; - // Classes of subobjects, created while CDO construction + // Classes of subobjects, created while CDO construction, including assets they depend on for their own construction CompactDataRef.CDODependency.bSerializationBeforeCreateDependency = SerializeBeforeCreateCDODependencies.Contains(const_cast(Asset)); // CDO is not serialized @@ -1270,33 +1285,8 @@ void FEmitDefaultValueHelper::AddStaticFunctionsForDependencies(FEmitterLocalCon , TSharedPtr ParentDependencies , FCompilerNativizationOptions NativizationOptions) { - // 1. GATHER UDS DEFAULT VALUE DEPENDENCIES - { - TSet References; - for (UUserDefinedStruct* UDS : Context.StructsWithDefaultValuesUsed) - { - FGatherConvertedClassDependencies::GatherAssetsReferencedByUDSDefaultValue(References, UDS); - } - for (UObject* Obj : References) - { - Context.UsedObjectInCurrentClass.AddUnique(Obj); - } - } - - // 2. ALL ASSETS TO LIST - TSet AllDependenciesToHandle = Context.Dependencies.AllDependencies(); - AllDependenciesToHandle.Append(Context.UsedObjectInCurrentClass); - AllDependenciesToHandle.Remove(nullptr); - - // Special case, we don't need to load any dependencies from CoreUObject. - UPackage* CoreUObjectPackage = UProperty::StaticClass()->GetOutermost(); - for (auto Iter = AllDependenciesToHandle.CreateIterator(); Iter; ++Iter) - { - if ((*Iter)->GetOutermost() == CoreUObjectPackage) - { - Iter.RemoveCurrent(); - } - } + constexpr bool bBootTimeEDL = USE_EVENT_DRIVEN_ASYNC_LOAD_AT_BOOT_TIME; + const bool bEnableBootTimeEDLOptimization = IsEventDrivenLoaderEnabledInCookedBuilds() && bBootTimeEDL; // HELPERS UStruct* SourceStruct = Context.Dependencies.GetActualStruct(); @@ -1308,7 +1298,7 @@ void FEmitDefaultValueHelper::AddStaticFunctionsForDependencies(FEmitterLocalCon const FString CppTypeName = FEmitHelper::GetCppName(SourceStruct); FFakeImportTableHelper FakeImportTableHelper(SourceStruct, OriginalClass, Context); - auto CreateAssetToLoadString = [&](const UObject* AssetObj) -> FString + auto CreateAssetToLoadString = [&Context](const UObject* AssetObj) -> FString { UClass* AssetType = AssetObj->GetClass(); if (AssetType->IsChildOf()) @@ -1341,7 +1331,7 @@ void FEmitDefaultValueHelper::AddStaticFunctionsForDependencies(FEmitterLocalCon , *OuterName); }; - auto CreateDependencyRecord = [&](const UObject* InAsset, FString& OptionalComment) -> FCompactBlueprintDependencyData + auto CreateDependencyRecord = [&NativizationOptions, &FakeImportTableHelper, &CppTypeName, OriginalClass, &CreateAssetToLoadString, bEnableBootTimeEDLOptimization](const UObject* InAsset, FString& OptionalComment) -> FCompactBlueprintDependencyData { ensure(InAsset); if (InAsset && IsEditorOnlyObject(InAsset)) @@ -1385,9 +1375,8 @@ void FEmitDefaultValueHelper::AddStaticFunctionsForDependencies(FEmitterLocalCon FakeImportTableHelper.FillDependencyData(InAsset, Result); return Result; }; - const bool bBootTimeEDL = USE_EVENT_DRIVEN_ASYNC_LOAD_AT_BOOT_TIME; - const bool bEnableBootTimeEDLOptimization = IsEventDrivenLoaderEnabledInCookedBuilds() && bBootTimeEDL; - auto AddAssetArray = [&](const TArray& Assets) + + auto AddAssetArray = [&Context, SourceStruct, &CreateDependencyRecord, bEnableBootTimeEDLOptimization](const TArray& Assets) { if (Assets.Num()) { @@ -1441,22 +1430,76 @@ void FEmitDefaultValueHelper::AddStaticFunctionsForDependencies(FEmitterLocalCon } }; - TSet OtherBPGCs; - if (!bEnableBootTimeEDLOptimization) + // 1. GATHER UDS DEFAULT VALUE DEPENDENCIES { - for (const UObject* It : AllDependenciesToHandle) + TSet References; + for (UUserDefinedStruct* UDS : Context.StructsWithDefaultValuesUsed) { - if (const UBlueprintGeneratedClass* OtherBPGC = Cast(It)) - { - const UBlueprint* BP = Cast(OtherBPGC->ClassGeneratedBy); - if (Context.Dependencies.WillClassBeConverted(OtherBPGC) && BP && (BP->BlueprintType != EBlueprintType::BPTYPE_Interface)) - { - OtherBPGCs.Add(OtherBPGC); - } - } + FGatherConvertedClassDependencies::GatherAssetsReferencedByUDSDefaultValue(References, UDS); + } + for (UObject* Obj : References) + { + Context.UsedObjectInCurrentClass.AddUnique(Obj); } } + // 2. ALL ASSETS TO LIST + TSet OtherBPGCs; + TSet AllDependenciesToHandle = Context.Dependencies.AllDependencies(); + { + // Append used objects. + AllDependenciesToHandle.Append(Context.UsedObjectInCurrentClass); + + // Remove invalid dependencies. + AllDependenciesToHandle.Remove(nullptr); + + // Remove unnecessary dependencies. + for (auto Iter = AllDependenciesToHandle.CreateIterator(); Iter; ++Iter) + { + bool bCanExclude = false; + + if (const UObject* ItObj = *Iter) + { + // Special case, we don't need to load any dependencies from CoreUObject. + static const UPackage* CoreUObjectPackage = UProperty::StaticClass()->GetOutermost(); + bCanExclude = ItObj->GetOutermost() == CoreUObjectPackage; + + // We can exclude native type dependencies if EDL is not going to be enabled at boot time. + if (!bCanExclude && !bEnableBootTimeEDLOptimization) + { + if (const UClass* ObjAsClass = Cast(ItObj)) + { + if (ObjAsClass->HasAnyClassFlags(CLASS_Native)) + { + bCanExclude = true; + } + else if (const UBlueprintGeneratedClass* OtherBPGC = Cast(ObjAsClass)) + { + // Gather the set of all non-native, non-interface class dependencies that will be converted. This is used below to help reduce code size when the EDL will not be enabled at boot time. + const UBlueprint* BP = Cast(OtherBPGC->ClassGeneratedBy); + if (Context.Dependencies.WillClassBeConverted(OtherBPGC) && BP && (BP->BlueprintType != EBlueprintType::BPTYPE_Interface)) + { + OtherBPGCs.Add(OtherBPGC); + } + } + } + else + { + // Exclude native UENUM() types that are not user-defined. + bCanExclude |= (ItObj->IsA() && !ItObj->IsA()); + + // Exclude native USTRUCT() types that are not user-defined. + bCanExclude |= (ItObj->IsA() && !ItObj->IsA()); + } + } + } + + if (bCanExclude) + { + Iter.RemoveCurrent(); + } + } + } // 3. LIST OF UsedAssets if (SourceStruct->IsA()) @@ -1470,9 +1513,11 @@ void FEmitDefaultValueHelper::AddStaticFunctionsForDependencies(FEmitterLocalCon for (int32 UsedAssetIndex = 0; UsedAssetIndex < Context.UsedObjectInCurrentClass.Num(); ++UsedAssetIndex) { const UObject* LocAsset = Context.UsedObjectInCurrentClass[UsedAssetIndex]; - ensure(AllDependenciesToHandle.Contains(LocAsset)); - AssetsToAdd.Add(LocAsset); - AllDependenciesToHandle.Remove(LocAsset); + if (AllDependenciesToHandle.Contains(LocAsset)) + { + AssetsToAdd.Add(LocAsset); + AllDependenciesToHandle.Remove(LocAsset); + } } AddAssetArray(AssetsToAdd); Context.DecreaseIndent(); @@ -1526,34 +1571,6 @@ void FEmitDefaultValueHelper::AddStaticFunctionsForDependencies(FEmitterLocalCon Context.AddLine(TEXT("}")); } } - - if (bEnableBootTimeEDLOptimization) - { - //TODO: remove stuff from CoreUObject - } - else - { - //WIthout EDL we don't need the native stuff. - for (auto Iter = AllDependenciesToHandle.CreateIterator(); Iter; ++Iter) - { - const UObject* ItObj = *Iter; - if (auto ObjAsClass = Cast(ItObj)) - { - if (ObjAsClass->HasAnyClassFlags(CLASS_Native)) - { - Iter.RemoveCurrent(); - } - } - else if (ItObj && ItObj->IsA() && !ItObj->IsA()) - { - Iter.RemoveCurrent(); - } - else if (ItObj && ItObj->IsA() && !ItObj->IsA()) - { - Iter.RemoveCurrent(); - } - } - } AddAssetArray(AllDependenciesToHandle.Array()); Context.DecreaseIndent(); diff --git a/Engine/Source/Developer/BlueprintCompilerCppBackend/Public/BlueprintCompilerCppBackendGatherDependencies.h b/Engine/Source/Developer/BlueprintCompilerCppBackend/Public/BlueprintCompilerCppBackendGatherDependencies.h index a3aae7d1fccc..321351eba102 100644 --- a/Engine/Source/Developer/BlueprintCompilerCppBackend/Public/BlueprintCompilerCppBackendGatherDependencies.h +++ b/Engine/Source/Developer/BlueprintCompilerCppBackend/Public/BlueprintCompilerCppBackendGatherDependencies.h @@ -13,12 +13,15 @@ class UUserDefinedStruct; /** The struct gathers dependencies of a converted BPGC */ struct BLUEPRINTCOMPILERCPPBACKEND_API FGatherConvertedClassDependencies { +private: + static TMap> CachedConvertedClassDependencies; + protected: UStruct* OriginalStruct; public: // Dependencies: - TArray Assets; + TSet Assets; TSet ConvertedClasses; TSet ConvertedStructs; @@ -34,7 +37,7 @@ public: FCompilerNativizationOptions NativizationOptions; public: - FGatherConvertedClassDependencies(UStruct* InStruct, const FCompilerNativizationOptions& InNativizationOptions); + static TSharedPtr Get(UStruct* InStruct, const FCompilerNativizationOptions& InNativizationOptions); UStruct* GetActualStruct() const { @@ -50,10 +53,17 @@ public: public: bool WillClassBeConverted(const UBlueprintGeneratedClass* InClass) const; + void GatherAssetsReferencedByConvertedTypes(TSet& Dependencies) const; + static void GatherAssetsReferencedByUDSDefaultValue(TSet& Dependencies, UUserDefinedStruct* Struct); static bool IsFieldFromExcludedPackage(const UField* Field, const TSet& InExcludedModules); protected: + FGatherConvertedClassDependencies(UStruct* InStruct, const FCompilerNativizationOptions& InNativizationOptions); + + /** Friend for access to constructor via MakeShared */ + friend class SharedPointerInternals::TIntrusiveReferenceController; + void DependenciesForHeader(); }; diff --git a/Engine/Source/Developer/BlueprintNativeCodeGen/Private/BlueprintNativeCodeGenModule.cpp b/Engine/Source/Developer/BlueprintNativeCodeGen/Private/BlueprintNativeCodeGenModule.cpp index c9c338d29f96..dc3913180573 100644 --- a/Engine/Source/Developer/BlueprintNativeCodeGen/Private/BlueprintNativeCodeGenModule.cpp +++ b/Engine/Source/Developer/BlueprintNativeCodeGen/Private/BlueprintNativeCodeGenModule.cpp @@ -80,9 +80,9 @@ private: void CollectBoundFunctions(UBlueprint* BP); void GenerateSingleAsset(UField* ForConversion, const FName PlatformName, TSharedPtr NativizationSummary = TSharedPtr()); void ReplaceAsset(const UObject* InAsset, const FCompilerNativizationOptions& NativizationOptions) const; - void GatherClassAssetsReferencedByStruct(TSet& Assets, const UStruct* OuterStruct, const UStruct* InnerStruct = nullptr) const; - void ReplaceAssetsWithCircularReferenceTo(const UBlueprintGeneratedClass* InClass, const FCompilerNativizationOptions& NativizationOptions) const; - bool HasCircularReferenceWithAnyConvertedAsset(const UBlueprintGeneratedClass* InClass, const FCompilerNativizationOptions& NativizationOptions) const; + void GatherConvertableAssetsReferencedByStruct(TSet& Assets, const UStruct* OuterStruct, const UStruct* InnerStruct = nullptr) const; + void ReplaceAssetsWithCircularReferenceTo(const UStruct* InStruct, const FCompilerNativizationOptions& NativizationOptions) const; + bool HasCircularReferenceWithAnyConvertedAsset(const UStruct* InStruct, const FCompilerNativizationOptions& NativizationOptions) const; struct FStatePerPlatform { @@ -900,7 +900,8 @@ EReplacementResult FBlueprintNativeCodeGenModule::IsTargetedForReplacement(const auto ObjectGeneratesOnlyStub = [&]() -> bool { - // ExcludedFolderPaths + // ExcludedFolderPaths - Only BPGCs are excluded by path. + if (BlueprintClass) { const FString ObjPathName = Object->GetPathName(); for (const FString& ExcludedPath : ExcludedFolderPaths) @@ -1055,19 +1056,22 @@ EReplacementResult FBlueprintNativeCodeGenModule::IsTargetedForReplacement(const StateForCurrentPlatform->CachedIsTargetedForReplacement.Add(ObjectKey, Result); - if (BlueprintClass) + if (const UStruct* ObjAsStruct = Cast(Object)) { if (Result == EReplacementResult::ReplaceCompletely) { // Look for any circular references with unconverted assets. We'll need to convert those as well in order to avoid creating an EDL cycle. - ReplaceAssetsWithCircularReferenceTo(BlueprintClass, NativizationOptions); + ReplaceAssetsWithCircularReferenceTo(ObjAsStruct, NativizationOptions); } - else if(HasCircularReferenceWithAnyConvertedAsset(BlueprintClass, NativizationOptions)) + else if(HasCircularReferenceWithAnyConvertedAsset(ObjAsStruct, NativizationOptions)) { - UE_LOG(LogBlueprintCodeGen, Log, TEXT("Forcing '%s' to be replaced as it has a circular reference to a converted asset"), *BlueprintClass->GetName()); + UE_LOG(LogBlueprintCodeGen, Log, TEXT("Forcing '%s' to be replaced as it has a circular reference to a converted asset"), *ObjAsStruct->GetName()); // Force unconverted assets to be replaced if it has a circular reference with any converted asset. - ReplaceAsset(BlueprintClass, NativizationOptions); + ReplaceAsset(ObjAsStruct, NativizationOptions); + + // Update the result. + Result = StateForCurrentPlatform->CachedIsTargetedForReplacement.FindChecked(ObjectKey); } } @@ -1086,7 +1090,7 @@ void FBlueprintNativeCodeGenModule::ReplaceAsset(const UObject* InAsset, const F } } -void FBlueprintNativeCodeGenModule::GatherClassAssetsReferencedByStruct(TSet& Assets, const UStruct* OuterStruct, const UStruct* InnerStruct) const +void FBlueprintNativeCodeGenModule::GatherConvertableAssetsReferencedByStruct(TSet& Assets, const UStruct* OuterStruct, const UStruct* InnerStruct) const { if (OuterStruct) { @@ -1115,18 +1119,26 @@ void FBlueprintNativeCodeGenModule::GatherClassAssetsReferencedByStruct(TSet(InnerProperty)) { - GatherClassAssetsReferencedByStruct(Assets, OuterStruct, StructProperty->Struct); + if (const UUserDefinedStruct* UDS = Cast(StructProperty->Struct)) + { + Assets.Add(StructProperty->Struct); + } + + GatherConvertableAssetsReferencedByStruct(Assets, OuterStruct, StructProperty->Struct); } else { const UBlueprintGeneratedClass* BPGC = nullptr; if (const UObjectPropertyBase* ObjectProperty = Cast(InnerProperty)) { - BPGC = Cast(ObjectProperty->PropertyClass); - } - else if (const UClassProperty* ClassProperty = Cast(InnerProperty)) - { - BPGC = Cast(ClassProperty->MetaClass); + if (const UClassProperty* ClassProperty = Cast(InnerProperty)) + { + BPGC = Cast(ClassProperty->MetaClass); + } + else + { + BPGC = Cast(ObjectProperty->PropertyClass); + } } if (BPGC) @@ -1139,33 +1151,33 @@ void FBlueprintNativeCodeGenModule::GatherClassAssetsReferencedByStruct(TSet ForwardReferencedAssets; - GatherClassAssetsReferencedByStruct(ForwardReferencedAssets, InClass); + TSet ForwardReferencedAssets; + GatherConvertableAssetsReferencedByStruct(ForwardReferencedAssets, InStruct); - for (const UBlueprintGeneratedClass* ForwardReference : ForwardReferencedAssets) + for (const UStruct* ForwardReference : ForwardReferencedAssets) { const EReplacementResult Result = IsTargetedForReplacement(ForwardReference, NativizationOptions); if (Result != EReplacementResult::ReplaceCompletely) { bool bForceConvert = false; - if (ForwardReference->IsChildOf(InClass)) + if (ForwardReference->IsChildOf(InStruct)) { - UE_LOG(LogBlueprintCodeGen, Log, TEXT("Forcing '%s' to be replaced as it has a circular reference with '%s'"), *ForwardReference->GetName(), *InClass->GetName()); + UE_LOG(LogBlueprintCodeGen, Log, TEXT("Forcing '%s' to be replaced as it has a circular reference with '%s'"), *ForwardReference->GetName(), *InStruct->GetName()); ReplaceAsset(ForwardReference, NativizationOptions); } else { - TSet ReverseReferencedAssets; - GatherClassAssetsReferencedByStruct(ReverseReferencedAssets, ForwardReference); + TSet ReverseReferencedAssets; + GatherConvertableAssetsReferencedByStruct(ReverseReferencedAssets, ForwardReference); - for (const UBlueprintGeneratedClass* ReverseReference : ReverseReferencedAssets) + for (const UStruct* ReverseReference : ReverseReferencedAssets) { - if (ReverseReference->IsChildOf(InClass)) + if (ReverseReference->IsChildOf(InStruct)) { - UE_LOG(LogBlueprintCodeGen, Log, TEXT("Forcing '%s' to be replaced as it has a circular reference to '%s'"), *ForwardReference->GetName(), *InClass->GetName()); + UE_LOG(LogBlueprintCodeGen, Log, TEXT("Forcing '%s' to be replaced as it has a circular reference to '%s'"), *ForwardReference->GetName(), *InStruct->GetName()); ReplaceAsset(ForwardReference, NativizationOptions); break; @@ -1176,22 +1188,22 @@ void FBlueprintNativeCodeGenModule::ReplaceAssetsWithCircularReferenceTo(const U } } -bool FBlueprintNativeCodeGenModule::HasCircularReferenceWithAnyConvertedAsset(const UBlueprintGeneratedClass* InClass, const FCompilerNativizationOptions& NativizationOptions) const +bool FBlueprintNativeCodeGenModule::HasCircularReferenceWithAnyConvertedAsset(const UStruct* InStruct, const FCompilerNativizationOptions& NativizationOptions) const { - TSet ForwardReferencedAssets; - GatherClassAssetsReferencedByStruct(ForwardReferencedAssets, InClass); + TSet ForwardReferencedAssets; + GatherConvertableAssetsReferencedByStruct(ForwardReferencedAssets, InStruct); - for (const UBlueprintGeneratedClass* ForwardReference : ForwardReferencedAssets) + for (const UStruct* ForwardReference : ForwardReferencedAssets) { const EReplacementResult Result = IsTargetedForReplacement(ForwardReference, NativizationOptions); if (Result == EReplacementResult::ReplaceCompletely) { - TSet ReverseReferencedAssets; - GatherClassAssetsReferencedByStruct(ReverseReferencedAssets, ForwardReference); + TSet ReverseReferencedAssets; + GatherConvertableAssetsReferencedByStruct(ReverseReferencedAssets, ForwardReference); - for (const UBlueprintGeneratedClass* ReverseReference : ReverseReferencedAssets) + for (const UStruct* ReverseReference : ReverseReferencedAssets) { - if (ReverseReference->IsChildOf(InClass)) + if (ReverseReference->IsChildOf(InStruct)) { return true; } diff --git a/Engine/Source/Developer/BlueprintNativeCodeGen/Private/NativeCodeGenerationTool.cpp b/Engine/Source/Developer/BlueprintNativeCodeGen/Private/NativeCodeGenerationTool.cpp index 1bacc63ffc32..4291d50b7ef4 100644 --- a/Engine/Source/Developer/BlueprintNativeCodeGen/Private/NativeCodeGenerationTool.cpp +++ b/Engine/Source/Developer/BlueprintNativeCodeGen/Private/NativeCodeGenerationTool.cpp @@ -66,16 +66,16 @@ struct FGeneratedCodeData void GatherUserDefinedDependencies(UBlueprint& InBlueprint) { FCompilerNativizationOptions BlankOptions{}; - FGatherConvertedClassDependencies ClassDependencies(InBlueprint.GeneratedClass, BlankOptions); - for (auto Iter : ClassDependencies.ConvertedClasses) + TSharedPtr ClassDependencies = FGatherConvertedClassDependencies::Get(InBlueprint.GeneratedClass, BlankOptions); + for (auto Iter : ClassDependencies->ConvertedClasses) { DependentObjects.Add(Iter); } - for (auto Iter : ClassDependencies.ConvertedStructs) + for (auto Iter : ClassDependencies->ConvertedStructs) { DependentObjects.Add(Iter); } - for (auto Iter : ClassDependencies.ConvertedEnum) + for (auto Iter : ClassDependencies->ConvertedEnum) { DependentObjects.Add(Iter); } @@ -96,7 +96,7 @@ struct FGeneratedCodeData DependentObjects.Add(InBlueprint.GeneratedClass); bool bUnconvertedHeader = false; - for (auto Asset : ClassDependencies.Assets) + for (auto Asset : ClassDependencies->Assets) { if (auto BPGC = Cast(Asset)) { diff --git a/Engine/Source/Developer/ShaderFormatVectorVM/Private/ir_vm_gen_bytecode_visitor.cpp b/Engine/Source/Developer/ShaderFormatVectorVM/Private/ir_vm_gen_bytecode_visitor.cpp index 4d065fe27202..7c631b2f8647 100644 --- a/Engine/Source/Developer/ShaderFormatVectorVM/Private/ir_vm_gen_bytecode_visitor.cpp +++ b/Engine/Source/Developer/ShaderFormatVectorVM/Private/ir_vm_gen_bytecode_visitor.cpp @@ -221,7 +221,8 @@ void BuildExpressionMap() Info->Add(FVMExpresssionInfo(EVectorVMOp::muli, glsl_type::int_type, glsl_type::int_type, glsl_type::int_type)); Info = &VMExpressionMap.Add(ir_binop_div); Info->Add(FVMExpresssionInfo(EVectorVMOp::div, glsl_type::float_type, glsl_type::float_type, glsl_type::float_type)); - //Currently don't have an integer division operation. + Info->Add(FVMExpresssionInfo(EVectorVMOp::divi, glsl_type::int_type, glsl_type::int_type, glsl_type::int_type)); + /** * Takes one of two combinations of arguments: @@ -277,8 +278,10 @@ void BuildExpressionMap() * \name Bit-wise binary operations. */ /*@{*/ - //Info = &VMExpressionMap.Add(ir_binop_lshift); - //Info = &VMExpressionMap.Add(ir_binop_rshift); + Info = &VMExpressionMap.Add(ir_binop_lshift); + Info->Add(FVMExpresssionInfo(EVectorVMOp::bit_lshift, glsl_type::int_type, glsl_type::int_type, glsl_type::int_type)); + Info = &VMExpressionMap.Add(ir_binop_rshift); + Info->Add(FVMExpresssionInfo(EVectorVMOp::bit_rshift, glsl_type::int_type, glsl_type::int_type, glsl_type::int_type)); Info = &VMExpressionMap.Add(ir_binop_bit_and); Info->Add(FVMExpresssionInfo(EVectorVMOp::bit_and, glsl_type::int_type, glsl_type::int_type, glsl_type::int_type)); Info = &VMExpressionMap.Add(ir_binop_bit_xor); @@ -2115,7 +2118,7 @@ class ir_gen_vvm_visitor : public ir_hierarchical_visitor case EVectorVMBaseTypes::Float: { float Val = *(float*)(CompilationOutput.InternalConstantData.GetData() + Offset); - OpsConstantTable += FString::Printf(TEXT("%d | %f\n"), TableOffset, Val); + OpsConstantTable += FString::Printf(TEXT("%d | %.9g\n"), TableOffset, Val); NumConstants++; } break; diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CallFunction.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CallFunction.h index eb6ab95d2f2c..fca35a6e718d 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CallFunction.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_CallFunction.h @@ -64,6 +64,8 @@ private: /** Flag used to track validity of pin tooltips, when tooltips are invalid they will be refreshed before being displayed */ mutable bool bPinTooltipsValid; + TArray ExpandAsEnumPins; + public: // UObject interface @@ -199,6 +201,8 @@ public: /** */ static FSlateIcon GetPaletteIconForFunction(UFunction const* Function, FLinearColor& OutColor); + static void GetExpandEnumPinNames(const UFunction* Function, TArray& EnumNamesToCheck); + private: /* Looks at function metadata and properties to determine if this node should be using enum to exec expansion */ void DetermineWantsEnumToExecExpansion(const UFunction* Function); diff --git a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_ConstructObjectFromClass.h b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_ConstructObjectFromClass.h index 2edbe3e60895..df349943d31b 100644 --- a/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_ConstructObjectFromClass.h +++ b/Engine/Source/Editor/BlueprintGraph/Classes/K2Node_ConstructObjectFromClass.h @@ -21,6 +21,7 @@ class BLUEPRINTGRAPH_API UK2Node_ConstructObjectFromClass : public UK2Node virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; virtual void PinDefaultValueChanged(UEdGraphPin* Pin) override; virtual FText GetTooltipText() const override; + virtual FText GetKeywords() const override; virtual bool HasExternalDependencies(TArray* OptionalOutput) const override; virtual bool IsCompatibleWithGraph(const UEdGraph* TargetGraph) const override; virtual void PinConnectionListChanged(UEdGraphPin* Pin); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/CallFunctionHandler.cpp b/Engine/Source/Editor/BlueprintGraph/Private/CallFunctionHandler.cpp index 44227d17004d..719aede9677c 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/CallFunctionHandler.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/CallFunctionHandler.cpp @@ -437,7 +437,7 @@ void FKCHandler_CallFunction::CreateFunctionCallStatement(FKismetFunctionContext } else { - FString WarningMessage = FText::Format(LOCTEXT("FindFunction_ErrorFmt", "Could not find the function '{0}' called from @@"), FText::FromString(GetFunctionNameFromNode(Node))).ToString(); + FString WarningMessage = FText::Format(LOCTEXT("FindFunction_ErrorFmt", "Could not find the function '{0}' called from @@"), FText::FromString(GetFunctionNameFromNode(Node).ToString())).ToString(); CompilerContext.MessageLog.Warning(*WarningMessage, Node); } } @@ -589,9 +589,8 @@ UFunction* FKCHandler_CallFunction::FindFunction(FKismetFunctionContext& Context if (CallingContext) { - FString FunctionName = GetFunctionNameFromNode(Node); - - return CallingContext->FindFunctionByName(*FunctionName); + const FName FunctionName = GetFunctionNameFromNode(Node); + return CallingContext->FindFunctionByName(FunctionName); } return nullptr; @@ -712,17 +711,17 @@ void FKCHandler_CallFunction::CheckIfFunctionIsCallable(UFunction* Function, FKi } // Get the name of the function to call from the node -FString FKCHandler_CallFunction::GetFunctionNameFromNode(UEdGraphNode* Node) const +FName FKCHandler_CallFunction::GetFunctionNameFromNode(UEdGraphNode* Node) const { UK2Node_CallFunction* CallFuncNode = Cast(Node); if (CallFuncNode) { - return CallFuncNode->FunctionReference.GetMemberName().ToString(); + return CallFuncNode->FunctionReference.GetMemberName(); } else { CompilerContext.MessageLog.Error(*NSLOCTEXT("KismetCompiler", "UnableResolveFunctionName_Error", "Unable to resolve function name for @@").ToString(), Node); - return TEXT(""); + return NAME_None; } } diff --git a/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2.cpp b/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2.cpp index 60c3bb232bbc..07897e208b31 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/EdGraphSchema_K2.cpp @@ -204,7 +204,7 @@ struct FUnloadedAssetData , AssetFriendlyName(FText::FromString(FName::NameToDisplayString(InAsset.AssetName.ToString(), false))) , PossibleObjectReferenceTypes(InPossibleObjectReferenceTypes) { - InAsset.GetTagValue("Tooltip", Tooltip); + InAsset.GetTagValue(FBlueprintMetadata::MD_Tooltip, Tooltip); if (Tooltip.IsEmpty()) { Tooltip = FText::FromString(InAsset.ObjectPath.ToString()); @@ -1451,11 +1451,24 @@ void UEdGraphSchema_K2::GetContextMenuActions(const UEdGraph* CurrentGraph, cons FText PinName = Pin->GetDisplayName(); FText NodeName = Pin->GetOwningNode()->GetNodeTitle(ENodeTitleType::ListView); + FText EntryTitle; + FText EntryTooltip; + if (PinName.IsEmpty()) + { + EntryTitle = FText::Format(LOCTEXT("StraightenDescription_SinglePin_NoDisplayName", "Straighten Connection to {0}"), NodeName); + EntryTooltip = FText::Format(LOCTEXT("StraightenDescription_SinglePin_NoDisplayName_Tip", "Straighten the connection between this pin, and {0}"), NodeName); + } + else + { + EntryTitle = FText::Format(LOCTEXT("StraightenDescription_SinglePin", "Straighten Connection to {0} ({1})"), NodeName, PinName); + EntryTooltip = FText::Format(LOCTEXT("StraightenDescription_SinglePin_Node_Tip", "Straighten the connection between this pin, and {0} ({1})"), NodeName, PinName); + } + MenuBuilder->AddMenuEntry( FGraphEditorCommands::Get().StraightenConnections, NAME_None, - FText::Format(LOCTEXT("StraightenDescription_SinglePin", "Straighten Connection to {0} ({1})"), NodeName, PinName), - FText::Format(LOCTEXT("StraightenDescription_SinglePin_Node_Tip", "Straighten the connection between this pin, and {0} ({1})"), NodeName, PinName), + EntryTitle, + EntryTooltip, FSlateIcon(NAME_None, NAME_None, NAME_None) ); } @@ -1964,14 +1977,15 @@ void UEdGraphSchema_K2::GetBreakLinkToSubMenuActions( class FMenuBuilder& MenuBu UEdGraphPin* Pin = *Links; FText Title = Pin->GetOwningNode()->GetNodeTitle(ENodeTitleType::ListView); FString TitleString = Title.ToString(); - if ( Pin->PinName != TEXT("") ) + const FText PinDisplayName = Pin->GetDisplayName(); + if (!PinDisplayName.IsEmpty()) { - TitleString = FString::Printf(TEXT("%s (%s)"), *TitleString, *Pin->GetDisplayName().ToString()); + TitleString = FString::Printf(TEXT("%s (%s)"), *TitleString, *PinDisplayName.ToString()); // Add name of connection if possible FFormatNamedArguments Args; Args.Add( TEXT("NodeTitle"), Title ); - Args.Add( TEXT("PinName"), Pin->GetDisplayName() ); + Args.Add( TEXT("PinName"), PinDisplayName ); Title = FText::Format( LOCTEXT("BreakDescPin", "{NodeTitle} ({PinName})"), Args ); } @@ -1984,11 +1998,11 @@ void UEdGraphSchema_K2::GetBreakLinkToSubMenuActions( class FMenuBuilder& MenuBu if ( Count == 0 ) { - Description = FText::Format( LOCTEXT("BreakDesc", "Break link to {NodeTitle}"), Args ); + Description = FText::Format( LOCTEXT("BreakDesc", "Break Link to {NodeTitle}"), Args ); } else { - Description = FText::Format( LOCTEXT("BreakDescMulti", "Break link to {NodeTitle} ({NumberOfNodes})"), Args ); + Description = FText::Format( LOCTEXT("BreakDescMulti", "Break Link to {NodeTitle} ({NumberOfNodes})"), Args ); } ++Count; @@ -2007,14 +2021,15 @@ void UEdGraphSchema_K2::GetJumpToConnectionSubMenuActions( class FMenuBuilder& M { FText Title = PinLink->GetOwningNode()->GetNodeTitle(ENodeTitleType::ListView); FString TitleString = Title.ToString(); - if ( PinLink->PinName != TEXT("") ) + const FText PinDisplayName = PinLink->GetDisplayName(); + if (!PinDisplayName.IsEmpty()) { - TitleString = FString::Printf(TEXT("%s (%s)"), *TitleString, *PinLink->GetDisplayName().ToString()); + TitleString = FString::Printf(TEXT("%s (%s)"), *TitleString, *PinDisplayName.ToString()); // Add name of connection if possible FFormatNamedArguments Args; Args.Add( TEXT("NodeTitle"), Title ); - Args.Add( TEXT("PinName"), PinLink->GetDisplayName() ); + Args.Add( TEXT("PinName"), PinDisplayName ); Title = FText::Format( LOCTEXT("JumpToDescPin", "{NodeTitle} ({PinName})"), Args ); } @@ -2076,15 +2091,43 @@ void UEdGraphSchema_K2::GetStraightenConnectionToSubMenuActions( class FMenuBuil NAME_None, LOCTEXT("StraightenAllConnections", "All Connected Pins"), TAttribute(), FSlateIcon(NAME_None, NAME_None, NAME_None) ); - for (auto& Pair : NodeToPins) + for (const TPair>& Pair : NodeToPins) { - FText NodeName = Pair.Key->GetNodeTitle(ENodeTitleType::ListView); for (UEdGraphPin* Pin : Pair.Value) { - FText PinName = Pin->GetDisplayName(); + FText Title = Pair.Key->GetNodeTitle(ENodeTitleType::ListView); + FString TitleString = Title.ToString(); + const FText PinDisplayName = Pin->GetDisplayName(); + if (!PinDisplayName.IsEmpty()) + { + TitleString = FString::Printf(TEXT("%s (%s)"), *TitleString, *PinDisplayName.ToString()); + + // Add name of connection if possible + FFormatNamedArguments Args; + Args.Add(TEXT("NodeTitle"), Title); + Args.Add(TEXT("PinName"), PinDisplayName); + Title = FText::Format(LOCTEXT("StraightenToDescPin", "{NodeTitle} ({PinName})"), Args); + } + uint32 &Count = LinkTitleCount.FindOrAdd(TitleString); + + FText Description; + FFormatNamedArguments Args; + Args.Add(TEXT("NodeTitle"), Title); + Args.Add(TEXT("NumberOfNodes"), Count); + + if (Count == 0) + { + Description = FText::Format(LOCTEXT("StraightenDesc", "Straighten connection to {NodeTitle}"), Args); + } + else + { + Description = FText::Format(LOCTEXT("StraightendDescMulti", "Straighten connection to {NodeTitle} ({NumberOfNodes})"), Args); + } + ++Count; + MenuBuilder.AddMenuEntry( - FText::Format(LOCTEXT("StraightenDescription_Node", "{0} ({1})"), NodeName, Pin->GetDisplayName()), - FText::Format(LOCTEXT("StraightenDescription_Node_Tip", "Straighten the connection between this pin, and {0} ({1})"), NodeName, Pin->GetDisplayName()), + Description, + Description, FSlateIcon(), FExecuteAction::CreateLambda([=]{ if (const FUIAction* UIAction = MenuCommandList->GetActionForCommand(FGraphEditorCommands::Get().StraightenConnections)) @@ -2565,6 +2608,15 @@ bool UEdGraphSchema_K2::SearchForAutocastFunction(const UEdGraphPin* OutputPin, FunctionOwner = Function->GetOwnerClass(); } } + else if (OutputPin->PinType.PinCategory == PC_Class) + { + if (InputPin->PinType.PinCategory == PC_String) + { + UFunction* Function = UKismetSystemLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetSystemLibrary, GetClassDisplayName)); + TargetFunction = Function->GetFName(); + FunctionOwner = Function->GetOwnerClass(); + } + } else if (OutputPin->PinType.PinCategory == PC_Struct) { const UScriptStruct* OutputStructType = Cast(OutputPin->PinType.PinSubCategoryObject.Get()); @@ -5658,6 +5710,39 @@ bool UEdGraphSchema_K2::FadeNodeWhenDraggingOffPin(const UEdGraphNode* Node, con struct FBackwardCompatibilityConversionHelper { + // Re-add orphaned pins to deal with any links that were lost during converstion + static bool RestoreOrphanLinks(UEdGraphPin* OldPin, UEdGraphPin* NewPin, UK2Node* NewNode, const TArray& OldLinks) + { + // See if there are any links that didn't get copied, including to orphan pins or if the newpin is null + TArray OrphanedLinks; + + for (UEdGraphPin* OldLink : OldLinks) + { + if (!NewPin || !NewPin->LinkedTo.Contains(OldLink)) + { + OrphanedLinks.Add(OldLink); + } + } + + if (OrphanedLinks.Num() > 0) + { + // Add an orphan pin so warning/connections are not silently lost + UEdGraphPin* OrphanPin = NewNode->CreatePin(OldPin->Direction, OldPin->PinType, OldPin->PinName); + + OrphanPin->bOrphanedPin = true; + OrphanPin->bNotConnectable = true; + + for (UEdGraphPin* OldLink : OrphanedLinks) + { + OrphanPin->MakeLinkTo(OldLink); + } + + return true; + } + + return false; + } + static bool ConvertNode( UK2Node* OldNode, const FString& BlueprintPinName, @@ -5758,7 +5843,7 @@ struct FBackwardCompatibilityConversionHelper UEdGraphPin* ExecPin = OldNode->GetExecPin(); UEdGraphPin* ExecCastPin = CastNode->GetExecPin(); check(ExecCastPin); - if (!ExecPin || !Schema.MovePinLinks(*ExecPin, *ExecCastPin).CanSafeConnect()) + if (!ExecPin || !Schema.MovePinLinks(*ExecPin, *ExecCastPin, false, true).CanSafeConnect()) { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), @@ -5788,7 +5873,7 @@ struct FBackwardCompatibilityConversionHelper UEdGraphPin* CastSourcePin = CastNode->GetCastSourcePin(); check(CastSourcePin); - if (!Schema.MovePinLinks(*OldBlueprintPin, *CastSourcePin).CanSafeConnect()) + if (!Schema.MovePinLinks(*OldBlueprintPin, *CastSourcePin, false, true).CanSafeConnect()) { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), @@ -5818,13 +5903,17 @@ struct FBackwardCompatibilityConversionHelper UEdGraphPin* OldPin = OldNode->FindPin(Pin->PinName); if (OldPin) { + TArray OldLinks = OldPin->LinkedTo; OldPins.Add(OldPin); - if (!Schema.MovePinLinks(*OldPin, *Pin).CanSafeConnect()) + + if (!Schema.MovePinLinks(*OldPin, *Pin, false, true).CanSafeConnect()) { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot connect' in blueprint: %s, pin: %s"), Blueprint ? *Blueprint->GetName() : TEXT("Unknown"), *Pin->PinName.ToString()); } + + FBackwardCompatibilityConversionHelper::RestoreOrphanLinks(OldPin, Pin, NewNode, OldLinks); } else { @@ -5997,13 +6086,14 @@ bool UEdGraphSchema_K2::ReplaceOldNodeWithNew(UK2Node* OldNode, UK2Node* NewNode { UEdGraphPin* OldPin = OldNode->Pins[PinIdx]; UEdGraphPin* NewPin = NewPinArray[PinIdx]; + TArray OldLinks = OldPin->LinkedTo; // could be null, meaning they didn't want to map this OldPin to anything if (NewPin == nullptr) { continue; } - else if (!Schema->MovePinLinks(*OldPin, *NewPin).CanSafeConnect()) + else if (!Schema->MovePinLinks(*OldPin, *NewPin, false, true).CanSafeConnect()) { UE_LOG(LogBlueprint, Warning, TEXT("BackwardCompatibilityNodeConversion Error 'cannot safely move pin %s to %s' in blueprint: %s"), *OldPin->PinName.ToString(), @@ -6015,6 +6105,8 @@ bool UEdGraphSchema_K2::ReplaceOldNodeWithNew(UK2Node* OldNode, UK2Node* NewNode // for wildcard pins, which may have to react to being connected with NewNode->NotifyPinConnectionListChanged(NewPin); } + + FBackwardCompatibilityConversionHelper::RestoreOrphanLinks(OldPin, NewPin, NewNode, OldLinks); } NewNode->NodeComment = OldNode->NodeComment; diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallFunction.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallFunction.cpp index 194c31d79d27..8911ce92da23 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallFunction.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_CallFunction.cpp @@ -19,6 +19,7 @@ #include "K2Node_FunctionEntry.h" #include "K2Node_IfThenElse.h" #include "K2Node_TemporaryVariable.h" +#include "K2Node_ExecutionSequence.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Classes/EditorStyleSettings.h" #include "Editor.h" @@ -544,7 +545,12 @@ void UK2Node_CallFunction::GetPinHoverText(const UEdGraphPin& Pin, FString& Hove { for (UEdGraphPin* P : Pins) { - P->PinToolTip.Empty(); + if (!P->PinToolTip.IsEmpty() && ExpandAsEnumPins.Contains(P)) + { + continue; + } + + P->PinToolTip.Reset(); GeneratePinTooltip(*P); } @@ -722,57 +728,105 @@ void UK2Node_CallFunction::CreateExecPinsForFunctionCall(const UFunction* Functi bool bCreateSingleExecInputPin = true; bool bCreateThenPin = true; + ExpandAsEnumPins.Reset(); + // If not pure, create exec pins if (!bIsPureFunc) { // If we want enum->exec expansion, and it is not disabled, do it now if(bWantsEnumToExecExpansion) { - const FString& EnumParamName = Function->GetMetaData(FBlueprintMetadata::MD_ExpandEnumAsExecs); + TArray EnumNames; + GetExpandEnumPinNames(Function, EnumNames); - UProperty* Prop = nullptr; - UEnum* Enum = nullptr; + UProperty* PreviousInput = nullptr; - if(UByteProperty* ByteProp = FindField(Function, FName(*EnumParamName))) + for (const FName& EnumParamName : EnumNames) { - Prop = ByteProp; - Enum = ByteProp->Enum; - } - else if(UEnumProperty* EnumProp = FindField(Function, FName(*EnumParamName))) - { - Prop = EnumProp; - Enum = EnumProp->GetEnum(); - } + UProperty* Prop = nullptr; + UEnum* Enum = nullptr; - if(Prop != nullptr && Enum != nullptr) - { - const bool bIsFunctionInput = !Prop->HasAnyPropertyFlags(CPF_ReturnParm) && - (!Prop->HasAnyPropertyFlags(CPF_OutParm) || - Prop->HasAnyPropertyFlags(CPF_ReferenceParm)); - const EEdGraphPinDirection Direction = bIsFunctionInput ? EGPD_Input : EGPD_Output; - - // yay, found it! Now create exec pin for each - int32 NumExecs = (Enum->NumEnums() - 1); - for(int32 ExecIdx=0; ExecIdx(Function, EnumParamName)) { - bool const bShouldBeHidden = Enum->HasMetaData(TEXT("Hidden"), ExecIdx) || Enum->HasMetaData(TEXT("Spacer"), ExecIdx); - if (!bShouldBeHidden) + Prop = ByteProp; + Enum = ByteProp->Enum; + } + else if (UEnumProperty* EnumProp = FindField(Function, EnumParamName)) + { + Prop = EnumProp; + Enum = EnumProp->GetEnum(); + } + + if (Prop != nullptr && Enum != nullptr) + { + const bool bIsFunctionInput = !Prop->HasAnyPropertyFlags(CPF_ReturnParm) && + (!Prop->HasAnyPropertyFlags(CPF_OutParm) || + Prop->HasAnyPropertyFlags(CPF_ReferenceParm)); + const EEdGraphPinDirection Direction = bIsFunctionInput ? EGPD_Input : EGPD_Output; + + if (bIsFunctionInput) { - // Can't use Enum->GetNameByIndex here because it doesn't do namespace mangling - const FName ExecName = *Enum->GetNameStringByIndex(ExecIdx); - CreatePin(Direction, UEdGraphSchema_K2::PC_Exec, ExecName); + if (PreviousInput) + { + bHasCompilerMessage = true; + ErrorType = EMessageSeverity::Error; + ErrorMsg = FString::Printf(TEXT("Parameter '%s' is listed as an ExpandEnumAsExecs input, but %s already was one. Only one is permitted."), *EnumParamName.ToString(), *PreviousInput->GetName()); + break; + } + PreviousInput = Prop; + } + + // yay, found it! Now create exec pin for each + int32 NumExecs = (Enum->NumEnums() - 1); + for (int32 ExecIdx = 0; ExecIdx < NumExecs; ExecIdx++) + { + bool const bShouldBeHidden = Enum->HasMetaData(TEXT("Hidden"), ExecIdx) || Enum->HasMetaData(TEXT("Spacer"), ExecIdx); + if (!bShouldBeHidden) + { + // Can't use Enum->GetNameByIndex here because it doesn't do namespace mangling + const FString NameStr = Enum->GetNameStringByIndex(ExecIdx); + + UEdGraphPin* CreatedPin = nullptr; + + // todo: really only makes sense if there are multiple outputs + if (bIsFunctionInput || EnumNames.Num() == 1) + { + CreatedPin = CreatePin(Direction, UEdGraphSchema_K2::PC_Exec, *NameStr); + } + else + { + CreatedPin = CreatePin(Direction, UEdGraphSchema_K2::PC_Exec, *NameStr); + CreatedPin->PinFriendlyName = FText::FromString(FString::Printf(TEXT("(%s) %s"), *Prop->GetDisplayNameText().ToString(), *NameStr)); + } + + ExpandAsEnumPins.Add(CreatedPin); + + if (Enum->HasMetaData(TEXT("Tooltip"), ExecIdx)) + { + FString EnumTooltip = Enum->GetMetaData(TEXT("Tooltip"), ExecIdx); + + if (const UEdGraphSchema_K2* const K2Schema = Cast(GetSchema())) + { + K2Schema->ConstructBasicPinTooltip(*CreatedPin, FText::FromString(EnumTooltip), CreatedPin->PinToolTip); + } + else + { + CreatedPin->PinToolTip = EnumTooltip; + } + } + } + } + + if (bIsFunctionInput) + { + // If using ExpandEnumAsExec for input, don't want to add a input exec pin + bCreateSingleExecInputPin = false; + } + else + { + // If using ExpandEnumAsExec for output, don't want to add a "then" pin + bCreateThenPin = false; } - } - - if (bIsFunctionInput) - { - // If using ExpandEnumAsExec for input, don't want to add a input exec pin - bCreateSingleExecInputPin = false; - } - else - { - // If using ExpandEnumAsExec for output, don't want to add a "then" pin - bCreateThenPin = false; } } } @@ -801,22 +855,73 @@ void UK2Node_CallFunction::DetermineWantsEnumToExecExpansion(const UFunction* Fu if (Function->HasMetaData(FBlueprintMetadata::MD_ExpandEnumAsExecs)) { - const FString& EnumParamName = Function->GetMetaData(FBlueprintMetadata::MD_ExpandEnumAsExecs); - UByteProperty* EnumProp = FindField(Function, FName(*EnumParamName)); - if((EnumProp != NULL && EnumProp->Enum != NULL) || FindField(Function, FName(*EnumParamName))) + TArray EnumNamesToCheck; + GetExpandEnumPinNames(Function, EnumNamesToCheck); + + for (int32 i = EnumNamesToCheck.Num() - 1; i >= 0; --i) { - bWantsEnumToExecExpansion = true; - } - else - { - if (!bHasCompilerMessage) + const FName& EnumParamName = EnumNamesToCheck[i]; + + UByteProperty* EnumProp = FindField(Function, EnumParamName); + if ((EnumProp != NULL && EnumProp->Enum != NULL) || FindField(Function, EnumParamName)) { - //put in warning state - bHasCompilerMessage = true; - ErrorType = EMessageSeverity::Warning; - ErrorMsg = FText::Format(LOCTEXT("EnumToExecExpansionFailedFmt", "Unable to find enum parameter with name '{0}' to expand for @@"), FText::FromString(EnumParamName)).ToString(); + bWantsEnumToExecExpansion = true; + EnumNamesToCheck.RemoveAt(i); } } + + if (bWantsEnumToExecExpansion && EnumNamesToCheck.Num() > 0 && !bHasCompilerMessage) + { + bHasCompilerMessage = true; + ErrorType = EMessageSeverity::Warning; + + if (EnumNamesToCheck.Num() == 1) + { + ErrorMsg = FText::Format(LOCTEXT("EnumToExecExpansionFailedFmt", "Unable to find enum parameter with name '{0}' to expand for @@"), FText::FromName(EnumNamesToCheck[0])).ToString(); + } + else + { + FString ParamNames; + + for (const FName& Name : EnumNamesToCheck) + { + if (!ParamNames.IsEmpty()) + { + ParamNames += TEXT(", "); + } + + ParamNames += Name.ToString(); + } + + ErrorMsg = FText::Format(LOCTEXT("EnumToExecExpansionFailedMultipleFmt", "Unable to find enum parameters for names:\n '{{0}}' \nto expand for @@"), FText::FromString(ParamNames)).ToString(); + } + } + } +} + +void UK2Node_CallFunction::GetExpandEnumPinNames(const UFunction* Function, TArray& EnumNamesToCheck) +{ + EnumNamesToCheck.Reset(); + + // todo: use metadatacache if/when that's accepted. + const FString& EnumParamString = Function->GetMetaData(FBlueprintMetadata::MD_ExpandEnumAsExecs); + + TArray RawGroupings; + EnumParamString.ParseIntoArray(RawGroupings, TEXT(","), false); + for (const FString& RawGroup : RawGroupings) + { + TArray IndividualEntries; + RawGroup.ParseIntoArray(IndividualEntries, TEXT("|")); + + for (const FString& Entry : IndividualEntries) + { + if (Entry.IsEmpty()) + { + continue; + } + + EnumNamesToCheck.Add(*Entry); + } } } @@ -973,14 +1078,19 @@ bool UK2Node_CallFunction::CreatePinsForFunctionCall(const UFunction* Function) bAllPinsGood = bAllPinsGood && bPinGood; } - // If we have an 'enum to exec' parameter, set its default value to something valid so we don't get warnings + // If we have 'enum to exec' parameters, set their default value to something valid so we don't get warnings if(bWantsEnumToExecExpansion) { - const FString& EnumParamName = Function->GetMetaData(FBlueprintMetadata::MD_ExpandEnumAsExecs); - UEdGraphPin* EnumParamPin = FindPin(EnumParamName); - if (UEnum* PinEnum = (EnumParamPin ? Cast(EnumParamPin->PinType.PinSubCategoryObject.Get()) : NULL)) + TArray EnumNamesToCheck; + GetExpandEnumPinNames(Function, EnumNamesToCheck); + + for (const FName& Name : EnumNamesToCheck) { - EnumParamPin->DefaultValue = PinEnum->GetNameStringByIndex(0); + UEdGraphPin* EnumParamPin = FindPin(Name); + if (UEnum* PinEnum = (EnumParamPin ? Cast(EnumParamPin->PinType.PinSubCategoryObject.Get()) : NULL)) + { + EnumParamPin->DefaultValue = PinEnum->GetNameStringByIndex(0); + } } } @@ -1605,7 +1715,7 @@ bool UK2Node_CallFunction::ShouldDrawAsBead() const bool UK2Node_CallFunction::ShouldShowNodeProperties() const { // Show node properties if this corresponds to a function graph - if (FunctionReference.GetMemberName() != NAME_None) + if (FunctionReference.GetMemberName() != NAME_None && HasValidBlueprint()) { return FindObject(GetBlueprint(), *(FunctionReference.GetMemberName().ToString())) != NULL; } @@ -1802,6 +1912,7 @@ void UK2Node_CallFunction::ValidateNodeDuringCompilation(class FCompilerResultsL } else if (Function->HasMetaData(FBlueprintMetadata::MD_ExpandEnumAsExecs) && bWantsEnumToExecExpansion == false) { + // will technically not have a properly formatted output for multiple params... but /shrug. const FString& EnumParamName = Function->GetMetaData(FBlueprintMetadata::MD_ExpandEnumAsExecs); MessageLog.Warning(*FText::Format(LOCTEXT("EnumToExecExpansionFailedFmt", "Unable to find enum parameter with name '{0}' to expand for @@"), FText::FromString(EnumParamName)).ToString(), this); } @@ -2006,103 +2117,163 @@ void UK2Node_CallFunction::ExpandNode(class FKismetCompilerContext& CompilerCont { if(Function) { - // Get the metadata that identifies which param is the enum, and try and find it - const FString& EnumParamName = Function->GetMetaData(FBlueprintMetadata::MD_ExpandEnumAsExecs); + TArray EnumNamesToCheck; + GetExpandEnumPinNames(Function, EnumNamesToCheck); - UEnum* Enum = nullptr; + bool bAlreadyHandleInput = false; - if (UByteProperty* ByteProp = FindField(Function, FName(*EnumParamName))) - { - Enum = ByteProp->Enum; - } - else if (UEnumProperty* EnumProp = FindField(Function, FName(*EnumParamName))) - { - Enum = EnumProp->GetEnum(); - } + UEdGraphPin* OutMainExecutePin = nullptr; + UK2Node_ExecutionSequence* SpawnedSequenceNode = nullptr; + int32 OutSequenceIndex = 0; - UEdGraphPin* EnumParamPin = FindPinChecked(EnumParamName); - if(Enum != nullptr) + for (const FName& EnumParamName : EnumNamesToCheck) { - // Expanded as input execs pins - if (EnumParamPin->Direction == EGPD_Input) + UEnum* Enum = nullptr; + + if (UByteProperty* ByteProp = FindField(Function, EnumParamName)) { - // Create normal exec input - UEdGraphPin* ExecutePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); + Enum = ByteProp->Enum; + } + else if (UEnumProperty* EnumProp = FindField(Function, EnumParamName)) + { + Enum = EnumProp->GetEnum(); + } - // Create temp enum variable - UK2Node_TemporaryVariable* TempEnumVarNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); - TempEnumVarNode->VariableType.PinCategory = UEdGraphSchema_K2::PC_Byte; - TempEnumVarNode->VariableType.PinSubCategoryObject = Enum; - TempEnumVarNode->AllocateDefaultPins(); - // Get the output pin - UEdGraphPin* TempEnumVarOutput = TempEnumVarNode->GetVariablePin(); - - // Connect temp enum variable to (hidden) enum pin - Schema->TryCreateConnection(TempEnumVarOutput, EnumParamPin); - - // Now we want to iterate over other exec inputs... - for(int32 PinIdx=Pins.Num()-1; PinIdx>=0; PinIdx--) + UEdGraphPin* EnumParamPin = FindPin(EnumParamName); + if (Enum && EnumParamPin) + { + // Expanded as input execs pins + if (EnumParamPin->Direction == EGPD_Input) { - UEdGraphPin* Pin = Pins[PinIdx]; - if( Pin != NULL && - Pin != ExecutePin && - Pin->Direction == EGPD_Input && - Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec ) + if (bAlreadyHandleInput) { - // Create node to set the temp enum var - UK2Node_AssignmentStatement* AssignNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); - AssignNode->AllocateDefaultPins(); + CompilerContext.MessageLog.Error(TEXT("@@ Already provided an input enum parameter for ExpandEnumAsExecs. Only one is permitted."), this); + return; + } - // Move connections from fake 'enum exec' pint to this assignment node - CompilerContext.MovePinLinksToIntermediate(*Pin, *AssignNode->GetExecPin()); + bAlreadyHandleInput = true; - // Connect this to out temp enum var - Schema->TryCreateConnection(AssignNode->GetVariablePin(), TempEnumVarOutput); + // Create normal exec input + UEdGraphPin* ExecutePin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); - // Connect exec output to 'real' exec pin - Schema->TryCreateConnection(AssignNode->GetThenPin(), ExecutePin); + // Create temp enum variable + UK2Node_TemporaryVariable* TempEnumVarNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); + TempEnumVarNode->VariableType.PinCategory = UEdGraphSchema_K2::PC_Byte; + TempEnumVarNode->VariableType.PinSubCategoryObject = Enum; + TempEnumVarNode->AllocateDefaultPins(); + // Get the output pin + UEdGraphPin* TempEnumVarOutput = TempEnumVarNode->GetVariablePin(); - // set the literal enum value to set to - AssignNode->GetValuePin()->DefaultValue = Pin->PinName.ToString(); + // Connect temp enum variable to (hidden) enum pin + Schema->TryCreateConnection(TempEnumVarOutput, EnumParamPin); - // Finally remove this 'cosmetic' exec pin - Pins[PinIdx]->MarkPendingKill(); - Pins.RemoveAt(PinIdx); + // Now we want to iterate over other exec inputs... + for (int32 PinIdx = Pins.Num() - 1; PinIdx >= 0; PinIdx--) + { + UEdGraphPin* Pin = Pins[PinIdx]; + if (Pin != NULL && + Pin != ExecutePin && + Pin->Direction == EGPD_Input && + Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) + { + // Create node to set the temp enum var + UK2Node_AssignmentStatement* AssignNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); + AssignNode->AllocateDefaultPins(); + + // Move connections from fake 'enum exec' pint to this assignment node + CompilerContext.MovePinLinksToIntermediate(*Pin, *AssignNode->GetExecPin()); + + // Connect this to out temp enum var + Schema->TryCreateConnection(AssignNode->GetVariablePin(), TempEnumVarOutput); + + // Connect exec output to 'real' exec pin + Schema->TryCreateConnection(AssignNode->GetThenPin(), ExecutePin); + + // set the literal enum value to set to + AssignNode->GetValuePin()->DefaultValue = Pin->PinName.ToString(); + + // Finally remove this 'cosmetic' exec pin + Pins[PinIdx]->MarkPendingKill(); + Pins.RemoveAt(PinIdx); + } } } - } - // Expanded as output execs pins - else if (EnumParamPin->Direction == EGPD_Output) - { - // Create normal exec output - UEdGraphPin* ExecutePin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute); - - // Create a SwitchEnum node to switch on the output enum - UK2Node_SwitchEnum* SwitchEnumNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); - UEnum* EnumObject = Cast(EnumParamPin->PinType.PinSubCategoryObject.Get()); - SwitchEnumNode->SetEnum(EnumObject); - SwitchEnumNode->AllocateDefaultPins(); - - // Hook up execution to the switch node - Schema->TryCreateConnection(ExecutePin, SwitchEnumNode->GetExecPin()); - // Connect (hidden) enum pin to switch node's selection pin - Schema->TryCreateConnection(EnumParamPin, SwitchEnumNode->GetSelectionPin()); - - // Now we want to iterate over other exec outputs - for(int32 PinIdx=Pins.Num()-1; PinIdx>=0; PinIdx--) + // Expanded as output execs pins + else if (EnumParamPin->Direction == EGPD_Output) { - UEdGraphPin* Pin = Pins[PinIdx]; - if( Pin != NULL && - Pin != ExecutePin && - Pin->Direction == EGPD_Output && - Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec ) + if (!OutMainExecutePin) { - // Move connections from fake 'enum exec' pin to this switch node - CompilerContext.MovePinLinksToIntermediate(*Pin, *SwitchEnumNode->FindPinChecked(Pin->PinName)); - - // Finally remove this 'cosmetic' exec pin - Pins[PinIdx]->MarkPendingKill(); - Pins.RemoveAt(PinIdx); + // Create normal exec output -- only once though. + OutMainExecutePin = CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then); + } + else + { + // set up a sequence so we can call one after another. + if (!SpawnedSequenceNode) + { + SpawnedSequenceNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); + SpawnedSequenceNode->AllocateDefaultPins(); + CompilerContext.MovePinLinksToIntermediate(*OutMainExecutePin, *SpawnedSequenceNode->GetThenPinGivenIndex(OutSequenceIndex++)); + Schema->TryCreateConnection(OutMainExecutePin, SpawnedSequenceNode->Pins[0]); + } + } + + // Create a SwitchEnum node to switch on the output enum + UK2Node_SwitchEnum* SwitchEnumNode = CompilerContext.SpawnIntermediateNode(this, SourceGraph); + UEnum* EnumObject = Cast(EnumParamPin->PinType.PinSubCategoryObject.Get()); + SwitchEnumNode->SetEnum(EnumObject); + SwitchEnumNode->AllocateDefaultPins(); + + // Hook up execution to the switch node + if (!SpawnedSequenceNode) + { + Schema->TryCreateConnection(OutMainExecutePin, SwitchEnumNode->GetExecPin()); + } + else + { + UEdGraphPin* SequenceOutput = SpawnedSequenceNode->GetThenPinGivenIndex(OutSequenceIndex); + + if (!SequenceOutput) + { + SpawnedSequenceNode->AddInputPin(); + SequenceOutput = SpawnedSequenceNode->GetThenPinGivenIndex(OutSequenceIndex); + } + + Schema->TryCreateConnection(SequenceOutput, SwitchEnumNode->GetExecPin()); + OutSequenceIndex++; + } + // Connect (hidden) enum pin to switch node's selection pin + Schema->TryCreateConnection(EnumParamPin, SwitchEnumNode->GetSelectionPin()); + + // Now we want to iterate over other exec outputs corresponding to the enum. + // the first pins created are the ExpandEnumAsExecs pins, and they're all made at the same time. + for (int32 PinIdx = Enum->NumEnums() - 2; PinIdx >= 0; PinIdx--) + { + UEdGraphPin* Pin = Pins[PinIdx]; + + if (Pin && + Pin != OutMainExecutePin && + Pin->Direction == EGPD_Output && + Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) + { + if (UEdGraphPin* FoundPin = SwitchEnumNode->FindPin(Pin->PinName)) + { + if (!FoundPin->LinkedTo.Contains(Pin)) + { + // Move connections from fake 'enum exec' pin to this switch node + CompilerContext.MovePinLinksToIntermediate(*Pin, *FoundPin); + + // Finally remove this 'cosmetic' exec pin + Pins[PinIdx]->MarkPendingKill(); + Pins.RemoveAt(PinIdx); + } + } + // Have passed the relevant entries... no more work to do here. + else + { + break; + } + } } } } diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ConstructObjectFromClass.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ConstructObjectFromClass.cpp index a435350c13ef..521547d5904f 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ConstructObjectFromClass.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_ConstructObjectFromClass.cpp @@ -398,4 +398,9 @@ bool UK2Node_ConstructObjectFromClass::HasExternalDependencies(TArrayDirection == EGPD_Output)) { - PotentialPin->PinName = GetPinNameGivenIndex(Idx); + PotentialPin->PinName = GetPinNameGivenIndex(ThenIndex); + ++ThenIndex; } } } diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MacroInstance.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MacroInstance.cpp index 2b60be30e828..bb39a446138c 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MacroInstance.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MacroInstance.cpp @@ -10,6 +10,7 @@ #include "Editor.h" #include "EditorCategoryUtils.h" #include "BlueprintActionFilter.h" +#include "Classes/EditorStyleSettings.h" #define LOCTEXT_NAMESPACE "K2Node_MacroInstance" @@ -177,6 +178,10 @@ FText UK2Node_MacroInstance::GetNodeTitle(ENodeTitleType::Type TitleType) const if (MacroGraph) { Result = FText::FromString(MacroGraph->GetName()); + if ((GEditor != NULL) && (GetDefault()->bShowFriendlyNames)) + { + Result = FText::FromString(FName::NameToDisplayString(Result.ToString(), false)); + } } return Result; diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeContainer.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeContainer.cpp index a3ad19a182d7..c76ca9510c33 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeContainer.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_MakeContainer.cpp @@ -344,7 +344,8 @@ void UK2Node_MakeContainer::PropagatePinType() } // Verify that all previous connections to this pin are still valid with the new type - for (UEdGraphPin* ConnectedPin : CurrentPin->LinkedTo) + TArray LinkedToCopy = CurrentPin->LinkedTo; + for (UEdGraphPin* ConnectedPin : LinkedToCopy) { if (!Schema->ArePinsCompatible(CurrentPin, ConnectedPin, CallingContext)) { diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_VariableGet.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_VariableGet.cpp index ebb0af78973f..4de6fb5af6a4 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_VariableGet.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_VariableGet.cpp @@ -189,7 +189,6 @@ FText UK2Node_VariableGet::GetPropertyTooltip(UProperty const* VariableProperty) UClass* SourceClass = VariableProperty->GetOwnerClass(); // discover if the variable property is a non blueprint user variable bool const bIsNativeVariable = (SourceClass != nullptr) && (SourceClass->ClassGeneratedBy == nullptr); - FName const TooltipMetaKey(TEXT("tooltip")); FText SubTooltip; if (bIsNativeVariable) @@ -199,7 +198,7 @@ FText UK2Node_VariableGet::GetPropertyTooltip(UProperty const* VariableProperty) { // See if the native property has a tooltip SubTooltip = PropertyTooltip; - FString TooltipName = FString::Printf(TEXT("%s.%s"), *VarName.ToString(), *TooltipMetaKey.ToString()); + FString TooltipName = FString::Printf(TEXT("%s.%s"), *VarName.ToString(), *FBlueprintMetadata::MD_Tooltip.ToString()); FText::FindText(*VariableProperty->GetFullGroupName(true), *TooltipName, SubTooltip); } } @@ -208,7 +207,7 @@ FText UK2Node_VariableGet::GetPropertyTooltip(UProperty const* VariableProperty) if (UBlueprint* VarBlueprint = Cast(SourceClass->ClassGeneratedBy)) { FString UserTooltipData; - if (FBlueprintEditorUtils::GetBlueprintVariableMetaData(VarBlueprint, VarName, VariableProperty->GetOwnerStruct(), TooltipMetaKey, UserTooltipData)) + if (FBlueprintEditorUtils::GetBlueprintVariableMetaData(VarBlueprint, VarName, VariableProperty->GetOwnerStruct(), FBlueprintMetadata::MD_Tooltip, UserTooltipData)) { SubTooltip = FText::FromString(UserTooltipData); } @@ -229,13 +228,12 @@ FText UK2Node_VariableGet::GetPropertyTooltip(UProperty const* VariableProperty) FText UK2Node_VariableGet::GetBlueprintVarTooltip(FBPVariableDescription const& VarDesc) { - FName const TooltipMetaKey(TEXT("tooltip")); - int32 const MetaIndex = VarDesc.FindMetaDataEntryIndexForKey(TooltipMetaKey); + int32 const MetaIndex = VarDesc.FindMetaDataEntryIndexForKey(FBlueprintMetadata::MD_Tooltip); bool const bHasTooltipData = (MetaIndex != INDEX_NONE); if (bHasTooltipData) { - FString UserTooltipData = VarDesc.GetMetaData(TooltipMetaKey); + FString UserTooltipData = VarDesc.GetMetaData(FBlueprintMetadata::MD_Tooltip); FFormatNamedArguments Args; Args.Add(TEXT("VarName"), FText::FromName(VarDesc.VarName)); diff --git a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_VariableSet.cpp b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_VariableSet.cpp index 08b8644965a1..6dc5ee9f1bb9 100644 --- a/Engine/Source/Editor/BlueprintGraph/Private/K2Node_VariableSet.cpp +++ b/Engine/Source/Editor/BlueprintGraph/Private/K2Node_VariableSet.cpp @@ -152,7 +152,6 @@ FText UK2Node_VariableSet::GetPropertyTooltip(UProperty const* VariableProperty) UClass* SourceClass = VariableProperty->GetOwnerClass(); // discover if the variable property is a non blueprint user variable bool const bIsNativeVariable = (SourceClass != nullptr) && (SourceClass->ClassGeneratedBy == nullptr); - FName const TooltipMetaKey(TEXT("tooltip")); FText SubTooltip; if (bIsNativeVariable) @@ -162,7 +161,7 @@ FText UK2Node_VariableSet::GetPropertyTooltip(UProperty const* VariableProperty) { // See if the native property has a tooltip SubTooltip = PropertyTooltip; - FString TooltipName = FString::Printf(TEXT("%s.%s"), *VarName.ToString(), *TooltipMetaKey.ToString()); + FString TooltipName = FString::Printf(TEXT("%s.%s"), *VarName.ToString(), *FBlueprintMetadata::MD_Tooltip.ToString()); FText::FindText(*VariableProperty->GetFullGroupName(true), *TooltipName, SubTooltip); } } @@ -171,7 +170,7 @@ FText UK2Node_VariableSet::GetPropertyTooltip(UProperty const* VariableProperty) if (UBlueprint* VarBlueprint = Cast(SourceClass->ClassGeneratedBy)) { FString UserTooltipData; - if (FBlueprintEditorUtils::GetBlueprintVariableMetaData(VarBlueprint, VarName, VariableProperty->GetOwnerStruct(), TooltipMetaKey, UserTooltipData)) + if (FBlueprintEditorUtils::GetBlueprintVariableMetaData(VarBlueprint, VarName, VariableProperty->GetOwnerStruct(), FBlueprintMetadata::MD_Tooltip, UserTooltipData)) { SubTooltip = FText::FromString(UserTooltipData); } @@ -205,13 +204,12 @@ FText UK2Node_VariableSet::GetPropertyTooltip(UProperty const* VariableProperty) FText UK2Node_VariableSet::GetBlueprintVarTooltip(FBPVariableDescription const& VarDesc) { - FName const TooltipMetaKey(TEXT("tooltip")); - int32 const MetaIndex = VarDesc.FindMetaDataEntryIndexForKey(TooltipMetaKey); + int32 const MetaIndex = VarDesc.FindMetaDataEntryIndexForKey(FBlueprintMetadata::MD_Tooltip); bool const bHasTooltipData = (MetaIndex != INDEX_NONE); if (bHasTooltipData) { - FString UserTooltipData = VarDesc.GetMetaData(TooltipMetaKey); + FString UserTooltipData = VarDesc.GetMetaData(FBlueprintMetadata::MD_Tooltip); FFormatNamedArguments Args; Args.Add(TEXT("VarName"), FText::FromName(VarDesc.VarName)); diff --git a/Engine/Source/Editor/BlueprintGraph/Public/CallFunctionHandler.h b/Engine/Source/Editor/BlueprintGraph/Public/CallFunctionHandler.h index e96667167599..3f78fb030c06 100644 --- a/Engine/Source/Editor/BlueprintGraph/Public/CallFunctionHandler.h +++ b/Engine/Source/Editor/BlueprintGraph/Public/CallFunctionHandler.h @@ -55,7 +55,7 @@ public: private: // Get the name of the function to call from the node - virtual FString GetFunctionNameFromNode(UEdGraphNode* Node) const; + virtual FName GetFunctionNameFromNode(UEdGraphNode* Node) const; UClass* GetCallingContext(FKismetFunctionContext& Context, UEdGraphNode* Node); UClass* GetTrueCallingClass(FKismetFunctionContext& Context, UEdGraphPin* SelfPin); diff --git a/Engine/Source/Editor/Blutility/Private/BlutilityMenuExtensions.cpp b/Engine/Source/Editor/Blutility/Private/BlutilityMenuExtensions.cpp index db42308551ab..8c04490a9a14 100644 --- a/Engine/Source/Editor/Blutility/Private/BlutilityMenuExtensions.cpp +++ b/Engine/Source/Editor/Blutility/Private/BlutilityMenuExtensions.cpp @@ -279,6 +279,7 @@ void FBlutilityMenuExtensions::CreateBlutilityActionsMenu(FMenuBuilder& MenuBuil { // We dont run this on the CDO, as bad things could occur! UObject* TempObject = NewObject(GetTransientPackage(), FunctionAndUtil.Util->GetClass()); + TempObject->AddToRoot(); // Some Blutility actions might run GC so the TempObject needs to be rooted to avoid getting destroyed if(FunctionAndUtil.Function->NumParms > 0) { @@ -321,6 +322,8 @@ void FBlutilityMenuExtensions::CreateBlutilityActionsMenu(FMenuBuilder& MenuBuil FEditorScriptExecutionGuard ScriptGuard; TempObject->ProcessEvent(FunctionAndUtil.Function, nullptr); } + + TempObject->RemoveFromRoot(); } })); } diff --git a/Engine/Source/Editor/ContentBrowser/Private/ContentBrowserUtils.cpp b/Engine/Source/Editor/ContentBrowser/Private/ContentBrowserUtils.cpp index 3aeb2daea9c8..dac0cea3ba65 100644 --- a/Engine/Source/Editor/ContentBrowser/Private/ContentBrowserUtils.cpp +++ b/Engine/Source/Editor/ContentBrowser/Private/ContentBrowserUtils.cpp @@ -1790,19 +1790,31 @@ int32 ContentBrowserUtils::GetPackageLengthForCooking(const FString& PackageName // We use "WindowsNoEditor" below as it's the longest platform name, so will also prove that any shorter platform names will validate correctly const FString AbsoluteRootPath = FPaths::ConvertRelativePathToFull(FPaths::RootDir()); const FString AbsoluteGamePath = FPaths::ConvertRelativePathToFull(FPaths::ProjectDir()); - const FString AbsoluteCookPath = AbsoluteGamePath / TEXT("Saved") / TEXT("Cooked") / TEXT("WindowsNoEditor") / GameName; + const FString AbsoluteEnginePath = FPaths::ConvertRelativePathToFull(FPaths::EngineDir()); + const FString AbsoluteEngineCookPath = AbsoluteGamePath / TEXT("Saved") / TEXT("Cooked") / TEXT("WindowsNoEditor") / TEXT("Engine"); + const FString AbsoluteGameCookPath = AbsoluteGamePath / TEXT("Saved") / TEXT("Cooked") / TEXT("WindowsNoEditor") / GameName; + + EPluginLoadedFrom PluginLoadedFrom; + const bool bIsPluginAsset = ContentBrowserUtils::IsPluginFolder(PackageName, &PluginLoadedFrom); + const bool bIsEngineAsset = ContentBrowserUtils::IsEngineFolder(PackageName) || (bIsPluginAsset && PluginLoadedFrom == EPluginLoadedFrom::Engine); + const bool bIsProjectAsset = !bIsEngineAsset; int32 AbsoluteCookPathToAssetLength = 0; FString RelativePathToAsset; + const FString AbsolutePath = bIsEngineAsset ? AbsoluteEnginePath : AbsoluteGamePath; + + const FString& AbsoluteCookPath = bIsEngineAsset ? AbsoluteEngineCookPath : AbsoluteGameCookPath; + if(FPackageName::TryConvertLongPackageNameToFilename(PackageName, RelativePathToAsset, FPackageName::GetAssetPackageExtension())) { const FString AbsolutePathToAsset = FPaths::ConvertRelativePathToFull(RelativePathToAsset); FString AssetPathWithinCookDir = AbsolutePathToAsset; FPaths::RemoveDuplicateSlashes(AssetPathWithinCookDir); - AssetPathWithinCookDir.RemoveFromStart(AbsoluteGamePath, ESearchCase::CaseSensitive); + AssetPathWithinCookDir.RemoveFromStart(AbsolutePath, ESearchCase::CaseSensitive); + if (IsInternalBuild) { @@ -1815,11 +1827,16 @@ int32 ContentBrowserUtils::GetPackageLengthForCooking(const FString& PackageName } else { - CookDirWithoutBasePath.RemoveFromStart(AbsoluteGamePath, ESearchCase::CaseSensitive); + CookDirWithoutBasePath.RemoveFromStart(AbsoluteCookPath, ESearchCase::CaseSensitive); } - + FString AbsoluteBuildMachineCookPathToAsset = FString(TEXT("D:/BuildFarm/buildmachine_++depot+UE4-Releases+4.10")) / CookDirWithoutBasePath / AssetPathWithinCookDir; - AbsoluteBuildMachineCookPathToAsset.ReplaceInline(*GameName, *GameNamePadded, ESearchCase::CaseSensitive); + + // only add game name padding if it is not an engine asset, otherwise it is considered portable already + if(!bIsEngineAsset) + { + AbsoluteBuildMachineCookPathToAsset.ReplaceInline(*GameName, *GameNamePadded, ESearchCase::CaseSensitive); + } AbsoluteCookPathToAssetLength = AbsoluteBuildMachineCookPathToAsset.Len(); } @@ -1827,7 +1844,12 @@ int32 ContentBrowserUtils::GetPackageLengthForCooking(const FString& PackageName { // Test that the package can be cooked based on the current project path FString AbsoluteCookPathToAsset = AbsoluteCookPath / AssetPathWithinCookDir; - AbsoluteCookPathToAsset.ReplaceInline(*GameName, *GameNamePadded, ESearchCase::CaseSensitive); + + // only add game name padding if it is not an engine asset, otherwise it is considered portable already + if (!bIsEngineAsset) + { + AbsoluteCookPathToAsset.ReplaceInline(*GameName, *GameNamePadded, ESearchCase::CaseSensitive); + } AbsoluteCookPathToAssetLength = AbsoluteCookPathToAsset.Len(); } diff --git a/Engine/Source/Editor/DetailCustomizations/Private/InputStructCustomization.cpp b/Engine/Source/Editor/DetailCustomizations/Private/InputStructCustomization.cpp index aa8868b7a5f1..31dad357615c 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/InputStructCustomization.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/InputStructCustomization.cpp @@ -64,8 +64,10 @@ void FInputActionMappingCustomization::CustomizeChildren( TSharedRef AltHandle = InStructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FInputActionKeyMapping, bAlt)); TSharedPtr CmdHandle = InStructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FInputActionKeyMapping, bCmd)); - TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateSP(this, &FInputActionMappingCustomization::RemoveActionMappingButton_OnClick), - LOCTEXT("RemoveActionMappingToolTip", "Removes Action Mapping")); + const TSharedPtr ParentArrayHandle = InStructPropertyHandle->GetParentHandle()->AsArray(); + + TSharedRef RemoveButton = (ParentArrayHandle.IsValid() ? PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateSP(this, &FInputActionMappingCustomization::RemoveActionMappingButton_OnClick), + LOCTEXT("RemoveActionMappingToolTip", "Removes Action Mapping")) : SNullWidget::NullWidget); StructBuilder.AddCustomRow( LOCTEXT("KeySearchStr", "Key") ) [ @@ -185,8 +187,10 @@ void FInputAxisMappingCustomization::CustomizeChildren( TSharedRef KeyHandle = InStructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FInputAxisKeyMapping, Key)); TSharedPtr ScaleHandle = InStructPropertyHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FInputAxisKeyMapping, Scale)); - TSharedRef RemoveButton = PropertyCustomizationHelpers::MakeDeleteButton( FSimpleDelegate::CreateSP( this, &FInputAxisMappingCustomization::RemoveAxisMappingButton_OnClick), - LOCTEXT("RemoveAxisMappingToolTip", "Removes Axis Mapping") ); + const TSharedPtr ParentArrayHandle = InStructPropertyHandle->GetParentHandle()->AsArray(); + + TSharedRef RemoveButton = (ParentArrayHandle.IsValid() ? PropertyCustomizationHelpers::MakeDeleteButton(FSimpleDelegate::CreateSP(this, &FInputAxisMappingCustomization::RemoveAxisMappingButton_OnClick), + LOCTEXT("RemoveAxisMappingToolTip", "Removes Axis Mapping")) : SNullWidget::NullWidget); StructBuilder.AddCustomRow( LOCTEXT("KeySearchStr", "Key") ) [ diff --git a/Engine/Source/Editor/DetailCustomizations/Private/MathStructProxyCustomizations.cpp b/Engine/Source/Editor/DetailCustomizations/Private/MathStructProxyCustomizations.cpp index 11803a23df66..a26c9df9df8c 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/MathStructProxyCustomizations.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/MathStructProxyCustomizations.cpp @@ -688,7 +688,11 @@ bool FQuatStructCustomization::FlushValues(TWeakPtr PropertyHan const FQuat NewValue = Rotation.Quaternion(); - if (!bNotifiedPreChange && (!QuatValue->Equals(NewValue, 0.0f) || (!bIsUsingSlider && bIsInteractiveChangeInProgress))) + // In some cases the FQuat pointed to in RawData is no longer aligned to 16 bytes. + // Make a local copy to guarantee the alignment criterions of the vector intrinsics inside FQuat::Equals + const FQuat AlignedQuatValue = *QuatValue; + + if (!bNotifiedPreChange && (!AlignedQuatValue.Equals(NewValue, 0.0f) || (!bIsUsingSlider && bIsInteractiveChangeInProgress))) { if (!bIsInteractiveChangeInProgress) { diff --git a/Engine/Source/Editor/DetailCustomizations/Private/SoftObjectPathCustomization.h b/Engine/Source/Editor/DetailCustomizations/Private/SoftObjectPathCustomization.h index fae165182e02..7bf2733486d7 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/SoftObjectPathCustomization.h +++ b/Engine/Source/Editor/DetailCustomizations/Private/SoftObjectPathCustomization.h @@ -8,7 +8,7 @@ class IPropertyHandle; /** - * Customizes a string asset reference to look like a UObject property + * Customizes a soft object path to look like a UObject property */ class FSoftObjectPathCustomization : public IPropertyTypeCustomization { diff --git a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp index 4a1968899d72..4327b5cfcf33 100644 --- a/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp +++ b/Engine/Source/Editor/EditorStyle/Private/SlateEditorStyle.cpp @@ -520,7 +520,8 @@ void FSlateEditorStyle::FStyle::SetupGeneralStyles() .SetCheckedPressedImage( IMAGE_BRUSH( "Common/CheckBox_Checked", Icon16x16 ) ) .SetUndeterminedImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined", Icon16x16 ) ) .SetUndeterminedHoveredImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined_Hovered", Icon16x16 ) ) - .SetUndeterminedPressedImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined_Hovered", Icon16x16, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ); + .SetUndeterminedPressedImage( IMAGE_BRUSH( "Common/CheckBox_Undetermined_Hovered", Icon16x16, FLinearColor( 0.5f, 0.5f, 0.5f ) ) ) + .SetPadding(1.0f); /* ... and set new style */ Set( "CheckboxLookToggleButtonCheckbox", CheckboxLookingToggleButtonStyle ); @@ -5556,8 +5557,8 @@ void FSlateEditorStyle::FStyle::SetupPersonaStyle() Set( "Kismet.VariableList.SetTypeIconLarge", new IMAGE_BRUSH( "/Icons/pillset_40x", Icon40x40 ) ); Set( "Kismet.VariableList.MapValueTypeIcon", new IMAGE_BRUSH( "/Icons/pillmapvalue_16x", Icon16x16 ) ); Set( "Kismet.VariableList.MapKeyTypeIcon", new IMAGE_BRUSH( "/Icons/pillmapkey_16x", Icon16x16 ) ); - Set( "Kismet.VariableList.ExposeForInstance", new IMAGE_BRUSH( "/Icons/icon_layer_visible", Icon16x16 ) ); - Set( "Kismet.VariableList.HideForInstance", new IMAGE_BRUSH( "/Icons/icon_layer_not_visible", Icon16x16 ) ); + Set( "Kismet.VariableList.ExposeForInstance", new IMAGE_BRUSH( "/Icons/icon_layer_visible_16x", Icon16x16 ) ); + Set( "Kismet.VariableList.HideForInstance", new IMAGE_BRUSH( "/Icons/icon_layer_not_visible_16x", Icon16x16 ) ); Set( "Kismet.VariableList.VariableIsUsed", new IMAGE_BRUSH( "/Icons/icon_variable_used_16x", Icon16x16 ) ); Set( "Kismet.VariableList.VariableNotUsed", new IMAGE_BRUSH( "/Icons/icon_variable_not_used_16x", Icon16x16 ) ); diff --git a/Engine/Source/Editor/GraphEditor/Private/GraphEditorActions.cpp b/Engine/Source/Editor/GraphEditor/Private/GraphEditorActions.cpp index af103b210df8..8e45ef8dc449 100644 --- a/Engine/Source/Editor/GraphEditor/Private/GraphEditorActions.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/GraphEditorActions.cpp @@ -43,14 +43,14 @@ void FGraphEditorCommandsImpl::RegisterCommands() UI_COMMAND( CollapseSelectionToFunction, "Collapse to Function", "Collapses selected nodes into a single function node.", EUserInterfaceActionType::Button, FInputChord() ) UI_COMMAND( CollapseSelectionToMacro, "Collapse to Macro", "Collapses selected nodes into a single macro node.", EUserInterfaceActionType::Button, FInputChord() ) - UI_COMMAND( AlignNodesTop, "Align Top", "Aligns the top edges of the selected nodes", EUserInterfaceActionType::Button, FInputChord() ) - UI_COMMAND( AlignNodesMiddle, "Align Middle", "Aligns the vertical middles of the selected nodes", EUserInterfaceActionType::Button, FInputChord() ) - UI_COMMAND( AlignNodesBottom, "Align Bottom", "Aligns the bottom edges of the selected nodes", EUserInterfaceActionType::Button, FInputChord() ) - UI_COMMAND( AlignNodesLeft, "Align Left", "Aligns the left edges of the selected nodes", EUserInterfaceActionType::Button, FInputChord() ) - UI_COMMAND( AlignNodesCenter, "Align Center", "Aligns the horizontal centers of the selected nodes", EUserInterfaceActionType::Button, FInputChord() ) - UI_COMMAND( AlignNodesRight, "Align Right", "Aligns the right edges of the selected nodes", EUserInterfaceActionType::Button, FInputChord() ) + UI_COMMAND( AlignNodesTop, "Align Top", "Aligns the top edges of the selected nodes", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift, EKeys::W) ) + UI_COMMAND( AlignNodesMiddle, "Align Middle", "Aligns the vertical middles of the selected nodes", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift|EModifierKey::Alt, EKeys::W) ) + UI_COMMAND( AlignNodesBottom, "Align Bottom", "Aligns the bottom edges of the selected nodes", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift, EKeys::S) ) + UI_COMMAND( AlignNodesLeft, "Align Left", "Aligns the left edges of the selected nodes", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift, EKeys::A) ) + UI_COMMAND( AlignNodesCenter, "Align Center", "Aligns the horizontal centers of the selected nodes", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift | EModifierKey::Alt, EKeys::S) ) + UI_COMMAND( AlignNodesRight, "Align Right", "Aligns the right edges of the selected nodes", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift, EKeys::D) ) - UI_COMMAND( StraightenConnections, "Straighten Connection(s)", "Straightens connections between the selected nodes.", EUserInterfaceActionType::Button, FInputChord() ) + UI_COMMAND( StraightenConnections, "Straighten Connection(s)", "Straightens connections between the selected nodes.", EUserInterfaceActionType::Button, FInputChord(EKeys::Q) ) UI_COMMAND( DistributeNodesHorizontally, "Distribute Horizontally", "Evenly distributes the selected nodes horizontally", EUserInterfaceActionType::Button, FInputChord() ) UI_COMMAND( DistributeNodesVertically, "Distribute Vertically", "Evenly distributes the selected nodes vertically", EUserInterfaceActionType::Button, FInputChord() ) diff --git a/Engine/Source/Editor/GraphEditor/Private/KismetNodes/SGraphNodeK2CreateDelegate.cpp b/Engine/Source/Editor/GraphEditor/Private/KismetNodes/SGraphNodeK2CreateDelegate.cpp index 9f9be2263e08..184e006ffa57 100644 --- a/Engine/Source/Editor/GraphEditor/Private/KismetNodes/SGraphNodeK2CreateDelegate.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/KismetNodes/SGraphNodeK2CreateDelegate.cpp @@ -5,6 +5,7 @@ #include "Widgets/Views/STableViewBase.h" #include "Widgets/Views/STableRow.h" #include "Widgets/Views/SListView.h" +#include "Widgets/SBoxPanel.h" #include "EdGraphSchema_K2.h" #include "K2Node_CreateDelegate.h" @@ -225,7 +226,7 @@ void SGraphNodeK2CreateDelegate::CreateBelowPinControls(TSharedPtr } TSharedRef SelectFunctionWidgetRef = SNew(SComboButton) - .Method(EPopupMethod::UseCurrentWindow) + .Method(EPopupMethod::CreateNewWindow) .ButtonContent() [ SNew(STextBlock) @@ -233,10 +234,16 @@ void SGraphNodeK2CreateDelegate::CreateBelowPinControls(TSharedPtr ] .MenuContent() [ - SNew(SListView >) - .ListItemsSource(&FunctionDataItems) - .OnGenerateRow(this, &SGraphNodeK2CreateDelegate::HandleGenerateRowFunction) - .OnSelectionChanged(this, &SGraphNodeK2CreateDelegate::OnFunctionSelected) + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .MaxHeight(500.f) + [ + SNew(SListView >) + .ListItemsSource(&FunctionDataItems) + .OnGenerateRow(this, &SGraphNodeK2CreateDelegate::HandleGenerateRowFunction) + .OnSelectionChanged(this, &SGraphNodeK2CreateDelegate::OnFunctionSelected) + ] ]; MainBox->AddSlot() diff --git a/Engine/Source/Editor/GraphEditor/Private/KismetPins/SGraphPinObject.cpp b/Engine/Source/Editor/GraphEditor/Private/KismetPins/SGraphPinObject.cpp index ea579a69f34d..5caadbf690d6 100644 --- a/Engine/Source/Editor/GraphEditor/Private/KismetPins/SGraphPinObject.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/KismetPins/SGraphPinObject.cpp @@ -40,9 +40,21 @@ void SGraphPinObject::Construct(const FArguments& InArgs, UEdGraphPin* InGraphPi TSharedRef SGraphPinObject::GetDefaultValueWidget() { + if (GraphPinObj == nullptr) + { + return SNullWidget::NullWidget; + } + + const UEdGraphSchema* Schema = GraphPinObj->GetSchema(); + + if (Schema == nullptr) + { + return SNullWidget::NullWidget; + } + if( AllowSelfPinWidget() ) { - if(GraphPinObj->GetSchema()->IsSelfPin(*GraphPinObj)) + if(Schema->IsSelfPin(*GraphPinObj)) { return SNew(SEditableTextBox) .Style( FEditorStyle::Get(), "Graph.EditableTextBox" ) @@ -54,7 +66,7 @@ TSharedRef SGraphPinObject::GetDefaultValueWidget() } } // Don't show literal buttons for component type objects - if (GraphPinObj->GetSchema()->ShouldShowAssetPickerForPin(GraphPinObj)) + if (Schema->ShouldShowAssetPickerForPin(GraphPinObj)) { return SNew(SHorizontalBox) diff --git a/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp b/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp index e707e83c0c1c..80f1959d616b 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SCommentBubble.cpp @@ -108,13 +108,7 @@ void SCommentBubble::Tick( const FGeometry& AllottedGeometry, const double InCur { CachedComment = CommentAttribute.Get(); CachedCommentText = FText::FromString( CachedComment ); - // Call text commit delegate - OnTextCommittedDelegate.ExecuteIfBound( CachedCommentText, ETextCommit::Default ); - // Reflect changes to the Textblock because it doesn't update itself. - if( TextBlock.IsValid() ) - { - TextBlock->SetText( CachedCommentText ); - } + // Toggle the comment on/off, provided it the parent isn't a comment node if( !bInvertLODCulling ) { @@ -252,7 +246,7 @@ void SCommentBubble::UpdateBubble() .AutoWidth() [ SAssignNew(TextBlock, SMultiLineEditableTextBox) - .Text( CachedCommentText ) + .Text(MakeAttributeLambda([this] { return CachedCommentText; })) .HintText( NSLOCTEXT( "CommentBubble", "EditCommentHint", "Click to edit" )) .IsReadOnly( this, &SCommentBubble::IsReadOnly ) .Font( FEditorStyle::GetFontStyle( TEXT("Graph.Node.CommentFont"))) @@ -412,9 +406,8 @@ FSlateColor SCommentBubble::GetTextForegroundColor() const void SCommentBubble::OnCommentTextCommitted( const FText& NewText, ETextCommit::Type CommitInfo ) { - if (CommitInfo != ETextCommit::OnEnter) + if (CommitInfo == ETextCommit::OnEnter || CommitInfo == ETextCommit::OnUserMovedFocus) { - // Don't respond to OnEnter, as it will be immediately followed by OnCleared anyway (due to loss of keyboard focus) and generate a second transaction CachedComment = NewText.ToString(); CachedCommentText = NewText; OnTextCommittedDelegate.ExecuteIfBound(CachedCommentText, CommitInfo); diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphActionMenu.cpp b/Engine/Source/Editor/GraphEditor/Private/SGraphActionMenu.cpp index 406c57c2f7ea..d87635220229 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphActionMenu.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphActionMenu.cpp @@ -1279,38 +1279,44 @@ FReply SGraphActionMenu::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent else if (!FilterTextBox->GetText().IsEmpty()) { // Needs to be done here in order not to eat up the text navigation key events when list isn't populated - if (FilteredActionNodes.Num() <= 0) + if (FilteredActionNodes.Num() == 0) { return FReply::Unhandled(); } - // Up and down move thru the filtered node list if (KeyEvent.GetKey() == EKeys::Up) { - SelectionDelta = -1; + SelectedSuggestion = FMath::Max(0, SelectedSuggestion - 1); } else if (KeyEvent.GetKey() == EKeys::Down) { - SelectionDelta = +1; + SelectedSuggestion = FMath::Min(FilteredActionNodes.Num() - 1, SelectedSuggestion + 1); } - - if (SelectionDelta != 0) + else if (KeyEvent.GetKey() == EKeys::PageUp) { - // If we have no selected suggestion then we need to use the items in the root to set the selection and set the focus - if( SelectedSuggestion == INDEX_NONE ) - { - SelectedSuggestion = (SelectedSuggestion + SelectionDelta + FilteredRootAction->Children.Num()) % FilteredRootAction->Children.Num(); - MarkActiveSuggestion(); - return FReply::Handled(); - } - - //Move up or down one, wrapping around - SelectedSuggestion = (SelectedSuggestion + SelectionDelta + FilteredActionNodes.Num()) % FilteredActionNodes.Num(); - - MarkActiveSuggestion(); - - return FReply::Handled(); + const int32 NumItemsInAPage = 15; // arbitrary jump because we can't get at the visible item count from here + SelectedSuggestion = FMath::Max(0, SelectedSuggestion - NumItemsInAPage); } + else if (KeyEvent.GetKey() == EKeys::PageDown) + { + const int32 NumItemsInAPage = 15; // arbitrary jump because we can't get at the visible item count from here + SelectedSuggestion = FMath::Min(FilteredActionNodes.Num() - 1, SelectedSuggestion + NumItemsInAPage); + } + else if (KeyEvent.GetKey() == EKeys::Home) + { + SelectedSuggestion = 0; + } + else if (KeyEvent.GetKey() == EKeys::End) + { + SelectedSuggestion = FilteredActionNodes.Num() - 1; + } + else + { + return FReply::Unhandled(); + } + + MarkActiveSuggestion(); + return FReply::Handled(); } else { diff --git a/Engine/Source/Editor/GraphEditor/Private/SGraphPanel.cpp b/Engine/Source/Editor/GraphEditor/Private/SGraphPanel.cpp index 3792c773e568..86a358403968 100644 --- a/Engine/Source/Editor/GraphEditor/Private/SGraphPanel.cpp +++ b/Engine/Source/Editor/GraphEditor/Private/SGraphPanel.cpp @@ -624,7 +624,7 @@ FReply SGraphPanel::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointe FReply SGraphPanel::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) { - if ((MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) && (MouseEvent.IsShiftDown())) + if (!NodeUnderMousePtr.IsValid() && !Marquee.IsValid() && (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) && (MouseEvent.IsShiftDown())) { if (SGraphPin* BestPinFromHoveredSpline = GetBestPinFromHoveredSpline()) { diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintCompilationManager.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintCompilationManager.cpp index 4ba79f13c89a..7fb61b588e3b 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintCompilationManager.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintCompilationManager.cpp @@ -489,7 +489,7 @@ void FBlueprintCompilationManagerImpl::FlushCompilationQueueImpl(TArrayIsGeneratedClassAuthoritative() && (QueuedBP->GeneratedClass != nullptr)) + if (QueuedBP->GeneratedClass != nullptr) { // set bIsRegeneratingOnLoad so that we don't reset loaders: QueuedBP->bIsRegeneratingOnLoad = true; diff --git a/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp b/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp index e19a966612a2..9f7aa2f7093e 100644 --- a/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp +++ b/Engine/Source/Editor/Kismet/Private/BlueprintDetailsCustomization.cpp @@ -2783,7 +2783,7 @@ void FBlueprintGraphArgumentLayout::GenerateChildContent( IDetailChildrenBuilder [ SNew(STextBlock) .Text(LOCTEXT("FunctionArgDetailsPassByReference", "Pass-by-Reference")) - .ToolTipText(LOCTEXT("FunctionArgDetailsPassByReferenceTooltip", "Pass this paremeter by reference?")) + .ToolTipText(LOCTEXT("FunctionArgDetailsPassByReferenceTooltip", "Pass this parameter by reference?")) .Font(IDetailLayoutBuilder::GetDetailFont()) ] .ValueContent() @@ -4144,7 +4144,7 @@ bool FBaseBlueprintGraphActionDetails::OnVerifyPinRename(UK2Node_EditablePinBase if (InNewName == TEXT("None")) { - OutErrorMessage = FText::Format( LOCTEXT("PinNameNone", "'None' is a reserved name"), FText::AsNumber( NAME_SIZE ) ); + OutErrorMessage = LOCTEXT("PinNameNone", "'None' is a reserved name"); return false; } diff --git a/Engine/Source/Editor/Kismet/Private/DiffUtils.cpp b/Engine/Source/Editor/Kismet/Private/DiffUtils.cpp index f3f24da6ac1d..5c8b2c9147a6 100644 --- a/Engine/Source/Editor/Kismet/Private/DiffUtils.cpp +++ b/Engine/Source/Editor/Kismet/Private/DiffUtils.cpp @@ -605,6 +605,15 @@ static void IdenticalHelper(const UProperty* AProperty, const UProperty* BProper bool DiffUtils::Identical(const FResolvedProperty& AProp, const FResolvedProperty& BProp, const FPropertySoftPath& RootPath, TArray& DifferingProperties) { + if( AProp.Property == nullptr && BProp.Property == nullptr ) + { + return true; + } + else if( AProp.Property == nullptr || BProp.Property == nullptr ) + { + return false; + } + const void* AValue = AProp.Property->ContainerPtrToValuePtr(AProp.Object); const void* BValue = BProp.Property->ContainerPtrToValuePtr(BProp.Object); diff --git a/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp b/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp index 36c174cd9582..01a4340523fb 100644 --- a/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp +++ b/Engine/Source/Editor/Kismet/Private/FindInBlueprintManager.cpp @@ -1322,6 +1322,10 @@ public: CacheParams.OnCached.ExecuteIfBound(UncachedAssets[TickCacheIndex]); } } + else + { + FailedToCacheList.Add(UncachedAssets[TickCacheIndex]); + } ++TickCacheIndex; @@ -2354,7 +2358,7 @@ void FFindInBlueprintSearchManager::FinishedCachingBlueprints(int32 InNumberCach // Update the list of cache failures FailedToCachePaths = InFailedToCacheList; - // Remove any failed attempts from the pending list (should only apply to unindexed caching, but just in case) + // Remove any failed attempts from the pending list if (InFailedToCacheList.Num() > 0) { PendingAssets = PendingAssets.Difference(InFailedToCacheList); diff --git a/Engine/Source/Editor/Kismet/Private/SCSEditorViewportClient.cpp b/Engine/Source/Editor/Kismet/Private/SCSEditorViewportClient.cpp index ff318b991fe3..307970cfe171 100644 --- a/Engine/Source/Editor/Kismet/Private/SCSEditorViewportClient.cpp +++ b/Engine/Source/Editor/Kismet/Private/SCSEditorViewportClient.cpp @@ -439,17 +439,17 @@ bool FSCSEditorViewportClient::InputWidgetDelta( FViewport* InViewport, EAxisLis if(bCanEdit) { + if (GUnrealEd->ComponentVisManager.HandleInputDelta(this, InViewport, Drag, Rot, Scale)) + { + GUnrealEd->RedrawLevelEditingViewports(); + Invalidate(); + return true; + } + USceneComponent* SceneComp = Cast(SelectedNodePtr->FindComponentInstanceInActor(PreviewActor)); USceneComponent* SelectedTemplate = Cast(SelectedNodePtr->GetEditableComponentTemplate(BlueprintEditor->GetBlueprintObj())); if(SceneComp != NULL && SelectedTemplate != NULL) { - if (GUnrealEd->ComponentVisManager.HandleInputDelta(this, InViewport, Drag, Rot, Scale)) - { - GUnrealEd->RedrawLevelEditingViewports(); - Invalidate(); - return true; - } - // Cache the current default values for propagation FVector OldRelativeLocation = SelectedTemplate->RelativeLocation; FRotator OldRelativeRotation = SelectedTemplate->RelativeRotation; diff --git a/Engine/Source/Editor/Kismet/Private/SSCSEditor.cpp b/Engine/Source/Editor/Kismet/Private/SSCSEditor.cpp index 5a019874e0ea..d569dcd51384 100644 --- a/Engine/Source/Editor/Kismet/Private/SSCSEditor.cpp +++ b/Engine/Source/Editor/Kismet/Private/SSCSEditor.cpp @@ -3073,27 +3073,36 @@ bool SSCS_RowWidget::OnNameTextVerifyChanged(const FText& InNewText, FText& OutE if (!InNewText.IsEmpty()) { - AActor* ExistingNameSearchScope = NodePtr->GetComponentTemplate()->GetOwner(); - if ((ExistingNameSearchScope == nullptr) && (Blueprint != nullptr)) + const UActorComponent* ComponentInstance = NodePtr->GetComponentTemplate(); + if (ensure(ComponentInstance)) { - ExistingNameSearchScope = Cast(Blueprint->GeneratedClass->GetDefaultObject()); - } + AActor* ExistingNameSearchScope = ComponentInstance->GetOwner(); + if ((ExistingNameSearchScope == nullptr) && (Blueprint != nullptr)) + { + ExistingNameSearchScope = Cast(Blueprint->GeneratedClass->GetDefaultObject()); + } - if (!FComponentEditorUtils::IsValidVariableNameString(NodePtr->GetComponentTemplate(), InNewText.ToString())) - { - OutErrorMessage = LOCTEXT("RenameFailed_EngineReservedName", "This name is reserved for engine use."); - return false; + if (!FComponentEditorUtils::IsValidVariableNameString(ComponentInstance, InNewText.ToString())) + { + OutErrorMessage = LOCTEXT("RenameFailed_EngineReservedName", "This name is reserved for engine use."); + return false; + } + else if (InNewText.ToString().Len() > NAME_SIZE) + { + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("CharCount"), NAME_SIZE); + OutErrorMessage = FText::Format(LOCTEXT("ComponentRenameFailed_TooLong", "Component name must be less than {CharCount} characters long."), Arguments); + return false; + } + else if (!FComponentEditorUtils::IsComponentNameAvailable(InNewText.ToString(), ExistingNameSearchScope, ComponentInstance)) + { + OutErrorMessage = LOCTEXT("RenameFailed_ExistingName", "Another component already has the same name."); + return false; + } } - else if (InNewText.ToString().Len() > NAME_SIZE) + else { - FFormatNamedArguments Arguments; - Arguments.Add(TEXT("CharCount"), NAME_SIZE); - OutErrorMessage = FText::Format(LOCTEXT("ComponentRenameFailed_TooLong", "Component name must be less than {CharCount} characters long."), Arguments); - return false; - } - else if (!FComponentEditorUtils::IsComponentNameAvailable(InNewText.ToString(), ExistingNameSearchScope, NodePtr->GetComponentTemplate())) - { - OutErrorMessage = LOCTEXT("RenameFailed_ExistingName", "Another component already has the same name."); + OutErrorMessage = LOCTEXT("RenameFailed_InvalidComponentInstance", "This node is referencing an invalid component instance and cannot be renamed. Perhaps it was destroyed?"); return false; } } @@ -4083,49 +4092,63 @@ void SSCSEditor::OnDuplicateComponent() { const FScopedTransaction Transaction(SelectedNodes.Num() > 1 ? LOCTEXT("DuplicateComponents", "Duplicate Components") : LOCTEXT("DuplicateComponent", "Duplicate Component")); + TMap DuplicateSceneComponentMap; for (int32 i = 0; i < SelectedNodes.Num(); ++i) { if (UActorComponent* ComponentTemplate = SelectedNodes[i]->GetComponentTemplate()) { UActorComponent* CloneComponent = AddNewComponent(ComponentTemplate->GetClass(), ComponentTemplate); - UActorComponent* OriginalComponent = ComponentTemplate; - - // If we've duplicated a scene component, attempt to reposition the duplicate in the hierarchy if the original - // was attached to another scene component as a child. By default, the duplicate is attached to the scene root node. - USceneComponent* NewSceneComponent = Cast(CloneComponent); - if(NewSceneComponent != NULL) + if (USceneComponent* SceneClone = Cast(CloneComponent)) { - if (EditorMode == EComponentEditorMode::BlueprintSCS) - { - // Ensure that any native attachment relationship inherited from the original copy is removed (to prevent a GLEO assertion) - NewSceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); - } + DuplicateSceneComponentMap.Add(CastChecked(ComponentTemplate), SceneClone); + } + } + } + + for (const TPair& DuplicatedPair : DuplicateSceneComponentMap) + { + USceneComponent* OriginalComponent = DuplicatedPair.Key; + USceneComponent* NewSceneComponent = DuplicatedPair.Value; + + if (EditorMode == EComponentEditorMode::BlueprintSCS) + { + // Ensure that any native attachment relationship inherited from the original copy is removed (to prevent a GLEO assertion) + NewSceneComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform); + } - // Attempt to locate the original node in the SCS tree - FSCSEditorTreeNodePtrType OriginalNodePtr = FindTreeNode(OriginalComponent); - if(OriginalNodePtr.IsValid()) + // Attempt to locate the original node in the SCS tree + FSCSEditorTreeNodePtrType OriginalNodePtr = FindTreeNode(OriginalComponent); + if(OriginalNodePtr.IsValid()) + { + // If we're duplicating the root then we're already a child of it so need to reparent, but we do need to reset the scale + // otherwise we'll end up with the square of the root's scale instead of being the same size. + if (OriginalNodePtr == SceneRootNodePtr) + { + NewSceneComponent->RelativeScale3D = FVector(1.f); + } + else + { + // If the original node was parented, attempt to add the duplicate as a child of the same parent node if the parent is not + // part of the duplicate set, otherwise parent to the parent's duplicate + FSCSEditorTreeNodePtrType ParentNodePtr = OriginalNodePtr->GetParent(); + if (ParentNodePtr.IsValid()) { - // If we're duplicating the root then we're already a child of it so need to reparent, but we do need to reset the scale - // otherwise we'll end up with the square of the root's scale instead of being the same size. - if (OriginalNodePtr == SceneRootNodePtr) + if (USceneComponent** ParentDuplicateComponent = DuplicateSceneComponentMap.Find(Cast(ParentNodePtr->GetComponentTemplate()))) { - NewSceneComponent->RelativeScale3D = FVector(1.f); - } - else - { - // If the original node was parented, attempt to add the duplicate as a child of the same parent node - FSCSEditorTreeNodePtrType ParentNodePtr = OriginalNodePtr->GetParent(); - if (ParentNodePtr.IsValid()) + FSCSEditorTreeNodePtrType DuplicateParentNodePtr = FindTreeNode(*ParentDuplicateComponent); + if (DuplicateParentNodePtr.IsValid()) { - // Locate the duplicate node (as a child of the current scene root node), and switch it to be a child of the original node's parent - FSCSEditorTreeNodePtrType NewChildNodePtr = SceneRootNodePtr->FindChild(NewSceneComponent, true); - if (NewChildNodePtr.IsValid()) - { - // Note: This method will handle removal from the scene root node as well - ParentNodePtr->AddChild(NewChildNodePtr); - } + ParentNodePtr = DuplicateParentNodePtr; } } + + // Locate the duplicate node (as a child of the current scene root node), and switch it to be a child of the original node's parent + FSCSEditorTreeNodePtrType NewChildNodePtr = SceneRootNodePtr->FindChild(NewSceneComponent, true); + if (NewChildNodePtr.IsValid()) + { + // Note: This method will handle removal from the scene root node as well + ParentNodePtr->AddChild(NewChildNodePtr); + } } } } @@ -5120,7 +5143,18 @@ UActorComponent* SSCSEditor::AddNewComponent( UClass* NewComponentClass, UObject bAllowTreeUpdates = false; } - const FName NewVariableName = (Asset ? FName(*FComponentEditorUtils::GenerateValidVariableNameFromAsset(Asset, nullptr)) : NAME_None); + FName NewVariableName; + if (ComponentTemplate) + { + FString TemplateName = ComponentTemplate->GetName(); + NewVariableName = (TemplateName.EndsWith(USimpleConstructionScript::ComponentTemplateNameSuffix) + ? FName(*TemplateName.LeftChop(USimpleConstructionScript::ComponentTemplateNameSuffix.Len())) + : ComponentTemplate->GetFName()); + } + else if (Asset) + { + NewVariableName = *FComponentEditorUtils::GenerateValidVariableNameFromAsset(Asset, nullptr); + } NewComponent = AddNewNode(Blueprint->SimpleConstructionScript->CreateNode(NewComponentClass, NewVariableName), Asset, bMarkBlueprintModified, bSetFocusToNewItem); if (ComponentTemplate) @@ -5417,11 +5451,41 @@ void SSCSEditor::CopySelectedNodes() if (ComponentTemplate) { ComponentsToCopy.Add(ComponentTemplate); + + if (EditorMode == EComponentEditorMode::BlueprintSCS) + { + // CopyComponents uses component attachment to maintain hierarchy, but the SCS templates are not + // setup with a relationship to each other. Briefly setup the attachment between the templates being + // copied so that the hierarchy is retained upon pasting + if (USceneComponent* SceneTemplate = Cast(ComponentTemplate)) + { + FSCSEditorTreeNodePtrType SelectedParentNodePtr = SelectedNodePtr->GetParent(); + if (SelectedParentNodePtr.IsValid()) + { + if (USceneComponent* ParentSceneTemplate = Cast(SelectedParentNodePtr->GetComponentTemplate())) + { + SceneTemplate->SetupAttachment(ParentSceneTemplate); + } + } + } + } } } // Copy the components to the clipboard FComponentEditorUtils::CopyComponents(ComponentsToCopy); + + if (EditorMode == EComponentEditorMode::BlueprintSCS) + { + for (UActorComponent* ComponentTemplate : ComponentsToCopy) + { + if (USceneComponent* SceneTemplate = Cast(ComponentTemplate)) + { + // clear back out any temporary attachments we set up for the copy + SceneTemplate->SetupAttachment(nullptr); + } + } + } } bool SSCSEditor::CanPasteNodes() const diff --git a/Engine/Source/Editor/Kismet/Private/UserDefinedStructureEditor.cpp b/Engine/Source/Editor/Kismet/Private/UserDefinedStructureEditor.cpp index 67ebff37e378..59831d00f7b7 100644 --- a/Engine/Source/Editor/Kismet/Private/UserDefinedStructureEditor.cpp +++ b/Engine/Source/Editor/Kismet/Private/UserDefinedStructureEditor.cpp @@ -152,7 +152,7 @@ public: if (Info != FStructureEditorUtils::DefaultValueChanged) { StructData->Initialize(UserDefinedStruct.Get()); - + // Force the set object call because we may be called multiple times in a row if more than one struct was changed at the same time DetailsView->SetObject(UserDefinedStruct.Get(), true); } diff --git a/Engine/Source/Editor/KismetCompiler/Private/KismetCompiler.cpp b/Engine/Source/Editor/KismetCompiler/Private/KismetCompiler.cpp index 5212fa6ecac5..19babbb70566 100644 --- a/Engine/Source/Editor/KismetCompiler/Private/KismetCompiler.cpp +++ b/Engine/Source/Editor/KismetCompiler/Private/KismetCompiler.cpp @@ -277,7 +277,8 @@ void FKismetCompilerContext::CleanAndSanitizeClass(UBlueprintGeneratedClass* Cla for( auto SubObjIt = ClassSubObjects.CreateIterator(); SubObjIt; ++SubObjIt ) { UObject* CurrSubObj = *SubObjIt; - CurrSubObj->Rename(*CurrSubObj->GetName(), TransientClass, RenFlags); + FName NewSubobjectName = MakeUniqueObjectName(TransientClass, CurrSubObj->GetClass(), CurrSubObj->GetFName()); + CurrSubObj->Rename(*NewSubobjectName.ToString(), TransientClass, RenFlags); if( UProperty* Prop = Cast(CurrSubObj) ) { FKismetCompilerUtilities::InvalidatePropertyExport(Prop); @@ -2256,8 +2257,9 @@ void FKismetCompilerContext::SetDefaultInputValueMetaData(UFunction* Function, c for (const TSharedPtr& InputDataPtr : InputData) { if ( InputDataPtr.IsValid() && + !InputDataPtr->PinName.IsNone() && + (InputDataPtr->PinName != UEdGraphSchema_K2::PN_Self) && (InputDataPtr->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec) && - (InputDataPtr->PinName != UEdGraphSchema_K2::PN_Self) && (InputDataPtr->PinType.PinCategory != UEdGraphSchema_K2::PC_Object) && (InputDataPtr->PinType.PinCategory != UEdGraphSchema_K2::PC_Class) && (InputDataPtr->PinType.PinCategory != UEdGraphSchema_K2::PC_Interface) ) @@ -3650,6 +3652,15 @@ void FKismetCompilerContext::ProcessOneFunctionGraph(UEdGraph* SourceGraph, bool ExpansionStep(FunctionGraph, false); + // Cull the entire construction script graph if after node culling it's trivial, this reduces event spam on object construction: + if (SourceGraph->GetFName() == Schema->FN_UserConstructionScript ) + { + if(FKismetCompilerUtilities::IsIntermediateFunctionGraphTrivial(Schema->FN_UserConstructionScript, FunctionGraph)) + { + return; + } + } + // If a function in the graph cannot be overridden/placed as event make sure that it is not. VerifyValidOverrideFunction(FunctionGraph); @@ -4160,15 +4171,6 @@ void FKismetCompilerContext::CompileFunctions(EInternalCompilerFlags InternalFla // Copy over the CDO properties if we're not already regenerating on load. In that case, the copy will be done after compile on load is complete FBlueprintEditorUtils::PropagateParentBlueprintDefaults(NewClass); - if (Blueprint->HasAnyFlags(RF_BeingRegenerated)) - { - if (CompileOptions.CompileType == EKismetCompileType::Full) - { - check(Blueprint->PRIVATE_InnermostPreviousCDO == NULL); - Blueprint->PRIVATE_InnermostPreviousCDO = OldCDO; - } - } - if(bPropagateValuesToCDO) { if( !Blueprint->HasAnyFlags(RF_BeingRegenerated) ) @@ -4199,17 +4201,6 @@ void FKismetCompilerContext::CompileFunctions(EInternalCompilerFlags InternalFla // Don't perform generated class validation since we didn't do any value propagation. bSkipGeneratedClassValidation = true; } - - // >>> Backwards Compatibility: Propagate data from the skel CDO to the gen CDO if we haven't already done so for this blueprint - if( !bIsSkeletonOnly && !Blueprint->IsGeneratedClassAuthoritative() && CompileOptions.CompileType != EKismetCompileType::Cpp ) - { - UEditorEngine::FCopyPropertiesForUnrelatedObjectsParams CopyDetails; - CopyDetails.bAggressiveDefaultSubobjectReplacement = false; - CopyDetails.bDoDelta = false; - UEditorEngine::CopyPropertiesForUnrelatedObjects(Blueprint->SkeletonGeneratedClass->GetDefaultObject(), NewCDO, CopyDetails); - Blueprint->SetLegacyGeneratedClassIsAuthoritative(); - } - // <<< End Backwards Compatibility } PropagateValuesToCDO(NewCDO, OldCDO); diff --git a/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerMisc.cpp b/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerMisc.cpp index a323898acab4..b78b012a48b4 100644 --- a/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerMisc.cpp +++ b/Engine/Source/Editor/KismetCompiler/Private/KismetCompilerMisc.cpp @@ -25,6 +25,7 @@ #include "K2Node_Event.h" #include "K2Node_CallFunction.h" #include "K2Node_CallArrayFunction.h" +#include "K2Node_CallParentFunction.h" #include "K2Node_ExecutionSequence.h" #include "K2Node_FunctionEntry.h" #include "K2Node_FunctionResult.h" @@ -517,33 +518,6 @@ void FKismetCompilerUtilities::RemoveObjectRedirectorIfPresent(UObject* Package, } } -void FKismetCompilerUtilities::EnsureFreeNameForNewClass(UClass* ClassToConsign, FString& ClassName, UBlueprint* Blueprint) -{ - check(Blueprint); - - UObject* OwnerOutermost = Blueprint->GetOutermost(); - - // Try to find a class with the name we want to use in the scope - UClass* AnyClassWithGoodName = (UClass*)StaticFindObject(UClass::StaticClass(), OwnerOutermost, *ClassName, false); - if (AnyClassWithGoodName == ClassToConsign) - { - // Ignore it if it's the class we're already consigning anyway - AnyClassWithGoodName = NULL; - } - - if( ClassToConsign ) - { - ConsignToOblivion(ClassToConsign, Blueprint->bIsRegeneratingOnLoad); - } - - // Consign the class with the name we want to use - if( AnyClassWithGoodName ) - { - ConsignToOblivion(AnyClassWithGoodName, Blueprint->bIsRegeneratingOnLoad); - } -} - - /** Finds a property by name, starting in the specified scope; Validates property type and returns NULL along with emitting an error if there is a mismatch. */ UProperty* FKismetCompilerUtilities::FindPropertyInScope(UStruct* Scope, UEdGraphPin* Pin, FCompilerResultsLog& MessageLog, const UEdGraphSchema_K2* Schema, UClass* SelfClass) { @@ -1556,6 +1530,52 @@ bool FKismetCompilerUtilities::IsMissingMemberPotentiallyLoading(const UBlueprin return bCouldBeCompiledInOnLoad; } +bool FKismetCompilerUtilities::IsIntermediateFunctionGraphTrivial(FName FunctionName, const UEdGraph* FunctionGraph) +{ + const auto HasFunctionEntry = [](const UEdGraph* InFunctionGraph ) -> bool + { + return InFunctionGraph->Nodes.FindByPredicate( + [](const UEdGraphNode* Node) { return Cast(Node); } + ) != nullptr; + }; + + const auto HasCallToParent = [](const UEdGraph* InFunctionGraph) -> bool + { + return InFunctionGraph->Nodes.FindByPredicate( + [](const UEdGraphNode* Node) { return Cast(Node); } + ) != nullptr; + }; + + if(FunctionGraph->Nodes.Num() <= 2) + { + if(const UBlueprint* OwningBP = FBlueprintEditorUtils::FindBlueprintForGraph(FunctionGraph)) + { + if(UFunction* Fn = OwningBP->ParentClass->FindFunctionByName(FunctionName)) + { + // this is an override, we consider this implementation trivial iff it contains + // an entry node and a call to the parent or it contains only an entry node + // and the parent is native and the FN is a Blueprint Event: + if(FunctionGraph->Nodes.Num() == 2) + { + return HasFunctionEntry(FunctionGraph) && HasCallToParent(FunctionGraph); + } + else if(Fn->HasAnyFunctionFlags(FUNC_BlueprintEvent)) + { + return FunctionGraph->Nodes.Num() == 1 && + HasFunctionEntry(FunctionGraph); + } + } + else + { + return FunctionGraph->Nodes.Num() == 1&& + HasFunctionEntry(FunctionGraph); + } + } + } + + return false; +} + ////////////////////////////////////////////////////////////////////////// // FNodeHandlingFunctor diff --git a/Engine/Source/Editor/KismetCompiler/Public/KismetCompilerMisc.h b/Engine/Source/Editor/KismetCompiler/Public/KismetCompilerMisc.h index dfc9cc6d195c..839395158df2 100644 --- a/Engine/Source/Editor/KismetCompiler/Public/KismetCompilerMisc.h +++ b/Engine/Source/Editor/KismetCompiler/Public/KismetCompilerMisc.h @@ -37,16 +37,6 @@ public: */ static void InvalidatePropertyExport(UProperty* PropertyToInvalidate); - /** - * Finds any class with the specified class name, and consigns it to oblivion, along with the specified class to consign. - * This should ensure that the specified name is free for use - * - * @param ClassToConsign Class to consign to oblivion no matter what - * @param ClassName Name that we want to ensure isn't used - * @param Blueprint The blueprint that is in charge of these classes, used for scoping - */ - static void EnsureFreeNameForNewClass(UClass* ClassToConsign, FString& ClassName, UBlueprint* Blueprint); - /** * Tests to see if a pin is schema compatible with a property. * @@ -124,6 +114,9 @@ public: * Blueprint's bytecode is recompiled. */ static bool IsMissingMemberPotentiallyLoading(const UBlueprint* SelfBlueprint, const UStruct* MemberOwner); + + /** @return true if the graph in question contains only an entry node or only an entry node and a call to its parent if the graph is an override */ + static bool IsIntermediateFunctionGraphTrivial(FName FunctionName, const UEdGraph* FunctionGraph); }; ////////////////////////////////////////////////////////////////////////// diff --git a/Engine/Source/Editor/LevelEditor/Private/LevelEditor.cpp b/Engine/Source/Editor/LevelEditor/Private/LevelEditor.cpp index 3acb02b46c43..63c61aed3955 100644 --- a/Engine/Source/Editor/LevelEditor/Private/LevelEditor.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/LevelEditor.cpp @@ -1715,7 +1715,8 @@ void FLevelEditorModule::BindGlobalLevelEditorCommands() Commands.ToggleFeatureLevelPreview, FExecuteAction::CreateStatic(&FLevelEditorActionCallbacks::ToggleFeatureLevelPreview), FIsActionChecked::CreateStatic(&FLevelEditorActionCallbacks::IsFeatureLevelPreviewEnabled), - FIsActionChecked::CreateStatic(&FLevelEditorActionCallbacks::IsFeatureLevelPreviewActive)); + FIsActionChecked::CreateStatic(&FLevelEditorActionCallbacks::IsFeatureLevelPreviewActive), + FIsActionButtonVisible::CreateStatic(FLevelEditorActionCallbacks::IsPreviewModeButtonVisible)); ActionList.MapAction( Commands.PreviewPlatformOverride_DefaultES2, diff --git a/Engine/Source/Editor/LevelEditor/Private/LevelEditorActions.cpp b/Engine/Source/Editor/LevelEditor/Private/LevelEditorActions.cpp index dad43ce423f6..9c920d3068ed 100644 --- a/Engine/Source/Editor/LevelEditor/Private/LevelEditorActions.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/LevelEditorActions.cpp @@ -578,22 +578,25 @@ bool FLevelEditorActionCallbacks::IsFeatureLevelPreviewEnabled() { return false; } + if (GEditor->PreviewFeatureLevel == ERHIFeatureLevel::SM5) + { + return true; + } return GEditor->IsFeatureLevelPreviewEnabled(); } bool FLevelEditorActionCallbacks::IsFeatureLevelPreviewActive() { - return GEditor->IsFeatureLevelPreviewEnabled() && GEditor->IsFeatureLevelPreviewActive(); -} - -bool FLevelEditorActionCallbacks::IsFeatureLevelPreviewDropdownEnabled() -{ - if (GEditor->PlayWorld || GUnrealEd->bIsSimulatingInEditor) + if (GEditor->PreviewFeatureLevel == ERHIFeatureLevel::SM5) { return false; } + return GEditor->IsFeatureLevelPreviewEnabled() && GEditor->IsFeatureLevelPreviewActive(); +} - return true; +bool FLevelEditorActionCallbacks::IsPreviewModeButtonVisible() +{ + return GEditor->PreviewFeatureLevel != ERHIFeatureLevel::SM5; } void FLevelEditorActionCallbacks::SetPreviewPlatform(FName MaterialQualityPlatform, ERHIFeatureLevel::Type PreviewFeatureLevel) @@ -3310,13 +3313,13 @@ void FLevelEditorCommands::RegisterCommands() UI_COMMAND(ToggleFeatureLevelPreview, "Preview Platform", "Preview Platform", EUserInterfaceActionType::ToggleButton, FInputChord()); - UI_COMMAND(PreviewPlatformOverride_AndroidGLES2, "Preview as Android ES2", "Mobile preview using Android's quality settings.", EUserInterfaceActionType::RadioButton, FInputChord()); - UI_COMMAND(PreviewPlatformOverride_DefaultES2, "Preview as HTML5", "HTML5 preview.", EUserInterfaceActionType::RadioButton, FInputChord()); + UI_COMMAND(PreviewPlatformOverride_AndroidGLES2, "Android ES2", "Mobile preview using Android's quality settings.", EUserInterfaceActionType::RadioButton, FInputChord()); + UI_COMMAND(PreviewPlatformOverride_DefaultES2, "HTML5", "HTML5 preview.", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND(PreviewPlatformOverride_DefaultES31, "Default High-End Mobile", "Use default mobile settings (no quality overrides).", EUserInterfaceActionType::RadioButton, FInputChord()); - UI_COMMAND(PreviewPlatformOverride_AndroidGLES31, "Preview as Android ES31", "Mobile preview using Android ES3.1 quality settings.", EUserInterfaceActionType::RadioButton, FInputChord()); - UI_COMMAND(PreviewPlatformOverride_AndroidVulkanES31, "Preview as Android Vulkan", "Mobile preview using Android Vulkan quality settings.", EUserInterfaceActionType::RadioButton, FInputChord()); - UI_COMMAND(PreviewPlatformOverride_IOSMetalES31, "Preview as iOS", "Mobile preview using iOS material quality settings.", EUserInterfaceActionType::RadioButton, FInputChord()); + UI_COMMAND(PreviewPlatformOverride_AndroidGLES31, "Android ES 3.1", "Mobile preview using Android ES3.1 quality settings.", EUserInterfaceActionType::RadioButton, FInputChord()); + UI_COMMAND(PreviewPlatformOverride_AndroidVulkanES31, "Android Vulkan", "Mobile preview using Android Vulkan quality settings.", EUserInterfaceActionType::RadioButton, FInputChord()); + UI_COMMAND(PreviewPlatformOverride_IOSMetalES31, "iOS", "Mobile preview using iOS material quality settings.", EUserInterfaceActionType::RadioButton, FInputChord()); UI_COMMAND( ConnectToSourceControl, "Connect to Source Control...", "Opens a dialog to connect to source control.", EUserInterfaceActionType::Button, FInputChord()); @@ -3332,8 +3335,8 @@ void FLevelEditorCommands::RegisterCommands() { NSLOCTEXT("LevelEditorCommands", "FeatureLevelPreviewType_ES2", "Mobile / HTML5"), NSLOCTEXT("LevelEditorCommands", "FeatureLevelPreviewType_ES31", "High-End Mobile"), - NSLOCTEXT("LevelEditorCommands", "FeatureLevelPreviewType_SM4", "Preview as SM4"), - NSLOCTEXT("LevelEditorCommands", "FeatureLevelPreviewType_SM5", "No Preview Device. (View as SM5)"), + NSLOCTEXT("LevelEditorCommands", "FeatureLevelPreviewType_SM4", "Shader Model 4"), + NSLOCTEXT("LevelEditorCommands", "FeatureLevelPreviewType_SM5", "Shader Model 5"), }; static const FText FeatureLevelToolTips[ERHIFeatureLevel::Num] = diff --git a/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp b/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp index a6b96e17f355..008a60583255 100644 --- a/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp +++ b/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.cpp @@ -1277,9 +1277,7 @@ TSharedRef< SWidget > FLevelEditorToolBar::MakeLevelEditorToolBar( const TShared ); } - ToolbarBuilder.EndSection(); - ToolbarBuilder.BeginSection(NAME_None); { struct FPreviewModeFunctionality { @@ -1296,22 +1294,16 @@ TSharedRef< SWidget > FLevelEditorToolBar::MakeLevelEditorToolBar( const TShared EShaderPlatform MaxRHIFeatureLevelPlatform = GetFeatureLevelShaderPlatform(GMaxRHIFeatureLevel); - if (GEditor->PreviewFeatureLevel == GMaxRHIFeatureLevel) - { - const FText& RenderingAsPlatformName = GetFriendlyShaderPlatformName(MaxRHIFeatureLevelPlatform); - return FText::Format(LOCTEXT("PreviewModeRenderingAs", "Viewing as {0}."), RenderingAsPlatformName); - } - else { const FText& RenderingAsPlatformName = GetFriendlyShaderPlatformName(GWorld->FeatureLevel == GMaxRHIFeatureLevel ? MaxRHIFeatureLevelPlatform : PreviewShaderPlatform); const FText& SwitchToPlatformName = GetFriendlyShaderPlatformName(GWorld->FeatureLevel == GMaxRHIFeatureLevel ? PreviewShaderPlatform : MaxRHIFeatureLevelPlatform); if (GWorld->FeatureLevel == GMaxRHIFeatureLevel) { - return FText::Format(LOCTEXT("PreviewModeViewingAsSwitchTo", "Viewing as {0}. Click to preview as {1}."), RenderingAsPlatformName, SwitchToPlatformName); + return FText::Format(LOCTEXT("PreviewModeViewingAsSwitchTo", "Viewing {0}. Click to preview {1}."), RenderingAsPlatformName, SwitchToPlatformName); } else { - return FText::Format(LOCTEXT("PreviewModePreviewingAsSwitchTo", "Previewing as {0}. Click to view as {1}."), RenderingAsPlatformName, SwitchToPlatformName); + return FText::Format(LOCTEXT("PreviewModePreviewingAsSwitchTo", "Previewing {0}. Click to view {1}."), RenderingAsPlatformName, SwitchToPlatformName); } } } @@ -1340,6 +1332,7 @@ TSharedRef< SWidget > FLevelEditorToolBar::MakeLevelEditorToolBar( const TShared { return FSlateIcon(FEditorStyle::GetStyleSetName(), GEditor->IsFeatureLevelPreviewActive() ? "LevelEditor.PreviewMode.iOS.Enabled" : "LevelEditor.PreviewMode.iOS.Disabled"); } + case SP_METAL_MACES2: case SP_OPENGL_ES2_WEBGL: { return FSlateIcon(FEditorStyle::GetStyleSetName(), GEditor->IsFeatureLevelPreviewActive() ? "LevelEditor.PreviewMode.HTML5.Enabled" : "LevelEditor.PreviewMode.HTML5.Disabled"); @@ -1347,13 +1340,9 @@ TSharedRef< SWidget > FLevelEditorToolBar::MakeLevelEditorToolBar( const TShared } switch (GEditor->PreviewFeatureLevel) { - case ERHIFeatureLevel::SM5: - { - return FSlateIcon(FEditorStyle::GetStyleSetName(), GEditor->IsFeatureLevelPreviewActive() ? "LevelEditor.PreviewMode.SM5.Enabled" : "LevelEditor.PreviewMode.SM5.Disabled"); - } case ERHIFeatureLevel::SM4: { - return FSlateIcon(FEditorStyle::GetStyleSetName(), GEditor->IsFeatureLevelPreviewActive() ? "LevelEditor.PreviewMode.SM4.Enabled" : "LevelEditor.PreviewMode.SM4.Enabled"); + return FSlateIcon(FEditorStyle::GetStyleSetName(), GEditor->IsFeatureLevelPreviewActive() ? "LevelEditor.PreviewMode.SM4.Enabled" : "LevelEditor.PreviewMode.SM4.Disabled"); } case ERHIFeatureLevel::ES2: { @@ -1378,18 +1367,6 @@ TSharedRef< SWidget > FLevelEditorToolBar::MakeLevelEditorToolBar( const TShared TAttribute::Create(&FPreviewModeFunctionality::GetPreviewModeTooltip), TAttribute::Create(&FPreviewModeFunctionality::GetPreviewModeIcon) ); - - FUIAction PreviewModeMenuCanExecute; - PreviewModeMenuCanExecute.CanExecuteAction = FIsActionChecked::CreateStatic(&FLevelEditorActionCallbacks::IsFeatureLevelPreviewDropdownEnabled); - - ToolbarBuilder.AddComboButton( - PreviewModeMenuCanExecute, - FOnGetContent::CreateStatic(&FLevelEditorToolBar::GeneratePreviewModeMenu, InCommandList), - LOCTEXT("PreviewModeCombo", "Preview Mode"), - LOCTEXT("PreviewModeCombo_ToolTip", "Preview Mode settings"), - FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.GameSettings"), - true - ); } ToolbarBuilder.EndSection(); @@ -1788,6 +1765,59 @@ static void MakeMaterialQualityLevelMenu( FMenuBuilder& MenuBuilder ) MenuBuilder.EndSection(); } +static void MakeShaderModelPreviewMenu( FMenuBuilder& MenuBuilder ) +{ +#define LOCTEXT_NAMESPACE "LevelToolBarViewMenu" + + MenuBuilder.BeginSection("EditorPreviewMode", LOCTEXT("EditorPreviewModeDevices", "Preview Devices")); + + for (int32 i = GMaxRHIFeatureLevel; i >= 0; --i) + { + switch (i) + { + case ERHIFeatureLevel::ES2: + MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_AndroidGLES2); + MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_DefaultES2); + break; + + case ERHIFeatureLevel::ES3_1: + { + //MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_DefaultES31); + + bool bAndroidBuildForES31 = false; + GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bBuildForES31"), bAndroidBuildForES31, GEngineIni); + if(bAndroidBuildForES31) + { + MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_AndroidGLES31); + } + + bool bAndroidSupportsVulkan = false; + GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bSupportsVulkan"), bAndroidSupportsVulkan, GEngineIni); + if(bAndroidSupportsVulkan) + { + MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_AndroidVulkanES31); + } + + bool bIOSSupportsMetal = false; + GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("bSupportsMetal"), bIOSSupportsMetal, GEngineIni); + if(bIOSSupportsMetal) + { + MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_IOSMetalES31); + } + + break; + } + + default: + MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().FeatureLevelPreview[i]); + } + } + + MenuBuilder.EndSection(); + +#undef LOCTEXT_NAMESPACE +} + static void MakeScalabilityMenu( FMenuBuilder& MenuBuilder ) { MenuBuilder.AddWidget(SNew(SScalabilitySettings), FText(), true); @@ -1880,6 +1910,11 @@ TSharedRef< SWidget > FLevelEditorToolBar::GenerateQuickSettingsMenu( TSharedRef LOCTEXT( "MaterialQualityLevelSubMenu", "Material Quality Level" ), LOCTEXT( "MaterialQualityLevelSubMenu_ToolTip", "Sets the value of the CVar \"r.MaterialQualityLevel\" (low=0, high=1, medium=2). This affects materials via the QualitySwitch material expression." ), FNewMenuDelegate::CreateStatic( &MakeMaterialQualityLevelMenu ) ); + + MenuBuilder.AddSubMenu( + LOCTEXT("FeatureLevelPreviewSubMenu", "Preview Rendering Level"), + LOCTEXT("FeatureLevelPreviewSubMenu_ToolTip", "Sets the rendering level used by the main editor"), + FNewMenuDelegate::CreateStatic(&MakeShaderModelPreviewMenu)); } MenuBuilder.EndSection(); @@ -1940,79 +1975,6 @@ TSharedRef< SWidget > FLevelEditorToolBar::GenerateQuickSettingsMenu( TSharedRef } -TSharedRef< SWidget > FLevelEditorToolBar::GeneratePreviewModeMenu( TSharedRef InCommandList ) -{ -#define LOCTEXT_NAMESPACE "LevelToolBarViewMenu" - - // Get all menu extenders for this context menu from the level editor module - FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked( TEXT("LevelEditor") ); - TArray MenuExtenderDelegates = LevelEditorModule.GetAllLevelEditorToolbarViewMenuExtenders(); - - TArray> Extenders; - for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i) - { - if (MenuExtenderDelegates[i].IsBound()) - { - Extenders.Add(MenuExtenderDelegates[i].Execute(InCommandList)); - } - } - TSharedPtr MenuExtender = FExtender::Combine(Extenders); - - const bool bShouldCloseWindowAfterMenuSelection = true; - FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, InCommandList, MenuExtender); - - MenuBuilder.BeginSection("EditorPreviewMode", LOCTEXT("EditorPreviewModeDevices", "Preview Devices")); - - for (int32 i = GMaxRHIFeatureLevel; i >= 0; --i) - { - switch (i) - { - case ERHIFeatureLevel::ES2: - MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_AndroidGLES2); - MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_DefaultES2); - break; - - case ERHIFeatureLevel::ES3_1: - { - //MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_DefaultES31); - - bool bAndroidBuildForES31 = false; - GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bBuildForES31"), bAndroidBuildForES31, GEngineIni); - if(bAndroidBuildForES31) - { - MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_AndroidGLES31); - } - - bool bAndroidSupportsVulkan = false; - GConfig->GetBool(TEXT("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings"), TEXT("bSupportsVulkan"), bAndroidSupportsVulkan, GEngineIni); - if(bAndroidSupportsVulkan) - { - MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_AndroidVulkanES31); - } - - bool bIOSSupportsMetal = false; - GConfig->GetBool(TEXT("/Script/IOSRuntimeSettings.IOSRuntimeSettings"), TEXT("bSupportsMetal"), bIOSSupportsMetal, GEngineIni); - if(bIOSSupportsMetal) - { - MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().PreviewPlatformOverride_IOSMetalES31); - } - - break; - } - - default: - MenuBuilder.AddMenuEntry(FLevelEditorCommands::Get().FeatureLevelPreview[i]); - } - } - - MenuBuilder.EndSection(); - -#undef LOCTEXT_NAMESPACE - - return MenuBuilder.MakeWidget(); -} - - TSharedRef< SWidget > FLevelEditorToolBar::GenerateSourceControlMenu(TSharedRef InCommandList) { #define LOCTEXT_NAMESPACE "LevelToolBarSourceControlMenu" diff --git a/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.h b/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.h index 1abded39bdc7..e0f31daa5da6 100644 --- a/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.h +++ b/Engine/Source/Editor/LevelEditor/Private/LevelEditorToolBar.h @@ -47,13 +47,6 @@ protected: */ static TSharedRef< SWidget > GenerateQuickSettingsMenu( TSharedRef InCommandList ); - /** - * Generates menu content for the preview mode combo button drop down menu - * - * @return Menu content widget - */ - static TSharedRef< SWidget > GeneratePreviewModeMenu( TSharedRef InCommandList ); - /** * Generates menu content for the source control combo button drop down menu * diff --git a/Engine/Source/Editor/LevelEditor/Public/LevelEditorActions.h b/Engine/Source/Editor/LevelEditor/Public/LevelEditorActions.h index e9e51425b5a2..4cf1d0ebab12 100644 --- a/Engine/Source/Editor/LevelEditor/Public/LevelEditorActions.h +++ b/Engine/Source/Editor/LevelEditor/Public/LevelEditorActions.h @@ -808,7 +808,7 @@ public: static void ToggleFeatureLevelPreview(); static bool IsFeatureLevelPreviewEnabled(); static bool IsFeatureLevelPreviewActive(); - static bool IsFeatureLevelPreviewDropdownEnabled(); + static bool IsPreviewModeButtonVisible(); static void SetPreviewPlatform(FName MaterialQualityPlatform,ERHIFeatureLevel::Type PreviewFeatureLevel); static bool IsPreviewPlatformChecked(FName MaterialQualityPlatform, ERHIFeatureLevel::Type PreviewFeatureLevel); static void SetFeatureLevelPreview(ERHIFeatureLevel::Type InFeatureLevel); diff --git a/Engine/Source/Editor/MovieSceneTools/Private/K2Node_GetSequenceBinding.cpp b/Engine/Source/Editor/MovieSceneTools/Private/K2Node_GetSequenceBinding.cpp index 4542e774f29e..1004baa6b303 100644 --- a/Engine/Source/Editor/MovieSceneTools/Private/K2Node_GetSequenceBinding.cpp +++ b/Engine/Source/Editor/MovieSceneTools/Private/K2Node_GetSequenceBinding.cpp @@ -205,7 +205,7 @@ FNodeHandlingFunctor* UK2Node_GetSequenceBinding::CreateNodeHandler(FKismetCompi void UK2Node_GetSequenceBinding::PreloadRequiredAssets() { - GetSequence(); + EnsureFullyLoaded(GetSequence()); } FText UK2Node_GetSequenceBinding::GetSequenceName() const diff --git a/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.cpp b/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.cpp index f9f77814931a..5a77960d16ee 100644 --- a/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.cpp +++ b/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.cpp @@ -42,6 +42,7 @@ #include "AnimGraphNode_StateMachine.h" #include "Engine/SkeletalMeshSocket.h" #include "Styling/CoreStyle.h" +#include "Widgets/Input/SCheckBox.h" #define LOCTEXT_NAMESPACE "KismetNodeWithOptionalPinsDetails" @@ -98,144 +99,218 @@ void FAnimGraphNodeDetails::CustomizeDetails(class IDetailLayoutBuilder& DetailB TSharedRef NodePropertyHandle = DetailBuilder.GetProperty(NodeProperty->GetFName(), AnimGraphNode->GetClass()); DetailBuilder.HideProperty(NodePropertyHandle); - // Now customize each property in the pins array - for (int CustomPinIndex = 0; CustomPinIndex < AnimGraphNode->ShowPinForProperties.Num(); ++CustomPinIndex) + uint32 NumChildHandles = 0; + FPropertyAccess::Result Result = NodePropertyHandle->GetNumChildren(NumChildHandles); + if(Result != FPropertyAccess::Fail) { - const FOptionalPinFromProperty& OptionalPin = AnimGraphNode->ShowPinForProperties[CustomPinIndex]; - - UProperty* TargetProperty = FindField(NodeProperty->Struct, OptionalPin.PropertyName); - - IDetailCategoryBuilder& CurrentCategory = DetailBuilder.EditCategory(FObjectEditorUtils::GetCategoryFName(TargetProperty)); - - const FName TargetPropertyPath(*FString::Printf(TEXT("%s.%s"), *NodeProperty->GetName(), *TargetProperty->GetName())); - TSharedRef TargetPropertyHandle = DetailBuilder.GetProperty(TargetPropertyPath, AnimGraphNode->GetClass() ); - - // Not optional - if (!OptionalPin.bCanToggleVisibility && OptionalPin.bShowPin) + for(uint32 ChildHandleIndex = 0; ChildHandleIndex < NumChildHandles; ++ChildHandleIndex) { - // Always displayed as a pin, so hide the property - DetailBuilder.HideProperty(TargetPropertyHandle); - continue; - } + TSharedPtr TargetPropertyHandle = NodePropertyHandle->GetChildHandle(ChildHandleIndex); + if(TargetPropertyHandle.IsValid()) + { + UProperty* TargetProperty = TargetPropertyHandle->GetProperty(); + IDetailCategoryBuilder& CurrentCategory = DetailBuilder.EditCategory(FObjectEditorUtils::GetCategoryFName(TargetProperty)); + + int32 CustomPinIndex = AnimGraphNode->ShowPinForProperties.IndexOfByPredicate([TargetProperty](const FOptionalPinFromProperty& InOptionalPin) + { + return TargetProperty->GetFName() == InOptionalPin.PropertyName; + }); - if(!TargetPropertyHandle->GetProperty()) - { - continue; - } + if(CustomPinIndex != INDEX_NONE) + { + const FOptionalPinFromProperty& OptionalPin = AnimGraphNode->ShowPinForProperties[CustomPinIndex]; - // if customized, do not do anything - if(TargetPropertyHandle->IsCustomized()) - { - continue; - } + // Not optional + if (!OptionalPin.bCanToggleVisibility && OptionalPin.bShowPin) + { + // Always displayed as a pin, so hide the property + DetailBuilder.HideProperty(TargetPropertyHandle); + continue; + } + + if(!TargetPropertyHandle->GetProperty()) + { + continue; + } + + // if customized, do not do anything + if(TargetPropertyHandle->IsCustomized()) + { + continue; + } - // sometimes because of order of customization - // this gets called first for the node you'd like to customize - // then the above statement won't work - // so you can mark certain property to have meta data "CustomizeProperty" - // which will trigger below statement - if (OptionalPin.bPropertyIsCustomized) - { - continue; - } + // sometimes because of order of customization + // this gets called first for the node you'd like to customize + // then the above statement won't work + // so you can mark certain property to have meta data "CustomizeProperty" + // which will trigger below statement + if (OptionalPin.bPropertyIsCustomized) + { + continue; + } - IDetailPropertyRow& PropertyRow = CurrentCategory.AddProperty( TargetPropertyHandle ); + TSharedRef InternalCustomWidget = CreatePropertyWidget(TargetProperty, TargetPropertyHandle.ToSharedRef(), AnimGraphNode->GetClass()); - TSharedRef InternalCustomWidget = CreatePropertyWidget(TargetProperty, TargetPropertyHandle, AnimGraphNode->GetClass()); + if (OptionalPin.bCanToggleVisibility) + { + IDetailPropertyRow& PropertyRow = CurrentCategory.AddProperty(TargetPropertyHandle); - if (OptionalPin.bCanToggleVisibility) - { - TSharedPtr NameWidget; - TSharedPtr ValueWidget; - FDetailWidgetRow Row; - PropertyRow.GetDefaultWidgets( NameWidget, ValueWidget, Row ); + TSharedPtr NameWidget; + TSharedPtr ValueWidget; + FDetailWidgetRow Row; + PropertyRow.GetDefaultWidgets( NameWidget, ValueWidget, Row ); + + ValueWidget = (InternalCustomWidget == SNullWidget::NullWidget) ? ValueWidget : InternalCustomWidget; - ValueWidget = (InternalCustomWidget == SNullWidget::NullWidget) ? ValueWidget : InternalCustomWidget; + const FName OptionalPinArrayEntryName(*FString::Printf(TEXT("ShowPinForProperties[%d].bShowPin"), CustomPinIndex)); + TSharedRef ShowHidePropertyHandle = DetailBuilder.GetProperty(OptionalPinArrayEntryName); - const FName OptionalPinArrayEntryName(*FString::Printf(TEXT("ShowPinForProperties[%d].bShowPin"), CustomPinIndex)); - TSharedRef ShowHidePropertyHandle = DetailBuilder.GetProperty(OptionalPinArrayEntryName); + ShowHidePropertyHandle->MarkHiddenByCustomization(); - ShowHidePropertyHandle->MarkHiddenByCustomization(); + TSharedRef ShowHidePropertyWidget = CreateAsPinWidget(ShowHidePropertyHandle); - const FText AsPinTooltip = LOCTEXT("AsPinTooltip", "Show this property as a pin on the node"); + ValueWidget->SetVisibility(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FAnimGraphNodeDetails::GetVisibilityOfProperty, ShowHidePropertyHandle))); - TSharedRef ShowHidePropertyWidget = ShowHidePropertyHandle->CreatePropertyValueWidget(); - ShowHidePropertyWidget->SetToolTipText(AsPinTooltip); + // If we have an edit condition, that comes as part of the default name widget, so just use a text block to avoid duplicate checkboxes + TSharedPtr PropertyNameWidget; + if(TargetProperty->HasMetaData(TEXT("EditCondition"))) + { + PropertyNameWidget = SNew(STextBlock) + .Text(TargetProperty->GetDisplayNameText()) + .Font(IDetailLayoutBuilder::GetDetailFont()) + .ToolTipText(TargetProperty->GetToolTipText()); + } + else + { + PropertyNameWidget = NameWidget; + } - ValueWidget->SetVisibility(TAttribute::Create(TAttribute::FGetter::CreateSP(this, &FAnimGraphNodeDetails::GetVisibilityOfProperty, ShowHidePropertyHandle))); + NameWidget = + SNew(SHorizontalBox) + +SHorizontalBox::Slot() + .HAlign(HAlign_Left) + .VAlign(VAlign_Center) + .FillWidth(1) + [ + SNew(SBox) + .Clipping(EWidgetClipping::ClipToBounds) + [ + PropertyNameWidget.ToSharedRef() + ] + ] + +SHorizontalBox::Slot() + .AutoWidth() + .HAlign(HAlign_Right) + .VAlign(VAlign_Center) + [ + ShowHidePropertyWidget + ]; - NameWidget = SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .HAlign(HAlign_Fill) - .FillWidth(1) - [ - SNew(SHorizontalBox) - +SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Left) - .VAlign(VAlign_Center) - [ - ShowHidePropertyWidget - ] - +SHorizontalBox::Slot() - .AutoWidth() - .HAlign(HAlign_Left) - .VAlign(VAlign_Center) - [ - SNew(STextBlock) - .Text(LOCTEXT("AsPin", " (As pin) ")) - .Font(DetailBuilder.IDetailLayoutBuilder::GetDetailFont()) - .ToolTipText(AsPinTooltip) - ] - +SHorizontalBox::Slot() - .AutoWidth() - [ - SNew(SSpacer) - ] - +SHorizontalBox::Slot() - .HAlign(HAlign_Right) - .VAlign(VAlign_Center) - .FillWidth(1) - .Padding(FMargin(0,0,4,0)) - [ - NameWidget.ToSharedRef() - ] - ]; - - // we only show children if visilibity is one - // whenever toggles, this gets called, so it will be refreshed - const bool bShowChildren = GetVisibilityOfProperty(ShowHidePropertyHandle) == EVisibility::Visible; - PropertyRow.CustomWidget(bShowChildren) - .NameContent() - .MinDesiredWidth(Row.NameWidget.MinWidth) - .MaxDesiredWidth(Row.NameWidget.MaxWidth) - [ - NameWidget.ToSharedRef() - ] - .ValueContent() - .MinDesiredWidth(Row.ValueWidget.MinWidth) - .MaxDesiredWidth(Row.ValueWidget.MaxWidth) - [ - ValueWidget.ToSharedRef() - ]; - } - else if(InternalCustomWidget != SNullWidget::NullWidget) - { - // A few properties are internally customized within this customization. Here we - // catch instances of these that don't have an optional pin flag. - PropertyRow.CustomWidget() - .NameContent() - [ - TargetPropertyHandle->CreatePropertyNameWidget() - ] - .ValueContent() - [ - InternalCustomWidget - ]; + // we only show children if visibility is one + // whenever toggles, this gets called, so it will be refreshed + const bool bShowChildren = GetVisibilityOfProperty(ShowHidePropertyHandle) == EVisibility::Visible; + PropertyRow.CustomWidget(bShowChildren) + .NameContent() + .MinDesiredWidth(Row.NameWidget.MinWidth) + .MaxDesiredWidth(Row.NameWidget.MaxWidth) + [ + NameWidget.ToSharedRef() + ] + .ValueContent() + .MinDesiredWidth(Row.ValueWidget.MinWidth) + .MaxDesiredWidth(Row.ValueWidget.MaxWidth) + [ + ValueWidget.ToSharedRef() + ]; + } + else if(InternalCustomWidget != SNullWidget::NullWidget) + { + // A few properties are internally customized within this customization. Here we + // catch instances of these that don't have an optional pin flag. + IDetailPropertyRow& PropertyRow = CurrentCategory.AddProperty(TargetPropertyHandle); + PropertyRow.CustomWidget() + .NameContent() + [ + TargetPropertyHandle->CreatePropertyNameWidget() + ] + .ValueContent() + [ + InternalCustomWidget + ]; + } + else + { + CurrentCategory.AddProperty(TargetPropertyHandle); + } + } + } } } } +TSharedRef FAnimGraphNodeDetails::CreateAsPinWidget(TSharedRef InPropertyHandle) +{ + TWeakPtr WeakPropertyHandle = InPropertyHandle; + + auto IsCheckedLambda = [WeakPropertyHandle]() + { + if(WeakPropertyHandle.IsValid()) + { + bool bValue; + FPropertyAccess::Result Result = WeakPropertyHandle.Pin()->GetValue(bValue); + if(Result == FPropertyAccess::MultipleValues) + { + return ECheckBoxState::Undetermined; + } + else + { + return bValue ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + } + } + + return ECheckBoxState::Unchecked; + }; + + auto OnCheckStateChangedLambda = [WeakPropertyHandle](ECheckBoxState InCheckBoxState) + { + if(WeakPropertyHandle.IsValid()) + { + bool bValue = InCheckBoxState == ECheckBoxState::Checked; + WeakPropertyHandle.Pin()->SetValue(bValue); + } + }; + + auto ImageLambda = [WeakPropertyHandle]() + { + if(WeakPropertyHandle.IsValid()) + { + bool bValue; + FPropertyAccess::Result Result = WeakPropertyHandle.Pin()->GetValue(bValue); + if(Result == FPropertyAccess::MultipleValues) + { + return FEditorStyle::GetBrush("Kismet.VariableList.HideForInstance"); + } + else + { + return bValue ? FEditorStyle::GetBrush("Kismet.VariableList.ExposeForInstance") : FEditorStyle::GetBrush("Kismet.VariableList.HideForInstance"); + } + } + + return FEditorStyle::GetBrush("Kismet.VariableList.HideForInstance"); + }; + + return SNew(SCheckBox) + .ToolTipText(LOCTEXT("AsPinTooltip", "Show/hide this property as a pin on the node")) + .IsChecked_Lambda(IsCheckedLambda) + .OnCheckStateChanged_Lambda(OnCheckStateChangedLambda) + .Style(FEditorStyle::Get(), "CheckboxLookToggleButtonCheckbox") + [ + SNew(SImage) + .Image_Lambda(ImageLambda) + .ColorAndOpacity(FLinearColor::Black) + ]; +} + TSharedRef FAnimGraphNodeDetails::CreatePropertyWidget(UProperty* TargetProperty, TSharedRef TargetPropertyHandle, UClass* NodeClass) { if(const UObjectPropertyBase* ObjectProperty = Cast( TargetProperty )) diff --git a/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.h b/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.h index d83ba0d9a39e..928cf705e6b6 100644 --- a/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.h +++ b/Engine/Source/Editor/Persona/Private/AnimGraphNodeDetails.h @@ -39,6 +39,9 @@ protected: // Creates a widget for the supplied property TSharedRef CreatePropertyWidget(UProperty* TargetProperty, TSharedRef TargetPropertyHandle, UClass* NodeClass); + // Creates the 'as pin' toggle widget for a property + TSharedRef CreateAsPinWidget(TSharedRef InPropertyHandle); + EVisibility GetVisibilityOfProperty(TSharedRef Handle) const; /** Delegate to handle filtering of asset pickers */ diff --git a/Engine/Source/Editor/Persona/Private/PersonaMeshDetails.cpp b/Engine/Source/Editor/Persona/Private/PersonaMeshDetails.cpp index 7b99d80b91e9..87568f6b034e 100644 --- a/Engine/Source/Editor/Persona/Private/PersonaMeshDetails.cpp +++ b/Engine/Source/Editor/Persona/Private/PersonaMeshDetails.cpp @@ -1441,6 +1441,25 @@ void FPersonaMeshDetails::RegenerateOneLOD(int32 LODIndex) FLODUtilities::RestoreSkeletalMeshLODImportedData(SkelMesh, LODIndex, true); return; } + else if (!CurrentLODInfo.bHasBeenSimplified + && !SkelMesh->IsReductionActive(LODIndex)) + { + //Nothing to reduce + return; + } + + //If we are doing inline reduction, restore the skeletalmesh LOD if it was already reduced and we want to reduced it again (user change the options) + //This will ensure the data is the same when starting the reduction + if (LODIndex == CurrentLODInfo.ReductionSettings.BaseLOD + && CurrentLODInfo.bHasBeenSimplified + && SkelMesh->IsReductionActive(LODIndex) + && SkelMesh->GetImportedModel()->OriginalReductionSourceMeshData.IsValidIndex(LODIndex) + && !SkelMesh->GetImportedModel()->OriginalReductionSourceMeshData[LODIndex]->IsEmpty()) + { + //Restore the base LOD data + CurrentLODInfo.bHasBeenSimplified = false; + FLODUtilities::RestoreSkeletalMeshLODImportedData(SkelMesh, LODIndex, false); + } FSkeletalMeshUpdateContext UpdateContext; UpdateContext.SkeletalMesh = SkelMesh; @@ -1502,7 +1521,7 @@ FReply FPersonaMeshDetails::RegenerateLOD(int32 LODIndex) else if (bIsReductionActive) { //Ask user a special permission when the base LOD can be reduce - const FText Text(LOCTEXT("Warning_ReductionApplyingToImportedMesh_ReduceBaseLOD", "Are you sure you'd like to apply mesh reduction to the base LOD?")); + const FText Text(LOCTEXT("Warning_ReductionApplyingToImportedMesh_ReduceBaseLOD", "Are you sure you'd like to apply mesh reduction to the non-generated base LOD?")); EAppReturnType::Type Ret = FMessageDialog::Open(EAppMsgType::YesNo, Text); if (Ret == EAppReturnType::No) { diff --git a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSharedData.cpp b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSharedData.cpp index bb3b5994a8d7..8fda058a68a5 100644 --- a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSharedData.cpp +++ b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetEditorSharedData.cpp @@ -84,7 +84,7 @@ void FPhysicsAssetEditorSharedData::Initialize(const TSharedRefPreviewSkeletalMesh.ToSoftObjectPath(); + FSoftObjectPath PreviewMeshStringRef = PhysicsAsset->PreviewSkeletalMesh.ToSoftObjectPath(); // Look for body setups with no shapes (how does this happen?). // If we find one- just bang on a default box. diff --git a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetGraph/PhysicsAssetGraphSchema.cpp b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetGraph/PhysicsAssetGraphSchema.cpp index 1fe325ab4cb1..190e0db54a12 100644 --- a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetGraph/PhysicsAssetGraphSchema.cpp +++ b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetGraph/PhysicsAssetGraphSchema.cpp @@ -52,13 +52,13 @@ void UPhysicsAssetGraphSchema::BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGra // Don't allow breaking any links } -FPinConnectionResponse UPhysicsAssetGraphSchema::MovePinLinks(UEdGraphPin& MoveFromPin, UEdGraphPin& MoveToPin, bool bIsItermeadiateMove) const +FPinConnectionResponse UPhysicsAssetGraphSchema::MovePinLinks(UEdGraphPin& MoveFromPin, UEdGraphPin& MoveToPin, bool bIsIntermediateMove, bool bNotifyLinkedNodes) const { // Don't allow moving any links return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("")); } -FPinConnectionResponse UPhysicsAssetGraphSchema::CopyPinLinks(UEdGraphPin& CopyFromPin, UEdGraphPin& CopyToPin, bool bIsItermeadiateCopy) const +FPinConnectionResponse UPhysicsAssetGraphSchema::CopyPinLinks(UEdGraphPin& CopyFromPin, UEdGraphPin& CopyToPin, bool bIsIntermediateCopy) const { // Don't allow copying any links return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, TEXT("")); diff --git a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetGraph/PhysicsAssetGraphSchema.h b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetGraph/PhysicsAssetGraphSchema.h index 62b1651cb246..85388c937591 100644 --- a/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetGraph/PhysicsAssetGraphSchema.h +++ b/Engine/Source/Editor/PhysicsAssetEditor/Private/PhysicsAssetGraph/PhysicsAssetGraphSchema.h @@ -25,8 +25,8 @@ public: virtual FLinearColor GetPinTypeColor(const FEdGraphPinType& PinType) const override; virtual void BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotifcation) const override; virtual void BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) const override; - virtual FPinConnectionResponse MovePinLinks(UEdGraphPin& MoveFromPin, UEdGraphPin& MoveToPin, bool bIsItermeadiateMove = false) const override; - virtual FPinConnectionResponse CopyPinLinks(UEdGraphPin& CopyFromPin, UEdGraphPin& CopyToPin, bool bIsItermeadiateCopy = false) const override; + virtual FPinConnectionResponse MovePinLinks(UEdGraphPin& MoveFromPin, UEdGraphPin& MoveToPin, bool bIsIntermediateMove = false, bool bNotifyLinkedNodes = false) const override; + virtual FPinConnectionResponse CopyPinLinks(UEdGraphPin& CopyFromPin, UEdGraphPin& CopyToPin, bool bIsIntermediateCopy = false) const override; virtual class FConnectionDrawingPolicy* CreateConnectionDrawingPolicy(int32 InBackLayerID, int32 InFrontLayerID, float InZoomFactor, const FSlateRect& InClippingRect, class FSlateWindowElementList& InDrawElements, class UEdGraph* InGraphObj) const override; virtual bool ShouldAlwaysPurgeOnModification() const override { return false; } virtual FPinConnectionResponse CanCreateNewNodes(UEdGraphPin* InSourcePin) const override; diff --git a/Engine/Source/Editor/PropertyEditor/Private/DetailLayoutBuilderImpl.cpp b/Engine/Source/Editor/PropertyEditor/Private/DetailLayoutBuilderImpl.cpp index ac43ce2e58ce..8a443fcde6a8 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/DetailLayoutBuilderImpl.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/DetailLayoutBuilderImpl.cpp @@ -69,6 +69,17 @@ IDetailCategoryBuilder& FDetailLayoutBuilderImpl::EditCategory( FName CategoryNa return *CategoryImpl; } +void FDetailLayoutBuilderImpl::GetCategoryNames(TArray& OutCategoryNames) const +{ + OutCategoryNames.Reserve(DefaultCategoryMap.Num() + CustomCategoryMap.Num()); + + TArray TempCategoryNames; + DefaultCategoryMap.GenerateKeyArray(TempCategoryNames); + OutCategoryNames.Append(TempCategoryNames); + CustomCategoryMap.GenerateKeyArray(TempCategoryNames); + OutCategoryNames.Append(TempCategoryNames); +} + IDetailPropertyRow& FDetailLayoutBuilderImpl::AddPropertyToCategory(TSharedPtr InPropertyHandle) { // Get the UProperty itself diff --git a/Engine/Source/Editor/PropertyEditor/Private/DetailLayoutBuilderImpl.h b/Engine/Source/Editor/PropertyEditor/Private/DetailLayoutBuilderImpl.h index b14962767266..525d749372c6 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/DetailLayoutBuilderImpl.h +++ b/Engine/Source/Editor/PropertyEditor/Private/DetailLayoutBuilderImpl.h @@ -30,6 +30,7 @@ public: virtual void GetObjectsBeingCustomized(TArray< TWeakObjectPtr >& OutObjects) const override; virtual void GetStructsBeingCustomized(TArray< TSharedPtr >& OutStructs) const override; virtual IDetailCategoryBuilder& EditCategory(FName CategoryName, const FText& NewLocalizedDisplayName = FText::GetEmpty(), ECategoryPriority::Type CategoryType = ECategoryPriority::Default) override; + virtual void GetCategoryNames(TArray& OutCategoryNames) const override; virtual IDetailPropertyRow& AddPropertyToCategory(TSharedPtr InPropertyHandle) override; virtual FDetailWidgetRow& AddCustomRowToCategory(TSharedPtr InPropertyHandle, const FText& InCustomSearchString, bool bForAdvanced = false) override; virtual IDetailPropertyRow* EditDefaultProperty(TSharedPtr InPropertyHandle) override; diff --git a/Engine/Source/Editor/PropertyEditor/Private/PropertyHandleImpl.cpp b/Engine/Source/Editor/PropertyEditor/Private/PropertyHandleImpl.cpp index d67000aa0109..42f73ead3afa 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/PropertyHandleImpl.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/PropertyHandleImpl.cpp @@ -1067,7 +1067,7 @@ void FPropertyValueImpl::AddChild() PropertyNodePin->GetReadAddress( !!PropertyNodePin->HasNodeFlags(EPropertyNodeFlags::SingleSelectOnly), ReadAddresses, true, false, true ); if ( ReadAddresses.Num() ) { - // determines whether we actually changed any values (if the user clicks the "emtpy" button when the array is already empty, + // determines whether we actually changed any values (if the user clicks the "empty" button when the array is already empty, // we don't want the objects to be marked dirty) bool bNotifiedPreChange = false; @@ -3684,7 +3684,7 @@ FPropertyAccess::Result FPropertyHandleObject::SetValue(const FAssetData& NewVal { if (!PropertyNode->GetProperty()->IsA(USoftObjectProperty::StaticClass())) { - // Make sure the asset is loaded if we are not a string asset reference. + // Make sure the asset is loaded if we are not a soft reference NewValue.GetAsset(); } diff --git a/Engine/Source/Editor/PropertyEditor/Public/DetailLayoutBuilder.h b/Engine/Source/Editor/PropertyEditor/Public/DetailLayoutBuilder.h index 10a1d2b9a7c8..f17e1fbafea6 100644 --- a/Engine/Source/Editor/PropertyEditor/Public/DetailLayoutBuilder.h +++ b/Engine/Source/Editor/PropertyEditor/Public/DetailLayoutBuilder.h @@ -96,6 +96,12 @@ public: */ virtual IDetailCategoryBuilder& EditCategory(FName CategoryName, const FText& NewLocalizedDisplayName = FText::GetEmpty(), ECategoryPriority::Type CategoryType = ECategoryPriority::Default) = 0; + /** + * Gets the current set of existing category names. This includes both categories derived from properties and categories added via EditCategory. + * @param OutCategoryNames The array of category names + */ + virtual void GetCategoryNames(TArray& OutCategoryNames) const = 0; + /** * Adds the property to its given category automatically. Useful in detail customizations which want to preserve categories. * @param InPropertyHandle The handle to the property that you want to add to its own category. diff --git a/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp b/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp index d8e45b360263..c6fa475d4dc8 100644 --- a/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp +++ b/Engine/Source/Editor/UMGEditor/Private/Customizations/UMGDetailCustomizations.cpp @@ -281,6 +281,9 @@ void FBlueprintWidgetCustomization::CreateMulticastEventCustomization(IDetailLay void FBlueprintWidgetCustomization::CustomizeDetails( IDetailLayoutBuilder& DetailLayout ) { static const FName LayoutCategoryKey(TEXT("Layout")); + static const FName LocalizationCategoryKey(TEXT("Localization")); + + DetailLayout.EditCategory(LocalizationCategoryKey, FText::GetEmpty(), ECategoryPriority::Uncommon); TArray< TWeakObjectPtr > OutObjects; DetailLayout.GetObjectsBeingCustomized(OutObjects); diff --git a/Engine/Source/Editor/UnrealEd/Private/ComponentTypeRegistry.cpp b/Engine/Source/Editor/UnrealEd/Private/ComponentTypeRegistry.cpp index 77c71031bfc0..9b1ce4ff8963 100644 --- a/Engine/Source/Editor/UnrealEd/Private/ComponentTypeRegistry.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/ComponentTypeRegistry.cpp @@ -28,6 +28,7 @@ struct FComponentTypeRegistryData : public FTickableEditorObject + , public FGCObject { FComponentTypeRegistryData(); @@ -40,7 +41,10 @@ struct FComponentTypeRegistryData virtual void Tick(float) override; virtual ETickableTickType GetTickableTickType() const override { return ETickableTickType::Always; } virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(FTypeDatabaseUpdater, STATGROUP_Tickables); } - + + /** Implementation of FGCObject */ + virtual void AddReferencedObjects(FReferenceCollector& Collector) override; + // Request a refresh of the components list next frame void Invalidate() { @@ -427,6 +431,19 @@ void FComponentTypeRegistryData::Tick(float) } } +void FComponentTypeRegistryData::AddReferencedObjects(FReferenceCollector& Collector) +{ + for(FComponentClassComboEntryPtr& ComboEntry : ComponentClassList) + { + ComboEntry->AddReferencedObjects(Collector); + } + + for(FComponentTypeEntry& TypeEntry : ComponentTypeList) + { + Collector.AddReferencedObject(TypeEntry.ComponentClass); + } +} + ////////////////////////////////////////////////////////////////////////// // FComponentTypeRegistry diff --git a/Engine/Source/Editor/UnrealEd/Private/CookOnTheFlyServer.cpp b/Engine/Source/Editor/UnrealEd/Private/CookOnTheFlyServer.cpp index 4726ab8028cd..bd9a0820776b 100644 --- a/Engine/Source/Editor/UnrealEd/Private/CookOnTheFlyServer.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/CookOnTheFlyServer.cpp @@ -6878,7 +6878,7 @@ void UCookOnTheFlyServer::StartCookByTheBook( const FCookByTheBookStartupOptions TArray FilesInPath; TSet StartupSoftObjectPackages; - // Get the list of string asset references, for both empty package and all startup packages + // Get the list of soft references, for both empty package and all startup packages GRedirectCollector.ProcessSoftObjectPathPackageList(NAME_None, false, StartupSoftObjectPackages); for (const FName& StartupPackage : CookByTheBookOptions->StartupPackages) diff --git a/Engine/Source/Editor/UnrealEd/Private/Factories/SkeletalMeshImport.cpp b/Engine/Source/Editor/UnrealEd/Private/Factories/SkeletalMeshImport.cpp index 5e059c9cdac1..c48b4b0b245f 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Factories/SkeletalMeshImport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Factories/SkeletalMeshImport.cpp @@ -429,8 +429,8 @@ ExistingSkelMeshData* SaveExistingSkelMeshData(USkeletalMesh* ExistingSkelMesh, int32 OffsetReductionLODIndex = 0; FSkeletalMeshLODInfo* LODInfo = ExistingSkelMesh->GetLODInfo(ReimportSpecificLOD); - ExistingMeshDataPtr->bIsReimportLODReduced = LODInfo->bHasBeenSimplified; - if (LODInfo && LODInfo->bHasBeenSimplified) + ExistingMeshDataPtr->bIsReimportLODReduced = (LODInfo && LODInfo->bHasBeenSimplified); + if (LODInfo && ExistingMeshDataPtr->bIsReimportLODReduced) { //Save the imported LOD reduction settings ExistingMeshDataPtr->ExistingReimportLODReductionSettings = LODInfo->ReductionSettings; @@ -810,6 +810,10 @@ void RestoreExistingSkelMeshData(ExistingSkelMeshData* MeshData, USkeletalMesh* FSkeletalMeshLODInfo& BaseLODInfo = SkeletalMesh->GetLODInfoArray()[ReimportLODIndex]; //Restore the reimport LOD reduction settings BaseLODInfo.ReductionSettings = MeshData->ExistingReimportLODReductionSettings; + if (SkeletalMeshImportedModel && SkeletalMeshImportedModel->OriginalReductionSourceMeshData.IsValidIndex(ReimportLODIndex)) + { + SkeletalMeshImportedModel->OriginalReductionSourceMeshData[ReimportLODIndex]->EmptyBulkData(); + } //Regenerate the reimport LOD GWarn->BeginSlowTask(LOCTEXT("RegenReimportedLOD", "Generating reimported LOD"), true); FSkeletalMeshUpdateContext UpdateContext; diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp index 7d4bee4fa7ef..2f5923eb75b0 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainExport.cpp @@ -3558,11 +3558,22 @@ void FFbxExporter::ExportObjectMetadata(const UObject* ObjectToExport, FbxNode* // Remaining tag follows the format NodeName.PropertyName, so replace '.' with '_' TagAsString.ReplaceInline(TEXT("."), TEXT("_")); - FbxProperty Property = FbxProperty::Create(Node, FbxStringDT, TCHAR_TO_UTF8(*TagAsString)); - FbxString ValueString(TCHAR_TO_UTF8(*MetadataIt.Value)); + if (MetadataIt.Value == TEXT("true") || MetadataIt.Value == TEXT("false")) + { + FbxProperty Property = FbxProperty::Create(Node, FbxBoolDT, TCHAR_TO_UTF8(*TagAsString)); + FbxBool ValueBool = MetadataIt.Value == TEXT("true") ? true : false; - Property.Set(ValueString); - Property.ModifyFlag(FbxPropertyFlags::eUserDefined, true); + Property.Set(ValueBool); + Property.ModifyFlag(FbxPropertyFlags::eUserDefined, true); + } + else + { + FbxProperty Property = FbxProperty::Create(Node, FbxStringDT, TCHAR_TO_UTF8(*TagAsString)); + FbxString ValueString(TCHAR_TO_UTF8(*MetadataIt.Value)); + + Property.Set(ValueString); + Property.ModifyFlag(FbxPropertyFlags::eUserDefined, true); + } } } } diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainImport.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainImport.cpp index accf90054dad..00c572ab66bb 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainImport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxMainImport.cpp @@ -3107,7 +3107,7 @@ FString GetFbxPropertyStringValue(const FbxProperty& Property) return ValueStr; } -void FFbxImporter::ImportNodeCustomProperties(UObject* Object, FbxNode* Node) +void FFbxImporter::ImportNodeCustomProperties(UObject* Object, FbxNode* Node, bool bPrefixTagWithNodeName) { if (!Object || !Node) { @@ -3125,7 +3125,7 @@ void FFbxImporter::ImportNodeCustomProperties(UObject* Object, FbxNode* Node) // Prefix the FBX metadata tag to make it distinguishable from other metadata // so that it can be exportable through FBX export FString MetadataTag = UTF8_TO_TCHAR(CurrentProperty.GetName()); - if (!MetadataTag.StartsWith(NodeName)) + if (bPrefixTagWithNodeName && !MetadataTag.StartsWith(NodeName)) { // Append the node name in the tag since all the metadata will be flattened on the Object MetadataTag = NodeName + TEXT(".") + MetadataTag; @@ -3141,7 +3141,7 @@ void FFbxImporter::ImportNodeCustomProperties(UObject* Object, FbxNode* Node) int NumChildren = Node->GetChildCount(); for (int i = 0; i < NumChildren; ++i) { - ImportNodeCustomProperties(Object, Node->GetChild(i)); + ImportNodeCustomProperties(Object, Node->GetChild(i), bPrefixTagWithNodeName); } } diff --git a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshExport.cpp b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshExport.cpp index 8b52ac6b7f2c..10a787970786 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshExport.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Fbx/FbxSkeletalMeshExport.cpp @@ -530,11 +530,22 @@ void ExportObjectMetadataToBones(const UObject* ObjectToExport, const TArraySkeleton, SkeletonNode); + ImportNodeCustomProperties(SkeletalMesh->Skeleton, SkeletonNode, true); } // ComponentContexts will now go out of scope, causing components to be re-registered diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp index a1ff189a9515..57a1181adbb9 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp @@ -1420,20 +1420,9 @@ UClass* FBlueprintEditorUtils::RegenerateBlueprintClass(UBlueprint* Blueprint, U } else { - if (Blueprint->IsGeneratedClassAuthoritative() && (Blueprint->GeneratedClass != nullptr)) + if (Blueprint->GeneratedClass != nullptr) { RemoveStaleFunctions(Cast(Blueprint->GeneratedClass), Blueprint); - - check(PreviousCDO != nullptr); - check(Blueprint->SkeletonGeneratedClass != nullptr); - - // We now know we're a data-only blueprint on the outer pass (generate class is valid), where generated class is authoritative - // If the PreviousCDO is to the skeleton, then it will corrupt data when copied over the AuthoriativeClass later on in this function - if (PreviousCDO == Blueprint->SkeletonGeneratedClass->GetDefaultObject()) - { - check(Blueprint->PRIVATE_InnermostPreviousCDO == nullptr); - Blueprint->PRIVATE_InnermostPreviousCDO = Blueprint->GeneratedClass->GetDefaultObject(); - } } // No actual compilation work to be done, but try to conform the class and fix up anything that might need to be updated if the native base class has changed in any way @@ -1470,81 +1459,12 @@ UClass* FBlueprintEditorUtils::RegenerateBlueprintClass(UBlueprint* Blueprint, U if (!FKismetEditorUtilities::IsClassABlueprintSkeleton(ClassToRegenerate)) { - if (Blueprint->bRecompileOnLoad) - { - // Verify that we had a skeleton generated class if we had a previous CDO, to make sure we have something to copy into - check((Blueprint->BlueprintType == BPTYPE_MacroLibrary) || Blueprint->SkeletonGeneratedClass); - - const bool bPreviousMatchesGenerated = (PreviousCDO == Blueprint->GeneratedClass->GetDefaultObject()); - - if (Blueprint->BlueprintType != BPTYPE_MacroLibrary) - { - UObject* CDOThatKickedOffCOL = PreviousCDO; - if (Blueprint->IsGeneratedClassAuthoritative() && !bPreviousMatchesGenerated && Blueprint->PRIVATE_InnermostPreviousCDO) - { - PreviousCDO = Blueprint->PRIVATE_InnermostPreviousCDO; - } - } - - // If this is the top of the compile-on-load stack for this object, copy the old CDO properties to the newly created one unless they are the same - UClass* AuthoritativeClass = (Blueprint->IsGeneratedClassAuthoritative() ? Blueprint->GeneratedClass : Blueprint->SkeletonGeneratedClass); - if (AuthoritativeClass != nullptr && PreviousCDO != AuthoritativeClass->GetDefaultObject()) - { - TGuardValue GuardTemplateNameFlag(GCompilingBlueprint, true); - - // Make sure the previous CDO has been fully loaded before we use it - FBlueprintEditorUtils::PreloadMembers(PreviousCDO); - - // Copy over the properties from the old CDO to the new - PropagateParentBlueprintDefaults(AuthoritativeClass); - UObject* NewCDO = AuthoritativeClass->GetDefaultObject(); - { - FSaveActorFlagsHelper SaveActorFlags(AuthoritativeClass); - UEditorEngine::FCopyPropertiesForUnrelatedObjectsParams CopyDetails; - CopyDetails.bAggressiveDefaultSubobjectReplacement = true; - CopyDetails.bDoDelta = false; - CopyDetails.bCopyDeprecatedProperties = true; - CopyDetails.bSkipCompilerGeneratedDefaults = true; - UEditorEngine::CopyPropertiesForUnrelatedObjects(PreviousCDO, NewCDO, CopyDetails); - } - - if (bRegenerated) - { - PatchCDOSubobjectsIntoExport(PreviousCDO, NewCDO); - // We purposefully do not call post load here, it happens later on in the normal flow - } - - // Update the custom property list used in post construction logic to include native class properties for which the regenerated Blueprint CDO now differs from the native CDO. - if (UBlueprintGeneratedClass* BPGClass = Cast(AuthoritativeClass)) - { - BPGClass->UpdateCustomPropertyListForPostConstruction(); - } - } - - Blueprint->PRIVATE_InnermostPreviousCDO = nullptr; - } - else + if (!Blueprint->bRecompileOnLoad) { // If we didn't recompile, we still need to propagate flags, and instance components FKismetEditorUtilities::ConformBlueprintFlagsAndComponents(Blueprint); } - // If this is the top of the compile-on-load stack for this object, copy the old CDO properties to the newly created one - if (!Blueprint->IsGeneratedClassAuthoritative() && Blueprint->GeneratedClass != nullptr) - { - TGuardValue GuardTemplateNameFlag(GCompilingBlueprint, true); - - UObject* SkeletonCDO = Blueprint->SkeletonGeneratedClass->GetDefaultObject(); - UObject* GeneratedCDO = Blueprint->GeneratedClass->GetDefaultObject(); - - UEditorEngine::FCopyPropertiesForUnrelatedObjectsParams CopyDetails; - CopyDetails.bAggressiveDefaultSubobjectReplacement = false; - CopyDetails.bDoDelta = false; - UEditorEngine::CopyPropertiesForUnrelatedObjects(SkeletonCDO, GeneratedCDO, CopyDetails); - - Blueprint->SetLegacyGeneratedClassIsAuthoritative(); - } - // Now that the CDO is valid, update the OwnedComponents, in case we've added or removed native components if (AActor* MyActor = Cast(Blueprint->GeneratedClass->GetDefaultObject())) { @@ -3214,7 +3134,7 @@ bool FBlueprintEditorUtils::IsBlueprintConst(const UBlueprint* Blueprint) { // Macros aren't marked as const because they can modify variables when instanced into a non const class // and will be caught at compile time if they're modifying variables on a const class. - return Blueprint->BlueprintType == BPTYPE_Const; + return Blueprint && Blueprint->BlueprintType == BPTYPE_Const; } bool FBlueprintEditorUtils::IsBlutility(const UBlueprint* Blueprint) @@ -3230,7 +3150,7 @@ bool FBlueprintEditorUtils::IsBlutility(const UBlueprint* Blueprint) bool FBlueprintEditorUtils::IsActorBased(const UBlueprint* Blueprint) { - return Blueprint->ParentClass && Blueprint->ParentClass->IsChildOf(AActor::StaticClass()); + return Blueprint && Blueprint->ParentClass && Blueprint->ParentClass->IsChildOf(AActor::StaticClass()); } bool FBlueprintEditorUtils::IsDelegateSignatureGraph(const UEdGraph* Graph) @@ -3256,22 +3176,22 @@ bool FBlueprintEditorUtils::IsMathExpressionGraph(const UEdGraph* InGraph) bool FBlueprintEditorUtils::IsInterfaceBlueprint(const UBlueprint* Blueprint) { - return (Blueprint->BlueprintType == BPTYPE_Interface); + return (Blueprint && Blueprint->BlueprintType == BPTYPE_Interface); } bool FBlueprintEditorUtils::IsLevelScriptBlueprint(const UBlueprint* Blueprint) { - return (Blueprint->BlueprintType == BPTYPE_LevelScript); + return (Blueprint && Blueprint->BlueprintType == BPTYPE_LevelScript); } bool FBlueprintEditorUtils::IsAnonymousBlueprintClass(const UClass* Class) { - return (Class->GetOutermost()->ContainsMap()); + return (Class && Class->GetOutermost()->ContainsMap()); } ULevel* FBlueprintEditorUtils::GetLevelFromBlueprint(const UBlueprint* Blueprint) { - return Cast(Blueprint->GetOuter()); + return (Blueprint ? Cast(Blueprint->GetOuter()) : nullptr); } bool FBlueprintEditorUtils::SupportsConstructionScript(const UBlueprint* Blueprint) @@ -4220,10 +4140,20 @@ void FBlueprintEditorUtils::GetHiddenPinsForFunction(UEdGraph const* Graph, UFun const FName& Key = It.Key(); - if (Key == NAME_LatentInfo || Key == NAME_HidePin || Key == FBlueprintMetadata::MD_ExpandEnumAsExecs) + if (Key == NAME_LatentInfo || Key == NAME_HidePin) { HiddenPins.Add(*It.Value()); } + else if (Key == FBlueprintMetadata::MD_ExpandEnumAsExecs) + { + TArray EnumPinNames; + UK2Node_CallFunction::GetExpandEnumPinNames(Function, EnumPinNames); + + for (const FName& EnumName : EnumPinNames) + { + HiddenPins.Add(EnumName); + } + } else if (Key == FBlueprintMetadata::MD_InternalUseParam) { const FName HiddenPinName = *It.Value(); @@ -4809,6 +4739,13 @@ void FBlueprintEditorUtils::ChangeMemberVariableType(UBlueprint* Blueprint, cons if(bChangeVariableType) { + const bool bBecameBoolean = Variable.VarType.PinCategory != UEdGraphSchema_K2::PC_Boolean && NewPinType.PinCategory == UEdGraphSchema_K2::PC_Boolean; + const bool bBecameNotBoolean = Variable.VarType.PinCategory == UEdGraphSchema_K2::PC_Boolean && NewPinType.PinCategory != UEdGraphSchema_K2::PC_Boolean; + if (bBecameBoolean || bBecameNotBoolean) + { + Variable.FriendlyName = FName::NameToDisplayString(Variable.VarName.ToString(), bBecameBoolean); + } + Variable.VarType = NewPinType; if(Variable.VarType.IsSet() || Variable.VarType.IsMap()) diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/ComponentEditorUtils.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/ComponentEditorUtils.cpp index 74f3acf6a957..8fdfbe272c54 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/ComponentEditorUtils.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/ComponentEditorUtils.cpp @@ -428,7 +428,7 @@ void FComponentEditorUtils::PasteComponents(TArray& OutPastedC check(NewActorComponent); // Relocate the instance from the transient package to the Actor and assign it a unique object name - FString NewComponentName = FComponentEditorUtils::GenerateValidVariableName(NewActorComponent->GetClass(), TargetActor); + FString NewComponentName = FComponentEditorUtils::GenerateValidVariableNameFromAsset(NewActorComponent, TargetActor); NewActorComponent->Rename(*NewComponentName, TargetActor, REN_DontCreateRedirectors | REN_DoNotDirty); if (USceneComponent* NewSceneComponent = Cast(NewActorComponent)) @@ -631,8 +631,7 @@ UActorComponent* FComponentEditorUtils::DuplicateComponent(UActorComponent* Temp if (!TemplateComponent->IsVisualizationComponent() && Actor) { Actor->Modify(); - UClass* ComponentClass = TemplateComponent->GetClass(); - FName NewComponentName = *FComponentEditorUtils::GenerateValidVariableName(ComponentClass, Actor); + FName NewComponentName = *FComponentEditorUtils::GenerateValidVariableNameFromAsset(TemplateComponent, Actor); bool bKeepWorldLocationOnAttach = false; diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/Kismet2.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/Kismet2.cpp index 6ab82140fb18..07b802180057 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/Kismet2.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/Kismet2.cpp @@ -1486,10 +1486,13 @@ void FKismetEditorUtilities::AddComponentsToBlueprint(UBlueprint* Blueprint, TAr { for (UBlueprint* ParentBlueprint : ParentBPStack) { - ParentSCSNode = ParentBlueprint->SimpleConstructionScript->FindSCSNode(SceneComponent->GetAttachParent()->GetFName()); - if (ParentSCSNode) + if (ParentBlueprint->SimpleConstructionScript) { - break; + ParentSCSNode = ParentBlueprint->SimpleConstructionScript->FindSCSNode(SceneComponent->GetAttachParent()->GetFName()); + if (ParentSCSNode) + { + break; + } } } } diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetDebugUtilities.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetDebugUtilities.cpp index 946f5396bbe0..93c4483952e6 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetDebugUtilities.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetDebugUtilities.cpp @@ -1273,7 +1273,7 @@ FKismetDebugUtilities::EWatchTextResult FKismetDebugUtilities::FindDebuggingData { void* PropertyValue = SelfPinProperty->ContainerPtrToValuePtr(SelfPinData); UObject* TempActiveObject = SelfPinPropertyBase->GetObjectPropertyValue(PropertyValue); - if (TempActiveObject) + if (TempActiveObject && TempActiveObject != ActiveObject) { return FindDebuggingData(Blueprint, TempActiveObject, WatchPin, OutProperty, OutData, OutDelta, OutParent); } diff --git a/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetReinstanceUtilities.cpp b/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetReinstanceUtilities.cpp index 67eb0be2e03f..e2e35bd0424d 100644 --- a/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetReinstanceUtilities.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/Kismet2/KismetReinstanceUtilities.cpp @@ -1678,18 +1678,6 @@ static void ReplaceObjectHelper(UObject*& OldObject, UClass* OldClass, UObject*& UEditorEngine::CopyPropertiesForUnrelatedObjects(OldObject, NewUObject); InstancedPropertyUtils::FArchiveInsertInstancedSubObjects InstancedSubObjSpawner(NewUObject, InstancedPropertyMap); - if (UAnimInstance* AnimTree = Cast(NewUObject)) - { - // Initialising the anim instance isn't enough to correctly set up the skeletal mesh again in a - // paused world, need to initialise the skeletal mesh component that contains the anim instance. - if (USkeletalMeshComponent* SkelComponent = Cast(AnimTree->GetOuter())) - { - SkelComponent->InitAnim(true); - // compile change ignores motion vector, so ignore this. - SkelComponent->ClearMotionVector(); - } - } - UWorld* RegisteredWorld = nullptr; bool bWasRegistered = false; if (bIsComponent) @@ -2068,12 +2056,7 @@ void FBlueprintCompileReinstancer::ReplaceInstancesOfClass_Inner(TMap SourceObjects; - TArray DstObjects; - OldToNewInstanceMap.GenerateKeyArray(SourceObjects); - OldToNewInstanceMap.GenerateValueArray(DstObjects); // Also look for references in new spawned objects. - - SourceObjects.Append(DstObjects); if (InOriginalCDO) { @@ -2126,6 +2109,27 @@ void FBlueprintCompileReinstancer::ReplaceInstancesOfClass_Inner(TMap(*NewObject)) + { + // Initialising the anim instance isn't enough to correctly set up the skeletal mesh again in a + // paused world, need to initialise the skeletal mesh component that contains the anim instance. + if (USkeletalMeshComponent* SkelComponent = Cast(AnimTree->GetOuter())) + { + SkelComponent->ClearAnimScriptInstance(); + SkelComponent->InitAnim(true); + // compile change ignores motion vector, so ignore this. + SkelComponent->ClearMotionVector(); + } + } + } + } + if(SelectedActors) { diff --git a/Engine/Source/Editor/UnrealEd/Private/LODUtilities.cpp b/Engine/Source/Editor/UnrealEd/Private/LODUtilities.cpp index 315dbd53b2bb..6a65d0e77870 100644 --- a/Engine/Source/Editor/UnrealEd/Private/LODUtilities.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/LODUtilities.cpp @@ -422,6 +422,11 @@ void CreateLODMorphTarget(USkeletalMesh* SkeletalMesh, FReductionBaseSkeletalMes ReductionBaseSkeletalMeshBulkData->LoadReductionData(TempBaseLODModel, BaseLODMorphTargetData); } + FSkeletalMeshModel* SkeletalMeshModel = SkeletalMesh->GetImportedModel(); + const FSkeletalMeshLODModel& TargetLODModel = SkeletalMeshModel->LODModels[DestinationLOD]; + + bool bInitializeMorphData = false; + for (UMorphTarget *MorphTarget : SkeletalMesh->MorphTargets) { if (!MorphTarget->HasDataForLOD(SourceLOD)) @@ -517,11 +522,17 @@ void CreateLODMorphTarget(USkeletalMesh* SkeletalMesh, FReductionBaseSkeletalMes } } - FSkeletalMeshModel* SkeletalMeshModel = SkeletalMesh->GetImportedModel(); - const FSkeletalMeshLODModel& TargetLODModel = SkeletalMeshModel->LODModels[DestinationLOD]; //Register the new morph target on the target LOD MorphTarget->PopulateDeltas(NewMorphTargetDeltas, DestinationLOD, TargetLODModel.Sections, false, true); - SkeletalMesh->RegisterMorphTarget(MorphTarget); + if (MorphTarget->HasValidData()) + { + bInitializeMorphData |= SkeletalMesh->RegisterMorphTarget(MorphTarget, false); + } + } + + if (bInitializeMorphData) + { + SkeletalMesh->InitMorphTargetsAndRebuildRenderData(); } } @@ -545,7 +556,7 @@ void FLODUtilities::ClearGeneratedMorphTarget(USkeletalMesh* SkeletalMesh, int32 continue; } - if (MorphTarget->MorphLODModels[TargetLOD].bGeneratedByEngine) + //if (MorphTarget->MorphLODModels[TargetLOD].bGeneratedByEngine) { MorphTarget->MorphLODModels[TargetLOD].Reset(); @@ -870,6 +881,7 @@ void FLODUtilities::RestoreSkeletalMeshLODImportedData(USkeletalMesh* SkeletalMe //Copy the SkeletalMeshLODModel SkeletalMesh->GetImportedModel()->LODModels[LodIndex] = ImportedBaseLODModel; //Copy the morph target deltas + bool bInitMorphTargetData = false; for (UMorphTarget *MorphTarget : SkeletalMesh->MorphTargets) { if (!ImportedBaseLODMorphTargetData.Contains(MorphTarget->GetFullName())) @@ -879,8 +891,9 @@ void FLODUtilities::RestoreSkeletalMeshLODImportedData(USkeletalMesh* SkeletalMe TArray& ImportedDeltas = ImportedBaseLODMorphTargetData[MorphTarget->GetFullName()]; MorphTarget->PopulateDeltas(ImportedDeltas, LodIndex, SkeletalMesh->GetImportedModel()->LODModels[LodIndex].Sections, false, false); - SkeletalMesh->RegisterMorphTarget(MorphTarget); + bInitMorphTargetData |= SkeletalMesh->RegisterMorphTarget(MorphTarget, false); } + SkeletalMesh->InitMorphTargetsAndRebuildRenderData(); //Empty the bulkdata since we restore it SkeletalMesh->GetImportedModel()->OriginalReductionSourceMeshData[LodIndex]->EmptyBulkData(); diff --git a/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp b/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp index 6a929f113e00..35c2777c8527 100644 --- a/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/PlayLevel.cpp @@ -3648,7 +3648,7 @@ UWorld* UEditorEngine::CreatePIEWorldByDuplication(FWorldContext &WorldContext, // Reset any GUID fixups with lazy pointers FLazyObjectPtr::ResetPIEFixups(); - // Prepare string asset references for fixup + // Prepare soft object paths for fixup FSoftObjectPath::AddPIEPackageName(FName(*PlayWorldMapName)); for (ULevelStreaming* StreamingLevel : InWorld->GetStreamingLevels()) { diff --git a/Engine/Source/Editor/UnrealEd/Private/SComponentClassCombo.cpp b/Engine/Source/Editor/UnrealEd/Private/SComponentClassCombo.cpp index 22cea2811254..bfb26e6179bd 100644 --- a/Engine/Source/Editor/UnrealEd/Private/SComponentClassCombo.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/SComponentClassCombo.cpp @@ -26,6 +26,22 @@ FString FComponentClassComboEntry::GetClassName() const return ComponentClass != nullptr ? ComponentClass->GetDisplayNameText().ToString() : ComponentName; } +void FComponentClassComboEntry::AddReferencedObjects(FReferenceCollector& Collector) +{ + UClass* RawClass = ComponentClass; + Collector.AddReferencedObject(RawClass); + if(RawClass && RawClass->IsChildOf(UActorComponent::StaticClass())) + { + ComponentClass = RawClass; + } + else + { + ComponentClass = nullptr; + } + + Collector.AddReferencedObject(IconClass); +} + void SComponentClassCombo::Construct(const FArguments& InArgs) { PrevSelectedIndex = INDEX_NONE; diff --git a/Engine/Source/Editor/UnrealEd/Private/ThumbnailHelpers.cpp b/Engine/Source/Editor/UnrealEd/Private/ThumbnailHelpers.cpp index c9d8b83d9804..c35167f0cba6 100644 --- a/Engine/Source/Editor/UnrealEd/Private/ThumbnailHelpers.cpp +++ b/Engine/Source/Editor/UnrealEd/Private/ThumbnailHelpers.cpp @@ -1044,7 +1044,7 @@ void FClassActorThumbnailScene::SpawnPreviewActor(UClass* InClass) PreviewActor->Destroy(); PreviewActor = nullptr; } - if (InClass) + if (InClass && !InClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_Abstract)) { // Create preview actor FActorSpawnParameters SpawnInfo; diff --git a/Engine/Source/Editor/UnrealEd/Public/CookerSettings.h b/Engine/Source/Editor/UnrealEd/Public/CookerSettings.h index ea741f5addba..96ba266c716d 100644 --- a/Engine/Source/Editor/UnrealEd/Public/CookerSettings.h +++ b/Engine/Source/Editor/UnrealEd/Public/CookerSettings.h @@ -89,7 +89,7 @@ public: UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooker, AdvancedDisplay) bool bCompileBlueprintsInDevelopmentMode; - /** Generate optimized component data to speed up Blueprint construction at runtime. This option can increase the overall Blueprint memory usage in a cooked build. */ + /** Generate optimized component data to speed up Blueprint construction at runtime. This option can increase the overall Blueprint memory usage in a cooked build. Requires Event-Driven Loading (EDL), which is enabled by default. */ UPROPERTY(GlobalConfig, EditAnywhere, Category = Cooker, AdvancedDisplay, meta = (DisplayName = "Generate optimized Blueprint component data")) EBlueprintComponentDataCookingMethod BlueprintComponentDataCookingMethod; diff --git a/Engine/Source/Editor/UnrealEd/Public/FbxImporter.h b/Engine/Source/Editor/UnrealEd/Public/FbxImporter.h index 1a134e8bbede..2ccb3f430e58 100644 --- a/Engine/Source/Editor/UnrealEd/Public/FbxImporter.h +++ b/Engine/Source/Editor/UnrealEd/Public/FbxImporter.h @@ -1719,7 +1719,7 @@ protected: bool RetrievePoseFromBindPose(const TArray& NodeArray, FbxArray & PoseArray) const; /** Import the user-defined properties on the node as FBX metadata on the object */ - void ImportNodeCustomProperties(UObject* Object, FbxNode* Node); + void ImportNodeCustomProperties(UObject* Object, FbxNode* Node, bool bPrefixTagWithNodeName = false); public: /** Import and set up animation related data from mesh **/ diff --git a/Engine/Source/Editor/UnrealEd/Public/SComponentClassCombo.h b/Engine/Source/Editor/UnrealEd/Public/SComponentClassCombo.h index 903cc8d2bcb8..7506cac1de1a 100644 --- a/Engine/Source/Editor/UnrealEd/Public/SComponentClassCombo.h +++ b/Engine/Source/Editor/UnrealEd/Public/SComponentClassCombo.h @@ -148,6 +148,8 @@ public: FName GetIconOverrideBrushName() const { return CustomizationArgs.IconOverrideBrushName; } int32 GetSortPriority() const { return CustomizationArgs.SortPriority; } + + void AddReferencedObjects(FReferenceCollector& Collector); private: TSubclassOf ComponentClass; const UClass* IconClass; diff --git a/Engine/Source/Editor/WorldBrowser/Private/LevelCollectionModel.cpp b/Engine/Source/Editor/WorldBrowser/Private/LevelCollectionModel.cpp index cf592f9b5686..9c1a6b4ef866 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/LevelCollectionModel.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/LevelCollectionModel.cpp @@ -664,6 +664,11 @@ void FLevelCollectionModel::UnloadLevels(const FLevelModelList& InLevelList) EditorLevelUtils::RemoveLevelFromWorld(Level); } } + else if (ULevelStreaming* StreamingLevel = Cast(LevelModel->GetNodeObject())) + { + StreamingLevel->MarkPendingKill(); + ThisWorld->RemoveStreamingLevel(StreamingLevel); + } } BroadcastPostLevelsUnloaded(); diff --git a/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.cpp b/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.cpp index 32f6d440760f..aa370db8eadf 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.cpp +++ b/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.cpp @@ -184,7 +184,7 @@ void FStreamingLevelCollectionModel::BindCommands() ActionList.MapAction( Commands.World_RemoveSelectedLevels, FExecuteAction::CreateSP( this, &FStreamingLevelCollectionModel::UnloadSelectedLevels_Executed ), - FCanExecuteAction::CreateSP( this, &FLevelCollectionModel::AreAllSelectedLevelsEditable ) ); + FCanExecuteAction::CreateSP( this, &FStreamingLevelCollectionModel::AreAllSelectedLevelsRemovable ) ); ActionList.MapAction( Commands.World_MergeSelectedLevels, FExecuteAction::CreateSP( this, &FStreamingLevelCollectionModel::MergeSelectedLevels_Executed ), @@ -566,6 +566,19 @@ bool FStreamingLevelCollectionModel::IsStreamingMethodChecked(UClass* InClass) c return false; } +bool FStreamingLevelCollectionModel::AreAllSelectedLevelsRemovable() const +{ + for (const TSharedPtr& LevelModel : SelectedLevelsList) + { + if (LevelModel->IsLocked() || LevelModel->IsPersistent()) + { + return false; + } + } + + return AreAnyLevelsSelected(); +} + void FStreamingLevelCollectionModel::SetStreamingLevelsClass_Executed(UClass* InClass) { // First prompt to save the selected levels, as changing the streaming method will unload/reload them diff --git a/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.h b/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.h index d03886261aa5..653d78950fad 100644 --- a/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.h +++ b/Engine/Source/Editor/WorldBrowser/Private/StreamingLevels/StreamingLevelCollectionModel.h @@ -106,6 +106,9 @@ private: /** Checks if the passed in streaming method is the current streaming method */ bool IsStreamingMethodChecked(UClass* InClass) const; + /** @return whether the selected levels are allowed to be removed from the world */ + bool AreAllSelectedLevelsRemovable() const; + /** Changes the streaming method for the selected levels. */ void SetStreamingLevelsClass_Executed(UClass* InClass); diff --git a/Engine/Source/Programs/DotNETCommon/DotNETUtilities/CommandLineArguments.cs b/Engine/Source/Programs/DotNETCommon/DotNETUtilities/CommandLineArguments.cs index b2d8b8f993bf..91205000dc46 100644 --- a/Engine/Source/Programs/DotNETCommon/DotNETUtilities/CommandLineArguments.cs +++ b/Engine/Source/Programs/DotNETCommon/DotNETUtilities/CommandLineArguments.cs @@ -639,9 +639,22 @@ namespace Tools.DotNETCommon } // Apply the value to the field - if(ApplyArgument(TargetObject, FieldInfo, Argument, ValueText, AssignedArgument)) + if(Attribute.ListSeparator == 0) { - AssignedArgument = Argument; + if(ApplyArgument(TargetObject, FieldInfo, Argument, ValueText, AssignedArgument)) + { + AssignedArgument = Argument; + } + } + else + { + foreach(string ItemValueText in ValueText.Split(Attribute.ListSeparator)) + { + if(ApplyArgument(TargetObject, FieldInfo, Argument, ItemValueText, AssignedArgument)) + { + AssignedArgument = Argument; + } + } } // Mark this argument as used diff --git a/Engine/Source/Programs/UnrealBuildTool/Modes/GenerateProjectFilesMode.cs b/Engine/Source/Programs/UnrealBuildTool/Modes/GenerateProjectFilesMode.cs index aa319e250800..4c9f2d6fdfe5 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Modes/GenerateProjectFilesMode.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Modes/GenerateProjectFilesMode.cs @@ -164,7 +164,8 @@ namespace UnrealBuildTool } // Check there are no superfluous command line arguments - Arguments.CheckAllArgumentsUsed(); + // TODO (still pass raw arguments below) + // Arguments.CheckAllArgumentsUsed(); // Now generate project files ProjectFileGenerator.bGenerateProjectFiles = true; diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Android/AndroidToolChain.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Android/AndroidToolChain.cs index 2381c3597734..6a908a11dc7b 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Android/AndroidToolChain.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Android/AndroidToolChain.cs @@ -334,20 +334,20 @@ namespace UnrealBuildTool if (NDKDefineInt >= 140200) { ToolchainParamsArm = " -target armv7-none-linux-androideabi" + - " --sysroot=\"" + Path.Combine(NDKPath, "sysroot") + "\"" + - " -isystem " + Path.Combine(NDKPath, "sysroot/usr/include/arm-linux-androideabi/") + + " --sysroot='" + Path.Combine(NDKPath, "sysroot") + "'" + + " -isystem '" + Path.Combine(NDKPath, "sysroot/usr/include/arm-linux-androideabi/") + "'" + " -D__ANDROID_API__=" + NDKApiLevel32Int; ToolchainParamsArm64 = " -target aarch64-none-linux-android" + - " --sysroot=\"" + Path.Combine(NDKPath, "sysroot") + "\"" + - " -isystem " + Path.Combine(NDKPath, "sysroot/usr/include/aarch64-linux-android/") + + " --sysroot='" + Path.Combine(NDKPath, "sysroot") + "'" + + " -isystem '" + Path.Combine(NDKPath, "sysroot/usr/include/aarch64-linux-android/") + "'" + " -D__ANDROID_API__=" + NDKApiLevel64Int; ToolchainParamsx86 = " -target i686-none-linux-android" + - " --sysroot=\"" + Path.Combine(NDKPath, "sysroot") + "\"" + - " -isystem " + Path.Combine(NDKPath, "sysroot/usr/include/i686-linux-android/") + + " --sysroot='" + Path.Combine(NDKPath, "sysroot") + "'" + + " -isystem '" + Path.Combine(NDKPath, "sysroot/usr/include/i686-linux-android/") + "'" + " -D__ANDROID_API__=" + NDKApiLevel32Int; ToolchainParamsx64 = " -target x86_64-none-linux-android" + - " --sysroot=\"" + Path.Combine(NDKPath, "sysroot") + "\"" + - " -isystem " + Path.Combine(NDKPath, "sysroot/usr/include/x86_64-linux-android/") + + " --sysroot='" + Path.Combine(NDKPath, "sysroot") + "'" + + " -isystem '" + Path.Combine(NDKPath, "sysroot/usr/include/x86_64-linux-android/") + "'" + " -D__ANDROID_API__=" + NDKApiLevel64Int; } else @@ -1455,14 +1455,20 @@ namespace UnrealBuildTool CompileAction.WorkingDirectory = UnrealBuildTool.EngineSourceDirectory; if(bExecuteCompilerThroughShell) { + string FixedClangPath = ClangPath; + if (FixedClangPath.Contains(' ')) + { + FixedClangPath = "'" + FixedClangPath + "'"; + } + CompileAction.CommandPath = BuildHostPlatform.Current.Shell; if (BuildHostPlatform.Current.ShellType == ShellType.Cmd) { - CompileAction.CommandArguments = String.Format("/c \"{0} {1}\"", Utils.MakePathSafeToUseWithCommandLine(ClangPath), ResponseArgument); + CompileAction.CommandArguments = String.Format("/c \"{0} {1}\"", FixedClangPath, ResponseArgument); } else { - CompileAction.CommandArguments = String.Format("-c \'{0} {1}\'", Utils.MakePathSafeToUseWithCommandLine(ClangPath), ResponseArgument); + CompileAction.CommandArguments = String.Format("-c \'{0} {1}\'", FixedClangPath, ResponseArgument); } CompileAction.CommandDescription = "Compile"; } @@ -1688,13 +1694,19 @@ namespace UnrealBuildTool if(bExecuteCompilerThroughShell) { + string LinkCommandPath = LinkAction.CommandPath.FullName; + if (LinkCommandPath.Contains(' ')) + { + LinkCommandPath = "'" + LinkCommandPath + "'"; + } + if (BuildHostPlatform.Current.ShellType == ShellType.Cmd) { - LinkAction.CommandArguments = String.Format("/c \"{0} {1}\"", LinkAction.CommandPath, LinkAction.CommandArguments); + LinkAction.CommandArguments = String.Format("/c \"{0} {1}\"", LinkCommandPath, LinkAction.CommandArguments); } else { - LinkAction.CommandArguments = String.Format("-c \'{0} {1}\'", LinkAction.CommandPath, LinkAction.CommandArguments); + LinkAction.CommandArguments = String.Format("-c \'{0} {1}\'", LinkCommandPath, LinkAction.CommandArguments); } LinkAction.CommandPath = BuildHostPlatform.Current.Shell; LinkAction.CommandDescription = "Link"; diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/Android/UEDeployAndroid.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/Android/UEDeployAndroid.cs index ce2ca6aa61e9..342623e8a758 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/Android/UEDeployAndroid.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/Android/UEDeployAndroid.cs @@ -18,7 +18,7 @@ namespace UnrealBuildTool private const string XML_HEADER = ""; // Minimum Android SDK that must be used for Java compiling - readonly int MinimumSDKLevel = 23; + readonly int MinimumSDKLevel = 28; // Minimum SDK version needed for Gradle based on active plugins (14 is for Google Play Services 11.0.4) private int MinimumSDKLevelForGradle = 14; @@ -248,7 +248,16 @@ namespace UnrealBuildTool SDKLevelInt = GetApiLevelInt(SDKLevel); if (SDKLevelInt < MinimumSDKLevel) { - throw new BuildException("Can't make an APK without SDK API 'android-" + MinimumSDKLevel.ToString() + "' minimum installed"); + if (bGradleEnabled) + { + SDKLevelInt = MinimumSDKLevel; + SDKLevel = "android-" + MinimumSDKLevel.ToString(); + Log.TraceInformation("Gradle will attempt to download SDK API level {0}", SDKLevelInt); + } + else + { + throw new BuildException("Can't make an APK without SDK API 'android-" + MinimumSDKLevel.ToString() + "' minimum installed"); + } } } @@ -2118,6 +2127,8 @@ namespace UnrealBuildTool Ini.GetString("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "Orientation", out Orientation); bool EnableFullScreen; Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bFullScreen", out EnableFullScreen); + bool bUseDisplayCutout; + Ini.GetBool("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "bUseDisplayCutout", out bUseDisplayCutout); List ExtraManifestNodeTags; Ini.GetArray("/Script/AndroidRuntimeSettings.AndroidRuntimeSettings", "ExtraManifestNodeTags", out ExtraManifestNodeTags); List ExtraApplicationNodeTags; @@ -2421,6 +2432,7 @@ namespace UnrealBuildTool Text.AppendLine(string.Format("\t\t", CookedFlavors)); Text.AppendLine(string.Format("\t\t", bValidateTextureFormats ? "true" : "false")); Text.AppendLine(string.Format("\t\t", bUseExternalFilesDir ? "true" : "false")); + Text.AppendLine(string.Format("\t\t", bUseDisplayCutout ? "true" : "false")); Text.AppendLine(string.Format("\t\t", bAllowIMU ? "true" : "false")); Text.AppendLine(string.Format("\t\t", bSupportsVulkan ? "true" : "false")); if (bUseNEONForArmV7) diff --git a/Engine/Source/Programs/UnrealBuildTool/Platform/TVOS/UEBuildTVOS.cs b/Engine/Source/Programs/UnrealBuildTool/Platform/TVOS/UEBuildTVOS.cs index d61ae5da7c18..947c7f28e006 100644 --- a/Engine/Source/Programs/UnrealBuildTool/Platform/TVOS/UEBuildTVOS.cs +++ b/Engine/Source/Programs/UnrealBuildTool/Platform/TVOS/UEBuildTVOS.cs @@ -17,7 +17,7 @@ namespace UnrealBuildTool /// public override string RuntimeVersion { - get { return "9.0"; } + get { return "10.0"; } } /// diff --git a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudio/VCProject.cs b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudio/VCProject.cs index 0d2caa09519a..c43945146180 100644 --- a/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudio/VCProject.cs +++ b/Engine/Source/Programs/UnrealBuildTool/ProjectFiles/VisualStudio/VCProject.cs @@ -1384,7 +1384,7 @@ namespace UnrealBuildTool BuildArguments.AppendFormat("{0} {1} {2}", TargetName, UBTPlatformName, UBTConfigurationName); if (IsForeignProject) { - BuildArguments.AppendFormat(" -Project=\"{0}\"", UProjectPath); + BuildArguments.AppendFormat(" -Project={0}", UProjectPath); } if (bUsePrecompiled) diff --git a/Engine/Source/Programs/UnrealBuildTool/System/RulesAssembly.cs b/Engine/Source/Programs/UnrealBuildTool/System/RulesAssembly.cs index 0ef6a5c13a79..c8451274c42f 100644 --- a/Engine/Source/Programs/UnrealBuildTool/System/RulesAssembly.cs +++ b/Engine/Source/Programs/UnrealBuildTool/System/RulesAssembly.cs @@ -518,7 +518,7 @@ namespace UnrealBuildTool if (Rules.bDisableUnverifiedCertificates) { Rules.GlobalDefinitions.Add("DISABLE_UNVERIFIED_CERTIFICATE_LOADING=1"); - } + } // Allow the platform to finalize the settings UEBuildPlatform Platform = UEBuildPlatform.GetBuildPlatform(Rules.Platform); @@ -526,8 +526,8 @@ namespace UnrealBuildTool // Some platforms may *require* monolithic compilation... if (Rules.LinkType != TargetLinkType.Monolithic && UEBuildPlatform.PlatformRequiresMonolithicBuilds(Rules.Platform, Rules.Configuration)) - { - throw new BuildException(String.Format("{0} does not support modular builds", Rules.Platform)); + { + throw new BuildException(String.Format("{0}: {1} does not support modular builds", Rules.Name, Rules.Platform)); } return Rules; diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/AutomationServer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/AutomationServer.cs new file mode 100644 index 000000000000..43d46bfe3277 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/AutomationServer.cs @@ -0,0 +1,239 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace UnrealGameSync +{ + enum AutomationRequestType + { + SyncProject, + FindProject, + OpenProject, + } + + class AutomationRequestInput + { + public AutomationRequestType Type; + public byte[] Data; + + public AutomationRequestInput(AutomationRequestType Type, byte[] Data) + { + this.Type = Type; + this.Data = Data; + } + + public static AutomationRequestInput Read(Stream InputStream) + { + BinaryReader Reader = new BinaryReader(InputStream); + + int Type = Reader.ReadInt32(); + int InputSize = Reader.ReadInt32(); + byte[] Input = Reader.ReadBytes(InputSize); + + return new AutomationRequestInput((AutomationRequestType)Type, Input); + } + + public void Write(Stream OutputStream) + { + BinaryWriter Writer = new BinaryWriter(OutputStream); + + Writer.Write((int)Type); + Writer.Write(Data.Length); + Writer.Write(Data); + } + } + + enum AutomationRequestResult + { + Ok, + Invalid, + Busy, + Canceled, + Error, + NotFound + } + + class AutomationRequestOutput + { + public AutomationRequestResult Result; + public byte[] Data; + + public AutomationRequestOutput(AutomationRequestResult Result) + { + this.Result = Result; + this.Data = new byte[0]; + } + + public AutomationRequestOutput(AutomationRequestResult Result, byte[] Data) + { + this.Result = Result; + this.Data = Data; + } + + public static AutomationRequestOutput Read(Stream InputStream) + { + using(BinaryReader Reader = new BinaryReader(InputStream)) + { + AutomationRequestResult Result = (AutomationRequestResult)Reader.ReadInt32(); + int DataSize = Reader.ReadInt32(); + byte[] Data = Reader.ReadBytes(DataSize); + return new AutomationRequestOutput(Result, Data); + } + } + + public void Write(Stream OutputStream) + { + using(BinaryWriter Writer = new BinaryWriter(OutputStream)) + { + Writer.Write((int)Result); + Writer.Write(Data.Length); + Writer.Write(Data); + } + } + } + + class AutomationRequest : IDisposable + { + public AutomationRequestInput Input; + public AutomationRequestOutput Output; + public ManualResetEventSlim Complete; + + public AutomationRequest(AutomationRequestInput Input) + { + this.Input = Input; + this.Complete = new ManualResetEventSlim(false); + } + + public void SetOutput(AutomationRequestOutput Output) + { + this.Output = Output; + Complete.Set(); + } + + public void Dispose() + { + if(Complete != null) + { + Complete.Dispose(); + Complete = null; + } + } + } + + class AutomationServer : IDisposable + { + TcpListener Listener; + TcpClient CurrentClient; + Thread BackgroundThread; + Action PostRequest; + bool bDisposing; + TextWriter Log; + + public AutomationServer(Action PostRequest, TextWriter Log) + { + this.PostRequest = PostRequest; + this.Log = Log; + + object PortValue = Registry.GetValue("HKEY_CURRENT_USER\\Software\\Epic Games\\UnrealGameSync", "AutomationPort", null); + if(PortValue != null) + { + try + { + int PortNumber = (int)PortValue; + + Listener = new TcpListener(IPAddress.Loopback, PortNumber); + Listener.Start(); + + BackgroundThread = new Thread(() => Run()); + BackgroundThread.Start(); + } + catch(Exception Ex) + { + Log.WriteLine("Unable to start automation server: {0}", Ex.ToString()); + } + } + } + + public void Run() + { + try + { + for(;;) + { + TcpClient Client = CurrentClient = Listener.AcceptTcpClient(); + try + { + Log.WriteLine("Accepted connection from {0}", Client.Client.RemoteEndPoint); + + NetworkStream Stream = Client.GetStream(); + + AutomationRequestInput Input = AutomationRequestInput.Read(Stream); + Log.WriteLine("Received input: {0} (+{1} bytes)", Input.Type, Input.Data.Length); + + AutomationRequestOutput Output; + using(AutomationRequest Request = new AutomationRequest(Input)) + { + PostRequest(Request); + Request.Complete.Wait(); + Output = Request.Output; + } + + Output.Write(Stream); + Log.WriteLine("Sent output: {0} (+{1} bytes)", Output.Result, Output.Data.Length); + } + catch(Exception Ex) + { + Log.WriteLine("Exception: {0}", Ex.ToString()); + } + finally + { + Client.Close(); + Log.WriteLine("Closed connection."); + } + CurrentClient = null; + } + } + catch(Exception Ex) + { + if(!bDisposing) + { + Log.WriteLine("Exception: {0}", Ex.ToString()); + } + } + Log.WriteLine("Closing socket."); + } + + public void Dispose() + { + bDisposing = true; + + TcpClient Client = CurrentClient; + if(Client != null) + { + try { Client.Close(); } catch { } + Client = null; + } + + if(Listener != null) + { + Listener.Stop(); + Listener = null; + } + + if(BackgroundThread != null) + { + BackgroundThread.Join(); + BackgroundThread = null; + } + } + } +} diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/BuildStep.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/BuildStep.cs index faee85bb87d4..ee3fd5c57e59 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/BuildStep.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/BuildStep.cs @@ -104,6 +104,21 @@ namespace UnrealGameSync } } + public bool IsValid() + { + switch(Type) + { + case BuildStepType.Compile: + return Target != null && Platform != null && Configuration != null; + case BuildStepType.Cook: + return FileName != null; + case BuildStepType.Other: + return FileName != null; + default: + return false; + } + } + public static void MergeBuildStepObjects(Dictionary BuildStepObjects, IEnumerable ModifyObjects) { foreach(ConfigObject ModifyObject in ModifyObjects) diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ConfigFile.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ConfigFile.cs index eb8cb58d753c..79845e95771f 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ConfigFile.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ConfigFile.cs @@ -300,7 +300,7 @@ namespace UnrealGameSync public void SetValues(string Key, string[] Values) { - if(Values == null) + if(Values == null || Values.Length == 0) { RemoveValue(Key); } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/StatusPanel.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/StatusPanel.cs index 21d1b4f93e36..795e13b905f3 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/StatusPanel.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/StatusPanel.cs @@ -6,12 +6,24 @@ using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace UnrealGameSync { + // For some reason, WinForms uses embedded resources for certain cursors, which don't scale correctly for high DPI modes + static class NativeCursors + { + [DllImport("user32.dll")] + public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName); + + const int IDC_HAND = 32649; + + public static readonly Cursor Hand = new Cursor(LoadCursor(IntPtr.Zero, new IntPtr(IDC_HAND))); + } + class StatusElementResources { Dictionary FontCache = new Dictionary(); @@ -148,7 +160,7 @@ namespace UnrealGameSync Text = InText; Style = InStyle; LinkAction = InLinkAction; - Cursor = Cursors.Hand; + Cursor = NativeCursors.Hand; } public override void OnClick(Point Location) @@ -198,7 +210,7 @@ namespace UnrealGameSync ClickAction = InClickAction; if(ClickAction != null) { - Cursor = Cursors.Hand; + Cursor = NativeCursors.Hand; } } @@ -461,6 +473,11 @@ namespace UnrealGameSync Resources = new StatusElementResources(Font); } + if(TintColor != NewTintColor) + { + Invalidate(); + } + InvalidateElements(); Lines.Clear(); Lines.AddRange(NewLines); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/SyncFilterControl.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/SyncFilterControl.Designer.cs index 1a6f2c5cdc87..33252245b36d 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/SyncFilterControl.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/SyncFilterControl.Designer.cs @@ -37,6 +37,7 @@ this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.SyncAllProjects = new System.Windows.Forms.CheckBox(); + this.IncludeAllProjectsInSolution = new System.Windows.Forms.CheckBox(); this.ViewGroupBox.SuspendLayout(); this.CategoriesGroupBox.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.SplitContainer)).BeginInit(); @@ -94,10 +95,10 @@ | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.CategoriesGroupBox.Controls.Add(this.CategoriesCheckList); - this.CategoriesGroupBox.Location = new System.Drawing.Point(0, 63); + this.CategoriesGroupBox.Location = new System.Drawing.Point(0, 88); this.CategoriesGroupBox.Margin = new System.Windows.Forms.Padding(0, 3, 0, 0); this.CategoriesGroupBox.Name = "CategoriesGroupBox"; - this.CategoriesGroupBox.Size = new System.Drawing.Size(1008, 367); + this.CategoriesGroupBox.Size = new System.Drawing.Size(1008, 342); this.CategoriesGroupBox.TabIndex = 4; this.CategoriesGroupBox.TabStop = false; this.CategoriesGroupBox.Text = "Categories"; @@ -114,7 +115,7 @@ this.CategoriesCheckList.Location = new System.Drawing.Point(12, 26); this.CategoriesCheckList.Margin = new System.Windows.Forms.Padding(7); this.CategoriesCheckList.Name = "CategoriesCheckList"; - this.CategoriesCheckList.Size = new System.Drawing.Size(986, 331); + this.CategoriesCheckList.Size = new System.Drawing.Size(986, 306); this.CategoriesCheckList.Sorted = true; this.CategoriesCheckList.TabIndex = 7; // @@ -158,11 +159,12 @@ // this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox1.Controls.Add(this.IncludeAllProjectsInSolution); this.groupBox1.Controls.Add(this.SyncAllProjects); this.groupBox1.Location = new System.Drawing.Point(0, 3); this.groupBox1.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3); this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(1008, 54); + this.groupBox1.Size = new System.Drawing.Size(1008, 79); this.groupBox1.TabIndex = 8; this.groupBox1.TabStop = false; this.groupBox1.Text = "General"; @@ -177,6 +179,16 @@ this.SyncAllProjects.Text = "Sync all projects in stream"; this.SyncAllProjects.UseVisualStyleBackColor = true; // + // IncludeAllProjectsInSolution + // + this.IncludeAllProjectsInSolution.AutoSize = true; + this.IncludeAllProjectsInSolution.Location = new System.Drawing.Point(12, 48); + this.IncludeAllProjectsInSolution.Name = "IncludeAllProjectsInSolution"; + this.IncludeAllProjectsInSolution.Size = new System.Drawing.Size(224, 19); + this.IncludeAllProjectsInSolution.TabIndex = 7; + this.IncludeAllProjectsInSolution.Text = "Include all synced projects in solution"; + this.IncludeAllProjectsInSolution.UseVisualStyleBackColor = true; + // // SyncFilterControl // this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); @@ -213,5 +225,6 @@ private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; private System.Windows.Forms.GroupBox groupBox1; public System.Windows.Forms.CheckBox SyncAllProjects; + public System.Windows.Forms.CheckBox IncludeAllProjectsInSolution; } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.Designer.cs index 56cd3716de99..e4c98a2b144c 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.Designer.cs @@ -17,6 +17,8 @@ namespace UnrealGameSync { this.components = new System.ComponentModel.Container(); this.OptionsContextMenu = new System.Windows.Forms.ContextMenuStrip(this.components); + this.OptionsContextMenu_ApplicationSettings = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); this.OptionsContextMenu_ScheduledSync = new System.Windows.Forms.ToolStripMenuItem(); this.OptionsContextMenu_SyncPrecompiledEditor = new System.Windows.Forms.ToolStripMenuItem(); this.OptionsContextMenu_AutoResolveConflicts = new System.Windows.Forms.ToolStripMenuItem(); @@ -42,8 +44,6 @@ namespace UnrealGameSync this.OptionsContextMenu_TimeZone = new System.Windows.Forms.ToolStripMenuItem(); this.OptionsContextMenu_TimeZone_Local = new System.Windows.Forms.ToolStripMenuItem(); this.OptionsContextMenu_TimeZone_PerforceServer = new System.Windows.Forms.ToolStripMenuItem(); - this.OptionsContextMenu_AutomaticallyRunAtStartup = new System.Windows.Forms.ToolStripMenuItem(); - this.OptionsContextMenu_KeepInTray = new System.Windows.Forms.ToolStripMenuItem(); this.toolStripSeparator6 = new System.Windows.Forms.ToolStripSeparator(); this.OptionsContextMenu_Diagnostics = new System.Windows.Forms.ToolStripMenuItem(); this.RunAfterSyncCheckBox = new System.Windows.Forms.CheckBox(); @@ -87,8 +87,8 @@ namespace UnrealGameSync this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); this.OpenSolutionAfterSyncCheckBox = new System.Windows.Forms.CheckBox(); this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); - this.label1 = new System.Windows.Forms.Label(); this.OptionsButton = new System.Windows.Forms.Button(); + this.FilterButton = new System.Windows.Forms.Button(); this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); this.Splitter = new UnrealGameSync.LogSplitContainer(); this.StatusLayoutPanel = new System.Windows.Forms.TableLayoutPanel(); @@ -124,6 +124,17 @@ namespace UnrealGameSync this.BuildListMultiContextMenu_TimeZoneSeparator = new System.Windows.Forms.ToolStripSeparator(); this.BuildListMultiContextMenu_ShowServerTimes = new System.Windows.Forms.ToolStripMenuItem(); this.BuildListMultiContextMenu_ShowLocalTimes = new System.Windows.Forms.ToolStripMenuItem(); + this.FilterContextMenu = new System.Windows.Forms.ContextMenuStrip(this.components); + this.FilterContextMenu_Default = new System.Windows.Forms.ToolStripMenuItem(); + this.FilterContextMenu_BeforeBadgeSeparator = new System.Windows.Forms.ToolStripSeparator(); + this.FilterContextMenu_Type = new System.Windows.Forms.ToolStripMenuItem(); + this.FilterContextMenu_Type_ShowAll = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparator10 = new System.Windows.Forms.ToolStripSeparator(); + this.FilterContextMenu_Type_Code = new System.Windows.Forms.ToolStripMenuItem(); + this.FilterContextMenu_Type_Content = new System.Windows.Forms.ToolStripMenuItem(); + this.FilterContextMenu_Badges = new System.Windows.Forms.ToolStripMenuItem(); + this.FilterContextMenu_AfterBadgeSeparator = new System.Windows.Forms.ToolStripSeparator(); + this.FilterContextMenu_ShowBuildMachineChanges = new System.Windows.Forms.ToolStripMenuItem(); this.OptionsContextMenu.SuspendLayout(); this.BuildListContextMenu.SuspendLayout(); this.flowLayoutPanel1.SuspendLayout(); @@ -139,11 +150,14 @@ namespace UnrealGameSync this.SyncContextMenu.SuspendLayout(); this.RecentMenu.SuspendLayout(); this.BuildListMultiContextMenu.SuspendLayout(); + this.FilterContextMenu.SuspendLayout(); this.SuspendLayout(); // // OptionsContextMenu // this.OptionsContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.OptionsContextMenu_ApplicationSettings, + this.toolStripSeparator2, this.OptionsContextMenu_ScheduledSync, this.OptionsContextMenu_SyncPrecompiledEditor, this.OptionsContextMenu_AutoResolveConflicts, @@ -158,52 +172,62 @@ namespace UnrealGameSync this.tabLabelsToolStripMenuItem, this.showChangesToolStripMenuItem, this.OptionsContextMenu_TimeZone, - this.OptionsContextMenu_AutomaticallyRunAtStartup, - this.OptionsContextMenu_KeepInTray, this.toolStripSeparator6, this.OptionsContextMenu_Diagnostics}); this.OptionsContextMenu.Name = "ToolsMenuStrip"; - this.OptionsContextMenu.Size = new System.Drawing.Size(268, 352); + this.OptionsContextMenu.Size = new System.Drawing.Size(262, 336); + // + // OptionsContextMenu_ApplicationSettings + // + this.OptionsContextMenu_ApplicationSettings.Name = "OptionsContextMenu_ApplicationSettings"; + this.OptionsContextMenu_ApplicationSettings.Size = new System.Drawing.Size(261, 22); + this.OptionsContextMenu_ApplicationSettings.Text = "Application Settings..."; + this.OptionsContextMenu_ApplicationSettings.Click += new System.EventHandler(this.OptionsContextMenu_ApplicationSettings_Click); + // + // toolStripSeparator2 + // + this.toolStripSeparator2.Name = "toolStripSeparator2"; + this.toolStripSeparator2.Size = new System.Drawing.Size(258, 6); // // OptionsContextMenu_ScheduledSync // this.OptionsContextMenu_ScheduledSync.Name = "OptionsContextMenu_ScheduledSync"; - this.OptionsContextMenu_ScheduledSync.Size = new System.Drawing.Size(267, 22); + this.OptionsContextMenu_ScheduledSync.Size = new System.Drawing.Size(261, 22); this.OptionsContextMenu_ScheduledSync.Text = "Scheduled Sync..."; this.OptionsContextMenu_ScheduledSync.Click += new System.EventHandler(this.OptionsContextMenu_ScheduleSync_Click); // // OptionsContextMenu_SyncPrecompiledEditor // this.OptionsContextMenu_SyncPrecompiledEditor.Name = "OptionsContextMenu_SyncPrecompiledEditor"; - this.OptionsContextMenu_SyncPrecompiledEditor.Size = new System.Drawing.Size(267, 22); + this.OptionsContextMenu_SyncPrecompiledEditor.Size = new System.Drawing.Size(261, 22); this.OptionsContextMenu_SyncPrecompiledEditor.Text = "Sync Precompiled Editor"; this.OptionsContextMenu_SyncPrecompiledEditor.Click += new System.EventHandler(this.OptionsContextMenu_SyncPrecompiledEditor_Click); // // OptionsContextMenu_AutoResolveConflicts // this.OptionsContextMenu_AutoResolveConflicts.Name = "OptionsContextMenu_AutoResolveConflicts"; - this.OptionsContextMenu_AutoResolveConflicts.Size = new System.Drawing.Size(267, 22); + this.OptionsContextMenu_AutoResolveConflicts.Size = new System.Drawing.Size(261, 22); this.OptionsContextMenu_AutoResolveConflicts.Text = "Auto-Resolve Conflicts"; this.OptionsContextMenu_AutoResolveConflicts.Click += new System.EventHandler(this.OptionsContextMenu_AutoResolveConflicts_Click); // // OptionsContextMenu_SyncFilter // this.OptionsContextMenu_SyncFilter.Name = "OptionsContextMenu_SyncFilter"; - this.OptionsContextMenu_SyncFilter.Size = new System.Drawing.Size(267, 22); + this.OptionsContextMenu_SyncFilter.Size = new System.Drawing.Size(261, 22); this.OptionsContextMenu_SyncFilter.Text = "Sync Filter..."; this.OptionsContextMenu_SyncFilter.Click += new System.EventHandler(this.OptionsContextMenu_SyncFilter_Click); // // OptionsContextMenu_PerforceSettings // this.OptionsContextMenu_PerforceSettings.Name = "OptionsContextMenu_PerforceSettings"; - this.OptionsContextMenu_PerforceSettings.Size = new System.Drawing.Size(267, 22); - this.OptionsContextMenu_PerforceSettings.Text = "Perforce Settings..."; + this.OptionsContextMenu_PerforceSettings.Size = new System.Drawing.Size(261, 22); + this.OptionsContextMenu_PerforceSettings.Text = "Perforce Sync Settings..."; this.OptionsContextMenu_PerforceSettings.Click += new System.EventHandler(this.OptionsContextMenu_PerforceSettings_Click); // // toolStripSeparator3 // this.toolStripSeparator3.Name = "toolStripSeparator3"; - this.toolStripSeparator3.Size = new System.Drawing.Size(264, 6); + this.toolStripSeparator3.Size = new System.Drawing.Size(258, 6); // // OptionsContextMenu_EditorBuildConfiguration // @@ -212,7 +236,7 @@ namespace UnrealGameSync this.OptionsContextMenu_BuildConfig_DebugGame, this.OptionsContextMenu_BuildConfig_Development}); this.OptionsContextMenu_EditorBuildConfiguration.Name = "OptionsContextMenu_EditorBuildConfiguration"; - this.OptionsContextMenu_EditorBuildConfiguration.Size = new System.Drawing.Size(267, 22); + this.OptionsContextMenu_EditorBuildConfiguration.Size = new System.Drawing.Size(261, 22); this.OptionsContextMenu_EditorBuildConfiguration.Text = "Editor Build Configuration"; // // OptionsContextMenu_BuildConfig_Debug @@ -241,28 +265,28 @@ namespace UnrealGameSync // OptionsContextMenu_UseIncrementalBuilds // this.OptionsContextMenu_UseIncrementalBuilds.Name = "OptionsContextMenu_UseIncrementalBuilds"; - this.OptionsContextMenu_UseIncrementalBuilds.Size = new System.Drawing.Size(267, 22); + this.OptionsContextMenu_UseIncrementalBuilds.Size = new System.Drawing.Size(261, 22); this.OptionsContextMenu_UseIncrementalBuilds.Text = "Use Incremental Builds"; this.OptionsContextMenu_UseIncrementalBuilds.Click += new System.EventHandler(this.OptionsContextMenu_UseIncrementalBuilds_Click); // // OptionsContextMenu_CustomizeBuildSteps // this.OptionsContextMenu_CustomizeBuildSteps.Name = "OptionsContextMenu_CustomizeBuildSteps"; - this.OptionsContextMenu_CustomizeBuildSteps.Size = new System.Drawing.Size(267, 22); + this.OptionsContextMenu_CustomizeBuildSteps.Size = new System.Drawing.Size(261, 22); this.OptionsContextMenu_CustomizeBuildSteps.Text = "Customize Build Steps..."; this.OptionsContextMenu_CustomizeBuildSteps.Click += new System.EventHandler(this.OptionsContextMenu_EditBuildSteps_Click); // // OptionsContextMenu_EditorArguments // this.OptionsContextMenu_EditorArguments.Name = "OptionsContextMenu_EditorArguments"; - this.OptionsContextMenu_EditorArguments.Size = new System.Drawing.Size(267, 22); + this.OptionsContextMenu_EditorArguments.Size = new System.Drawing.Size(261, 22); this.OptionsContextMenu_EditorArguments.Text = "Editor Command Line Arguments..."; this.OptionsContextMenu_EditorArguments.Click += new System.EventHandler(this.OptionsContextMenu_EditorArguments_Click); // // toolStripSeparator5 // this.toolStripSeparator5.Name = "toolStripSeparator5"; - this.toolStripSeparator5.Size = new System.Drawing.Size(264, 6); + this.toolStripSeparator5.Size = new System.Drawing.Size(258, 6); // // tabLabelsToolStripMenuItem // @@ -272,7 +296,7 @@ namespace UnrealGameSync this.OptionsContextMenu_TabNames_WorkspaceRoot, this.OptionsContextMenu_TabNames_ProjectFile}); this.tabLabelsToolStripMenuItem.Name = "tabLabelsToolStripMenuItem"; - this.tabLabelsToolStripMenuItem.Size = new System.Drawing.Size(267, 22); + this.tabLabelsToolStripMenuItem.Size = new System.Drawing.Size(261, 22); this.tabLabelsToolStripMenuItem.Text = "Tab Names"; // // OptionsContextMenu_TabNames_Stream @@ -309,7 +333,7 @@ namespace UnrealGameSync this.OptionsContextMenu_ShowChanges_ShowUnreviewed, this.OptionsContextMenu_ShowChanges_ShowAutomated}); this.showChangesToolStripMenuItem.Name = "showChangesToolStripMenuItem"; - this.showChangesToolStripMenuItem.Size = new System.Drawing.Size(267, 22); + this.showChangesToolStripMenuItem.Size = new System.Drawing.Size(261, 22); this.showChangesToolStripMenuItem.Text = "Show Changes"; // // OptionsContextMenu_ShowChanges_ShowUnreviewed @@ -332,7 +356,7 @@ namespace UnrealGameSync this.OptionsContextMenu_TimeZone_Local, this.OptionsContextMenu_TimeZone_PerforceServer}); this.OptionsContextMenu_TimeZone.Name = "OptionsContextMenu_TimeZone"; - this.OptionsContextMenu_TimeZone.Size = new System.Drawing.Size(267, 22); + this.OptionsContextMenu_TimeZone.Size = new System.Drawing.Size(261, 22); this.OptionsContextMenu_TimeZone.Text = "Time Zone"; // // OptionsContextMenu_TimeZone_Local @@ -349,29 +373,15 @@ namespace UnrealGameSync this.OptionsContextMenu_TimeZone_PerforceServer.Text = "Perforce Server"; this.OptionsContextMenu_TimeZone_PerforceServer.Click += new System.EventHandler(this.BuildListContextMenu_ShowServerTimes_Click); // - // OptionsContextMenu_AutomaticallyRunAtStartup - // - this.OptionsContextMenu_AutomaticallyRunAtStartup.Name = "OptionsContextMenu_AutomaticallyRunAtStartup"; - this.OptionsContextMenu_AutomaticallyRunAtStartup.Size = new System.Drawing.Size(267, 22); - this.OptionsContextMenu_AutomaticallyRunAtStartup.Text = "Automatically run at startup"; - this.OptionsContextMenu_AutomaticallyRunAtStartup.Click += new System.EventHandler(this.OptionsContextMenu_AutomaticallyRunAtStartup_Click); - // - // OptionsContextMenu_KeepInTray - // - this.OptionsContextMenu_KeepInTray.Name = "OptionsContextMenu_KeepInTray"; - this.OptionsContextMenu_KeepInTray.Size = new System.Drawing.Size(267, 22); - this.OptionsContextMenu_KeepInTray.Text = "Stay in notification area when closed"; - this.OptionsContextMenu_KeepInTray.Click += new System.EventHandler(this.OptionsContextMenu_KeepInTray_Click); - // // toolStripSeparator6 // this.toolStripSeparator6.Name = "toolStripSeparator6"; - this.toolStripSeparator6.Size = new System.Drawing.Size(264, 6); + this.toolStripSeparator6.Size = new System.Drawing.Size(258, 6); // // OptionsContextMenu_Diagnostics // this.OptionsContextMenu_Diagnostics.Name = "OptionsContextMenu_Diagnostics"; - this.OptionsContextMenu_Diagnostics.Size = new System.Drawing.Size(267, 22); + this.OptionsContextMenu_Diagnostics.Size = new System.Drawing.Size(261, 22); this.OptionsContextMenu_Diagnostics.Text = "Diagnostics..."; this.OptionsContextMenu_Diagnostics.Click += new System.EventHandler(this.OptionsContextMenu_Diagnostics_Click); // @@ -665,7 +675,7 @@ namespace UnrealGameSync this.flowLayoutPanel1.Controls.Add(this.BuildAfterSyncCheckBox); this.flowLayoutPanel1.Controls.Add(this.RunAfterSyncCheckBox); this.flowLayoutPanel1.Controls.Add(this.OpenSolutionAfterSyncCheckBox); - this.flowLayoutPanel1.Location = new System.Drawing.Point(529, 17); + this.flowLayoutPanel1.Location = new System.Drawing.Point(526, 17); this.flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(0, 4, 0, 3); this.flowLayoutPanel1.Name = "flowLayoutPanel1"; this.flowLayoutPanel1.Size = new System.Drawing.Size(311, 19); @@ -693,9 +703,9 @@ namespace UnrealGameSync this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); - this.tableLayoutPanel3.Controls.Add(this.label1, 0, 0); this.tableLayoutPanel3.Controls.Add(this.flowLayoutPanel1, 1, 0); this.tableLayoutPanel3.Controls.Add(this.OptionsButton, 2, 0); + this.tableLayoutPanel3.Controls.Add(this.FilterButton, 0, 0); this.tableLayoutPanel3.Dock = System.Windows.Forms.DockStyle.Fill; this.tableLayoutPanel3.Location = new System.Drawing.Point(0, 713); this.tableLayoutPanel3.Margin = new System.Windows.Forms.Padding(0); @@ -707,13 +717,6 @@ namespace UnrealGameSync this.tableLayoutPanel3.Size = new System.Drawing.Size(1363, 39); this.tableLayoutPanel3.TabIndex = 11; // - // label1 - // - this.label1.Location = new System.Drawing.Point(3, 13); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(141, 26); - this.label1.TabIndex = 1; - // // OptionsButton // this.OptionsButton.Anchor = System.Windows.Forms.AnchorStyles.Right; @@ -729,6 +732,21 @@ namespace UnrealGameSync this.OptionsButton.UseVisualStyleBackColor = true; this.OptionsButton.Click += new System.EventHandler(this.OptionsButton_Click); // + // FilterButton + // + this.FilterButton.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.FilterButton.AutoSize = true; + this.FilterButton.Image = global::UnrealGameSync.Properties.Resources.DropList; + this.FilterButton.ImageAlign = System.Drawing.ContentAlignment.MiddleRight; + this.FilterButton.Location = new System.Drawing.Point(0, 13); + this.FilterButton.Margin = new System.Windows.Forms.Padding(0); + this.FilterButton.Name = "FilterButton"; + this.FilterButton.Size = new System.Drawing.Size(141, 26); + this.FilterButton.TabIndex = 9; + this.FilterButton.Text = "Filter"; + this.FilterButton.UseVisualStyleBackColor = true; + this.FilterButton.Click += new System.EventHandler(this.FilterButton_Click); + // // tableLayoutPanel2 // this.tableLayoutPanel2.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; @@ -1035,6 +1053,85 @@ namespace UnrealGameSync this.BuildListMultiContextMenu_ShowLocalTimes.Text = "Show local times"; this.BuildListMultiContextMenu_ShowLocalTimes.Click += new System.EventHandler(this.BuildListContextMenu_ShowServerTimes_Click); // + // FilterContextMenu + // + this.FilterContextMenu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.FilterContextMenu_Default, + this.FilterContextMenu_BeforeBadgeSeparator, + this.FilterContextMenu_Type, + this.FilterContextMenu_Badges, + this.FilterContextMenu_AfterBadgeSeparator, + this.FilterContextMenu_ShowBuildMachineChanges}); + this.FilterContextMenu.Name = "FilterContextMenu"; + this.FilterContextMenu.Size = new System.Drawing.Size(232, 126); + // + // FilterContextMenu_Default + // + this.FilterContextMenu_Default.Name = "FilterContextMenu_Default"; + this.FilterContextMenu_Default.Size = new System.Drawing.Size(231, 22); + this.FilterContextMenu_Default.Text = "Default"; + this.FilterContextMenu_Default.Click += new System.EventHandler(this.FilterContextMenu_Default_Click); + // + // FilterContextMenu_BeforeBadgeSeparator + // + this.FilterContextMenu_BeforeBadgeSeparator.Name = "FilterContextMenu_BeforeBadgeSeparator"; + this.FilterContextMenu_BeforeBadgeSeparator.Size = new System.Drawing.Size(228, 6); + // + // FilterContextMenu_Type + // + this.FilterContextMenu_Type.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.FilterContextMenu_Type_ShowAll, + this.toolStripSeparator10, + this.FilterContextMenu_Type_Code, + this.FilterContextMenu_Type_Content}); + this.FilterContextMenu_Type.Name = "FilterContextMenu_Type"; + this.FilterContextMenu_Type.Size = new System.Drawing.Size(231, 22); + this.FilterContextMenu_Type.Text = "Type"; + // + // FilterContextMenu_Type_ShowAll + // + this.FilterContextMenu_Type_ShowAll.Name = "FilterContextMenu_Type_ShowAll"; + this.FilterContextMenu_Type_ShowAll.Size = new System.Drawing.Size(120, 22); + this.FilterContextMenu_Type_ShowAll.Text = "Show All"; + this.FilterContextMenu_Type_ShowAll.Click += new System.EventHandler(this.FilterContextMenu_Type_ShowAll_Click); + // + // toolStripSeparator10 + // + this.toolStripSeparator10.Name = "toolStripSeparator10"; + this.toolStripSeparator10.Size = new System.Drawing.Size(117, 6); + // + // FilterContextMenu_Type_Code + // + this.FilterContextMenu_Type_Code.Name = "FilterContextMenu_Type_Code"; + this.FilterContextMenu_Type_Code.Size = new System.Drawing.Size(120, 22); + this.FilterContextMenu_Type_Code.Text = "Code"; + this.FilterContextMenu_Type_Code.Click += new System.EventHandler(this.FilterContextMenu_Type_Code_Click); + // + // FilterContextMenu_Type_Content + // + this.FilterContextMenu_Type_Content.Name = "FilterContextMenu_Type_Content"; + this.FilterContextMenu_Type_Content.Size = new System.Drawing.Size(120, 22); + this.FilterContextMenu_Type_Content.Text = "Content"; + this.FilterContextMenu_Type_Content.Click += new System.EventHandler(this.FilterContextMenu_Type_Content_Click); + // + // FilterContextMenu_Badges + // + this.FilterContextMenu_Badges.Name = "FilterContextMenu_Badges"; + this.FilterContextMenu_Badges.Size = new System.Drawing.Size(231, 22); + this.FilterContextMenu_Badges.Text = "Badges"; + // + // FilterContextMenu_AfterBadgeSeparator + // + this.FilterContextMenu_AfterBadgeSeparator.Name = "FilterContextMenu_AfterBadgeSeparator"; + this.FilterContextMenu_AfterBadgeSeparator.Size = new System.Drawing.Size(228, 6); + // + // FilterContextMenu_ShowBuildMachineChanges + // + this.FilterContextMenu_ShowBuildMachineChanges.Name = "FilterContextMenu_ShowBuildMachineChanges"; + this.FilterContextMenu_ShowBuildMachineChanges.Size = new System.Drawing.Size(231, 22); + this.FilterContextMenu_ShowBuildMachineChanges.Text = "Show Build Machine Changes"; + this.FilterContextMenu_ShowBuildMachineChanges.Click += new System.EventHandler(this.FilterContextMenu_ShowBuildMachineChanges_Click); + // // WorkspaceControl // this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); @@ -1065,6 +1162,7 @@ namespace UnrealGameSync this.SyncContextMenu.ResumeLayout(false); this.RecentMenu.ResumeLayout(false); this.BuildListMultiContextMenu.ResumeLayout(false); + this.FilterContextMenu.ResumeLayout(false); this.ResumeLayout(false); } @@ -1130,7 +1228,6 @@ namespace UnrealGameSync private System.Windows.Forms.ToolStripMenuItem OptionsContextMenu_TimeZone_Local; private System.Windows.Forms.ToolStripMenuItem OptionsContextMenu_TimeZone_PerforceServer; private System.Windows.Forms.CheckBox OpenSolutionAfterSyncCheckBox; - private System.Windows.Forms.ToolStripMenuItem OptionsContextMenu_AutomaticallyRunAtStartup; private System.Windows.Forms.ToolStripMenuItem OptionsContextMenu_SyncPrecompiledEditor; private System.Windows.Forms.TableLayoutPanel StatusLayoutPanel; private StatusPanel StatusPanel; @@ -1150,7 +1247,6 @@ namespace UnrealGameSync private System.Windows.Forms.ToolStripSeparator toolStripSeparator8; private System.Windows.Forms.ToolStripMenuItem SyncContexMenu_EnterChangelist; private System.Windows.Forms.ToolStripMenuItem SyncContextMenu_LatestStarredChange; - private System.Windows.Forms.ToolStripMenuItem OptionsContextMenu_KeepInTray; private System.Windows.Forms.ContextMenuStrip StreamContextMenu; private System.Windows.Forms.ToolStripMenuItem OptionsContextMenu_PerforceSettings; private System.Windows.Forms.ToolStripMenuItem tabLabelsToolStripMenuItem; @@ -1165,7 +1261,6 @@ namespace UnrealGameSync private System.Windows.Forms.ToolStripSeparator toolStripSeparator9; private System.Windows.Forms.ToolStripSeparator BuildListContextMenu_CustomTool_Start; private System.Windows.Forms.ToolStripMenuItem showChangesToolStripMenuItem; - private System.Windows.Forms.Label label1; private System.Windows.Forms.ToolStripMenuItem OptionsContextMenu_ShowChanges_ShowUnreviewed; private System.Windows.Forms.ToolStripMenuItem OptionsContextMenu_ShowChanges_ShowAutomated; private System.Windows.Forms.ColumnHeader TypeColumn; @@ -1179,5 +1274,19 @@ namespace UnrealGameSync private System.Windows.Forms.ToolStripMenuItem BuildListContextMenu_Bisect_Exclude; private System.Windows.Forms.ToolStripMenuItem BuildListContextMenu_Bisect_Include; private System.Windows.Forms.ToolStripSeparator BuildListContextMenu_Bisect_Separator; + private System.Windows.Forms.ToolStripMenuItem OptionsContextMenu_ApplicationSettings; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator2; + private System.Windows.Forms.Button FilterButton; + private System.Windows.Forms.ContextMenuStrip FilterContextMenu; + private System.Windows.Forms.ToolStripMenuItem FilterContextMenu_Default; + private System.Windows.Forms.ToolStripSeparator FilterContextMenu_BeforeBadgeSeparator; + private System.Windows.Forms.ToolStripSeparator FilterContextMenu_AfterBadgeSeparator; + private System.Windows.Forms.ToolStripMenuItem FilterContextMenu_ShowBuildMachineChanges; + private System.Windows.Forms.ToolStripMenuItem FilterContextMenu_Badges; + private System.Windows.Forms.ToolStripMenuItem FilterContextMenu_Type; + private System.Windows.Forms.ToolStripMenuItem FilterContextMenu_Type_Code; + private System.Windows.Forms.ToolStripMenuItem FilterContextMenu_Type_Content; + private System.Windows.Forms.ToolStripMenuItem FilterContextMenu_Type_ShowAll; + private System.Windows.Forms.ToolStripSeparator toolStripSeparator10; } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.cs index 890dec82a35f..3ca54772be04 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.cs @@ -41,8 +41,12 @@ namespace UnrealGameSync void SetTabNames(TabLabels TabNames); void SetupScheduledSync(); void UpdateProgress(); + void ModifyApplicationSettings(); } + delegate void WorkspaceStartupCallback(WorkspaceControl Workspace, bool bCancel); + delegate void WorkspaceUpdateCallback(WorkspaceUpdateResult Result); + partial class WorkspaceControl : UserControl, IMainWindowTabPanel { enum HorizontalAlignment @@ -69,26 +73,26 @@ namespace UnrealGameSync public int Height; public Color BackgroundColor; public Color HoverBackgroundColor; - public object UserData; + public Action ClickHandler; public string ToolTip; public BadgeInfo(string Label, string Group, Color BadgeColor) - : this(Label, Group, null, BadgeColor, BadgeColor) + : this(Label, Group, null, BadgeColor, BadgeColor, null) { } - public BadgeInfo(string Label, string Group, string UniqueId, Color BackgroundColor, Color HoverBackgroundColor, object UserData = null) + public BadgeInfo(string Label, string Group, string UniqueId, Color BackgroundColor, Color HoverBackgroundColor, Action ClickHandler) { this.Label = Label; this.Group = Group; this.UniqueId = UniqueId; this.BackgroundColor = BackgroundColor; this.HoverBackgroundColor = HoverBackgroundColor; - this.UserData = UserData; + this.ClickHandler = ClickHandler; } public BadgeInfo(BadgeInfo Other) - : this(Other.Label, Other.Group, Other.UniqueId, Other.BackgroundColor, Other.HoverBackgroundColor, Other.UserData) + : this(Other.Label, Other.Group, Other.UniqueId, Other.BackgroundColor, Other.HoverBackgroundColor, Other.ClickHandler) { this.Offset = Other.Offset; this.Width = Other.Width; @@ -132,6 +136,8 @@ namespace UnrealGameSync [DllImport("uxtheme.dll", CharSet = CharSet.Unicode)] static extern int SetWindowTheme(IntPtr hWnd, string pszSubAppName, string pszSubIdList); + const int BuildListExpandCount = 250; + const string EditorArchiveType = "Editor"; IWorkspaceControlOwner Owner; @@ -178,6 +184,7 @@ namespace UnrealGameSync SynchronizationContext MainThreadSynchronizationContext; bool bIsDisposing; + bool bUnstable; string EditorTargetName; bool bIsEnterpriseProject; PerforceMonitor PerforceMonitor; @@ -196,7 +203,7 @@ namespace UnrealGameSync Dictionary NotifiedBuildTypeToChangeNumber = new Dictionary(); - TimeSpan ServerTimeZone; + bool bMouseOverExpandLink; string HoverBadgeUniqueId = null; bool bHoverSync; @@ -206,7 +213,7 @@ namespace UnrealGameSync Font BadgeFont; List> BadgeNameAndGroupPairs = new List>(); Dictionary BadgeLabelToSize = new Dictionary(); - List> ServiceBadges = new List>(); + List> ServiceBadges = new List>(); string OriginalExecutableFileName; @@ -226,12 +233,18 @@ namespace UnrealGameSync string LastColumnSettings; List CustomColumns; int MaxBuildBadgeChars; + ListViewItem ExpandItem; bool bUpdateBuildListPosted; bool bUpdateBuildMetadataPosted; bool bUpdateReviewsPosted; - public WorkspaceControl(IWorkspaceControlOwner InOwner, string InApiUrl, string InOriginalExecutableFileName, DetectProjectSettingsTask DetectSettings, LineBasedTextWriter InLog, UserSettings InSettings) + WorkspaceUpdateCallback UpdateCallback; + + System.Threading.Timer StartupTimer; + List StartupCallbacks; + + public WorkspaceControl(IWorkspaceControlOwner InOwner, string InApiUrl, string InOriginalExecutableFileName, bool bInUnstable, DetectProjectSettingsTask DetectSettings, LineBasedTextWriter InLog, UserSettings InSettings) { InitializeComponent(); @@ -241,6 +254,7 @@ namespace UnrealGameSync ApiUrl = InApiUrl; DataFolder = DetectSettings.DataFolder; OriginalExecutableFileName = InOriginalExecutableFileName; + bUnstable = bInUnstable; Log = InLog; Settings = InSettings; WorkspaceSettings = InSettings.FindOrAddWorkspace(DetectSettings.BranchClientPath); @@ -280,7 +294,6 @@ namespace UnrealGameSync EditorTargetName = DetectSettings.NewProjectEditorTarget; bIsEnterpriseProject = DetectSettings.bIsEnterpriseProject; StreamName = DetectSettings.StreamName; - ServerTimeZone = DetectSettings.ServerTimeZone; // Update the branch directory BranchDirectoryName = DetectSettings.BranchDirectoryName; @@ -301,7 +314,7 @@ namespace UnrealGameSync string ProjectLogBaseName = Path.Combine(DataFolder, String.Format("{0}@{1}", PerforceClient.ClientName, DetectSettings.BranchClientPath.Replace("//" + PerforceClient.ClientName + "/", "").Trim('/').Replace("/", "$"))); - PerforceMonitor = new PerforceMonitor(PerforceClient, DetectSettings.BranchClientPath, DetectSettings.NewSelectedClientFileName, DetectSettings.NewSelectedProjectIdentifier, ProjectLogBaseName + ".p4.log", DetectSettings.bIsEnterpriseProject, DetectSettings.LatestProjectConfigFile, DetectSettings.LocalConfigFiles); + PerforceMonitor = new PerforceMonitor(PerforceClient, DetectSettings.BranchClientPath, DetectSettings.NewSelectedClientFileName, DetectSettings.NewSelectedProjectIdentifier, ProjectLogBaseName + ".p4.log", DetectSettings.bIsEnterpriseProject, DetectSettings.LatestProjectConfigFile, DetectSettings.CacheFolder, DetectSettings.LocalConfigFiles); PerforceMonitor.OnUpdate += UpdateBuildListCallback; PerforceMonitor.OnUpdateMetadata += UpdateBuildMetadataCallback; PerforceMonitor.OnStreamChange += StreamChangedCallback; @@ -324,13 +337,53 @@ namespace UnrealGameSync UpdateStatusPanel(); UpdateServiceBadges(); - if(CurrentChangeNumber != -1) - { - SelectChange(CurrentChangeNumber); - } - PerforceMonitor.Start(); EventMonitor.Start(); + + StartupTimer = new System.Threading.Timer(x => MainThreadSynchronizationContext.Post((o) => { if(!IsDisposed){ StartupTimerElapsed(false); } }, null), null, TimeSpan.FromSeconds(20.0), TimeSpan.FromMilliseconds(-1.0)); + StartupCallbacks = new List(); + } + + private void CheckForStartupComplete() + { + if(StartupTimer != null) + { + int LatestChangeNumber; + if(FindChangeToSync(Settings.SyncType, out LatestChangeNumber)) + { + StartupTimerElapsed(false); + } + } + } + + private void StartupTimerElapsed(bool bCancel) + { + if(StartupTimer != null) + { + StartupTimer.Dispose(); + StartupTimer = null; + } + + if(StartupCallbacks != null) + { + foreach(WorkspaceStartupCallback StartupCallback in StartupCallbacks) + { + StartupCallback(this, bCancel); + } + StartupCallbacks = null; + } + } + + public void AddStartupCallback(WorkspaceStartupCallback StartupCallback) + { + if(StartupTimer == null) + { + StartupCallback(this, false); + } + else + { + StartupCallbacks.Add(StartupCallback); + } } private void UpdateColumnSettings() @@ -472,9 +525,9 @@ namespace UnrealGameSync ServiceBadges.Clear(); foreach(string ServiceBadgeName in ServiceBadgeNames) { - BuildData LatestBuild; - EventMonitor.TryGetLatestBuild(ServiceBadgeName, out LatestBuild); - ServiceBadges.Add(new KeyValuePair(ServiceBadgeName, LatestBuild)); + BadgeData LatestBuild; + EventMonitor.TryGetLatestBadge(ServiceBadgeName, out LatestBuild); + ServiceBadges.Add(new KeyValuePair(ServiceBadgeName, LatestBuild)); } } @@ -517,6 +570,20 @@ namespace UnrealGameSync UpdateTimer.Stop(); + if(StartupCallbacks != null) + { + foreach(WorkspaceStartupCallback StartupCallback in StartupCallbacks) + { + StartupCallback(this, true); + } + StartupCallbacks = null; + } + + if(StartupTimer != null) + { + StartupTimer.Dispose(); + StartupTimer = null; + } if(NotificationWindow != null) { NotificationWindow.Dispose(); @@ -638,65 +705,54 @@ namespace UnrealGameSync private void BuildList_OnScroll() { PendingSelectedChangeNumber = -1; - UpdateNumRequestedBuilds(false); } - void UpdateNumRequestedBuilds(bool bAllowShrink) + void ShrinkNumRequestedBuilds() { - if(PerforceMonitor != null) + if(PerforceMonitor != null && BuildList.Items.Count > 0 && PendingSelectedChangeNumber == -1) { - if(!Settings.bShowUnreviewedChanges) + // Find the number of visible items using a (slightly wasteful) binary search + int VisibleItemCount = 1; + for(int StepSize = BuildList.Items.Count / 2; StepSize >= 1; ) { - PerforceMonitor.PendingMaxChanges = 1000; + int TestIndex = VisibleItemCount + StepSize; + if(TestIndex < BuildList.Items.Count && BuildList.GetItemRect(TestIndex).Top < BuildList.Height) + { + VisibleItemCount += StepSize; + } + else + { + StepSize /= 2; + } } - else + + // Figure out the last index to ensure is visible + int LastVisibleIndex = VisibleItemCount; + if(LastVisibleIndex >= ListIndexToChangeIndex.Count) { - int NumItemsPerPage = Math.Max(BuildList.GetVisibleItemsPerPage(), 10); + LastVisibleIndex = ListIndexToChangeIndex.Count - 1; + } - // Find the number of visible items using a (slightly wasteful) binary search - int VisibleItemCount = 1; - for(int StepSize = BuildList.Items.Count / 2; StepSize >= 1; ) - { - int TestIndex = VisibleItemCount + StepSize; - if(TestIndex < BuildList.Items.Count && BuildList.GetItemRect(TestIndex).Top < BuildList.Height) - { - VisibleItemCount += StepSize; - } - else - { - StepSize /= 2; - } - } + // Get the max number of changes to ensure this + int NewPendingMaxChanges = ListIndexToChangeIndex[LastVisibleIndex]; + NewPendingMaxChanges = PerforceMonitor.InitialMaxChangesValue + ((Math.Max(NewPendingMaxChanges - PerforceMonitor.InitialMaxChangesValue, 0) + BuildListExpandCount - 1) / BuildListExpandCount) * BuildListExpandCount; - // Increase or decrease the number of builds we want, with a bit of rubber-banding - const int IncreaseStep = 50; - if(VisibleItemCount > BuildList.Items.Count - 20) - { - PerforceMonitor.PendingMaxChanges = NumChanges + IncreaseStep; - } - else if(bAllowShrink) - { - int NewNumChanges = ListIndexToChangeIndex[VisibleItemCount - 1] + IncreaseStep; - if(NewNumChanges < NumChanges) - { - PerforceMonitor.PendingMaxChanges = NewNumChanges; - } - } + // Shrink the number of changes retained by the PerforceMonitor class + if(PerforceMonitor.PendingMaxChanges > NewPendingMaxChanges) + { + PerforceMonitor.PendingMaxChanges = NewPendingMaxChanges; } } } void StartSync(int ChangeNumber) + { + StartSync(ChangeNumber, null); + } + + void StartSync(int ChangeNumber, WorkspaceUpdateCallback Callback) { WorkspaceUpdateOptions Options = WorkspaceUpdateOptions.Sync | WorkspaceUpdateOptions.SyncArchives | WorkspaceUpdateOptions.GenerateProjectFiles; - if(Settings.bAutoResolveConflicts) - { - Options |= WorkspaceUpdateOptions.AutoResolveChanges; - } - if(Settings.bUseIncrementalBuilds) - { - Options |= WorkspaceUpdateOptions.UseIncrementalBuilds; - } if(Settings.bBuildAfterSync) { Options |= WorkspaceUpdateOptions.Build; @@ -709,14 +765,15 @@ namespace UnrealGameSync { Options |= WorkspaceUpdateOptions.OpenSolutionAfterSync; } - if(WorkspaceSettings.bSyncAllProjects ?? Settings.bSyncAllProjects) - { - Options |= WorkspaceUpdateOptions.SyncAllProjects; - } - StartWorkspaceUpdate(ChangeNumber, Options); + StartWorkspaceUpdate(ChangeNumber, Options, Callback); } void StartWorkspaceUpdate(int ChangeNumber, WorkspaceUpdateOptions Options) + { + StartWorkspaceUpdate(ChangeNumber, Options, null); + } + + void StartWorkspaceUpdate(int ChangeNumber, WorkspaceUpdateOptions Options, WorkspaceUpdateCallback Callback) { if((Options & (WorkspaceUpdateOptions.Sync | WorkspaceUpdateOptions.Build)) != 0 && GetProcessesRunningInWorkspace().Length > 0) { @@ -753,11 +810,30 @@ namespace UnrealGameSync } Context.ArchiveTypeToDepotPath.Add(EditorArchiveType, EditorArchivePath); } - StartWorkspaceUpdate(Context); + StartWorkspaceUpdate(Context, Callback); } - void StartWorkspaceUpdate(WorkspaceUpdateContext Context) + void StartWorkspaceUpdate(WorkspaceUpdateContext Context, WorkspaceUpdateCallback Callback) { + if(Settings.bAutoResolveConflicts) + { + Context.Options |= WorkspaceUpdateOptions.AutoResolveChanges; + } + if(Settings.bUseIncrementalBuilds) + { + Context.Options |= WorkspaceUpdateOptions.UseIncrementalBuilds; + } + if(WorkspaceSettings.bSyncAllProjects ?? Settings.bSyncAllProjects) + { + Context.Options |= WorkspaceUpdateOptions.SyncAllProjects | WorkspaceUpdateOptions.IncludeAllProjectsInSolution; + } + if(WorkspaceSettings.bIncludeAllProjectsInSolution ?? Settings.bIncludeAllProjectsInSolution) + { + Context.Options |= WorkspaceUpdateOptions.IncludeAllProjectsInSolution; + } + + UpdateCallback = Callback; + Context.StartTime = DateTime.UtcNow; Context.PerforceSyncOptions = (PerforceSyncOptions)Settings.SyncOptions.Clone(); @@ -810,6 +886,12 @@ namespace UnrealGameSync Workspace.CancelUpdate(); + if(UpdateCallback != null) + { + UpdateCallback(WorkspaceUpdateResult.Canceled); + UpdateCallback = null; + } + UpdateTimer.Stop(); UpdateSyncActionCheckboxes(); @@ -863,7 +945,7 @@ namespace UnrealGameSync DeleteWindow Window = new DeleteWindow(Context.DeleteFiles); if(Window.ShowDialog(this) == DialogResult.OK) { - StartWorkspaceUpdate(Context); + StartWorkspaceUpdate(Context, UpdateCallback); return; } } @@ -875,7 +957,7 @@ namespace UnrealGameSync ClobberWindow Window = new ClobberWindow(Context.ClobberFiles); if(Window.ShowDialog(this) == DialogResult.OK) { - StartWorkspaceUpdate(Context); + StartWorkspaceUpdate(Context, UpdateCallback); return; } } @@ -899,6 +981,12 @@ namespace UnrealGameSync } } + if(UpdateCallback != null) + { + UpdateCallback(Result); + UpdateCallback = null; + } + DesiredTaskbarState = Tuple.Create((Result == WorkspaceUpdateResult.Success)? TaskbarState.NoProgress : TaskbarState.Error, 0.0f); Owner.UpdateProgress(); @@ -937,11 +1025,11 @@ namespace UnrealGameSync } } - int SelectedChange = (BuildList.SelectedItems.Count > 0)? ((PerforceChangeSummary)BuildList.SelectedItems[0].Tag).Number : -1; - ChangeNumberToArchivePath.Clear(); ChangeNumberToLayoutInfo.Clear(); + ExpandItem = null; + BuildList.BeginUpdate(); foreach(ListViewGroup Group in BuildList.Groups) @@ -987,7 +1075,13 @@ namespace UnrealGameSync ListViewGroup Group; - string GroupName = Change.Date.ToString("D");//"dddd\\,\\ h\\.mmtt"); + DateTime DisplayTime = Change.Date; + if(Settings.bShowLocalTimes) + { + DisplayTime = (DisplayTime - PerforceMonitor.ServerTimeZone).ToLocalTime(); + } + + string GroupName = DisplayTime.ToString("D");//"dddd\\,\\ h\\.mmtt"); for(int Idx = 0;;Idx++) { if(Idx == BuildList.Groups.Count) @@ -1004,12 +1098,6 @@ namespace UnrealGameSync } } - DateTime DisplayTime = Change.Date; - if(Settings.bShowLocalTimes) - { - DisplayTime = (DisplayTime - ServerTimeZone).ToLocalTime(); - } - string[] SubItemLabels = new string[BuildList.Columns.Count]; SubItemLabels[ChangeColumn.Index] = String.Format("{0}", Change.Number); SubItemLabels[TimeColumn.Index] = DisplayTime.ToString("h\\.mmtt"); @@ -1052,6 +1140,19 @@ namespace UnrealGameSync BuildList.Groups.RemoveAt(0); } + if(Changes.Count > 0) + { + ExpandItem = new ListViewItem((BuildList.Groups.Count > 0)? BuildList.Groups[BuildList.Groups.Count - 1] : null); + ExpandItem.Tag = null; + ExpandItem.Selected = false; + ExpandItem.Text = ""; + for(int ColumnIdx = 1; ColumnIdx < BuildList.Columns.Count; ColumnIdx++) + { + ExpandItem.SubItems.Add(new ListViewItem.ListViewSubItem(ExpandItem, "")); + } + BuildList.Items.Add(ExpandItem); + } + BuildList.EndUpdate(); if(PendingSelectedChangeNumber != -1) @@ -1073,6 +1174,30 @@ namespace UnrealGameSync bool ShouldShowChange(PerforceChangeSummary Change, string[] ExcludeChanges) { + if(ProjectSettings.FilterType != FilterType.None) + { + PerforceChangeDetails Details; + if(!PerforceMonitor.TryGetChangeDetails(Change.Number, out Details)) + { + return false; + } + if(ProjectSettings.FilterType == FilterType.Code && !Details.bContainsCode) + { + return false; + } + if(ProjectSettings.FilterType == FilterType.Content && !Details.bContainsContent) + { + return false; + } + } + if(ProjectSettings.FilterBadges.Count > 0) + { + EventSummary Summary = EventMonitor.GetSummaryForChange(Change.Number); + if(Summary == null || !Summary.Badges.Any(x => ProjectSettings.FilterBadges.Contains(x.BadgeName))) + { + return false; + } + } if(!Settings.bShowAutomatedChanges) { foreach(string ExcludeChange in ExcludeChanges) @@ -1133,22 +1258,25 @@ namespace UnrealGameSync // Add a dummy group for any other badges we have foreach(ListViewItem Item in BuildList.Items) { - EventSummary Summary = EventMonitor.GetSummaryForChange(((PerforceChangeSummary)Item.Tag).Number); - if(Summary != null) + if(Item.Tag != null) { - foreach(BuildData Build in Summary.Builds) + EventSummary Summary = EventMonitor.GetSummaryForChange(((PerforceChangeSummary)Item.Tag).Number); + if(Summary != null) { - string BadgeSlot = Build.BadgeName; - if(!BadgeNameToGroup.ContainsKey(BadgeSlot)) + foreach(BadgeData Badge in Summary.Badges) { - BadgeNameToGroup.Add(BadgeSlot, "XXXX"); + string BadgeSlot = Badge.BadgeName; + if(!BadgeNameToGroup.ContainsKey(BadgeSlot)) + { + BadgeNameToGroup.Add(BadgeSlot, "XXXX"); + } } } } } // Remove anything that's a service badge - foreach(KeyValuePair ServiceBadge in ServiceBadges) + foreach(KeyValuePair ServiceBadge in ServiceBadges) { BadgeNameToGroup.Remove(ServiceBadge.Key); } @@ -1176,6 +1304,13 @@ namespace UnrealGameSync UpdateServiceBadges(); UpdateStatusPanel(); UpdateBuildFailureNotification(); + CheckForStartupComplete(); + + // If we are filtering by badges, we may also need to update the build list + if(ProjectSettings.FilterType != FilterType.None || ProjectSettings.FilterBadges.Count > 0) + { + UpdateBuildList(); + } } void UpdateMaxBuildBadgeChars() @@ -1270,6 +1405,7 @@ namespace UnrealGameSync EventMonitor.ApplyUpdates(); Refresh(); UpdateBuildFailureNotification(); + CheckForStartupComplete(); } void UpdateBuildFailureNotification() @@ -1282,22 +1418,22 @@ namespace UnrealGameSync ContentBadges.UnionWith(PerforceMonitor.LatestProjectConfigFile.GetValues("Notifications.ContentBadges", new string[0])); // Find the most recent build of each type, and the last time it succeeded - Dictionary TypeToLastBuild = new Dictionary(); - Dictionary TypeToLastSucceededBuild = new Dictionary(); + Dictionary TypeToLastBuild = new Dictionary(); + Dictionary TypeToLastSucceededBuild = new Dictionary(); for(int Idx = SortedChangeNumbers.Count - 1; Idx >= 0; Idx--) { EventSummary Summary = EventMonitor.GetSummaryForChange(SortedChangeNumbers[Idx]); if(Summary != null) { - foreach(BuildData Build in Summary.Builds) + foreach(BadgeData Badge in Summary.Badges) { - if(!TypeToLastBuild.ContainsKey(Build.BuildType) && (Build.Result == BuildDataResult.Success || Build.Result == BuildDataResult.Warning || Build.Result == BuildDataResult.Failure)) + if(!TypeToLastBuild.ContainsKey(Badge.BuildType) && (Badge.Result == BadgeResult.Success || Badge.Result == BadgeResult.Warning || Badge.Result == BadgeResult.Failure)) { - TypeToLastBuild.Add(Build.BuildType, Build); + TypeToLastBuild.Add(Badge.BuildType, Badge); } - if(!TypeToLastSucceededBuild.ContainsKey(Build.BuildType) && Build.Result == BuildDataResult.Success) + if(!TypeToLastSucceededBuild.ContainsKey(Badge.BuildType) && Badge.Result == BadgeResult.Success) { - TypeToLastSucceededBuild.Add(Build.BuildType, Build); + TypeToLastSucceededBuild.Add(Badge.BuildType, Badge); } } } @@ -1305,10 +1441,10 @@ namespace UnrealGameSync // Find all the build types that the user needs to be notified about. int RequireNotificationForChange = -1; - List NotifyBuilds = new List(); - foreach(BuildData LastBuild in TypeToLastBuild.Values.OrderBy(x => x.BuildType)) + List NotifyBuilds = new List(); + foreach(BadgeData LastBuild in TypeToLastBuild.Values.OrderBy(x => x.BuildType)) { - if(LastBuild.Result == BuildDataResult.Failure || LastBuild.Result == BuildDataResult.Warning) + if(LastBuild.Result == BadgeResult.Failure || LastBuild.Result == BadgeResult.Warning) { // Get the last submitted changelist by this user of the correct type int LastChangeByCurrentUserOfType; @@ -1325,7 +1461,7 @@ namespace UnrealGameSync if(LastChangeByCurrentUserOfType > 0 && LastBuild.ChangeNumber >= LastChangeByCurrentUserOfType) { // And check that there wasn't a successful build after we submitted (if there was, we're in the clear) - BuildData LastSuccessfulBuild; + BadgeData LastSuccessfulBuild; if(!TypeToLastSucceededBuild.TryGetValue(LastBuild.BuildType, out LastSuccessfulBuild) || LastSuccessfulBuild.ChangeNumber < LastChangeByCurrentUserOfType) { // Add it to the list of notifications @@ -1357,7 +1493,7 @@ namespace UnrealGameSync } // Show the balloon tooltip - if(NotifyBuilds.Any(x => x.Result == BuildDataResult.Failure)) + if(NotifyBuilds.Any(x => x.Result == BadgeResult.Failure)) { string Title = String.Format("{0} Errors", PlatformList.ToString()); string Message = String.Format("CIS failed after your last submitted changelist ({0}).", RequireNotificationForChange); @@ -1375,7 +1511,7 @@ namespace UnrealGameSync NotificationWindow.OnMoreInformation = () => { Owner.ShowAndActivate(); SelectChange(HighlightChange); }; // Don't show messages for this change again - foreach(BuildData NotifyBuild in NotifyBuilds) + foreach(BadgeData NotifyBuild in NotifyBuilds) { NotifiedBuildTypeToChangeNumber[NotifyBuild.BuildType] = RequireNotificationForChange; } @@ -1387,23 +1523,106 @@ namespace UnrealGameSync e.DrawDefault = true; } - private void BuildList_DrawItem(object sender, DrawListViewItemEventArgs e) + class ExpandRowLayout { - if(e.State.HasFlag(ListViewItemStates.Selected)) + public string MainText; + public Rectangle MainRect; + public string LinkText; + public Rectangle LinkRect; + } + + private ExpandRowLayout LayoutExpandRow(Graphics Graphics, Rectangle Bounds) + { + ExpandRowLayout Layout = new ExpandRowLayout(); + + int CurrentMaxChanges = PerforceMonitor.CurrentMaxChanges; + + string ShowingChanges; + if(ListIndexToChangeIndex.Count == CurrentMaxChanges) { - BuildList.DrawSelectedBackground(e.Graphics, e.Bounds); - } - else if(e.ItemIndex == BuildList.HoverItem) - { - BuildList.DrawTrackedBackground(e.Graphics, e.Bounds); - } - else if(((PerforceChangeSummary)e.Item.Tag).Number == Workspace.PendingChangeNumber) - { - BuildList.DrawTrackedBackground(e.Graphics, e.Bounds); + ShowingChanges = String.Format("Showing {0} changes.", ListIndexToChangeIndex.Count); } else + { + ShowingChanges = String.Format("Showing {0}/{1} changes.", ListIndexToChangeIndex.Count, CurrentMaxChanges); + } + + if(PerforceMonitor.PendingMaxChanges > CurrentMaxChanges) + { + Layout.MainText = String.Format("{0}. Fetching {1} more from server... ", ShowingChanges, PerforceMonitor.PendingMaxChanges - CurrentMaxChanges); + Layout.LinkText = "Cancel"; + } + else + { + Layout.MainText = String.Format("{0} ", ShowingChanges); + Layout.LinkText = "Show more..."; + } + + Size MainTextSize = TextRenderer.MeasureText(Graphics, Layout.MainText, BuildFont, new Size(1000, 1000), TextFormatFlags.NoPadding); + Size LinkTextSize = TextRenderer.MeasureText(Graphics, Layout.LinkText, BuildFont, new Size(1000, 1000), TextFormatFlags.NoPadding); + + int MinX = Bounds.Left + (Bounds.Width - MainTextSize.Width - LinkTextSize.Width) / 2; + int MinY = Bounds.Bottom - MainTextSize.Height - 1; + + Layout.MainRect = new Rectangle(new Point(MinX, MinY), MainTextSize); + Layout.LinkRect = new Rectangle(new Point(MinX + MainTextSize.Width, MinY), LinkTextSize); + + return Layout; + } + + private void DrawExpandRow(Graphics Graphics, ExpandRowLayout Layout) + { + TextRenderer.DrawText(Graphics, Layout.MainText, BuildFont, Layout.MainRect, SystemColors.WindowText, TextFormatFlags.SingleLine | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPrefix | TextFormatFlags.NoPadding); + + Color LinkColor = SystemColors.HotTrack; + if(bMouseOverExpandLink) + { + LinkColor = Color.FromArgb(LinkColor.B, LinkColor.G, LinkColor.R); + } + + TextRenderer.DrawText(Graphics, Layout.LinkText, BuildFont, Layout.LinkRect, LinkColor, TextFormatFlags.SingleLine | TextFormatFlags.VerticalCenter | TextFormatFlags.NoPrefix | TextFormatFlags.NoPadding); + } + + private bool HitTestExpandLink(Point Location) + { + if(ExpandItem == null) + { + return false; + } + using(Graphics Graphics = Graphics.FromHwnd(IntPtr.Zero)) + { + ExpandRowLayout Layout = LayoutExpandRow(Graphics, ExpandItem.Bounds); + return Layout.LinkRect.Contains(Location); + } + } + + private void BuildList_DrawItem(object sender, DrawListViewItemEventArgs e) + { + if(e.Item == ExpandItem) { BuildList.DrawDefaultBackground(e.Graphics, e.Bounds); + + ExpandRowLayout ExpandLayout = LayoutExpandRow(e.Graphics, e.Bounds); + DrawExpandRow(e.Graphics, ExpandLayout); + } + else + { + if(e.State.HasFlag(ListViewItemStates.Selected)) + { + BuildList.DrawSelectedBackground(e.Graphics, e.Bounds); + } + else if(e.ItemIndex == BuildList.HoverItem) + { + BuildList.DrawTrackedBackground(e.Graphics, e.Bounds); + } + else if(((PerforceChangeSummary)e.Item.Tag).Number == Workspace.PendingChangeNumber) + { + BuildList.DrawTrackedBackground(e.Graphics, e.Bounds); + } + else + { + BuildList.DrawDefaultBackground(e.Graphics, e.Bounds); + } } } @@ -1487,6 +1706,10 @@ namespace UnrealGameSync private void BuildList_DrawSubItem(object sender, DrawListViewSubItemEventArgs e) { PerforceChangeSummary Change = (PerforceChangeSummary)e.Item.Tag; + if(Change == null) + { + return; + } float DpiScaleX = e.Graphics.DpiX / 96.0f; float DpiScaleY = e.Graphics.DpiY / 96.0f; @@ -1504,6 +1727,14 @@ namespace UnrealGameSync int BadgeAlpha = bAllowSync? 255 : 128; Color TextColor = (bAllowSync || Change.Number == Workspace.PendingChangeNumber || Change.Number == Workspace.CurrentChangeNumber || (WorkspaceSettings != null && WorkspaceSettings.AdditionalChangeNumbers.Contains(Change.Number)))? SystemColors.WindowText : Blend(SystemColors.Window, SystemColors.WindowText, 0.25f); + const int FadeRange = 6; + if(e.ItemIndex >= BuildList.Items.Count - FadeRange) + { + float Opacity = (float)(BuildList.Items.Count - e.ItemIndex - 0.9f) / FadeRange; + BadgeAlpha = (int)(BadgeAlpha * Opacity); + TextColor = Blend(SystemColors.Window, TextColor, Opacity); + } + if(e.ColumnIndex == IconColumn.Index) { EventSummary Summary = EventMonitor.GetSummaryForChange(Change.Number); @@ -1833,6 +2064,7 @@ namespace UnrealGameSync string Color = BadgeDefinitionObject.GetValue("Color", "#909090"); string HoverColor = BadgeDefinitionObject.GetValue("HoverColor", "#b0b0b0"); string Url = BadgeDefinitionObject.GetValue("Url", null); + string Arguments = BadgeDefinitionObject.GetValue("Arguments", null); if(!String.IsNullOrEmpty(Name) && !String.IsNullOrEmpty(Pattern)) { foreach(Match MatchResult in Regex.Matches(Description, Pattern, RegexOptions.Multiline)) @@ -1841,7 +2073,25 @@ namespace UnrealGameSync Color HoverBadgeColor = System.Drawing.ColorTranslator.FromHtml(HoverColor); string UniqueId = String.IsNullOrEmpty(Url)? null : String.Format("Description:{0}:{1}", Change.Number, Badges.Count); - Badges.Add(new BadgeInfo(ReplaceRegexMatches(Name, MatchResult), Group, UniqueId, BadgeColor, HoverBadgeColor, ReplaceRegexMatches(Url, MatchResult))); + + string ExpandedUrl = ReplaceRegexMatches(Url, MatchResult); + string ExpandedArguments = ReplaceRegexMatches(Arguments, MatchResult); + + Action ClickHandler; + if(String.IsNullOrEmpty(ExpandedUrl)) + { + ClickHandler = null; + } + else if(String.IsNullOrEmpty(ExpandedArguments)) + { + ClickHandler = () => Process.Start(ExpandedUrl); + } + else + { + ClickHandler = () => Process.Start(ExpandedUrl, ExpandedArguments); + } + + Badges.Add(new BadgeInfo(ReplaceRegexMatches(Name, MatchResult), Group, UniqueId, BadgeColor, HoverBadgeColor, ClickHandler)); } } } @@ -1961,28 +2211,28 @@ namespace UnrealGameSync { List Badges = new List(); - if(Summary != null && Summary.Builds.Count > 0) + if(Summary != null && Summary.Badges.Count > 0) { // Create a lookup for build data for each badge name - Dictionary BadgeNameToBuildData = new Dictionary(); - foreach(BuildData Build in Summary.Builds) + Dictionary BadgeNameToBuildData = new Dictionary(); + foreach(BadgeData Badge in Summary.Badges) { - BadgeNameToBuildData[Build.BadgeName] = Build; + BadgeNameToBuildData[Badge.BadgeName] = Badge; } // Add all the badges, sorted by group foreach(KeyValuePair BadgeNameAndGroup in BadgeNameAndGroupPairs) { - BuildData Build; - BadgeNameToBuildData.TryGetValue(BadgeNameAndGroup.Key, out Build); + BadgeData BadgeData; + BadgeNameToBuildData.TryGetValue(BadgeNameAndGroup.Key, out BadgeData); - BadgeInfo Badge = CreateBadge(ChangeNumber, BadgeNameAndGroup.Key, BadgeNameAndGroup.Value, Build); - if(MaxBuildBadgeChars != -1 && Badge.Label.Length > MaxBuildBadgeChars) + BadgeInfo BadgeInfo = CreateBadge(ChangeNumber, BadgeNameAndGroup.Key, BadgeNameAndGroup.Value, BadgeData); + if(MaxBuildBadgeChars != -1 && BadgeInfo.Label.Length > MaxBuildBadgeChars) { - Badge.ToolTip = Badge.Label; - Badge.Label = Badge.Label.Substring(0, MaxBuildBadgeChars); + BadgeInfo.ToolTip = BadgeInfo.Label; + BadgeInfo.Label = BadgeInfo.Label.Substring(0, MaxBuildBadgeChars); } - Badges.Add(Badge); + Badges.Add(BadgeInfo); } } @@ -1991,27 +2241,37 @@ namespace UnrealGameSync return Badges; } - private BadgeInfo CreateBadge(int ChangeNumber, string BadgeName, string BadgeGroup, BuildData Build) + private BadgeInfo CreateBadge(int ChangeNumber, string BadgeName, string BadgeGroup, BadgeData BadgeData) { string BadgeLabel = BadgeName; Color BadgeColor = Color.FromArgb(0, Color.White); - if(Build != null) + if(BadgeData != null) { - BadgeLabel = Build.BadgeLabel; - BadgeColor = GetBuildBadgeColor(Build.Result); + BadgeLabel = BadgeData.BadgeLabel; + BadgeColor = GetBuildBadgeColor(BadgeData.Result); } Color HoverBadgeColor = Color.FromArgb(BadgeColor.A, Math.Min(BadgeColor.R + 32, 255), Math.Min(BadgeColor.G + 32, 255), Math.Min(BadgeColor.B + 32, 255)); + Action ClickHandler; + if(BadgeData == null || String.IsNullOrEmpty(BadgeData.Url)) + { + ClickHandler = null; + } + else + { + ClickHandler = () => Process.Start(BadgeData.Url); + } + string UniqueId = String.Format("{0}:{1}", ChangeNumber, BadgeName); - return new BadgeInfo(BadgeLabel, BadgeGroup, UniqueId, BadgeColor, HoverBadgeColor, Build); + return new BadgeInfo(BadgeLabel, BadgeGroup, UniqueId, BadgeColor, HoverBadgeColor, ClickHandler); } private Dictionary> CreateCustomBadges(int ChangeNumber, EventSummary Summary) { Dictionary> ColumnNameToBadges = new Dictionary>(); - if(Summary != null && Summary.Builds.Count > 0) + if(Summary != null && Summary.Badges.Count > 0) { foreach(ColumnHeader CustomColumn in CustomColumns) { @@ -2023,7 +2283,7 @@ namespace UnrealGameSync string[] BadgeNames = Config.GetValue("Badges", "").Split(new char[]{ ',' }, StringSplitOptions.RemoveEmptyEntries); foreach(string BadgeName in BadgeNames) { - BadgeInfo Badge = CreateBadge(ChangeNumber, BadgeName, "XXXX", Summary.Builds.FirstOrDefault(x => x.BadgeName == BadgeName)); + BadgeInfo Badge = CreateBadge(ChangeNumber, BadgeName, "XXXX", Summary.Badges.FirstOrDefault(x => x.BadgeName == BadgeName)); Badges.Add(Badge); } @@ -2036,21 +2296,21 @@ namespace UnrealGameSync return ColumnNameToBadges; } - private static Color GetBuildBadgeColor(BuildDataResult Result) + private static Color GetBuildBadgeColor(BadgeResult Result) { - if(Result == BuildDataResult.Starting) + if(Result == BadgeResult.Starting) { return Color.FromArgb(128, 192, 255); } - else if(Result == BuildDataResult.Warning) + else if(Result == BadgeResult.Warning) { return Color.FromArgb(255, 192, 0); } - else if(Result == BuildDataResult.Failure) + else if(Result == BadgeResult.Failure) { return Color.FromArgb(192, 64, 0); } - else if(Result == BuildDataResult.Skipped) + else if(Result == BadgeResult.Skipped) { return Color.FromArgb(192, 192, 192); } @@ -2224,13 +2484,16 @@ namespace UnrealGameSync if(HitTest.Item != null) { PerforceChangeSummary Change = (PerforceChangeSummary)HitTest.Item.Tag; - if(Change.Number == Workspace.CurrentChangeNumber) + if(Change != null) { - LaunchEditor(); - } - else - { - StartSync(Change.Number); + if(Change.Number == Workspace.CurrentChangeNumber) + { + LaunchEditor(); + } + else + { + StartSync(Change.Number, null); + } } } } @@ -2287,7 +2550,7 @@ namespace UnrealGameSync } WorkspaceUpdateContext Context = new WorkspaceUpdateContext(Workspace.CurrentChangeNumber, Options, null, GetDefaultBuildStepObjects(), ProjectSettings.BuildSteps, null, GetWorkspaceVariables(Workspace.CurrentChangeNumber)); - StartWorkspaceUpdate(Context); + StartWorkspaceUpdate(Context, null); } } } @@ -2586,12 +2849,12 @@ namespace UnrealGameSync ProgramsLine.AddText(" | "); ProgramsLine.AddLink("Windows Explorer", FontStyle.Regular, () => { Process.Start("explorer.exe", String.Format("\"{0}\"", Path.GetDirectoryName(SelectedFileName))); }); - foreach(KeyValuePair ServiceBadge in ServiceBadges) + foreach(KeyValuePair ServiceBadge in ServiceBadges) { ProgramsLine.AddText(" | "); if(ServiceBadge.Value == null) { - ProgramsLine.AddBadge(ServiceBadge.Key, GetBuildBadgeColor(BuildDataResult.Skipped), null); + ProgramsLine.AddBadge(ServiceBadge.Key, GetBuildBadgeColor(BadgeResult.Skipped), null); } else { @@ -2741,6 +3004,7 @@ namespace UnrealGameSync if(Idx + 1 < Text.Length && Text[Idx + 1] == '[') { ElementText.Append(Text[Idx]); + Idx += 2; continue; } @@ -2798,41 +3062,39 @@ namespace UnrealGameSync bool bShownContextMenu = false; if(StreamName != null) { - IReadOnlyList OtherStreamFilter = Workspace.ProjectStreamFilter; - if(OtherStreamFilter != null) + IReadOnlyList OtherStreamNames = Workspace.ProjectStreamFilter; + if(OtherStreamNames != null) { - IReadOnlyList OtherStreamNames = PerforceMonitor.OtherStreamNames.Where(x => OtherStreamFilter.Any(y => y.Equals(x, StringComparison.InvariantCultureIgnoreCase)) || x.Equals(StreamName, StringComparison.InvariantCultureIgnoreCase)).ToList().AsReadOnly(); - if(OtherStreamNames.Count > 0) + StreamContextMenu.Items.Clear(); + + ToolStripMenuItem CurrentStreamItem = new ToolStripMenuItem(StreamName, null, new EventHandler((S, E) => SelectStream(StreamName))); + CurrentStreamItem.Checked = true; + StreamContextMenu.Items.Add(CurrentStreamItem); + + StreamContextMenu.Items.Add(new ToolStripSeparator()); + + foreach (string OtherStreamName in OtherStreamNames.OrderBy(x => x).Where(x => !x.EndsWith("/Dev-Binaries"))) { - StreamContextMenu.Items.Clear(); - - foreach (string OtherStreamName in OtherStreamNames.OrderBy(x => x).Where(x => !x.EndsWith("/Dev-Binaries"))) + string ThisStreamName = OtherStreamName; // Local for lambda capture + if(String.Compare(StreamName, OtherStreamName, StringComparison.InvariantCultureIgnoreCase) != 0) { - string ThisStreamName = OtherStreamName; // Local for lambda capture - ToolStripMenuItem Item = new ToolStripMenuItem(ThisStreamName, null, new EventHandler((S, E) => SelectStream(ThisStreamName))); - if(String.Compare(StreamName, OtherStreamName, StringComparison.InvariantCultureIgnoreCase) == 0) - { - Item.Checked = true; - StreamContextMenu.Items.Insert(0, Item); - StreamContextMenu.Items.Insert(1, new ToolStripSeparator()); - } - else - { - StreamContextMenu.Items.Add(Item); - } + StreamContextMenu.Items.Add(Item); } - - StreamContextMenu.Items.Add(new ToolStripSeparator()); - StreamContextMenu.Items.Add(new ToolStripMenuItem("Select Other...", null, new EventHandler((S, E) => SelectOtherStreamDialog()))); - - int X = (Bounds.Left + Bounds.Right) / 2 + StreamContextMenu.Bounds.Width / 2; - int Y = Bounds.Bottom + 2; - StreamContextMenu.Show(StatusPanel, new Point(X, Y), ToolStripDropDownDirection.Left); - - bShownContextMenu = true; - } + + if(StreamContextMenu.Items.Count > 2) + { + StreamContextMenu.Items.Add(new ToolStripSeparator()); + } + + StreamContextMenu.Items.Add(new ToolStripMenuItem("Select Other...", null, new EventHandler((S, E) => SelectOtherStreamDialog()))); + + int X = (Bounds.Left + Bounds.Right) / 2 + StreamContextMenu.Bounds.Width / 2; + int Y = Bounds.Bottom + 2; + StreamContextMenu.Show(StatusPanel, new Point(X, Y), ToolStripDropDownDirection.Left); + + bShownContextMenu = true; } } if(!bShownContextMenu) @@ -2860,7 +3122,7 @@ namespace UnrealGameSync } else if(Workspace.Perforce != null) { - if(!Workspace.Perforce.HasOpenFiles(Log) || MessageBox.Show("You have files open for edit in this workspace. If you continue, you will not be able to submit them until you switch back.\n\nContinue switching workspaces?", "Files checked out", MessageBoxButtons.YesNo) == DialogResult.Yes) + if(!Workspace.Perforce.HasOpenFiles(Log) || MessageBox.Show("You have files open for edit in this workspace. If you continue, you will not be able to submit them until you switch back.\n\nContinue switching streams?", "Files checked out", MessageBoxButtons.YesNo) == DialogResult.Yes) { if(Workspace.Perforce.SwitchStream(NewStreamName, Log)) { @@ -2920,12 +3182,15 @@ namespace UnrealGameSync PendingSelectedChangeNumber = ChangeNumber; - if(BuildList.Items.Count > 0) + int CurrentMaxChanges = PerforceMonitor.CurrentMaxChanges; + if(PerforceMonitor.PendingMaxChanges <= CurrentMaxChanges && BuildList.SelectedItems.Count == 0) { - BuildList.Items[BuildList.Items.Count - 1].EnsureVisible(); + PerforceMonitor.PendingMaxChanges = CurrentMaxChanges + BuildListExpandCount; + if(ExpandItem != null) + { + ExpandItem.EnsureVisible(); + } } - - UpdateNumRequestedBuilds(false); } private void BuildList_MouseClick(object Sender, MouseEventArgs Args) @@ -2935,107 +3200,118 @@ namespace UnrealGameSync ListViewHitTestInfo HitTest = BuildList.HitTest(Args.Location); if(HitTest.Item != null) { - PerforceChangeSummary Change = (PerforceChangeSummary)HitTest.Item.Tag; - if(Workspace.PendingChangeNumber == Change.Number) + if(HitTest.Item == ExpandItem) { - Rectangle SubItemRect = HitTest.Item.SubItems[StatusColumn.Index].Bounds; - - if(Workspace.IsBusy()) + if(HitTestExpandLink(Args.Location)) { - Rectangle CancelRect = new Rectangle(SubItemRect.Right - 16, SubItemRect.Top, 16, SubItemRect.Height); - Rectangle InfoRect = new Rectangle(SubItemRect.Right - 32, SubItemRect.Top, 16, SubItemRect.Height); - if(CancelRect.Contains(Args.Location)) + int CurrentMaxChanges = PerforceMonitor.CurrentMaxChanges; + if(PerforceMonitor.PendingMaxChanges > CurrentMaxChanges) { - CancelWorkspaceUpdate(); + PerforceMonitor.PendingMaxChanges = CurrentMaxChanges; } - else if(InfoRect.Contains(Args.Location) && !Splitter.IsLogVisible()) + else { - ToggleLogVisibility(); - } - } - else - { - Rectangle HappyRect = new Rectangle(SubItemRect.Right - 32, SubItemRect.Top, 16, SubItemRect.Height); - Rectangle FrownRect = new Rectangle(SubItemRect.Right - 16, SubItemRect.Top, 16, SubItemRect.Height); - if(HappyRect.Contains(Args.Location)) - { - EventMonitor.PostEvent(Change.Number, EventType.Good); - BuildList.Invalidate(); - } - else if(FrownRect.Contains(Args.Location)) - { - EventMonitor.PostEvent(Change.Number, EventType.Bad); - BuildList.Invalidate(); + PerforceMonitor.PendingMaxChanges = CurrentMaxChanges + BuildListExpandCount; } + BuildList.Invalidate(); } } else { - Rectangle SyncBadgeRectangle = GetSyncBadgeRectangle(HitTest.Item.SubItems[StatusColumn.Index].Bounds); - if(SyncBadgeRectangle.Contains(Args.Location) && CanSyncChange(Change.Number)) + PerforceChangeSummary Change = (PerforceChangeSummary)HitTest.Item.Tag; + if(Workspace.PendingChangeNumber == Change.Number) { - StartSync(Change.Number); - } - } + Rectangle SubItemRect = HitTest.Item.SubItems[StatusColumn.Index].Bounds; - if(DescriptionColumn.Index < HitTest.Item.SubItems.Count && HitTest.Item.SubItems[DescriptionColumn.Index] == HitTest.SubItem) - { - ChangeLayoutInfo LayoutInfo = GetChangeLayoutInfo(Change); - if(LayoutInfo.DescriptionBadges.Count > 0) - { - Point BuildListLocation = GetBadgeListLocation(LayoutInfo.DescriptionBadges, HitTest.SubItem.Bounds, HorizontalAlign.Right, VerticalAlignment.Middle); - BuildListLocation.Offset(-2, 0); - - foreach (BadgeInfo Badge in LayoutInfo.DescriptionBadges) + if(Workspace.IsBusy()) { - Rectangle BadgeBounds = Badge.GetBounds(BuildListLocation); - if(BadgeBounds.Contains(Args.Location)) + Rectangle CancelRect = new Rectangle(SubItemRect.Right - 16, SubItemRect.Top, 16, SubItemRect.Height); + Rectangle InfoRect = new Rectangle(SubItemRect.Right - 32, SubItemRect.Top, 16, SubItemRect.Height); + if(CancelRect.Contains(Args.Location)) { - Process.Start((string)Badge.UserData); - break; + CancelWorkspaceUpdate(); + } + else if(InfoRect.Contains(Args.Location) && !Splitter.IsLogVisible()) + { + ToggleLogVisibility(); + } + } + else + { + Rectangle HappyRect = new Rectangle(SubItemRect.Right - 32, SubItemRect.Top, 16, SubItemRect.Height); + Rectangle FrownRect = new Rectangle(SubItemRect.Right - 16, SubItemRect.Top, 16, SubItemRect.Height); + if(HappyRect.Contains(Args.Location)) + { + EventMonitor.PostEvent(Change.Number, EventType.Good); + BuildList.Invalidate(); + } + else if(FrownRect.Contains(Args.Location)) + { + EventMonitor.PostEvent(Change.Number, EventType.Bad); + BuildList.Invalidate(); } } } - } - - if(CISColumn.Index < HitTest.Item.SubItems.Count && HitTest.Item.SubItems[CISColumn.Index] == HitTest.SubItem) - { - ChangeLayoutInfo LayoutInfo = GetChangeLayoutInfo(Change); - if(LayoutInfo.BuildBadges.Count > 0) + else { - Point BuildListLocation = GetBadgeListLocation(LayoutInfo.BuildBadges, HitTest.SubItem.Bounds, HorizontalAlign.Center, VerticalAlignment.Middle); - BuildListLocation.X = Math.Max(BuildListLocation.X, HitTest.SubItem.Bounds.Left); - - BadgeInfo Badge = HitTestBadge(Args.Location, LayoutInfo.BuildBadges, BuildListLocation); - if(Badge != null) + Rectangle SyncBadgeRectangle = GetSyncBadgeRectangle(HitTest.Item.SubItems[StatusColumn.Index].Bounds); + if(SyncBadgeRectangle.Contains(Args.Location) && CanSyncChange(Change.Number)) { - BuildData Build = (BuildData)Badge.UserData; - if(Build != null) - { - Process.Start(Build.Url); - } + StartSync(Change.Number, null); } } - } - foreach(ColumnHeader CustomColumn in CustomColumns) - { - if(CustomColumn.Index < HitTest.Item.SubItems.Count && HitTest.Item.SubItems[CustomColumn.Index] == HitTest.SubItem) + if(DescriptionColumn.Index < HitTest.Item.SubItems.Count && HitTest.Item.SubItems[DescriptionColumn.Index] == HitTest.SubItem) { - ChangeLayoutInfo LayoutInfo = GetChangeLayoutInfo((PerforceChangeSummary)HitTest.Item.Tag); - - List Badges; - if(LayoutInfo.CustomBadges.TryGetValue(CustomColumn.Text, out Badges) && Badges.Count > 0) + ChangeLayoutInfo LayoutInfo = GetChangeLayoutInfo(Change); + if(LayoutInfo.DescriptionBadges.Count > 0) { - Point ListLocation = GetBadgeListLocation(Badges, HitTest.SubItem.Bounds, HorizontalAlign.Center, VerticalAlignment.Middle); + Point BuildListLocation = GetBadgeListLocation(LayoutInfo.DescriptionBadges, HitTest.SubItem.Bounds, HorizontalAlign.Right, VerticalAlignment.Middle); + BuildListLocation.Offset(-2, 0); - BadgeInfo Badge = HitTestBadge(Args.Location, Badges, ListLocation); - if(Badge != null) + foreach (BadgeInfo Badge in LayoutInfo.DescriptionBadges) { - BuildData Build = (BuildData)Badge.UserData; - if(Build != null) + Rectangle BadgeBounds = Badge.GetBounds(BuildListLocation); + if(BadgeBounds.Contains(Args.Location) && Badge.ClickHandler != null) { - Process.Start(Build.Url); + Badge.ClickHandler(); + break; + } + } + } + } + + if(CISColumn.Index < HitTest.Item.SubItems.Count && HitTest.Item.SubItems[CISColumn.Index] == HitTest.SubItem) + { + ChangeLayoutInfo LayoutInfo = GetChangeLayoutInfo(Change); + if(LayoutInfo.BuildBadges.Count > 0) + { + Point BuildListLocation = GetBadgeListLocation(LayoutInfo.BuildBadges, HitTest.SubItem.Bounds, HorizontalAlign.Center, VerticalAlignment.Middle); + BuildListLocation.X = Math.Max(BuildListLocation.X, HitTest.SubItem.Bounds.Left); + + BadgeInfo BadgeInfo = HitTestBadge(Args.Location, LayoutInfo.BuildBadges, BuildListLocation); + if(BadgeInfo != null && BadgeInfo.ClickHandler != null) + { + BadgeInfo.ClickHandler(); + } + } + } + + foreach(ColumnHeader CustomColumn in CustomColumns) + { + if(CustomColumn.Index < HitTest.Item.SubItems.Count && HitTest.Item.SubItems[CustomColumn.Index] == HitTest.SubItem) + { + ChangeLayoutInfo LayoutInfo = GetChangeLayoutInfo((PerforceChangeSummary)HitTest.Item.Tag); + + List Badges; + if(LayoutInfo.CustomBadges.TryGetValue(CustomColumn.Text, out Badges) && Badges.Count > 0) + { + Point ListLocation = GetBadgeListLocation(Badges, HitTest.SubItem.Bounds, HorizontalAlign.Center, VerticalAlignment.Middle); + + BadgeInfo BadgeInfo = HitTestBadge(Args.Location, Badges, ListLocation); + if(BadgeInfo != null && BadgeInfo.ClickHandler != null) + { + BadgeInfo.ClickHandler(); } } } @@ -3159,8 +3435,16 @@ namespace UnrealGameSync { ListViewHitTestInfo HitTest = BuildList.HitTest(e.Location); + bool bNewMouseOverExpandLink = HitTest.Item != null && HitTestExpandLink(e.Location); + if(bMouseOverExpandLink != bNewMouseOverExpandLink) + { + bMouseOverExpandLink = bNewMouseOverExpandLink; + Cursor = bMouseOverExpandLink? NativeCursors.Hand : Cursors.Arrow; + BuildList.Invalidate(); + } + string NewHoverBadgeUniqueId = null; - if(HitTest.Item != null) + if(HitTest.Item != null && HitTest.Item.Tag is PerforceChangeSummary) { int ColumnIndex = HitTest.Item.SubItems.IndexOf(HitTest.SubItem); if(ColumnIndex == DescriptionColumn.Index) @@ -3183,9 +3467,16 @@ namespace UnrealGameSync BadgeInfo Badge = HitTestBadge(e.Location, LayoutInfo.BuildBadges, BuildListLocation); NewHoverBadgeUniqueId = (Badge != null)? Badge.UniqueId : null; - if(Badge != null && Badge.ToolTip != null && HoverBadgeUniqueId != NewHoverBadgeUniqueId) + if(HoverBadgeUniqueId != NewHoverBadgeUniqueId) { - BuildListToolTip.Show(Badge.ToolTip, BuildList, new Point(BuildListLocation.X + Badge.Offset, HitTest.Item.Bounds.Bottom + 2)); + if(Badge != null && Badge.ToolTip != null && Badge.BackgroundColor.A != 0) + { + BuildListToolTip.Show(Badge.ToolTip, BuildList, new Point(BuildListLocation.X + Badge.Offset, HitTest.Item.Bounds.Bottom + 2)); + } + else + { + BuildListToolTip.Hide(BuildList); + } } } } @@ -3249,8 +3540,6 @@ namespace UnrealGameSync OptionsContextMenu_ScheduledSync.Checked = Settings.bScheduleEnabled; OptionsContextMenu_TimeZone_Local.Checked = Settings.bShowLocalTimes; OptionsContextMenu_TimeZone_PerforceServer.Checked = !Settings.bShowLocalTimes; - OptionsContextMenu_AutomaticallyRunAtStartup.Checked = IsAutomaticallyRunAtStartup(); - OptionsContextMenu_KeepInTray.Checked = Settings.bKeepInTray; OptionsContextMenu_ShowChanges_ShowUnreviewed.Checked = Settings.bShowUnreviewedChanges; OptionsContextMenu_ShowChanges_ShowAutomated.Checked = Settings.bShowAutomatedChanges; OptionsContextMenu_TabNames_Stream.Checked = Settings.TabLabels == TabLabels.Stream; @@ -3328,6 +3617,11 @@ namespace UnrealGameSync } public void SyncLatestChange() + { + SyncLatestChange(null); + } + + public void SyncLatestChange(WorkspaceUpdateCallback Callback) { if(Workspace != null) { @@ -3344,7 +3638,7 @@ namespace UnrealGameSync { Owner.ShowAndActivate(); SelectChange(ChangeNumber); - StartSync(ChangeNumber); + StartSync(ChangeNumber, Callback); } } } @@ -3468,7 +3762,7 @@ namespace UnrealGameSync private void OnlyShowReviewedCheckBox_CheckedChanged(object sender, EventArgs e) { UpdateBuildList(); - UpdateNumRequestedBuilds(true); + ShrinkNumRequestedBuilds(); } public void Activate() @@ -3489,7 +3783,7 @@ namespace UnrealGameSync public void Deactivate() { - UpdateNumRequestedBuilds(true); + ShrinkNumRequestedBuilds(); } private void BuildList_ItemMouseHover(object sender, ListViewItemMouseHoverEventArgs Args) @@ -3500,6 +3794,10 @@ namespace UnrealGameSync if(Args.Item.Bounds.Contains(ClientPoint)) { PerforceChangeSummary Change = (PerforceChangeSummary)Args.Item.Tag; + if(Change == null) + { + return; + } Rectangle CISBounds = Args.Item.SubItems[CISColumn.Index].Bounds; if(CISBounds.Contains(ClientPoint)) @@ -3566,6 +3864,13 @@ namespace UnrealGameSync HoverBadgeUniqueId = null; BuildList.Invalidate(); } + + if(bMouseOverExpandLink) + { + Cursor = Cursors.Arrow; + bMouseOverExpandLink = false; + BuildList.Invalidate(); + } } private void OptionsContextMenu_ShowLog_Click(object sender, EventArgs e) @@ -3726,30 +4031,33 @@ namespace UnrealGameSync for(int Idx = 0; Idx < BuildList.Items.Count; Idx++) { PerforceChangeSummary Change = (PerforceChangeSummary)BuildList.Items[Idx].Tag; - if(ChangeType == LatestChangeType.Any) + if(Change != null) { - if(CanSyncChange(Change.Number)) + if(ChangeType == LatestChangeType.Any) { - ChangeNumber = FindNewestGoodContentChange(Change.Number); - return true; + if(CanSyncChange(Change.Number)) + { + ChangeNumber = FindNewestGoodContentChange(Change.Number); + return true; + } } - } - else if(ChangeType == LatestChangeType.Good) - { - EventSummary Summary = EventMonitor.GetSummaryForChange(Change.Number); - if(Summary != null && Summary.Verdict == ReviewVerdict.Good && CanSyncChange(Change.Number)) + else if(ChangeType == LatestChangeType.Good) { - ChangeNumber = FindNewestGoodContentChange(Change.Number); - return true; + EventSummary Summary = EventMonitor.GetSummaryForChange(Change.Number); + if(Summary != null && Summary.Verdict == ReviewVerdict.Good && CanSyncChange(Change.Number)) + { + ChangeNumber = FindNewestGoodContentChange(Change.Number); + return true; + } } - } - else if(ChangeType == LatestChangeType.Starred) - { - EventSummary Summary = EventMonitor.GetSummaryForChange(Change.Number); - if(((Summary != null && Summary.LastStarReview != null) || PromotedChangeNumbers.Contains(Change.Number)) && CanSyncChange(Change.Number)) + else if(ChangeType == LatestChangeType.Starred) { - ChangeNumber = FindNewestGoodContentChange(Change.Number); - return true; + EventSummary Summary = EventMonitor.GetSummaryForChange(Change.Number); + if(((Summary != null && Summary.LastStarReview != null) || PromotedChangeNumbers.Contains(Change.Number)) && CanSyncChange(Change.Number)) + { + ChangeNumber = FindNewestGoodContentChange(Change.Number); + return true; + } } } } @@ -3895,7 +4203,7 @@ namespace UnrealGameSync else { WorkspaceUpdateContext Context = new WorkspaceUpdateContext(Workspace.CurrentChangeNumber, WorkspaceUpdateOptions.Build, null, GetDefaultBuildStepObjects(), ProjectSettings.BuildSteps, new HashSet{ UniqueId }, GetWorkspaceVariables(Workspace.CurrentChangeNumber)); - StartWorkspaceUpdate(Context); + StartWorkspaceUpdate(Context, null); } } } @@ -3957,13 +4265,16 @@ namespace UnrealGameSync List ModifiedBuildSteps = new List(); foreach(BuildStep Step in UserSteps) { - ConfigObject DefaultObject; - ProjectBuildStepObjects.TryGetValue(Step.UniqueId, out DefaultObject); - - ConfigObject UserConfigObject = Step.ToConfigObject(DefaultObject); - if(UserConfigObject != null) + if(Step.IsValid()) { - ModifiedBuildSteps.Add(UserConfigObject); + ConfigObject DefaultObject; + ProjectBuildStepObjects.TryGetValue(Step.UniqueId, out DefaultObject); + + ConfigObject UserConfigObject = Step.ToConfigObject(DefaultObject); + if(UserConfigObject != null && UserConfigObject.Pairs.Any(x => x.Key != "UniqueId")) + { + ModifiedBuildSteps.Add(UserConfigObject); + } } } @@ -4059,25 +4370,6 @@ namespace UnrealGameSync return UserBuildStepObjects.Values.Select(x => new BuildStep(x)).OrderBy(x => x.OrderIndex).ToList(); } - private void OptionsContextMenu_AutomaticallyRunAtStartup_Click(object sender, EventArgs e) - { - RegistryKey Key = Registry.CurrentUser.CreateSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"); - if(IsAutomaticallyRunAtStartup()) - { - Key.DeleteValue("UnrealGameSync", false); - } - else - { - Key.SetValue("UnrealGameSync", String.Format("\"{0}\" -RestoreState", OriginalExecutableFileName)); - } - } - - private bool IsAutomaticallyRunAtStartup() - { - RegistryKey Key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"); - return (Key.GetValue("UnrealGameSync") != null); - } - private void OptionsContextMenu_SyncPrecompiledEditor_Click(object sender, EventArgs e) { OptionsContextMenu_SyncPrecompiledEditor.Checked ^= true; @@ -4117,16 +4409,18 @@ namespace UnrealGameSync private void OptionsContextMenu_SyncFilter_Click(object sender, EventArgs e) { - SyncFilter Filter = new SyncFilter(Workspace.GetSyncCategories(), Settings.SyncView, Settings.SyncExcludedCategories, Settings.bSyncAllProjects, WorkspaceSettings.SyncView, WorkspaceSettings.SyncIncludedCategories, WorkspaceSettings.SyncExcludedCategories, WorkspaceSettings.bSyncAllProjects); + SyncFilter Filter = new SyncFilter(Workspace.GetSyncCategories(), Settings.SyncView, Settings.SyncExcludedCategories, Settings.bSyncAllProjects, Settings.bIncludeAllProjectsInSolution, WorkspaceSettings.SyncView, WorkspaceSettings.SyncIncludedCategories, WorkspaceSettings.SyncExcludedCategories, WorkspaceSettings.bSyncAllProjects, WorkspaceSettings.bIncludeAllProjectsInSolution); if(Filter.ShowDialog() == DialogResult.OK) { Settings.SyncExcludedCategories = Filter.GlobalExcludedCategories; Settings.SyncView = Filter.GlobalView; Settings.bSyncAllProjects = Filter.bGlobalSyncAllProjects; + Settings.bIncludeAllProjectsInSolution = Filter.bGlobalIncludeAllProjectsInSolution; WorkspaceSettings.SyncIncludedCategories = Filter.WorkspaceIncludedCategories; WorkspaceSettings.SyncExcludedCategories = Filter.WorkspaceExcludedCategories; WorkspaceSettings.SyncView = Filter.WorkspaceView; WorkspaceSettings.bSyncAllProjects = Filter.bWorkspaceSyncAllProjects; + WorkspaceSettings.bIncludeAllProjectsInSolution = Filter.bWorkspaceIncludeAllProjectsInSolution; Settings.Save(); } } @@ -4171,12 +4465,6 @@ namespace UnrealGameSync } } - private void OptionsContextMenu_KeepInTray_Click(object sender, EventArgs e) - { - Settings.bKeepInTray ^= true; - Settings.Save(); - } - private void BuildList_KeyDown(object Sender, KeyEventArgs Args) { if(Args.Control && Args.KeyCode == Keys.C && BuildList.SelectedItems.Count > 0) @@ -4186,10 +4474,15 @@ namespace UnrealGameSync } } + private void OptionsContextMenu_ApplicationSettings_Click(object sender, EventArgs e) + { + Owner.ModifyApplicationSettings(); + } + private void OptionsContextMenu_PerforceSettings_Click(object sender, EventArgs e) { - PerforceSettingsWindow Perforce = new PerforceSettingsWindow(Settings); - Perforce.ShowDialog(); + PerforceSyncSettingsWindow Window = new PerforceSyncSettingsWindow(Settings); + Window.ShowDialog(); } private void OptionsContextMenu_TabNames_Stream_Click(object sender, EventArgs e) @@ -4246,7 +4539,7 @@ namespace UnrealGameSync Settings.Save(); UpdateBuildList(); - UpdateNumRequestedBuilds(true); + ShrinkNumRequestedBuilds(); } private void OptionsContextMenu_ShowChanges_ShowAutomated_Click(object sender, EventArgs e) @@ -4254,8 +4547,7 @@ namespace UnrealGameSync Settings.bShowAutomatedChanges ^= true; Settings.Save(); - UpdateBuildList(); - UpdateNumRequestedBuilds(true); + UpdateBuildListFilter(); } private void BuildList_ColumnWidthChanging(object sender, ColumnWidthChangingEventArgs e) @@ -4551,5 +4843,110 @@ namespace UnrealGameSync PerforceMonitor.IsActive = Visible; } } + + private void FilterButton_Click(object sender, EventArgs e) + { + FilterContextMenu_Default.Checked = !Settings.bShowAutomatedChanges && ProjectSettings.FilterType == FilterType.None && ProjectSettings.FilterBadges.Count == 0; + + FilterContextMenu_Type.Checked = ProjectSettings.FilterType != FilterType.None; + FilterContextMenu_Type_ShowAll.Checked = ProjectSettings.FilterType == FilterType.None; + FilterContextMenu_Type_Code.Checked = ProjectSettings.FilterType == FilterType.Code; + FilterContextMenu_Type_Content.Checked = ProjectSettings.FilterType == FilterType.Content; + + FilterContextMenu_Badges.DropDownItems.Clear(); + FilterContextMenu_Badges.Checked = ProjectSettings.FilterBadges.Count > 0; + + HashSet BadgeNames = new HashSet(ProjectSettings.FilterBadges, StringComparer.OrdinalIgnoreCase); + BadgeNames.ExceptWith(BadgeNameAndGroupPairs.Select(x => x.Key)); + + List> DisplayBadgeNameAndGroupPairs = new List>(BadgeNameAndGroupPairs); + DisplayBadgeNameAndGroupPairs.AddRange(BadgeNames.Select(x => new KeyValuePair(x, "User"))); + + string LastGroup = null; + foreach(KeyValuePair BadgeNameAndGroupPair in DisplayBadgeNameAndGroupPairs) + { + if(LastGroup != BadgeNameAndGroupPair.Value) + { + if(LastGroup != null) + { + FilterContextMenu_Badges.DropDownItems.Add(new ToolStripSeparator()); + } + LastGroup = BadgeNameAndGroupPair.Value; + } + + ToolStripMenuItem Item = new ToolStripMenuItem(BadgeNameAndGroupPair.Key); + Item.Checked = ProjectSettings.FilterBadges.Contains(BadgeNameAndGroupPair.Key, StringComparer.OrdinalIgnoreCase); + Item.Click += (Sender, Args) => FilterContextMenu_Badge_Click(BadgeNameAndGroupPair.Key); + FilterContextMenu_Badges.DropDownItems.Add(Item); + } + + FilterContextMenu_Badges.Enabled = FilterContextMenu_Badges.DropDownItems.Count > 0; + + FilterContextMenu_ShowBuildMachineChanges.Checked = Settings.bShowAutomatedChanges; + FilterContextMenu.Show(FilterButton, new Point(0, FilterButton.Height)); + } + + private void FilterContextMenu_Badge_Click(string BadgeName) + { + if(ProjectSettings.FilterBadges.Contains(BadgeName)) + { + ProjectSettings.FilterBadges.Remove(BadgeName); + } + else + { + ProjectSettings.FilterBadges.Add(BadgeName); + } + + UpdateBuildListFilter(); + } + + private void FilterContextMenu_Default_Click(object sender, EventArgs e) + { + ProjectSettings.FilterBadges.Clear(); + ProjectSettings.FilterType = FilterType.None; + + Settings.bShowAutomatedChanges = false; + Settings.Save(); + + UpdateBuildListFilter(); + } + + private void FilterContextMenu_ShowBuildMachineChanges_Click(object sender, EventArgs e) + { + Settings.bShowAutomatedChanges ^= true; + Settings.Save(); + + UpdateBuildListFilter(); + } + + private void FilterContextMenu_Type_ShowAll_Click(object sender, EventArgs e) + { + ProjectSettings.FilterType = FilterType.None; + Settings.Save(); + + UpdateBuildListFilter(); + } + + private void FilterContextMenu_Type_Code_Click(object sender, EventArgs e) + { + ProjectSettings.FilterType = FilterType.Code; + Settings.Save(); + + UpdateBuildListFilter(); + } + + private void FilterContextMenu_Type_Content_Click(object sender, EventArgs e) + { + ProjectSettings.FilterType = FilterType.Content; + Settings.Save(); + + UpdateBuildListFilter(); + } + + private void UpdateBuildListFilter() + { + UpdateBuildList(); + ShrinkNumRequestedBuilds(); + } } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.resx b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.resx index 326e6170bc94..b2d39bbc5b23 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.resx +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Controls/WorkspaceControl.resx @@ -118,30 +118,33 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 17, 17 + 169, 17 - 186, 17 + 338, 17 - 594, 17 + 708, 17 - 702, 17 + 816, 17 - 840, 17 + 954, 17 - 1024, 17 + 1138, 17 - 1175, 17 + 1289, 17 - 1339, 17 + 1453, 17 - 357, 17 + 509, 17 + + + 17, 17 \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DeploymentSettings.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DeploymentSettings.cs new file mode 100644 index 000000000000..60982affaf12 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DeploymentSettings.cs @@ -0,0 +1,28 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace UnrealGameSync +{ + /// + /// This class contains settings for a site-specific deployment of UGS. Epic's internal implementation uses a static constructor in a NotForLicensees folder to initialize these values. + /// + static partial class DeploymentSettings + { + /// + /// SQL connection string used to connect to the database for telemetry and review data. The 'Program' class is a partial class, to allow an + /// opportunistically included C# source file in NotForLicensees/ProgramSettings.cs to override this value in a static constructor. + /// + public static readonly string ApiUrl = null; + + /// + /// Specifies the depot path to sync down the stable version of UGS from, without a trailing slash (eg. //depot/UnrealGameSync/bin). This is a site-specific setting. + /// The UnrealGameSync executable should be located at Release/UnrealGameSync.exe under this path, with any dependent DLLs. + /// + public static readonly string DefaultDepotPath = null; + } +} diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DetectProjectSettingsTask.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DetectProjectSettingsTask.cs index d49b3a068e64..f865fdbaa2b8 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DetectProjectSettingsTask.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/DetectProjectSettingsTask.cs @@ -23,17 +23,18 @@ namespace UnrealGameSync public string NewSelectedClientFileName; public string StreamName; public Image ProjectLogo; - public TimeSpan ServerTimeZone; public string DataFolder; + public string CacheFolder; public bool bIsEnterpriseProject; public ConfigFile LatestProjectConfigFile; public List> LocalConfigFiles; TextWriter Log; - public DetectProjectSettingsTask(UserSelectedProjectSettings SelectedProject, string InDataFolder, TextWriter InLog) + public DetectProjectSettingsTask(UserSelectedProjectSettings SelectedProject, string InDataFolder, string InCacheFolder, TextWriter InLog) { this.SelectedProject = SelectedProject; DataFolder = InDataFolder; + CacheFolder = InCacheFolder; Log = InLog; } @@ -86,19 +87,8 @@ namespace UnrealGameSync public bool Run(PerforceConnection Perforce, TextWriter Log, out string ErrorMessage) { - // Get the perforce server settings - PerforceInfoRecord PerforceInfo; - if(!Perforce.Info(out PerforceInfo, Log)) - { - ErrorMessage = String.Format("Couldn't get Perforce server info"); - return false; - } - - // Configure the time zone - ServerTimeZone = PerforceInfo.ServerTimeZone; - - // If we're using the legacy path of specifying a file, figure out the workspace name now - if(SelectedProject.Type == UserSelectedProjectType.Client) + // Use the cached client path to the file if it's available; it's much quicker than trying to find the correct workspace. + if(!String.IsNullOrEmpty(SelectedProject.ClientPath)) { // Get the client path NewSelectedClientFileName = SelectedProject.ClientPath; @@ -114,15 +104,30 @@ namespace UnrealGameSync // Create the client PerforceClient = new PerforceConnection(Perforce.UserName, ClientName, Perforce.ServerAndPort); - // Figure out the path on the client - if(!PerforceClient.ConvertToLocalPath(NewSelectedClientFileName, out NewSelectedFileName, Log)) + // Figure out the path on the client. Use the cached location if it's valid. + if(SelectedProject.LocalPath != null && File.Exists(SelectedProject.LocalPath)) { - ErrorMessage = String.Format("Couldn't get client path for {0}", NewSelectedFileName); - return false; + NewSelectedFileName = SelectedProject.LocalPath; + } + else + { + if(!PerforceClient.ConvertToLocalPath(NewSelectedClientFileName, out NewSelectedFileName, Log)) + { + ErrorMessage = String.Format("Couldn't get client path for {0}", NewSelectedFileName); + return false; + } } } - else if(SelectedProject.Type == UserSelectedProjectType.Local) + else { + // Get the perforce server settings + PerforceInfoRecord PerforceInfo; + if(!Perforce.Info(out PerforceInfo, Log)) + { + ErrorMessage = String.Format("Couldn't get Perforce server info"); + return false; + } + // Use the path as the selected filename NewSelectedFileName = SelectedProject.LocalPath; @@ -133,67 +138,40 @@ namespace UnrealGameSync return false; } - // Find all the clients on this machine - Log.WriteLine("Enumerating clients on local machine..."); + // Find all the clients for this user + Log.WriteLine("Enumerating clients for {0}...", PerforceInfo.UserName); + List Clients; - if(!Perforce.FindClients(out Clients, Log)) + if(!Perforce.FindClients(PerforceInfo.UserName, out Clients, Log)) { ErrorMessage = String.Format("Couldn't find any clients for this host."); return false; } - // Find any clients which are valid. If this is not exactly one, we should fail. - List CandidateClients = new List(); - foreach(PerforceClientRecord Client in Clients) + List CandidateClients = FilterClients(Clients, Perforce.ServerAndPort, PerforceInfo.HostName, PerforceInfo.UserName); + if(CandidateClients.Count == 0) { - // Make sure the client is well formed - if(!String.IsNullOrEmpty(Client.Name) && (!String.IsNullOrEmpty(Client.Host) || !String.IsNullOrEmpty(Client.Owner)) && !String.IsNullOrEmpty(Client.Root)) + // Search through all workspaces. We may find a suitable workspace which is for any user. + Log.WriteLine("Enumerating shared clients..."); + if(!Perforce.FindClients("", out Clients, Log)) { - // Require either a username or host name match - if((String.IsNullOrEmpty(Client.Host) || String.Compare(Client.Host, PerforceInfo.HostName, StringComparison.InvariantCultureIgnoreCase) == 0) && (String.IsNullOrEmpty(Client.Owner) || String.Compare(Client.Owner, PerforceInfo.UserName, StringComparison.InvariantCultureIgnoreCase) == 0)) - { - if(!Utility.SafeIsFileUnderDirectory(NewSelectedFileName, Client.Root)) - { - Log.WriteLine("Rejecting {0} due to root mismatch ({1})", Client.Name, Client.Root); - continue; - } + ErrorMessage = "Failed to enumerate clients."; + return false; + } - PerforceConnection CandidateClient = new PerforceConnection(PerforceInfo.UserName, Client.Name, Perforce.ServerAndPort); + // Filter this list of clients + CandidateClients = FilterClients(Clients, Perforce.ServerAndPort, PerforceInfo.HostName, PerforceInfo.UserName); - bool bFileExists; - if(!CandidateClient.FileExists(NewSelectedFileName, out bFileExists, Log) || !bFileExists) - { - Log.WriteLine("Rejecting {0} due to file not existing in workspace", Client.Name); - continue; - } - - List Records; - if(!CandidateClient.Stat(NewSelectedFileName, out Records, Log)) - { - Log.WriteLine("Rejecting {0} due to {1} not in depot", Client.Name, NewSelectedFileName); - continue; - } - - Records.RemoveAll(x => !x.IsMapped); - if(Records.Count == 0) - { - Log.WriteLine("Rejecting {0} due to {1} matching records", Client.Name, Records.Count); - continue; - } - - Log.WriteLine("Found valid client {0}", Client.Name); - CandidateClients.Add(CandidateClient); - } + // If we still couldn't find any, fail. + if(CandidateClients.Count == 0) + { + ErrorMessage = String.Format("Couldn't find any Perforce workspace containing {0}. Check your connection settings.", NewSelectedFileName); + return false; } } // Check there's only one client - if(CandidateClients.Count == 0) - { - ErrorMessage = String.Format("Couldn't find any Perforce workspace containing {0}. Check your connection settings.", NewSelectedFileName); - return false; - } - else if(CandidateClients.Count > 1) + if(CandidateClients.Count > 1) { ErrorMessage = String.Format("Found multiple workspaces containing {0}:\n\n{1}\n\nCannot determine which to use.", Path.GetFileName(NewSelectedFileName), String.Join("\n", CandidateClients.Select(x => x.ClientName))); return false; @@ -209,10 +187,6 @@ namespace UnrealGameSync return false; } } - else - { - throw new InvalidDataException("Invalid selected project type"); - } // Normalize the filename NewSelectedFileName = Path.GetFullPath(NewSelectedFileName).Replace('/', Path.DirectorySeparatorChar); @@ -224,7 +198,12 @@ namespace UnrealGameSync SelectedProject = new UserSelectedProjectSettings(Perforce.ServerAndPort, Perforce.UserName, SelectedProject.Type, NewSelectedClientFileName, NewSelectedFileName); // Figure out where the engine is in relation to it - for(int EndIdx = NewSelectedClientFileName.Length - 1;;EndIdx--) + int EndIdx = NewSelectedClientFileName.Length - 1; + if(EndIdx != -1 && NewSelectedClientFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase)) + { + EndIdx = NewSelectedClientFileName.LastIndexOf('/') - 1; + } + for(;;EndIdx--) { if(EndIdx < 2) { @@ -233,38 +212,32 @@ namespace UnrealGameSync } if(NewSelectedClientFileName[EndIdx] == '/') { - bool bFileExists; - if(PerforceClient.FileExists(NewSelectedClientFileName.Substring(0, EndIdx) + "/Engine/Build/Build.version", out bFileExists, Log) && bFileExists) + List FileRecords; + if(PerforceClient.Stat(NewSelectedClientFileName.Substring(0, EndIdx) + "/Engine/Build/Build.version", out FileRecords, Log) && FileRecords.Count > 0) { + if(FileRecords[0].ClientPath == null) + { + ErrorMessage = String.Format("Missing client path for {0}", FileRecords[0].DepotPath); + return false; + } + BranchClientPath = NewSelectedClientFileName.Substring(0, EndIdx); + BranchDirectoryName = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(FileRecords[0].ClientPath), "..", "..")); break; } } } Log.WriteLine("Found branch root at {0}", BranchClientPath); - // Get the local branch root - string BuildVersionPath; - if(!PerforceClient.ConvertToLocalPath(BranchClientPath + "/Engine/Build/Build.version", out BuildVersionPath, Log)) - { - ErrorMessage = String.Format("Couldn't get local path for Engine/Build/Build.version"); - return false; - } - BranchDirectoryName = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(BuildVersionPath), "..", "..")); - // Find the editor target for this project if(NewSelectedFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase)) { List Files; if(PerforceClient.FindFiles(PerforceUtils.GetClientOrDepotDirectoryName(NewSelectedClientFileName) + "/Source/*Editor.Target.cs", out Files, Log) && Files.Count >= 1) { - PerforceFileRecord File = Files.FirstOrDefault(x => x.Action == null || !x.Action.Contains("delete")); - if(File != null) - { - string DepotPath = File.DepotPath; - NewProjectEditorTarget = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(DepotPath.Substring(DepotPath.LastIndexOf('/') + 1))); - Log.WriteLine("Using {0} as editor target name (from {1})", NewProjectEditorTarget, Files[0]); - } + string DepotPath = Files[0].DepotPath; + NewProjectEditorTarget = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(DepotPath.Substring(DepotPath.LastIndexOf('/') + 1))); + Log.WriteLine("Using {0} as editor target name (from {1})", NewProjectEditorTarget, Files[0]); } if (NewProjectEditorTarget == null) { @@ -319,23 +292,84 @@ namespace UnrealGameSync } } - // Figure out if it's an enterprise project - List ProjectLines; - if(NewSelectedClientFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase) && PerforceClient.Print(NewSelectedClientFileName, out ProjectLines, Log)) + // Figure out if it's an enterprise project. Use the synced version if we have it. + if(NewSelectedClientFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase)) { - string Text = String.Join("\n", ProjectLines); + string Text; + if(File.Exists(NewSelectedFileName)) + { + Text = File.ReadAllText(NewSelectedFileName); + } + else + { + List ProjectLines; + if(PerforceClient.Print(NewSelectedClientFileName, out ProjectLines, Log)) + { + ErrorMessage = String.Format("Unable to get contents of {0}", NewSelectedClientFileName); + return false; + } + Text = String.Join("\n", ProjectLines); + } bIsEnterpriseProject = Utility.IsEnterpriseProjectFromText(Text); } // Read the initial config file LocalConfigFiles = new List>(); - LatestProjectConfigFile = PerforceMonitor.ReadProjectConfigFile(PerforceClient, BranchClientPath, NewSelectedClientFileName, LocalConfigFiles, Log); + LatestProjectConfigFile = PerforceMonitor.ReadProjectConfigFile(PerforceClient, BranchClientPath, NewSelectedClientFileName, CacheFolder, LocalConfigFiles, Log); // Succeed! ErrorMessage = null; return true; } + List FilterClients(List Clients, string ServerAndPort, string HostName, string UserName) + { + List CandidateClients = new List(); + foreach(PerforceClientRecord Client in Clients) + { + // Make sure the client is well formed + if(!String.IsNullOrEmpty(Client.Name) && (!String.IsNullOrEmpty(Client.Host) || !String.IsNullOrEmpty(Client.Owner)) && !String.IsNullOrEmpty(Client.Root)) + { + // Require either a username or host name match + if((String.IsNullOrEmpty(Client.Host) || String.Compare(Client.Host, HostName, StringComparison.InvariantCultureIgnoreCase) == 0) && (String.IsNullOrEmpty(Client.Owner) || String.Compare(Client.Owner, UserName, StringComparison.InvariantCultureIgnoreCase) == 0)) + { + if(!Utility.SafeIsFileUnderDirectory(NewSelectedFileName, Client.Root)) + { + Log.WriteLine("Rejecting {0} due to root mismatch ({1})", Client.Name, Client.Root); + continue; + } + + PerforceConnection CandidateClient = new PerforceConnection(UserName, Client.Name, ServerAndPort); + + bool bFileExists; + if(!CandidateClient.FileExists(NewSelectedFileName, out bFileExists, Log) || !bFileExists) + { + Log.WriteLine("Rejecting {0} due to file not existing in workspace", Client.Name); + continue; + } + + List Records; + if(!CandidateClient.Stat(NewSelectedFileName, out Records, Log)) + { + Log.WriteLine("Rejecting {0} due to {1} not in depot", Client.Name, NewSelectedFileName); + continue; + } + + Records.RemoveAll(x => !x.IsMapped); + if(Records.Count == 0) + { + Log.WriteLine("Rejecting {0} due to {1} matching records", Client.Name, Records.Count); + continue; + } + + Log.WriteLine("Found valid client {0}", Client.Name); + CandidateClients.Add(CandidateClient); + } + } + } + return CandidateClients; + } + bool TryGetStreamPrefix(PerforceConnection Perforce, string StreamName, TextWriter Log, out string StreamPrefix) { string CurrentStreamName = StreamName; @@ -382,12 +416,12 @@ namespace UnrealGameSync class DetectMultipleProjectSettingsTask : IModalTask, IDisposable { - public List Tasks; - public List Results; + public DetectProjectSettingsTask[] Tasks; + public DetectProjectSettingsResult[] Results; public DetectMultipleProjectSettingsTask(IEnumerable Tasks) { - this.Tasks = new List(Tasks); + this.Tasks = Tasks.ToArray(); } public void Dispose() @@ -398,7 +432,7 @@ namespace UnrealGameSync { Task.Dispose(); } - foreach(DetectProjectSettingsResult Result in Results) + foreach(DetectProjectSettingsResult Result in Results.Where(x => x != null)) { Result.Dispose(); } @@ -408,17 +442,19 @@ namespace UnrealGameSync public bool Run(out string ErrorMessage) { - Results = new List(); - for(int Idx = 0; Idx < Tasks.Count; Idx++) - { - string TaskErrorMessage; - bool bTaskSucceeded = Tasks[Idx].Run(out TaskErrorMessage); - Results.Add(new DetectProjectSettingsResult(Tasks[Idx], bTaskSucceeded, TaskErrorMessage)); - Tasks[Idx] = null; - } + Results = new DetectProjectSettingsResult[Tasks.Length]; + Parallel.For(0, Tasks.Length, new ParallelOptions(){ MaxDegreeOfParallelism = 4 }, Idx => RunTask(Idx)); ErrorMessage = null; return true; } + + void RunTask(int Idx) + { + string TaskErrorMessage; + bool bTaskSucceeded = Tasks[Idx].Run(out TaskErrorMessage); + Results[Idx] = new DetectProjectSettingsResult(Tasks[Idx], bTaskSucceeded, TaskErrorMessage); + Tasks[Idx] = null; + } } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/EventMonitor.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/EventMonitor.cs index b2747d0d7684..d5e8826e03c0 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/EventMonitor.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/EventMonitor.cs @@ -34,12 +34,14 @@ namespace UnrealGameSync Investigating, Resolved, } + class LatestData { public long LastEventId { get; set; } public long LastCommentId { get; set; } public long LastBuildId { get; set; } } + class EventData { public long Id { get; set; } @@ -58,7 +60,7 @@ namespace UnrealGameSync public string Project { get; set; } } - enum BuildDataResult + enum BadgeResult { Starting, Failure, @@ -67,23 +69,23 @@ namespace UnrealGameSync Skipped, } - class BuildData + class BadgeData { public long Id { get; set; } public int ChangeNumber { get; set; } public string BuildType { get; set; } - public BuildDataResult Result { get; set; } + public BadgeResult Result { get; set; } public string Url { get; set; } public string Project { get; set; } public bool IsSuccess { - get { return Result == BuildDataResult.Success || Result == BuildDataResult.Warning; } + get { return Result == BadgeResult.Success || Result == BadgeResult.Warning; } } public bool IsFailure { - get { return Result == BuildDataResult.Failure; } + get { return Result == BadgeResult.Failure; } } public string BadgeName @@ -135,7 +137,7 @@ namespace UnrealGameSync public List Reviews = new List(); public List CurrentUsers = new List(); public EventData LastStarReview; - public List Builds = new List(); + public List Badges = new List(); public List Comments = new List(); } @@ -150,10 +152,10 @@ namespace UnrealGameSync ConcurrentQueue IncomingEvents = new ConcurrentQueue(); ConcurrentQueue OutgoingComments = new ConcurrentQueue(); ConcurrentQueue IncomingComments = new ConcurrentQueue(); - ConcurrentQueue IncomingBuilds = new ConcurrentQueue(); + ConcurrentQueue IncomingBadges = new ConcurrentQueue(); Dictionary ChangeNumberToSummary = new Dictionary(); Dictionary UserNameToLastSyncEvent = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - Dictionary BadgeNameToLatestBuild = new Dictionary(); + Dictionary BadgeNameToLatestData = new Dictionary(); BoundedLogWriter LogWriter; bool bDisposing; LatestData LatestIds; @@ -258,10 +260,10 @@ namespace UnrealGameSync ApplyEventUpdate(Event); } - BuildData Build; - while(IncomingBuilds.TryDequeue(out Build)) + BadgeData Badge; + while(IncomingBadges.TryDequeue(out Badge)) { - ApplyBuildUpdate(Build); + ApplyBadgeUpdate(Badge); } CommentData Comment; @@ -320,7 +322,7 @@ namespace UnrealGameSync // Add the new review, and find the new verdict for this change Summary.Reviews.Add(Event); - Summary.Verdict = GetVerdict(Summary.Reviews, Summary.Builds); + Summary.Verdict = GetVerdict(Summary.Reviews, Summary.Badges); } else { @@ -328,16 +330,16 @@ namespace UnrealGameSync } } - void ApplyBuildUpdate(BuildData Build) + void ApplyBadgeUpdate(BadgeData Badge) { - EventSummary Summary = FindOrAddSummary(Build.ChangeNumber); + EventSummary Summary = FindOrAddSummary(Badge.ChangeNumber); - BuildData ExistingBuild = Summary.Builds.Find(x => x.ChangeNumber == Build.ChangeNumber && x.BuildType == Build.BuildType); - if(ExistingBuild != null) + BadgeData ExistingBadge = Summary.Badges.Find(x => x.ChangeNumber == Badge.ChangeNumber && x.BuildType == Badge.BuildType); + if(ExistingBadge != null) { - if(ExistingBuild.Id <= Build.Id) + if(ExistingBadge.Id <= Badge.Id) { - Summary.Builds.Remove(ExistingBuild); + Summary.Badges.Remove(ExistingBadge); } else { @@ -345,13 +347,13 @@ namespace UnrealGameSync } } - Summary.Builds.Add(Build); - Summary.Verdict = GetVerdict(Summary.Reviews, Summary.Builds); + Summary.Badges.Add(Badge); + Summary.Verdict = GetVerdict(Summary.Reviews, Summary.Badges); - BuildData LatestBuild; - if(!BadgeNameToLatestBuild.TryGetValue(Build.BadgeName, out LatestBuild) || Build.ChangeNumber > LatestBuild.ChangeNumber || (Build.ChangeNumber == LatestBuild.ChangeNumber && Build.Id > LatestBuild.Id)) + BadgeData LatestBadge; + if(!BadgeNameToLatestData.TryGetValue(Badge.BadgeName, out LatestBadge) || Badge.ChangeNumber > LatestBadge.ChangeNumber || (Badge.ChangeNumber == LatestBadge.ChangeNumber && Badge.Id > LatestBadge.Id)) { - BadgeNameToLatestBuild[Build.BadgeName] = Build; + BadgeNameToLatestData[Badge.BadgeName] = Badge; } } @@ -391,7 +393,7 @@ namespace UnrealGameSync return true; } - static ReviewVerdict GetVerdict(IEnumerable Events, IEnumerable Builds) + static ReviewVerdict GetVerdict(IEnumerable Events, IEnumerable Badges) { int NumPositiveReviews = Events.Count(x => x.Type == EventType.Good); int NumNegativeReviews = Events.Count(x => x.Type == EventType.Bad); @@ -407,11 +409,11 @@ namespace UnrealGameSync return GetVerdict(NumCompiles, NumFailedCompiles); } - int NumBuilds = Builds.Count(x => x.BuildType == "Editor" && x.IsSuccess); - int NumFailedBuilds = Builds.Count(x => x.BuildType == "Editor" && x.IsFailure); - if(NumBuilds > 0 || NumFailedBuilds > 0) + int NumBadges = Badges.Count(x => x.BuildType == "Editor" && x.IsSuccess); + int NumFailedBadges = Badges.Count(x => x.BuildType == "Editor" && x.IsFailure); + if(NumBadges > 0 || NumFailedBadges > 0) { - return GetVerdict(NumBuilds, NumFailedBuilds); + return GetVerdict(NumBadges, NumFailedBadges); } return ReviewVerdict.Unknown; @@ -460,10 +462,13 @@ namespace UnrealGameSync { EventData Event = null; CommentData Comment = null; - while(!bDisposing) + bool bUpdateThrottledRequests = true; + double RequestThrottle = 90; // seconds to wait for throttled request; + Stopwatch Timer = Stopwatch.StartNew(); + while (!bDisposing) { // If there's no connection string, just empty out the queue - if(ApiUrl != null) + if (ApiUrl != null) { // Post all the reviews to the database. We don't send them out of order, so keep the review outside the queue until the next update if it fails while(Event != null || OutgoingEvents.TryDequeue(out Event)) @@ -479,18 +484,24 @@ namespace UnrealGameSync Comment = null; } - // Read all the new reviews - ReadEventsFromBackend(); + if(Timer.Elapsed > TimeSpan.FromSeconds(RequestThrottle)) + { + bUpdateThrottledRequests = true; + Timer.Reset(); + } + + // Read all the new reviews, pass whether or not to fire the throttled requests + ReadEventsFromBackend(bUpdateThrottledRequests); // Send a notification that we're ready to update - if((IncomingEvents.Count > 0 || IncomingBuilds.Count > 0 || IncomingComments.Count > 0) && OnUpdatesReady != null) + if((IncomingEvents.Count > 0 || IncomingBadges.Count > 0 || IncomingComments.Count > 0) && OnUpdatesReady != null) { OnUpdatesReady(); } } // Wait for something else to do - RefreshEvent.WaitOne(30 * 1000); + bUpdateThrottledRequests = RefreshEvent.WaitOne(30 * 1000); } } @@ -527,7 +538,7 @@ namespace UnrealGameSync } } - bool ReadEventsFromBackend() + bool ReadEventsFromBackend(bool bFireThrottledRequests) { try { @@ -545,36 +556,42 @@ namespace UnrealGameSync LatestIds.LastEventId = InitialIds.LastEventId; } - ////////////// - /// Reviews - ////////////// - List Events = RESTApi.GET>(ApiUrl, "event", string.Format("project={0}", Project), string.Format("lasteventid={0}", LatestIds.LastEventId)); - foreach(EventData Review in Events) - { - IncomingEvents.Enqueue(Review); - LatestIds.LastEventId = Math.Max(LatestIds.LastEventId, Review.Id); - } - - ////////////// - /// Comments - ////////////// - List Comments = RESTApi.GET>(ApiUrl, "comment", string.Format("project={0}", Project), string.Format("lastcommentid={0}", LatestIds.LastCommentId)); - foreach (CommentData Comment in Comments) - { - IncomingComments.Enqueue(Comment); - LatestIds.LastCommentId = Math.Max(LatestIds.LastCommentId, Comment.Id); - } - ////////////// /// Bulids ////////////// - List Builds = RESTApi.GET>(ApiUrl, "build", string.Format("project={0}", Project), string.Format("lastbuildid={0}", LatestIds.LastBuildId)); - foreach (BuildData Build in Builds) + List Builds = RESTApi.GET>(ApiUrl, "build", string.Format("project={0}", Project), string.Format("lastbuildid={0}", LatestIds.LastBuildId)); + foreach (BadgeData Build in Builds) { - IncomingBuilds.Enqueue(Build); + IncomingBadges.Enqueue(Build); LatestIds.LastBuildId = Math.Max(LatestIds.LastBuildId, Build.Id); } + ////////////////////////// + /// Throttled Requests + ////////////////////////// + if (bFireThrottledRequests) + { + ////////////// + /// Reviews + ////////////// + List Events = RESTApi.GET>(ApiUrl, "event", string.Format("project={0}", Project), string.Format("lasteventid={0}", LatestIds.LastEventId)); + foreach (EventData Review in Events) + { + IncomingEvents.Enqueue(Review); + LatestIds.LastEventId = Math.Max(LatestIds.LastEventId, Review.Id); + } + + ////////////// + /// Comments + ////////////// + List Comments = RESTApi.GET>(ApiUrl, "comment", string.Format("project={0}", Project), string.Format("lastcommentid={0}", LatestIds.LastCommentId)); + foreach (CommentData Comment in Comments) + { + IncomingComments.Enqueue(Comment); + LatestIds.LastCommentId = Math.Max(LatestIds.LastCommentId, Comment.Id); + } + } + LastStatusMessage = String.Format("Last update took {0}ms", Timer.ElapsedMilliseconds); LogWriter.WriteLine("Done in {0}ms.", Timer.ElapsedMilliseconds); return true; @@ -671,9 +688,9 @@ namespace UnrealGameSync return Summary; } - public bool TryGetLatestBuild(string BuildType, out BuildData BuildData) + public bool TryGetLatestBadge(string BuildType, out BadgeData BadgeData) { - return BadgeNameToLatestBuild.TryGetValue(BuildType, out BuildData); + return BadgeNameToLatestData.TryGetValue(BuildType, out BadgeData); } public static bool IsReview(EventType Type) diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/FindFoldersToCleanTask.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/FindFoldersToCleanTask.cs index 6962f1438a65..785141e2422f 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/FindFoldersToCleanTask.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/FindFoldersToCleanTask.cs @@ -152,7 +152,13 @@ namespace UnrealGameSync } // Find all the files that should be deleted - LocalFolder.FilesToDelete.AddRange(LocalFolder.NameToFile.Values.Where(x => !PerforceFolder.NameToFile.ContainsKey(x.Name))); + foreach(FileInfo LocalFileInfo in LocalFolder.NameToFile.Values) + { + if(!PerforceFolder.NameToFile.ContainsKey(LocalFileInfo.Name) && !OpenClientPaths.Contains(LocalFileInfo.FullName)) + { + LocalFolder.FilesToDelete.Add(LocalFileInfo); + } + } } // Figure out if this folder is just an empty directory that needs to be removed diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.Designer.cs new file mode 100644 index 000000000000..0c371584ffb2 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.Designer.cs @@ -0,0 +1,307 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +namespace UnrealGameSync +{ + partial class ApplicationSettingsWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.UserNameTextBox = new UnrealGameSync.TextBoxWithCueBanner(); + this.ServerTextBox = new UnrealGameSync.TextBoxWithCueBanner(); + this.label3 = new System.Windows.Forms.Label(); + this.label4 = new System.Windows.Forms.Label(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.UseUnstableBuildCheckBox = new System.Windows.Forms.CheckBox(); + this.DepotPathTextBox = new UnrealGameSync.TextBoxWithCueBanner(); + this.OkBtn = new System.Windows.Forms.Button(); + this.CancelBtn = new System.Windows.Forms.Button(); + this.ViewLogBtn = new System.Windows.Forms.Button(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); + this.KeepInTrayCheckBox = new System.Windows.Forms.CheckBox(); + this.AutomaticallyRunAtStartupCheckBox = new System.Windows.Forms.CheckBox(); + this.groupBox1.SuspendLayout(); + this.tableLayoutPanel1.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.tableLayoutPanel3.SuspendLayout(); + this.SuspendLayout(); + // + // label1 + // + this.label1.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(3, 8); + this.label1.Margin = new System.Windows.Forms.Padding(3, 0, 10, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(42, 15); + this.label1.TabIndex = 0; + this.label1.Text = "Server:"; + // + // groupBox1 + // + this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox1.Controls.Add(this.tableLayoutPanel1); + this.groupBox1.Location = new System.Drawing.Point(17, 106); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(822, 143); + this.groupBox1.TabIndex = 1; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Auto-Update"; + // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.AutoSize = true; + this.tableLayoutPanel1.ColumnCount = 2; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.UserNameTextBox, 1, 1); + this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.ServerTextBox, 1, 0); + this.tableLayoutPanel1.Controls.Add(this.label3, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.label4, 0, 2); + this.tableLayoutPanel1.Controls.Add(this.tableLayoutPanel2, 1, 2); + this.tableLayoutPanel1.Location = new System.Drawing.Point(22, 27); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 3; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); + this.tableLayoutPanel1.Size = new System.Drawing.Size(787, 97); + this.tableLayoutPanel1.TabIndex = 0; + // + // UserNameTextBox + // + this.UserNameTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.UserNameTextBox.CueBanner = "Default"; + this.UserNameTextBox.Location = new System.Drawing.Point(85, 36); + this.UserNameTextBox.Name = "UserNameTextBox"; + this.UserNameTextBox.Size = new System.Drawing.Size(699, 23); + this.UserNameTextBox.TabIndex = 1; + // + // ServerTextBox + // + this.ServerTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.ServerTextBox.CueBanner = "Default"; + this.ServerTextBox.Location = new System.Drawing.Point(85, 4); + this.ServerTextBox.Name = "ServerTextBox"; + this.ServerTextBox.Size = new System.Drawing.Size(699, 23); + this.ServerTextBox.TabIndex = 0; + // + // label3 + // + this.label3.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(3, 40); + this.label3.Margin = new System.Windows.Forms.Padding(3, 0, 10, 0); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(33, 15); + this.label3.TabIndex = 2; + this.label3.Text = "User:"; + // + // label4 + // + this.label4.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(3, 73); + this.label4.Margin = new System.Windows.Forms.Padding(3, 0, 10, 0); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(69, 15); + this.label4.TabIndex = 4; + this.label4.Text = "Depot Path:"; + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel2.AutoSize = true; + this.tableLayoutPanel2.ColumnCount = 2; + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.Controls.Add(this.UseUnstableBuildCheckBox, 1, 0); + this.tableLayoutPanel2.Controls.Add(this.DepotPathTextBox, 0, 0); + this.tableLayoutPanel2.Location = new System.Drawing.Point(82, 66); + this.tableLayoutPanel2.Margin = new System.Windows.Forms.Padding(0); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 1; + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel2.Size = new System.Drawing.Size(705, 29); + this.tableLayoutPanel2.TabIndex = 5; + // + // UseUnstableBuildCheckBox + // + this.UseUnstableBuildCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.UseUnstableBuildCheckBox.AutoSize = true; + this.UseUnstableBuildCheckBox.Location = new System.Drawing.Point(578, 5); + this.UseUnstableBuildCheckBox.Margin = new System.Windows.Forms.Padding(10, 3, 3, 3); + this.UseUnstableBuildCheckBox.Name = "UseUnstableBuildCheckBox"; + this.UseUnstableBuildCheckBox.Size = new System.Drawing.Size(124, 19); + this.UseUnstableBuildCheckBox.TabIndex = 1; + this.UseUnstableBuildCheckBox.Text = "Use Unstable Build"; + this.UseUnstableBuildCheckBox.UseVisualStyleBackColor = true; + // + // DepotPathTextBox + // + this.DepotPathTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.DepotPathTextBox.CueBanner = null; + this.DepotPathTextBox.Location = new System.Drawing.Point(3, 3); + this.DepotPathTextBox.Name = "DepotPathTextBox"; + this.DepotPathTextBox.Size = new System.Drawing.Size(562, 23); + this.DepotPathTextBox.TabIndex = 0; + // + // OkBtn + // + this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.OkBtn.Location = new System.Drawing.Point(661, 261); + this.OkBtn.Name = "OkBtn"; + this.OkBtn.Size = new System.Drawing.Size(89, 27); + this.OkBtn.TabIndex = 2; + this.OkBtn.Text = "Ok"; + this.OkBtn.UseVisualStyleBackColor = true; + this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click); + // + // CancelBtn + // + this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.CancelBtn.Location = new System.Drawing.Point(756, 261); + this.CancelBtn.Name = "CancelBtn"; + this.CancelBtn.Size = new System.Drawing.Size(89, 27); + this.CancelBtn.TabIndex = 3; + this.CancelBtn.Text = "Cancel"; + this.CancelBtn.UseVisualStyleBackColor = true; + this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click); + // + // ViewLogBtn + // + this.ViewLogBtn.Location = new System.Drawing.Point(0, 0); + this.ViewLogBtn.Name = "ViewLogBtn"; + this.ViewLogBtn.Size = new System.Drawing.Size(75, 23); + this.ViewLogBtn.TabIndex = 0; + // + // groupBox2 + // + this.groupBox2.Controls.Add(this.tableLayoutPanel3); + this.groupBox2.Location = new System.Drawing.Point(17, 12); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(822, 88); + this.groupBox2.TabIndex = 0; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Startup and shutdown"; + // + // tableLayoutPanel3 + // + this.tableLayoutPanel3.ColumnCount = 1; + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel3.Controls.Add(this.KeepInTrayCheckBox, 0, 1); + this.tableLayoutPanel3.Controls.Add(this.AutomaticallyRunAtStartupCheckBox, 0, 0); + this.tableLayoutPanel3.Location = new System.Drawing.Point(22, 24); + this.tableLayoutPanel3.Name = "tableLayoutPanel3"; + this.tableLayoutPanel3.RowCount = 2; + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F)); + this.tableLayoutPanel3.Size = new System.Drawing.Size(787, 52); + this.tableLayoutPanel3.TabIndex = 6; + // + // KeepInTrayCheckBox + // + this.KeepInTrayCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.KeepInTrayCheckBox.AutoSize = true; + this.KeepInTrayCheckBox.Location = new System.Drawing.Point(3, 29); + this.KeepInTrayCheckBox.Name = "KeepInTrayCheckBox"; + this.KeepInTrayCheckBox.Size = new System.Drawing.Size(377, 19); + this.KeepInTrayCheckBox.TabIndex = 1; + this.KeepInTrayCheckBox.Text = "Keep program running in the system notification area when closed"; + this.KeepInTrayCheckBox.UseVisualStyleBackColor = true; + // + // AutomaticallyRunAtStartupCheckBox + // + this.AutomaticallyRunAtStartupCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left; + this.AutomaticallyRunAtStartupCheckBox.AutoSize = true; + this.AutomaticallyRunAtStartupCheckBox.Location = new System.Drawing.Point(3, 3); + this.AutomaticallyRunAtStartupCheckBox.Name = "AutomaticallyRunAtStartupCheckBox"; + this.AutomaticallyRunAtStartupCheckBox.Size = new System.Drawing.Size(174, 19); + this.AutomaticallyRunAtStartupCheckBox.TabIndex = 0; + this.AutomaticallyRunAtStartupCheckBox.Text = "Automatically run at startup"; + this.AutomaticallyRunAtStartupCheckBox.UseVisualStyleBackColor = true; + // + // ApplicationSettingsWindow + // + this.AcceptButton = this.OkBtn; + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.CancelBtn; + this.ClientSize = new System.Drawing.Size(857, 300); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.CancelBtn); + this.Controls.Add(this.OkBtn); + this.Controls.Add(this.groupBox1); + this.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.Icon = global::UnrealGameSync.Properties.Resources.Icon; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "ApplicationSettingsWindow"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Application Settings"; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.tableLayoutPanel3.ResumeLayout(false); + this.tableLayoutPanel3.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + private System.Windows.Forms.Label label1; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.Button OkBtn; + private System.Windows.Forms.Button CancelBtn; + private System.Windows.Forms.Button ViewLogBtn; + private System.Windows.Forms.Label label3; + public System.Windows.Forms.CheckBox UseUnstableBuildCheckBox; + private TextBoxWithCueBanner DepotPathTextBox; + private System.Windows.Forms.Label label4; + private TextBoxWithCueBanner ServerTextBox; + private TextBoxWithCueBanner UserNameTextBox; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.CheckBox AutomaticallyRunAtStartupCheckBox; + private System.Windows.Forms.CheckBox KeepInTrayCheckBox; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3; + } +} \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.cs new file mode 100644 index 000000000000..5f616c32ccc5 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.cs @@ -0,0 +1,213 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; +using UnrealGameSync; + +namespace UnrealGameSync +{ + partial class ApplicationSettingsWindow : Form + { + class GetDefaultSettingsTask : IModalTask + { + TextWriter Log; + + public string ServerAndPort; + public string UserName; + + public GetDefaultSettingsTask(TextWriter Log) + { + this.Log = Log; + } + + public bool Run(out string ErrorMessage) + { + if(PerforceModalTask.TryGetServerSettings(null, ref ServerAndPort, ref UserName, Log)) + { + ErrorMessage = null; + return true; + } + else + { + ErrorMessage = "Unable to query server settings"; + return false; + } + } + } + + class PerforceTestConnectionTask : IPerforceModalTask + { + string DepotPath; + + public PerforceTestConnectionTask(string DepotPath) + { + this.DepotPath = DepotPath ?? DeploymentSettings.DefaultDepotPath; + } + + public bool Run(PerforceConnection Perforce, TextWriter Log, out string ErrorMessage) + { + string CheckFilePath = String.Format("{0}/Release/UnrealGameSync.exe", DepotPath); + + List FileRecords; + if(!Perforce.FindFiles(CheckFilePath, out FileRecords, Log) || FileRecords.Count == 0) + { + ErrorMessage = String.Format("Unable to find {0}", CheckFilePath); + return false; + } + + ErrorMessage = null; + return true; + } + } + + string OriginalExecutableFileName; + UserSettings Settings; + TextWriter Log; + + string InitialServerAndPort; + string InitialUserName; + string InitialDepotPath; + bool bInitialUnstable; + + bool? bRestartUnstable; + + private ApplicationSettingsWindow(string DefaultServerAndPort, string DefaultUserName, bool bUnstable, string OriginalExecutableFileName, UserSettings Settings, TextWriter Log) + { + InitializeComponent(); + + this.OriginalExecutableFileName = OriginalExecutableFileName; + this.Settings = Settings; + this.Log = Log; + + Utility.ReadGlobalPerforceSettings(ref InitialServerAndPort, ref InitialUserName, ref InitialDepotPath); + bInitialUnstable = bUnstable; + + this.AutomaticallyRunAtStartupCheckBox.Checked = IsAutomaticallyRunAtStartup(); + this.KeepInTrayCheckBox.Checked = Settings.bKeepInTray; + + this.ServerTextBox.Text = InitialServerAndPort; + this.ServerTextBox.Select(ServerTextBox.TextLength, 0); + this.ServerTextBox.CueBanner = (DefaultServerAndPort == null)? "Default" : String.Format("Default ({0})", DefaultServerAndPort); + + this.UserNameTextBox.Text = InitialUserName; + this.UserNameTextBox.Select(UserNameTextBox.TextLength, 0); + this.UserNameTextBox.CueBanner = (DefaultUserName == null)? "Default" : String.Format("Default ({0})", DefaultUserName); + + this.DepotPathTextBox.Text = InitialDepotPath; + this.DepotPathTextBox.Select(DepotPathTextBox.TextLength, 0); + this.DepotPathTextBox.CueBanner = DeploymentSettings.DefaultDepotPath; + + this.UseUnstableBuildCheckBox.Checked = bUnstable; + } + + public static bool? ShowModal(IWin32Window Owner, bool bUnstable, string OriginalExecutableFileName, UserSettings Settings, TextWriter Log) + { + GetDefaultSettingsTask DefaultSettings = new GetDefaultSettingsTask(Log); + + string ErrorMessage; + ModalTask.Execute(Owner, DefaultSettings, "Checking Settings", "Checking settings, please wait...", out ErrorMessage); + + ApplicationSettingsWindow ApplicationSettings = new ApplicationSettingsWindow(DefaultSettings.ServerAndPort, DefaultSettings.UserName, bUnstable, OriginalExecutableFileName, Settings, Log); + if(ApplicationSettings.ShowDialog() == DialogResult.OK) + { + return ApplicationSettings.bRestartUnstable; + } + else + { + return null; + } + } + + private bool IsAutomaticallyRunAtStartup() + { + RegistryKey Key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"); + return (Key.GetValue("UnrealGameSync") != null); + } + + private void OkBtn_Click(object sender, EventArgs e) + { + // Update the settings + string ServerAndPort = ServerTextBox.Text.Trim(); + if(ServerAndPort.Length == 0) + { + ServerAndPort = null; + } + + string UserName = UserNameTextBox.Text.Trim(); + if(UserName.Length == 0) + { + UserName = null; + } + + string DepotPath = DepotPathTextBox.Text.Trim(); + if(DepotPath.Length == 0 || DepotPath == DeploymentSettings.DefaultDepotPath) + { + DepotPath = null; + } + + bool bUnstable = UseUnstableBuildCheckBox.Checked; + + if(ServerAndPort != InitialServerAndPort || UserName != InitialUserName || DepotPath != InitialDepotPath || bUnstable != bInitialUnstable) + { + // Try to log in to the new server, and check the application is there + if(ServerAndPort != InitialServerAndPort || UserName != InitialUserName || DepotPath != InitialDepotPath) + { + string ErrorMessage; + ModalTaskResult Result = PerforceModalTask.Execute(this, null, ServerAndPort, UserName, new PerforceTestConnectionTask(DepotPath), "Connecting", "Checking connection, please wait...", Log, out ErrorMessage); + if(Result != ModalTaskResult.Succeeded) + { + if(Result == ModalTaskResult.Failed) + { + MessageBox.Show(ErrorMessage, "Unable to connect"); + } + return; + } + } + + if(MessageBox.Show("UnrealGameSync must be restarted to apply these settings.\n\nWould you like to restart now?", "Restart Required", MessageBoxButtons.OKCancel) != DialogResult.OK) + { + return; + } + + bRestartUnstable = UseUnstableBuildCheckBox.Checked; + Utility.SaveGlobalPerforceSettings(ServerAndPort, UserName, DepotPath); + } + + RegistryKey Key = Registry.CurrentUser.CreateSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"); + if(IsAutomaticallyRunAtStartup()) + { + Key.DeleteValue("UnrealGameSync", false); + } + else + { + Key.SetValue("UnrealGameSync", String.Format("\"{0}\" -RestoreState", OriginalExecutableFileName)); + } + + if(Settings.bKeepInTray != KeepInTrayCheckBox.Checked) + { + Settings.bKeepInTray = KeepInTrayCheckBox.Checked; + Settings.Save(); + } + + DialogResult = DialogResult.OK; + Close(); + } + + private void CancelBtn_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + Close(); + } + } +} diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.resx b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.resx new file mode 100644 index 000000000000..1af7de150c99 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ApplicationSettingsWindow.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.Designer.cs new file mode 100644 index 000000000000..628de5810910 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.Designer.cs @@ -0,0 +1,216 @@ +namespace UnrealGameSync +{ + partial class AutomatedSyncWindow + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.ProjectTextBox = new System.Windows.Forms.TextBox(); + this.groupBox1 = new System.Windows.Forms.GroupBox(); + this.groupBox2 = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this.WorkspaceNameTextBox = new System.Windows.Forms.TextBox(); + this.WorkspaceNameNewBtn = new System.Windows.Forms.Button(); + this.WorkspaceNameBrowseBtn = new System.Windows.Forms.Button(); + this.OkBtn = new System.Windows.Forms.Button(); + this.CancelBtn = new System.Windows.Forms.Button(); + this.ChangeLink = new System.Windows.Forms.LinkLabel(); + this.ServerLabel = new System.Windows.Forms.Label(); + this.groupBox1.SuspendLayout(); + this.groupBox2.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); + this.SuspendLayout(); + // + // ProjectTextBox + // + this.ProjectTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.ProjectTextBox.Location = new System.Drawing.Point(20, 30); + this.ProjectTextBox.Name = "ProjectTextBox"; + this.ProjectTextBox.ReadOnly = true; + this.ProjectTextBox.Size = new System.Drawing.Size(787, 23); + this.ProjectTextBox.TabIndex = 0; + // + // groupBox1 + // + this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox1.Controls.Add(this.ProjectTextBox); + this.groupBox1.Font = new System.Drawing.Font("Segoe UI", 9F); + this.groupBox1.Location = new System.Drawing.Point(18, 46); + this.groupBox1.Name = "groupBox1"; + this.groupBox1.Size = new System.Drawing.Size(824, 73); + this.groupBox1.TabIndex = 2; + this.groupBox1.TabStop = false; + this.groupBox1.Text = "Project"; + // + // groupBox2 + // + this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBox2.Controls.Add(this.tableLayoutPanel2); + this.groupBox2.Location = new System.Drawing.Point(18, 125); + this.groupBox2.Name = "groupBox2"; + this.groupBox2.Size = new System.Drawing.Size(824, 71); + this.groupBox2.TabIndex = 3; + this.groupBox2.TabStop = false; + this.groupBox2.Text = "Workspace"; + // + // tableLayoutPanel2 + // + this.tableLayoutPanel2.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel2.ColumnCount = 3; + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20F)); + this.tableLayoutPanel2.Controls.Add(this.WorkspaceNameTextBox, 0, 0); + this.tableLayoutPanel2.Controls.Add(this.WorkspaceNameNewBtn, 1, 0); + this.tableLayoutPanel2.Controls.Add(this.WorkspaceNameBrowseBtn, 2, 0); + this.tableLayoutPanel2.Location = new System.Drawing.Point(20, 19); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + this.tableLayoutPanel2.RowCount = 1; + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 36F)); + this.tableLayoutPanel2.Size = new System.Drawing.Size(787, 36); + this.tableLayoutPanel2.TabIndex = 10; + // + // WorkspaceNameTextBox + // + this.WorkspaceNameTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.WorkspaceNameTextBox.Location = new System.Drawing.Point(3, 6); + this.WorkspaceNameTextBox.Name = "WorkspaceNameTextBox"; + this.WorkspaceNameTextBox.Size = new System.Drawing.Size(586, 23); + this.WorkspaceNameTextBox.TabIndex = 2; + // + // WorkspaceNameNewBtn + // + this.WorkspaceNameNewBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.WorkspaceNameNewBtn.Location = new System.Drawing.Point(595, 5); + this.WorkspaceNameNewBtn.Name = "WorkspaceNameNewBtn"; + this.WorkspaceNameNewBtn.Size = new System.Drawing.Size(89, 26); + this.WorkspaceNameNewBtn.TabIndex = 3; + this.WorkspaceNameNewBtn.Text = "New..."; + this.WorkspaceNameNewBtn.UseVisualStyleBackColor = true; + this.WorkspaceNameNewBtn.Click += new System.EventHandler(this.WorkspaceNameNewBtn_Click); + // + // WorkspaceNameBrowseBtn + // + this.WorkspaceNameBrowseBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this.WorkspaceNameBrowseBtn.Location = new System.Drawing.Point(690, 5); + this.WorkspaceNameBrowseBtn.Name = "WorkspaceNameBrowseBtn"; + this.WorkspaceNameBrowseBtn.Size = new System.Drawing.Size(94, 26); + this.WorkspaceNameBrowseBtn.TabIndex = 4; + this.WorkspaceNameBrowseBtn.Text = "Browse..."; + this.WorkspaceNameBrowseBtn.UseVisualStyleBackColor = true; + this.WorkspaceNameBrowseBtn.Click += new System.EventHandler(this.WorkspaceNameBrowseBtn_Click); + // + // OkBtn + // + this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.OkBtn.Location = new System.Drawing.Point(677, 205); + this.OkBtn.Name = "OkBtn"; + this.OkBtn.Size = new System.Drawing.Size(84, 26); + this.OkBtn.TabIndex = 4; + this.OkBtn.Text = "Ok"; + this.OkBtn.UseVisualStyleBackColor = true; + this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click); + // + // CancelBtn + // + this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.CancelBtn.Location = new System.Drawing.Point(767, 205); + this.CancelBtn.Name = "CancelBtn"; + this.CancelBtn.Size = new System.Drawing.Size(84, 26); + this.CancelBtn.TabIndex = 5; + this.CancelBtn.Text = "Cancel"; + this.CancelBtn.UseVisualStyleBackColor = true; + // + // ChangeLink + // + this.ChangeLink.AutoSize = true; + this.ChangeLink.Location = new System.Drawing.Point(349, 18); + this.ChangeLink.Name = "ChangeLink"; + this.ChangeLink.Size = new System.Drawing.Size(57, 15); + this.ChangeLink.TabIndex = 8; + this.ChangeLink.TabStop = true; + this.ChangeLink.Text = "Change..."; + this.ChangeLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.ChangeLink_LinkClicked); + // + // ServerLabel + // + this.ServerLabel.AutoSize = true; + this.ServerLabel.Location = new System.Drawing.Point(12, 18); + this.ServerLabel.Name = "ServerLabel"; + this.ServerLabel.Size = new System.Drawing.Size(331, 15); + this.ServerLabel.TabIndex = 7; + this.ServerLabel.Text = "Using default Perforce connection (perforce:1666, Ben.Marsh)"; + // + // AutomatedSyncWindow + // + this.AcceptButton = this.OkBtn; + this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; + this.CancelButton = this.CancelBtn; + this.ClientSize = new System.Drawing.Size(863, 243); + this.Controls.Add(this.ChangeLink); + this.Controls.Add(this.ServerLabel); + this.Controls.Add(this.CancelBtn); + this.Controls.Add(this.OkBtn); + this.Controls.Add(this.groupBox2); + this.Controls.Add(this.groupBox1); + this.Font = new System.Drawing.Font("Segoe UI", 9F); + this.Icon = global::UnrealGameSync.Properties.Resources.Icon; + this.Name = "AutomatedSyncWindow"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Sync project"; + this.groupBox1.ResumeLayout(false); + this.groupBox1.PerformLayout(); + this.groupBox2.ResumeLayout(false); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox ProjectTextBox; + private System.Windows.Forms.GroupBox groupBox1; + private System.Windows.Forms.GroupBox groupBox2; + private System.Windows.Forms.Button OkBtn; + private System.Windows.Forms.Button CancelBtn; + private System.Windows.Forms.LinkLabel ChangeLink; + private System.Windows.Forms.Label ServerLabel; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; + private System.Windows.Forms.TextBox WorkspaceNameTextBox; + private System.Windows.Forms.Button WorkspaceNameNewBtn; + private System.Windows.Forms.Button WorkspaceNameBrowseBtn; + } +} \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.cs new file mode 100644 index 000000000000..139f366fda09 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.cs @@ -0,0 +1,243 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace UnrealGameSync +{ + partial class AutomatedSyncWindow : Form + { + class FindDefaultWorkspaceTask : IPerforceModalTask + { + string StreamName; + public string WorkspaceName; + + public FindDefaultWorkspaceTask(string StreamName) + { + this.StreamName = StreamName; + } + + public bool Run(PerforceConnection Perforce, TextWriter Log, out string ErrorMessage) + { + PerforceInfoRecord Info; + if(!Perforce.Info(out Info, Log)) + { + ErrorMessage = "Unable to query Perforce info."; + return false; + } + + List Clients; + if(!Perforce.FindClients(Info.UserName, out Clients, Log)) + { + ErrorMessage = "Unable to enumerate clients from Perforce"; + return false; + } + + List CandidateClients = new List(); + foreach(PerforceClientRecord Client in Clients) + { + if(Client.Host == null || Client.Host.Equals(Info.HostName, StringComparison.OrdinalIgnoreCase)) + { + if(Client.Stream != null && Client.Stream.Equals(StreamName, StringComparison.OrdinalIgnoreCase)) + { + CandidateClients.Add(Client); + } + } + } + + if(CandidateClients.Count == 1) + { + WorkspaceName = CandidateClients[0].Name; + } + + ErrorMessage = null; + return true; + } + } + + class ValidateWorkspaceTask : IPerforceModalTask + { + public string WorkspaceName; + public string StreamName; + public string ServerAndPort; + public string UserName; + public bool bRequiresStreamSwitch; + public bool bHasOpenFiles; + + public ValidateWorkspaceTask(string WorkspaceName, string StreamName) + { + this.WorkspaceName = WorkspaceName; + this.StreamName = StreamName; + } + + public bool Run(PerforceConnection Perforce, TextWriter Log, out string ErrorMessage) + { + this.ServerAndPort = Perforce.ServerAndPort; + this.UserName = Perforce.UserName; + + PerforceSpec Spec; + if(!Perforce.TryGetClientSpec(WorkspaceName, out Spec, Log)) + { + ErrorMessage = String.Format("Unable to get info for client '{0}'", WorkspaceName); + return false; + } + + string CurrentStreamName = Spec.GetField("Stream"); + if(CurrentStreamName == null || CurrentStreamName != StreamName) + { + bRequiresStreamSwitch = true; + bHasOpenFiles = Perforce.HasOpenFiles(Log); + } + + ErrorMessage = null; + return true; + } + } + + public class WorkspaceInfo + { + public string ServerAndPort; + public string UserName; + public string WorkspaceName; + public bool bRequiresStreamSwitch; + } + + string StreamName; + TextWriter Log; + + string ServerAndPort; + string UserName; + WorkspaceInfo SelectedWorkspaceInfo; + + private AutomatedSyncWindow(string StreamName, string ProjectPath, string WorkspaceName, TextWriter Log) + { + this.StreamName = StreamName; + this.Log = Log; + + InitializeComponent(); + + ActiveControl = WorkspaceNameTextBox; + + MinimumSize = Size; + MaximumSize = new Size(32768, Size.Height); + + ProjectTextBox.Text = StreamName + ProjectPath; + + if(WorkspaceName != null) + { + WorkspaceNameTextBox.Text = WorkspaceName; + WorkspaceNameTextBox.Select(WorkspaceNameTextBox.Text.Length, 0); + } + + UpdateServerLabel(); + UpdateOkButton(); + } + + public static bool ShowModal(IWin32Window Owner, string StreamName, string ProjectPath, out WorkspaceInfo WorkspaceInfo, TextWriter Log) + { + FindDefaultWorkspaceTask FindWorkspace = new FindDefaultWorkspaceTask(StreamName); + + string ErrorMessage; + PerforceModalTask.Execute(Owner, null, null, null, FindWorkspace, "Finding workspace", "Finding default workspace, please wait...", Log, out ErrorMessage); + + AutomatedSyncWindow Window = new AutomatedSyncWindow(StreamName, ProjectPath, FindWorkspace.WorkspaceName, Log); + if(Window.ShowDialog() == DialogResult.OK) + { + WorkspaceInfo = Window.SelectedWorkspaceInfo; + return true; + } + else + { + WorkspaceInfo = null; + return false; + } + } + + private void ChangeLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + string NewServerAndPort; + string NewUserName; + if(ConnectWindow.ShowModal(this, ServerAndPort, UserName, out NewServerAndPort, out NewUserName)) + { + ServerAndPort = NewServerAndPort; + UserName = NewUserName; + UpdateServerLabel(); + } + } + + private void UpdateServerLabel() + { + ServerLabel.Text = OpenProjectWindow.GetServerLabelText(ServerAndPort, UserName); + ChangeLink.Location = new Point(ServerLabel.Right + 5, ChangeLink.Location.Y); + } + + private void WorkspaceNameNewBtn_Click(object sender, EventArgs e) + { + string WorkspaceName; + if(NewWorkspaceWindow.ShowModal(this, ServerAndPort, UserName, StreamName, WorkspaceNameTextBox.Text, Log, out WorkspaceName)) + { + WorkspaceNameTextBox.Text = WorkspaceName; + UpdateOkButton(); + } + } + + private void WorkspaceNameBrowseBtn_Click(object sender, EventArgs e) + { + string WorkspaceName = WorkspaceNameTextBox.Text; + if(SelectWorkspaceWindow.ShowModal(this, ServerAndPort, UserName, WorkspaceName, Log, out WorkspaceName)) + { + WorkspaceNameTextBox.Text = WorkspaceName; + UpdateOkButton(); + } + } + + private void UpdateOkButton() + { + OkBtn.Enabled = (WorkspaceNameTextBox.Text.Length > 0); + } + + private void OkBtn_Click(object sender, EventArgs e) + { + ValidateWorkspaceTask ValidateWorkspace = new ValidateWorkspaceTask(WorkspaceNameTextBox.Text, StreamName); + + string ErrorMessage; + ModalTaskResult Result = PerforceModalTask.Execute(Owner, null, ServerAndPort, UserName, ValidateWorkspace, "Checking workspace", "Checking workspace, please wait...", Log, out ErrorMessage); + if(Result == ModalTaskResult.Failed) + { + MessageBox.Show(ErrorMessage); + } + else if(Result == ModalTaskResult.Succeeded) + { + if(ValidateWorkspace.bRequiresStreamSwitch) + { + string Message; + if(ValidateWorkspace.bHasOpenFiles) + { + Message = String.Format("You have files open for edit in this workspace. If you switch this workspace to {0}, you will not be able to submit them until you switch back.\n\nContinue switching streams?", StreamName); + } + else + { + Message = String.Format("Switch this workspace to {0}?", StreamName); + } + if(MessageBox.Show(Message, "Switch Streams", MessageBoxButtons.YesNo) != DialogResult.Yes) + { + return; + } + } + + SelectedWorkspaceInfo = new WorkspaceInfo(){ ServerAndPort = ValidateWorkspace.ServerAndPort, UserName = ValidateWorkspace.UserName, WorkspaceName = ValidateWorkspace.WorkspaceName, bRequiresStreamSwitch = ValidateWorkspace.bRequiresStreamSwitch }; + + DialogResult = DialogResult.OK; + Close(); + } + } + } +} diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.resx b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.resx new file mode 100644 index 000000000000..1af7de150c99 --- /dev/null +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/AutomatedSyncWindow.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/BuildStepWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/BuildStepWindow.Designer.cs index ecb0018b5284..95e9c2818496 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/BuildStepWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/BuildStepWindow.Designer.cs @@ -327,10 +327,10 @@ namespace UnrealGameSync // OkButton // this.OkButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.OkButton.Location = new System.Drawing.Point(655, 455); + this.OkButton.Location = new System.Drawing.Point(553, 455); this.OkButton.Name = "OkButton"; this.OkButton.Size = new System.Drawing.Size(96, 27); - this.OkButton.TabIndex = 10; + this.OkButton.TabIndex = 9; this.OkButton.Text = "Ok"; this.OkButton.UseVisualStyleBackColor = true; this.OkButton.Click += new System.EventHandler(this.OkButton_Click); @@ -339,10 +339,10 @@ namespace UnrealGameSync // this.NewCancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.NewCancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.NewCancelButton.Location = new System.Drawing.Point(553, 455); + this.NewCancelButton.Location = new System.Drawing.Point(655, 455); this.NewCancelButton.Name = "NewCancelButton"; this.NewCancelButton.Size = new System.Drawing.Size(96, 27); - this.NewCancelButton.TabIndex = 9; + this.NewCancelButton.TabIndex = 10; this.NewCancelButton.Text = "Cancel"; this.NewCancelButton.UseVisualStyleBackColor = true; this.NewCancelButton.Click += new System.EventHandler(this.NewCancelButton_Click); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ChangelistWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ChangelistWindow.Designer.cs index 880309b344da..21691f8ee1a0 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ChangelistWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ChangelistWindow.Designer.cs @@ -50,10 +50,10 @@ namespace UnrealGameSync // OkBtn // this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.OkBtn.Location = new System.Drawing.Point(381, 54); + this.OkBtn.Location = new System.Drawing.Point(279, 54); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(92, 26); - this.OkBtn.TabIndex = 3; + this.OkBtn.TabIndex = 2; this.OkBtn.Text = "Ok"; this.OkBtn.UseVisualStyleBackColor = true; this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click); @@ -70,10 +70,10 @@ namespace UnrealGameSync // CancelBtn // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.CancelBtn.Location = new System.Drawing.Point(279, 54); + this.CancelBtn.Location = new System.Drawing.Point(381, 54); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(96, 26); - this.CancelBtn.TabIndex = 2; + this.CancelBtn.TabIndex = 3; this.CancelBtn.Text = "Cancel"; this.CancelBtn.UseVisualStyleBackColor = true; this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/CleanWorkspaceWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/CleanWorkspaceWindow.Designer.cs index 41e2e3d67050..acb74607f4a2 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/CleanWorkspaceWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/CleanWorkspaceWindow.Designer.cs @@ -66,7 +66,7 @@ namespace UnrealGameSync // this.CleanBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CleanBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CleanBtn.Location = new System.Drawing.Point(714, 560); + this.CleanBtn.Location = new System.Drawing.Point(607, 560); this.CleanBtn.Name = "CleanBtn"; this.CleanBtn.Size = new System.Drawing.Size(101, 26); this.CleanBtn.TabIndex = 1; @@ -78,7 +78,7 @@ namespace UnrealGameSync // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(607, 560); + this.CancelBtn.Location = new System.Drawing.Point(714, 560); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(101, 26); this.CancelBtn.TabIndex = 2; diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ConnectWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ConnectWindow.Designer.cs index 482d0963ac21..def6c48222c0 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ConnectWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ConnectWindow.Designer.cs @@ -44,7 +44,7 @@ this.UserNameLabel.Location = new System.Drawing.Point(17, 79); this.UserNameLabel.Name = "UserNameLabel"; this.UserNameLabel.Size = new System.Drawing.Size(66, 15); - this.UserNameLabel.TabIndex = 6; + this.UserNameLabel.TabIndex = 3; this.UserNameLabel.Text = "User name:"; // // UserNameTextBox @@ -54,7 +54,7 @@ this.UserNameTextBox.Location = new System.Drawing.Point(104, 76); this.UserNameTextBox.Name = "UserNameTextBox"; this.UserNameTextBox.Size = new System.Drawing.Size(475, 23); - this.UserNameTextBox.TabIndex = 5; + this.UserNameTextBox.TabIndex = 4; // // UseDefaultConnectionSettings // @@ -62,7 +62,7 @@ this.UseDefaultConnectionSettings.Location = new System.Drawing.Point(20, 16); this.UseDefaultConnectionSettings.Name = "UseDefaultConnectionSettings"; this.UseDefaultConnectionSettings.Size = new System.Drawing.Size(192, 19); - this.UseDefaultConnectionSettings.TabIndex = 4; + this.UseDefaultConnectionSettings.TabIndex = 0; this.UseDefaultConnectionSettings.Text = "Use default connection settings"; this.UseDefaultConnectionSettings.UseVisualStyleBackColor = true; this.UseDefaultConnectionSettings.CheckedChanged += new System.EventHandler(this.UseCustomSettings_CheckedChanged); @@ -74,7 +74,7 @@ this.ServerAndPortTextBox.Location = new System.Drawing.Point(104, 47); this.ServerAndPortTextBox.Name = "ServerAndPortTextBox"; this.ServerAndPortTextBox.Size = new System.Drawing.Size(475, 23); - this.ServerAndPortTextBox.TabIndex = 3; + this.ServerAndPortTextBox.TabIndex = 2; // // ServerAndPortLabel // @@ -88,10 +88,10 @@ // OkBtn // this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.OkBtn.Location = new System.Drawing.Point(480, 114); + this.OkBtn.Location = new System.Drawing.Point(375, 114); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(99, 27); - this.OkBtn.TabIndex = 2; + this.OkBtn.TabIndex = 5; this.OkBtn.Text = "Ok"; this.OkBtn.UseVisualStyleBackColor = true; this.OkBtn.Click += new System.EventHandler(this.OkBtn_Click); @@ -100,10 +100,10 @@ // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(375, 114); + this.CancelBtn.Location = new System.Drawing.Point(480, 114); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(99, 27); - this.CancelBtn.TabIndex = 3; + this.CancelBtn.TabIndex = 6; this.CancelBtn.Text = "Cancel"; this.CancelBtn.UseVisualStyleBackColor = true; this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/DiagnosticsWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/DiagnosticsWindow.Designer.cs index 985bafa980e8..f2ca6c365c2a 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/DiagnosticsWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/DiagnosticsWindow.Designer.cs @@ -97,6 +97,7 @@ namespace UnrealGameSync this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Name = "DiagnosticsWindow"; this.ShowInTaskbar = false; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Diagnostics"; this.ResumeLayout(false); this.PerformLayout(); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/LeaveCommentWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/LeaveCommentWindow.Designer.cs index f8bd51f6636c..dac3f5f698b5 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/LeaveCommentWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/LeaveCommentWindow.Designer.cs @@ -38,20 +38,20 @@ namespace UnrealGameSync // OkBtn // this.OkBtn.DialogResult = System.Windows.Forms.DialogResult.OK; - this.OkBtn.Location = new System.Drawing.Point(496, 46); + this.OkBtn.Location = new System.Drawing.Point(403, 46); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(87, 26); - this.OkBtn.TabIndex = 3; + this.OkBtn.TabIndex = 2; this.OkBtn.Text = "Ok"; this.OkBtn.UseVisualStyleBackColor = true; // // CancelBtn // this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(403, 46); + this.CancelBtn.Location = new System.Drawing.Point(496, 46); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(87, 26); - this.CancelBtn.TabIndex = 2; + this.CancelBtn.TabIndex = 3; this.CancelBtn.Text = "Cancel"; this.CancelBtn.UseVisualStyleBackColor = true; // diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/MainWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/MainWindow.Designer.cs index 7b752a0c30af..7f0f8a76be2d 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/MainWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/MainWindow.Designer.cs @@ -172,6 +172,7 @@ namespace UnrealGameSync this.Activated += new System.EventHandler(this.MainWindow_Activated); this.Deactivate += new System.EventHandler(this.MainWindow_Deactivate); this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainWindow_FormClosing); + this.Load += new System.EventHandler(this.MainWindow_Load); this.TabPanel.ResumeLayout(false); this.TabMenu.ResumeLayout(false); this.ResumeLayout(false); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/MainWindow.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/MainWindow.cs index 468aa8fdcd3e..500b956a32fd 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/MainWindow.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/MainWindow.cs @@ -9,6 +9,9 @@ using System.Linq; using System.Runtime.InteropServices; using System.Windows.Forms; using System.Threading; +using System.Reflection; +using System.Text; +using System.Diagnostics; namespace UnrealGameSync { @@ -53,10 +56,12 @@ namespace UnrealGameSync private const int WM_SETREDRAW = 11; + UpdateMonitor UpdateMonitor; SynchronizationContext MainThreadSynchronizationContext; string ApiUrl; string DataFolder; + string CacheFolder; LineBasedTextWriter Log; UserSettings Settings; int TabMenu_TabIdx = -1; @@ -70,21 +75,34 @@ namespace UnrealGameSync System.Threading.Timer ScheduleSettledTimer; string OriginalExecutableFileName; + bool bUnstable; IMainWindowTabPanel CurrentTabPanel; - public MainWindow(string InApiUrl, string InDataFolder, bool bInRestoreStateOnLoad, string InOriginalExecutableFileName, List StartupProjects, LineBasedTextWriter InLog, UserSettings InSettings) + AutomationServer AutomationServer; + TextWriter AutomationLog; + + bool bAllowCreatingHandle; + + public MainWindow(UpdateMonitor InUpdateMonitor, string InApiUrl, string InDataFolder, string InCacheFolder, bool bInRestoreStateOnLoad, string InOriginalExecutableFileName, bool bInUnstable, DetectProjectSettingsResult[] StartupProjects, LineBasedTextWriter InLog, UserSettings InSettings) { InitializeComponent(); + UpdateMonitor = InUpdateMonitor; MainThreadSynchronizationContext = SynchronizationContext.Current; ApiUrl = InApiUrl; DataFolder = InDataFolder; + CacheFolder = InCacheFolder; bRestoreStateOnLoad = bInRestoreStateOnLoad; OriginalExecutableFileName = InOriginalExecutableFileName; + bUnstable = bInUnstable; Log = InLog; Settings = InSettings; + // While creating tab controls during startup, we need to prevent layout calls resulting in the window handle being created too early. Disable layout calls here. + SuspendLayout(); + TabPanel.SuspendLayout(); + TabControl.OnTabChanged += TabControl_OnTabChanged; TabControl.OnNewTabClick += TabControl_OnNewTabClick; TabControl.OnTabClicked += TabControl_OnTabClicked; @@ -124,11 +142,187 @@ namespace UnrealGameSync } StartScheduleTimer(); + + if(bUnstable) + { + Text += String.Format(" (UNSTABLE BUILD {0})", Assembly.GetExecutingAssembly().GetName().Version); + } + + AutomationLog = new TimestampLogWriter(new BoundedLogWriter(Path.Combine(DataFolder, "Automation.log"))); + AutomationServer = new AutomationServer(Request => { MainThreadSynchronizationContext.Post(Obj => PostAutomationRequest(Request), null); }, AutomationLog); + + // Allow creating controls from now on + TabPanel.ResumeLayout(false); + ResumeLayout(false); + + bAllowCreatingHandle = true; + } + + void PostAutomationRequest(AutomationRequest Request) + { + try + { + if(!CanFocus) + { + Request.SetOutput(new AutomationRequestOutput(AutomationRequestResult.Busy)); + } + else if(Request.Input.Type == AutomationRequestType.SyncProject) + { + AutomationRequestOutput Output = StartAutomatedSync(Request, true); + if(Output != null) + { + Request.SetOutput(Output); + } + } + else if(Request.Input.Type == AutomationRequestType.FindProject) + { + AutomationRequestOutput Output = FindProject(Request); + Request.SetOutput(Output); + } + else if(Request.Input.Type == AutomationRequestType.OpenProject) + { + AutomationRequestOutput Output = StartAutomatedSync(Request, false); + if(Output != null) + { + Request.SetOutput(Output); + } + } + else + { + Request.SetOutput(new AutomationRequestOutput(AutomationRequestResult.Invalid)); + } + } + catch(Exception Ex) + { + Log.WriteLine("Exception running automation request: {0}", Ex); + Request.SetOutput(new AutomationRequestOutput(AutomationRequestResult.Invalid)); + } + } + + AutomationRequestOutput StartAutomatedSync(AutomationRequest Request, bool bForceSync) + { + ShowAndActivate(); + + BinaryReader Reader = new BinaryReader(new MemoryStream(Request.Input.Data)); + string StreamName = Reader.ReadString(); + string ProjectPath = Reader.ReadString(); + + AutomatedSyncWindow.WorkspaceInfo WorkspaceInfo; + if(!AutomatedSyncWindow.ShowModal(this, StreamName, ProjectPath, out WorkspaceInfo, Log)) + { + return new AutomationRequestOutput(AutomationRequestResult.Canceled); + } + + if(WorkspaceInfo.bRequiresStreamSwitch) + { + // Close any tab containing this window + for(int ExistingTabIdx = 0; ExistingTabIdx < TabControl.GetTabCount(); ExistingTabIdx++) + { + WorkspaceControl ExistingWorkspace = TabControl.GetTabData(ExistingTabIdx) as WorkspaceControl; + if(ExistingWorkspace != null && ExistingWorkspace.ClientName.Equals(WorkspaceInfo.WorkspaceName)) + { + TabControl.RemoveTab(ExistingTabIdx); + break; + } + } + + // Switch the stream + PerforceConnection Perforce = new PerforceConnection(WorkspaceInfo.UserName, WorkspaceInfo.WorkspaceName, WorkspaceInfo.ServerAndPort); + if(!Perforce.SwitchStream(StreamName, Log)) + { + Log.WriteLine("Unable to switch stream"); + return new AutomationRequestOutput(AutomationRequestResult.Error); + } + } + + UserSelectedProjectSettings SelectedProject = new UserSelectedProjectSettings(WorkspaceInfo.ServerAndPort, WorkspaceInfo.UserName, UserSelectedProjectType.Client, String.Format("//{0}{1}", WorkspaceInfo.WorkspaceName, ProjectPath), null); + + int TabIdx = TryOpenProject(SelectedProject, -1, OpenProjectOptions.None); + if(TabIdx == -1) + { + Log.WriteLine("Unable to open project"); + return new AutomationRequestOutput(AutomationRequestResult.Error); + } + + WorkspaceControl Workspace = TabControl.GetTabData(TabIdx) as WorkspaceControl; + if(Workspace == null) + { + Log.WriteLine("Workspace was unable to open"); + return new AutomationRequestOutput(AutomationRequestResult.Error); + } + + if(!bForceSync && Workspace.CanLaunchEditor()) + { + return new AutomationRequestOutput(AutomationRequestResult.Ok, Encoding.UTF8.GetBytes(Workspace.SelectedFileName)); + } + + Workspace.AddStartupCallback((Control, bCancel) => StartAutomatedSyncAfterStartup(Control, bCancel, Request)); + return null; + } + + private void StartAutomatedSyncAfterStartup(WorkspaceControl Workspace, bool bCancel, AutomationRequest Request) + { + if(bCancel) + { + Request.SetOutput(new AutomationRequestOutput(AutomationRequestResult.Canceled)); + } + else + { + Workspace.SyncLatestChange(Result => CompleteAutomatedSync(Result, Workspace.SelectedFileName, Request)); + } + } + + void CompleteAutomatedSync(WorkspaceUpdateResult Result, string SelectedFileName, AutomationRequest Request) + { + if(Result == WorkspaceUpdateResult.Success) + { + Request.SetOutput(new AutomationRequestOutput(AutomationRequestResult.Ok, Encoding.UTF8.GetBytes(SelectedFileName))); + } + else if(Result == WorkspaceUpdateResult.Canceled) + { + Request.SetOutput(new AutomationRequestOutput(AutomationRequestResult.Canceled)); + } + else + { + Request.SetOutput(new AutomationRequestOutput(AutomationRequestResult.Error)); + } + } + + AutomationRequestOutput FindProject(AutomationRequest Request) + { + BinaryReader Reader = new BinaryReader(new MemoryStream(Request.Input.Data)); + string StreamName = Reader.ReadString(); + string ProjectPath = Reader.ReadString(); + + for(int ExistingTabIdx = 0; ExistingTabIdx < TabControl.GetTabCount(); ExistingTabIdx++) + { + WorkspaceControl ExistingWorkspace = TabControl.GetTabData(ExistingTabIdx) as WorkspaceControl; + if(ExistingWorkspace != null && String.Compare(ExistingWorkspace.StreamName, StreamName, StringComparison.OrdinalIgnoreCase) == 0 && ExistingWorkspace.SelectedProject != null) + { + string ClientPath = ExistingWorkspace.SelectedProject.ClientPath; + if(ClientPath != null && ClientPath.StartsWith("//")) + { + int SlashIdx = ClientPath.IndexOf('/', 2); + if(SlashIdx != -1) + { + string ExistingProjectPath = ClientPath.Substring(SlashIdx); + if(String.Compare(ExistingProjectPath, ProjectPath, StringComparison.OrdinalIgnoreCase) == 0) + { + return new AutomationRequestOutput(AutomationRequestResult.Ok, Encoding.UTF8.GetBytes(ExistingWorkspace.SelectedFileName)); + } + } + } + } + } + + return new AutomationRequestOutput(AutomationRequestResult.NotFound); } protected override void OnHandleCreated(EventArgs e) { base.OnHandleCreated(e); + + Debug.Assert(bAllowCreatingHandle, "Window handle should not be created before constructor has run."); } void TabControl_OnButtonClick(int ButtonIdx, Point Location, MouseButtons Buttons) @@ -222,6 +416,18 @@ namespace UnrealGameSync StopScheduleTimer(); + if(AutomationServer != null) + { + AutomationServer.Dispose(); + AutomationServer = null; + } + + if(AutomationLog != null) + { + AutomationLog.Close(); + AutomationLog = null; + } + base.Dispose(disposing); } @@ -231,9 +437,6 @@ namespace UnrealGameSync { Hide(); EventArgs.Cancel = true; - - Settings.bWindowVisible = Visible; - Settings.Save(); } else { @@ -248,10 +451,20 @@ namespace UnrealGameSync } StopScheduleTimer(); - - Settings.bWindowVisible = Visible; - Settings.Save(); } + + Settings.bWindowVisible = Visible; + Settings.WindowState = WindowState; + if(WindowState == FormWindowState.Normal) + { + Settings.WindowBounds = new Rectangle(Location, Size); + } + else + { + Settings.WindowBounds = RestoreBounds; + } + + Settings.Save(); } private void SetupDefaultControl() @@ -278,8 +491,7 @@ namespace UnrealGameSync ErrorPanel.BorderStyle = BorderStyle.FixedSingle; ErrorPanel.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(250)))), ((int)(((byte)(250)))), ((int)(((byte)(250))))); ErrorPanel.Location = new Point(0, 0); - ErrorPanel.Size = new Size(TabPanel.Width, TabPanel.Height); - ErrorPanel.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom; + ErrorPanel.Dock = DockStyle.Fill; ErrorPanel.Hide(); string SummaryText = String.Format("Unable to open '{0}'.", Project.ToString()); @@ -606,7 +818,7 @@ namespace UnrealGameSync public void OpenNewProject() { DetectProjectSettingsTask DetectedProjectSettings; - if(OpenProjectWindow.ShowModal(this, null, out DetectedProjectSettings, Settings, DataFolder, Log)) + if(OpenProjectWindow.ShowModal(this, null, out DetectedProjectSettings, Settings, DataFolder, CacheFolder, Log)) { int NewTabIdx = TryOpenProject(DetectedProjectSettings, -1, OpenProjectOptions.None); if(NewTabIdx != -1) @@ -658,7 +870,7 @@ namespace UnrealGameSync public void EditSelectedProject(int TabIdx, UserSelectedProjectSettings SelectedProject) { DetectProjectSettingsTask DetectedProjectSettings; - if(OpenProjectWindow.ShowModal(this, SelectedProject, out DetectedProjectSettings, Settings, DataFolder, Log)) + if(OpenProjectWindow.ShowModal(this, SelectedProject, out DetectedProjectSettings, Settings, DataFolder, CacheFolder, Log)) { int NewTabIdx = TryOpenProject(DetectedProjectSettings, TabIdx, OpenProjectOptions.None); if(NewTabIdx != -1) @@ -676,7 +888,7 @@ namespace UnrealGameSync int TryOpenProject(UserSelectedProjectSettings Project, int ReplaceTabIdx, OpenProjectOptions Options = OpenProjectOptions.None) { Log.WriteLine("Detecting settings for {0}", Project); - using(DetectProjectSettingsTask DetectProjectSettings = new DetectProjectSettingsTask(Project, DataFolder, new PrefixedTextWriter(" ", Log))) + using(DetectProjectSettingsTask DetectProjectSettings = new DetectProjectSettingsTask(Project, DataFolder, CacheFolder, new PrefixedTextWriter(" ", Log))) { string ErrorMessage; @@ -753,11 +965,9 @@ namespace UnrealGameSync } // Now that we have the project settings, we can construct the tab - WorkspaceControl NewWorkspace = new WorkspaceControl(this, ApiUrl, OriginalExecutableFileName, ProjectSettings, Log, Settings); + WorkspaceControl NewWorkspace = new WorkspaceControl(this, ApiUrl, OriginalExecutableFileName, bUnstable, ProjectSettings, Log, Settings); NewWorkspace.Parent = TabPanel; - NewWorkspace.Location = new Point(0, 0); - NewWorkspace.Size = new Size(TabPanel.Width, TabPanel.Height); - NewWorkspace.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right | AnchorStyles.Bottom; + NewWorkspace.Dock = DockStyle.Fill; NewWorkspace.Hide(); // Add the tab @@ -945,5 +1155,24 @@ namespace UnrealGameSync } } } + + public void ModifyApplicationSettings() + { + bool? bRelaunchUnstable = ApplicationSettingsWindow.ShowModal(this, bUnstable, OriginalExecutableFileName, Settings, Log); + if(bRelaunchUnstable.HasValue) + { + UpdateMonitor.TriggerUpdate(UpdateType.UserInitiated, bRelaunchUnstable); + } + } + + private void MainWindow_Load(object sender, EventArgs e) + { + if(Settings.WindowBounds != null) + { + Location = Settings.WindowBounds.Value.Location; + Size = Settings.WindowBounds.Value.Size; + } + WindowState = Settings.WindowState; + } } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/NewWorkspaceWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/NewWorkspaceWindow.Designer.cs index 04b01a74a630..9a8538c53574 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/NewWorkspaceWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/NewWorkspaceWindow.Designer.cs @@ -106,7 +106,7 @@ namespace UnrealGameSync // OkBtn // this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.OkBtn.Location = new System.Drawing.Point(733, 175); + this.OkBtn.Location = new System.Drawing.Point(642, 175); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(85, 27); this.OkBtn.TabIndex = 1; @@ -118,7 +118,7 @@ namespace UnrealGameSync // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(642, 175); + this.CancelBtn.Location = new System.Drawing.Point(733, 175); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(85, 27); this.CancelBtn.TabIndex = 2; diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/NewWorkspaceWindow.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/NewWorkspaceWindow.cs index 31bf4298bae8..6b461a686315 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/NewWorkspaceWindow.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/NewWorkspaceWindow.cs @@ -112,7 +112,7 @@ namespace UnrealGameSync NewWorkspaceSettings Settings; string DefaultRootPath; - private NewWorkspaceWindow(string ServerAndPort, string UserName, string DefaultStream, PerforceInfoRecord Info, List Clients, TextWriter Log) + private NewWorkspaceWindow(string ServerAndPort, string UserName, string ForceStream, string DefaultStream, PerforceInfoRecord Info, List Clients, TextWriter Log) { InitializeComponent(); @@ -159,17 +159,28 @@ namespace UnrealGameSync } } - StreamTextBox.Text = DefaultStream ?? ""; + if(ForceStream != null) + { + StreamTextBox.Text = ForceStream; + StreamTextBox.Enabled = false; + } + else + { + StreamTextBox.Text = DefaultStream ?? ""; + StreamTextBox.Enabled = true; + } StreamTextBox.SelectionStart = StreamTextBox.Text.Length; StreamTextBox.SelectionLength = 0; StreamTextBox.Focus(); + StreamBrowseBtn.Enabled = (ForceStream == null); + UpdateOkButton(); UpdateNameCueBanner(); UpdateRootDirCueBanner(); } - public static bool ShowModal(IWin32Window Owner, string ServerAndPort, string UserName, string CurrentWorkspaceName, TextWriter Log, out string WorkspaceName) + public static bool ShowModal(IWin32Window Owner, string ServerAndPort, string UserName, string ForceStreamName, string CurrentWorkspaceName, TextWriter Log, out string WorkspaceName) { FindWorkspaceSettingsTask FindSettings = new FindWorkspaceSettingsTask(CurrentWorkspaceName); @@ -180,9 +191,12 @@ namespace UnrealGameSync { MessageBox.Show(ErrorMessage); } + + WorkspaceName = null; + return false; } - NewWorkspaceWindow Window = new NewWorkspaceWindow(ServerAndPort, UserName, FindSettings.CurrentStream, FindSettings.Info, FindSettings.Clients, Log); + NewWorkspaceWindow Window = new NewWorkspaceWindow(ServerAndPort, UserName, ForceStreamName, FindSettings.CurrentStream, FindSettings.Info, FindSettings.Clients, Log); if(Window.ShowDialog(Owner) == DialogResult.OK) { WorkspaceName = Window.Settings.Name; diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/OpenProjectWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/OpenProjectWindow.Designer.cs index 8443b3157574..26c16cc94e7a 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/OpenProjectWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/OpenProjectWindow.Designer.cs @@ -174,7 +174,7 @@ namespace UnrealGameSync // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(672, 266); + this.CancelBtn.Location = new System.Drawing.Point(776, 266); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(98, 27); this.CancelBtn.TabIndex = 3; @@ -184,7 +184,7 @@ namespace UnrealGameSync // OkBtn // this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.OkBtn.Location = new System.Drawing.Point(776, 266); + this.OkBtn.Location = new System.Drawing.Point(672, 266); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(97, 27); this.OkBtn.TabIndex = 4; diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/OpenProjectWindow.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/OpenProjectWindow.cs index f3afb055d7e5..a89f82919162 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/OpenProjectWindow.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/OpenProjectWindow.cs @@ -19,16 +19,18 @@ namespace UnrealGameSync string UserName; DetectProjectSettingsTask DetectedProjectSettings; string DataFolder; + string CacheFolder; TextWriter Log; UserSettings Settings; - private OpenProjectWindow(UserSelectedProjectSettings Project, UserSettings Settings, string DataFolder, TextWriter Log) + private OpenProjectWindow(UserSelectedProjectSettings Project, UserSettings Settings, string DataFolder, string CacheFolder, TextWriter Log) { InitializeComponent(); this.Settings = Settings; this.DetectedProjectSettings = null; this.DataFolder = DataFolder; + this.CacheFolder = CacheFolder; this.Log = Log; if(Project == null) @@ -77,9 +79,9 @@ namespace UnrealGameSync UpdateOkButton(); } - public static bool ShowModal(IWin32Window Owner, UserSelectedProjectSettings Project, out DetectProjectSettingsTask NewDetectedProjectSettings, UserSettings Settings, string DataFolder, TextWriter Log) + public static bool ShowModal(IWin32Window Owner, UserSelectedProjectSettings Project, out DetectProjectSettingsTask NewDetectedProjectSettings, UserSettings Settings, string DataFolder, string CacheFolder, TextWriter Log) { - OpenProjectWindow Window = new OpenProjectWindow(Project, Settings, DataFolder, Log); + OpenProjectWindow Window = new OpenProjectWindow(Project, Settings, DataFolder, CacheFolder, Log); if(Window.ShowDialog(Owner) == DialogResult.OK) { NewDetectedProjectSettings = Window.DetectedProjectSettings; @@ -111,11 +113,11 @@ namespace UnrealGameSync UpdateWorkspacePathBrowseButton(); } - private void UpdateServerLabel() + public static string GetServerLabelText(string ServerAndPort, string UserName) { if(ServerAndPort == null && UserName == null) { - ServerLabel.Text = "Using default Perforce server settings."; + return "Using default Perforce server settings."; } else { @@ -137,9 +139,13 @@ namespace UnrealGameSync { Text.AppendFormat("server '{0}'.", ServerAndPort); } - ServerLabel.Text = Text.ToString(); + return Text.ToString(); } + } + private void UpdateServerLabel() + { + ServerLabel.Text = GetServerLabelText(ServerAndPort, UserName); ChangeLink.Location = new Point(ServerLabel.Right + 5, ChangeLink.Location.Y); } @@ -160,7 +166,7 @@ namespace UnrealGameSync WorkspaceRadioBtn.Checked = true; string WorkspaceName; - if(NewWorkspaceWindow.ShowModal(this, ServerAndPort, UserName, WorkspaceNameTextBox.Text, Log, out WorkspaceName)) + if(NewWorkspaceWindow.ShowModal(this, ServerAndPort, UserName, null, WorkspaceNameTextBox.Text, Log, out WorkspaceName)) { WorkspaceNameTextBox.Text = WorkspaceName; UpdateOkButton(); @@ -270,7 +276,7 @@ namespace UnrealGameSync UserSelectedProjectSettings SelectedProject; if(TryGetSelectedProject(out SelectedProject)) { - DetectProjectSettingsTask NewDetectedProjectSettings = new DetectProjectSettingsTask(SelectedProject, DataFolder, Log); + DetectProjectSettingsTask NewDetectedProjectSettings = new DetectProjectSettingsTask(SelectedProject, DataFolder, CacheFolder, Log); try { string ProjectFileName = null; @@ -325,12 +331,15 @@ namespace UnrealGameSync Dialog.Filter = "Project files (*.uproject)|*.uproject|Project directory lists (*.uprojectdirs)|*.uprojectdirs|All supported files (*.uproject;*.uprojectdirs)|*.uproject;*.uprojectdirs|All files (*.*)|*.*" ; Dialog.FilterIndex = Settings.FilterIndex; - try - { - Dialog.InitialDirectory = Path.GetDirectoryName(LocalFileTextBox.Text); - } - catch + if(!String.IsNullOrEmpty(LocalFileTextBox.Text)) { + try + { + Dialog.InitialDirectory = Path.GetDirectoryName(LocalFileTextBox.Text); + } + catch + { + } } if(Dialog.ShowDialog(this) == System.Windows.Forms.DialogResult.OK) diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PasswordWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PasswordWindow.Designer.cs index 35af021ebce9..da62b1194243 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PasswordWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PasswordWindow.Designer.cs @@ -58,7 +58,7 @@ // this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.OkBtn.DialogResult = System.Windows.Forms.DialogResult.OK; - this.OkBtn.Location = new System.Drawing.Point(483, 78); + this.OkBtn.Location = new System.Drawing.Point(390, 78); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(87, 27); this.OkBtn.TabIndex = 2; @@ -70,7 +70,7 @@ // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(390, 78); + this.CancelBtn.Location = new System.Drawing.Point(483, 78); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(87, 27); this.CancelBtn.TabIndex = 3; diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSettingsWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSyncSettingsWindow.Designer.cs similarity index 94% rename from Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSettingsWindow.Designer.cs rename to Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSyncSettingsWindow.Designer.cs index 090435c10c2e..312609470b27 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSettingsWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSyncSettingsWindow.Designer.cs @@ -1,6 +1,6 @@ -namespace UnrealGameSync +namespace UnrealGameSync { - partial class PerforceSettingsWindow + partial class PerforceSyncSettingsWindow { /// /// Required designer variable. @@ -28,7 +28,7 @@ /// private void InitializeComponent() { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PerforceSettingsWindow)); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(PerforceSyncSettingsWindow)); this.groupBox1 = new System.Windows.Forms.GroupBox(); this.NumThreadsTextBox = new System.Windows.Forms.TextBox(); this.label3 = new System.Windows.Forms.Label(); @@ -109,7 +109,7 @@ // // OkButton // - this.OkButton.Location = new System.Drawing.Point(371, 167); + this.OkButton.Location = new System.Drawing.Point(278, 167); this.OkButton.Name = "OkButton"; this.OkButton.Size = new System.Drawing.Size(87, 26); this.OkButton.TabIndex = 1; @@ -120,7 +120,7 @@ // CancButton // this.CancButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancButton.Location = new System.Drawing.Point(278, 167); + this.CancButton.Location = new System.Drawing.Point(371, 167); this.CancButton.Name = "CancButton"; this.CancButton.Size = new System.Drawing.Size(87, 26); this.CancButton.TabIndex = 2; @@ -128,7 +128,7 @@ this.CancButton.UseVisualStyleBackColor = true; this.CancButton.Click += new System.EventHandler(this.CancButton_Click); // - // PerforceSettingsWindow + // PerforceSyncSettingsWindow // this.AcceptButton = this.OkButton; this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); @@ -140,10 +140,10 @@ this.Controls.Add(this.groupBox1); this.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.Name = "PerforceSettingsWindow"; + this.Name = "PerforceSyncSettingsWindow"; this.ShowInTaskbar = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; - this.Text = "Perforce Settings"; + this.Text = "Perforce Sync Settings"; this.Load += new System.EventHandler(this.PerforceSettingsWindow_Load); this.groupBox1.ResumeLayout(false); this.groupBox1.PerformLayout(); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSettingsWindow.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSyncSettingsWindow.cs similarity index 94% rename from Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSettingsWindow.cs rename to Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSyncSettingsWindow.cs index f01b76250087..beba97da5c15 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSettingsWindow.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSyncSettingsWindow.cs @@ -12,11 +12,11 @@ using System.Windows.Forms; namespace UnrealGameSync { - partial class PerforceSettingsWindow : Form + partial class PerforceSyncSettingsWindow : Form { UserSettings Settings; - public PerforceSettingsWindow(UserSettings Settings) + public PerforceSyncSettingsWindow(UserSettings Settings) { this.Settings = Settings; InitializeComponent(); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSettingsWindow.resx b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSyncSettingsWindow.resx similarity index 100% rename from Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSettingsWindow.resx rename to Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/PerforceSyncSettingsWindow.resx diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ProgramsRunningWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ProgramsRunningWindow.Designer.cs index de5e08018e40..51665b9a28be 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ProgramsRunningWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ProgramsRunningWindow.Designer.cs @@ -32,13 +32,17 @@ this.CancelBtn = new System.Windows.Forms.Button(); this.ProgramListBox = new System.Windows.Forms.ListBox(); this.label1 = new System.Windows.Forms.Label(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); + this.tableLayoutPanel1.SuspendLayout(); + this.flowLayoutPanel1.SuspendLayout(); this.SuspendLayout(); // // IgnoreBtn // this.IgnoreBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.IgnoreBtn.DialogResult = System.Windows.Forms.DialogResult.OK; - this.IgnoreBtn.Location = new System.Drawing.Point(12, 188); + this.IgnoreBtn.Location = new System.Drawing.Point(3, 3); this.IgnoreBtn.Name = "IgnoreBtn"; this.IgnoreBtn.Size = new System.Drawing.Size(94, 27); this.IgnoreBtn.TabIndex = 0; @@ -49,7 +53,7 @@ // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(523, 188); + this.CancelBtn.Location = new System.Drawing.Point(104, 3); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(95, 27); this.CancelBtn.TabIndex = 1; @@ -64,30 +68,62 @@ this.ProgramListBox.FormattingEnabled = true; this.ProgramListBox.IntegralHeight = false; this.ProgramListBox.ItemHeight = 15; - this.ProgramListBox.Location = new System.Drawing.Point(23, 44); + this.ProgramListBox.Location = new System.Drawing.Point(6, 29); + this.ProgramListBox.Margin = new System.Windows.Forms.Padding(6, 8, 6, 8); this.ProgramListBox.Name = "ProgramListBox"; - this.ProgramListBox.Size = new System.Drawing.Size(586, 136); + this.ProgramListBox.Size = new System.Drawing.Size(617, 152); this.ProgramListBox.TabIndex = 2; // // label1 // + this.label1.Anchor = System.Windows.Forms.AnchorStyles.Left; this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(12, 18); + this.label1.Location = new System.Drawing.Point(3, 3); + this.label1.Margin = new System.Windows.Forms.Padding(3); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(264, 15); this.label1.TabIndex = 3; this.label1.Text = "Please close the following programs to continue:"; // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel1.ColumnCount = 1; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.label1, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.ProgramListBox, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel1, 0, 2); + this.tableLayoutPanel1.Location = new System.Drawing.Point(12, 12); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 3; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.Size = new System.Drawing.Size(629, 222); + this.tableLayoutPanel1.TabIndex = 4; + // + // flowLayoutPanel1 + // + this.flowLayoutPanel1.Anchor = System.Windows.Forms.AnchorStyles.Right; + this.flowLayoutPanel1.AutoSize = true; + this.flowLayoutPanel1.Controls.Add(this.CancelBtn); + this.flowLayoutPanel1.Controls.Add(this.IgnoreBtn); + this.flowLayoutPanel1.Location = new System.Drawing.Point(428, 189); + this.flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(0); + this.flowLayoutPanel1.Name = "flowLayoutPanel1"; + this.flowLayoutPanel1.Size = new System.Drawing.Size(201, 33); + this.flowLayoutPanel1.TabIndex = 5; + this.flowLayoutPanel1.WrapContents = false; + // // ProgramsRunningWindow // this.AcceptButton = this.CancelBtn; this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; - this.ClientSize = new System.Drawing.Size(630, 227); - this.Controls.Add(this.label1); - this.Controls.Add(this.ProgramListBox); - this.Controls.Add(this.CancelBtn); - this.Controls.Add(this.IgnoreBtn); + this.ClientSize = new System.Drawing.Size(653, 246); + this.Controls.Add(this.tableLayoutPanel1); this.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.Name = "ProgramsRunningWindow"; this.ShowIcon = false; @@ -96,8 +132,10 @@ this.Text = "Programs Running"; this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.ProgramsRunningWindow_FormClosed); this.Load += new System.EventHandler(this.ProgramsRunningWindow_Load); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.flowLayoutPanel1.ResumeLayout(false); this.ResumeLayout(false); - this.PerformLayout(); } @@ -107,5 +145,7 @@ private System.Windows.Forms.Button CancelBtn; private System.Windows.Forms.ListBox ProgramListBox; private System.Windows.Forms.Label label1; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; } } \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ScheduleWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ScheduleWindow.Designer.cs index 27af80a36bea..955a6c292aeb 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ScheduleWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/ScheduleWindow.Designer.cs @@ -37,13 +37,20 @@ namespace UnrealGameSync this.CancelBtn = new System.Windows.Forms.Button(); this.ChangeComboBox = new System.Windows.Forms.ComboBox(); this.ProjectListBox = new System.Windows.Forms.CheckedListBox(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this.flowLayoutPanel1 = new System.Windows.Forms.FlowLayoutPanel(); + this.flowLayoutPanel2 = new System.Windows.Forms.FlowLayoutPanel(); + this.tableLayoutPanel1.SuspendLayout(); + this.flowLayoutPanel1.SuspendLayout(); + this.flowLayoutPanel2.SuspendLayout(); this.SuspendLayout(); // // TimePicker // + this.TimePicker.Anchor = System.Windows.Forms.AnchorStyles.Left; this.TimePicker.CustomFormat = "h:mm tt"; this.TimePicker.Format = System.Windows.Forms.DateTimePickerFormat.Custom; - this.TimePicker.Location = new System.Drawing.Point(460, 15); + this.TimePicker.Location = new System.Drawing.Point(439, 3); this.TimePicker.Name = "TimePicker"; this.TimePicker.ShowUpDown = true; this.TimePicker.Size = new System.Drawing.Size(118, 23); @@ -51,8 +58,10 @@ namespace UnrealGameSync // // EnableCheckBox // + this.EnableCheckBox.Anchor = System.Windows.Forms.AnchorStyles.Left; this.EnableCheckBox.AutoSize = true; - this.EnableCheckBox.Location = new System.Drawing.Point(15, 17); + this.EnableCheckBox.Location = new System.Drawing.Point(0, 5); + this.EnableCheckBox.Margin = new System.Windows.Forms.Padding(0, 3, 3, 3); this.EnableCheckBox.Name = "EnableCheckBox"; this.EnableCheckBox.Size = new System.Drawing.Size(180, 19); this.EnableCheckBox.TabIndex = 1; @@ -62,21 +71,24 @@ namespace UnrealGameSync // // label1 // + this.label1.Anchor = System.Windows.Forms.AnchorStyles.Left; this.label1.AutoSize = true; - this.label1.Location = new System.Drawing.Point(379, 18); + this.label1.Location = new System.Drawing.Point(363, 7); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(70, 15); this.label1.TabIndex = 3; this.label1.Text = "every day at"; + this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; // // OkBtn // this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.OkBtn.DialogResult = System.Windows.Forms.DialogResult.OK; - this.OkBtn.Location = new System.Drawing.Point(602, 238); + this.OkBtn.Location = new System.Drawing.Point(3, 3); + this.OkBtn.Margin = new System.Windows.Forms.Padding(3, 3, 0, 3); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(87, 26); - this.OkBtn.TabIndex = 5; + this.OkBtn.TabIndex = 4; this.OkBtn.Text = "Ok"; this.OkBtn.UseVisualStyleBackColor = true; // @@ -84,22 +96,23 @@ namespace UnrealGameSync // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(509, 238); + this.CancelBtn.Location = new System.Drawing.Point(93, 3); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(87, 26); - this.CancelBtn.TabIndex = 4; + this.CancelBtn.TabIndex = 5; this.CancelBtn.Text = "Cancel"; this.CancelBtn.UseVisualStyleBackColor = true; // // ChangeComboBox // + this.ChangeComboBox.Anchor = System.Windows.Forms.AnchorStyles.Left; this.ChangeComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.ChangeComboBox.FormattingEnabled = true; this.ChangeComboBox.Items.AddRange(new object[] { "latest change", "latest good change", "latest starred change"}); - this.ChangeComboBox.Location = new System.Drawing.Point(203, 14); + this.ChangeComboBox.Location = new System.Drawing.Point(186, 3); this.ChangeComboBox.Name = "ChangeComboBox"; this.ChangeComboBox.Size = new System.Drawing.Size(171, 23); this.ChangeComboBox.TabIndex = 2; @@ -112,25 +125,65 @@ namespace UnrealGameSync this.ProjectListBox.CheckOnClick = true; this.ProjectListBox.FormattingEnabled = true; this.ProjectListBox.IntegralHeight = false; - this.ProjectListBox.Location = new System.Drawing.Point(14, 50); + this.ProjectListBox.Location = new System.Drawing.Point(0, 35); + this.ProjectListBox.Margin = new System.Windows.Forms.Padding(0, 6, 0, 6); this.ProjectListBox.Name = "ProjectListBox"; - this.ProjectListBox.Size = new System.Drawing.Size(675, 180); + this.ProjectListBox.Size = new System.Drawing.Size(763, 199); this.ProjectListBox.TabIndex = 8; // + // tableLayoutPanel1 + // + this.tableLayoutPanel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.tableLayoutPanel1.ColumnCount = 1; + this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel1, 0, 0); + this.tableLayoutPanel1.Controls.Add(this.ProjectListBox, 0, 1); + this.tableLayoutPanel1.Controls.Add(this.flowLayoutPanel2, 0, 2); + this.tableLayoutPanel1.Location = new System.Drawing.Point(14, 12); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + this.tableLayoutPanel1.RowCount = 3; + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel1.Size = new System.Drawing.Size(763, 272); + this.tableLayoutPanel1.TabIndex = 9; + // + // flowLayoutPanel1 + // + this.flowLayoutPanel1.AutoSize = true; + this.flowLayoutPanel1.Controls.Add(this.EnableCheckBox); + this.flowLayoutPanel1.Controls.Add(this.ChangeComboBox); + this.flowLayoutPanel1.Controls.Add(this.label1); + this.flowLayoutPanel1.Controls.Add(this.TimePicker); + this.flowLayoutPanel1.Location = new System.Drawing.Point(0, 0); + this.flowLayoutPanel1.Margin = new System.Windows.Forms.Padding(0); + this.flowLayoutPanel1.Name = "flowLayoutPanel1"; + this.flowLayoutPanel1.Size = new System.Drawing.Size(560, 29); + this.flowLayoutPanel1.TabIndex = 0; + this.flowLayoutPanel1.WrapContents = false; + // + // flowLayoutPanel2 + // + this.flowLayoutPanel2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.flowLayoutPanel2.AutoSize = true; + this.flowLayoutPanel2.Controls.Add(this.OkBtn); + this.flowLayoutPanel2.Controls.Add(this.CancelBtn); + this.flowLayoutPanel2.Location = new System.Drawing.Point(580, 240); + this.flowLayoutPanel2.Margin = new System.Windows.Forms.Padding(0); + this.flowLayoutPanel2.Name = "flowLayoutPanel2"; + this.flowLayoutPanel2.Size = new System.Drawing.Size(183, 32); + this.flowLayoutPanel2.TabIndex = 1; + // // ScheduleWindow // this.AcceptButton = this.OkBtn; this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.CancelButton = this.CancelBtn; - this.ClientSize = new System.Drawing.Size(704, 276); - this.Controls.Add(this.ProjectListBox); - this.Controls.Add(this.CancelBtn); - this.Controls.Add(this.OkBtn); - this.Controls.Add(this.label1); - this.Controls.Add(this.EnableCheckBox); - this.Controls.Add(this.ChangeComboBox); - this.Controls.Add(this.TimePicker); + this.ClientSize = new System.Drawing.Size(793, 296); + this.Controls.Add(this.tableLayoutPanel1); this.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.Icon = global::UnrealGameSync.Properties.Resources.Icon; this.MinimumSize = new System.Drawing.Size(720, 315); @@ -138,8 +191,12 @@ namespace UnrealGameSync this.ShowInTaskbar = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Schedule"; + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this.flowLayoutPanel1.ResumeLayout(false); + this.flowLayoutPanel1.PerformLayout(); + this.flowLayoutPanel2.ResumeLayout(false); this.ResumeLayout(false); - this.PerformLayout(); } @@ -152,5 +209,8 @@ namespace UnrealGameSync private System.Windows.Forms.Button CancelBtn; private System.Windows.Forms.ComboBox ChangeComboBox; private System.Windows.Forms.CheckedListBox ProjectListBox; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel1; + private System.Windows.Forms.FlowLayoutPanel flowLayoutPanel2; } } \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectProjectFromWorkspaceWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectProjectFromWorkspaceWindow.Designer.cs index 96bd7e2ad002..f63284b7ebec 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectProjectFromWorkspaceWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectProjectFromWorkspaceWindow.Designer.cs @@ -31,6 +31,7 @@ namespace UnrealGameSync this.ProjectTreeView = new System.Windows.Forms.TreeView(); this.OkBtn = new System.Windows.Forms.Button(); this.CancelBtn = new System.Windows.Forms.Button(); + this.ShowProjectDirsFiles = new System.Windows.Forms.CheckBox(); this.SuspendLayout(); // // ProjectTreeView @@ -48,7 +49,7 @@ namespace UnrealGameSync // OkBtn // this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.OkBtn.Location = new System.Drawing.Point(520, 450); + this.OkBtn.Location = new System.Drawing.Point(427, 450); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(87, 27); this.OkBtn.TabIndex = 1; @@ -60,7 +61,7 @@ namespace UnrealGameSync // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(427, 450); + this.CancelBtn.Location = new System.Drawing.Point(520, 450); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(87, 27); this.CancelBtn.TabIndex = 2; @@ -68,6 +69,17 @@ namespace UnrealGameSync this.CancelBtn.UseVisualStyleBackColor = true; this.CancelBtn.Click += new System.EventHandler(this.CancelBtn_Click); // + // ShowProjectDirsFiles + // + this.ShowProjectDirsFiles.AutoSize = true; + this.ShowProjectDirsFiles.Location = new System.Drawing.Point(12, 455); + this.ShowProjectDirsFiles.Name = "ShowProjectDirsFiles"; + this.ShowProjectDirsFiles.Size = new System.Drawing.Size(153, 19); + this.ShowProjectDirsFiles.TabIndex = 3; + this.ShowProjectDirsFiles.Text = "Show *.uprojectdirs files"; + this.ShowProjectDirsFiles.UseVisualStyleBackColor = true; + this.ShowProjectDirsFiles.CheckedChanged += new System.EventHandler(this.ShowProjectDirsFiles_CheckedChanged); + // // SelectProjectFromWorkspaceWindow // this.AcceptButton = this.OkBtn; @@ -75,6 +87,7 @@ namespace UnrealGameSync this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.CancelButton = this.CancelBtn; this.ClientSize = new System.Drawing.Size(619, 489); + this.Controls.Add(this.ShowProjectDirsFiles); this.Controls.Add(this.CancelBtn); this.Controls.Add(this.OkBtn); this.Controls.Add(this.ProjectTreeView); @@ -85,6 +98,7 @@ namespace UnrealGameSync this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Select Project"; this.ResumeLayout(false); + this.PerformLayout(); } @@ -93,5 +107,6 @@ namespace UnrealGameSync private System.Windows.Forms.TreeView ProjectTreeView; private System.Windows.Forms.Button OkBtn; private System.Windows.Forms.Button CancelBtn; + private System.Windows.Forms.CheckBox ShowProjectDirsFiles; } } \ No newline at end of file diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectProjectFromWorkspaceWindow.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectProjectFromWorkspaceWindow.cs index f25cd052f30d..ead82d2125f6 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectProjectFromWorkspaceWindow.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectProjectFromWorkspaceWindow.cs @@ -84,6 +84,7 @@ namespace UnrealGameSync [DllImport("Shell32.dll", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)] private static extern int ExtractIconEx(string sFile, int iIndex, IntPtr piLargeVersion, out IntPtr piSmallVersion, int amountIcons); + List ProjectFiles; string SelectedProjectFile; class ProjectNode @@ -106,6 +107,7 @@ namespace UnrealGameSync { InitializeComponent(); + this.ProjectFiles = ProjectFiles; this.SelectedProjectFile = SelectedProjectFile; // Make the image strip containing icons for nodes in the tree @@ -135,8 +137,25 @@ namespace UnrealGameSync RootNode.Expand(); ProjectTreeView.Nodes.Add(RootNode); + // Populate the tree + Populate(); + } + + private void Populate() + { + // Clear out the existing nodes + TreeNode RootNode = ProjectTreeView.Nodes[0]; + RootNode.Nodes.Clear(); + + // Filter the project files + List FilteredProjectFiles = new List(ProjectFiles); + if(!ShowProjectDirsFiles.Checked) + { + FilteredProjectFiles.RemoveAll(x => x.EndsWith(".uprojectdirs", StringComparison.OrdinalIgnoreCase)); + } + // Sort by paths, then files - List ProjectNodes = ProjectFiles.Select(x => new ProjectNode(x)).OrderBy(x => x.Folder).ThenBy(x => x.Name).ToList(); + List ProjectNodes = FilteredProjectFiles.Select(x => new ProjectNode(x)).OrderBy(x => x.Folder).ThenBy(x => x.Name).ToList(); // Add the folders for each project TreeNode[] ProjectParentNodes = new TreeNode[ProjectNodes.Count]; @@ -236,5 +255,10 @@ namespace UnrealGameSync DialogResult = DialogResult.Cancel; Close(); } + + private void ShowProjectDirsFiles_CheckedChanged(object sender, EventArgs e) + { + Populate(); + } } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectStreamWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectStreamWindow.Designer.cs index ed912ed1a309..43e3dd5e2fa3 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectStreamWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectStreamWindow.Designer.cs @@ -60,7 +60,7 @@ // OkBtn // this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.OkBtn.Location = new System.Drawing.Point(683, 445); + this.OkBtn.Location = new System.Drawing.Point(586, 445); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(90, 27); this.OkBtn.TabIndex = 2; @@ -72,7 +72,7 @@ // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(586, 445); + this.CancelBtn.Location = new System.Drawing.Point(683, 445); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(91, 27); this.CancelBtn.TabIndex = 3; diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectWorkspaceWindow.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectWorkspaceWindow.Designer.cs index b2837c07e32a..8196b6648918 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectWorkspaceWindow.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SelectWorkspaceWindow.Designer.cs @@ -81,7 +81,7 @@ // OkBtn // this.OkBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.OkBtn.Location = new System.Drawing.Point(829, 479); + this.OkBtn.Location = new System.Drawing.Point(736, 479); this.OkBtn.Name = "OkBtn"; this.OkBtn.Size = new System.Drawing.Size(87, 27); this.OkBtn.TabIndex = 1; @@ -93,7 +93,7 @@ // this.CancelBtn.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CancelBtn.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancelBtn.Location = new System.Drawing.Point(736, 479); + this.CancelBtn.Location = new System.Drawing.Point(829, 479); this.CancelBtn.Name = "CancelBtn"; this.CancelBtn.Size = new System.Drawing.Size(87, 27); this.CancelBtn.TabIndex = 2; diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SyncFilter.Designer.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SyncFilter.Designer.cs index a822ac51bc34..1dc4c18f1f2d 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SyncFilter.Designer.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SyncFilter.Designer.cs @@ -49,7 +49,7 @@ namespace UnrealGameSync // OkButton // this.OkButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.OkButton.Location = new System.Drawing.Point(981, 688); + this.OkButton.Location = new System.Drawing.Point(888, 688); this.OkButton.Margin = new System.Windows.Forms.Padding(3, 5, 3, 5); this.OkButton.Name = "OkButton"; this.OkButton.Size = new System.Drawing.Size(87, 26); @@ -62,7 +62,7 @@ namespace UnrealGameSync // this.CancButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); this.CancButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; - this.CancButton.Location = new System.Drawing.Point(888, 688); + this.CancButton.Location = new System.Drawing.Point(981, 688); this.CancButton.Margin = new System.Windows.Forms.Padding(3, 5, 3, 5); this.CancButton.Name = "CancButton"; this.CancButton.Size = new System.Drawing.Size(87, 26); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SyncFilter.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SyncFilter.cs index 02aafd3bab83..65e7eeea620f 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SyncFilter.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Forms/SyncFilter.cs @@ -1,4 +1,4 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; @@ -18,12 +18,14 @@ namespace UnrealGameSync public string[] GlobalView; public Guid[] GlobalExcludedCategories; public bool bGlobalSyncAllProjects; + public bool bGlobalIncludeAllProjectsInSolution; public string[] WorkspaceView; public Guid[] WorkspaceIncludedCategories; public Guid[] WorkspaceExcludedCategories; public bool? bWorkspaceSyncAllProjects; + public bool? bWorkspaceIncludeAllProjectsInSolution; - public SyncFilter(Dictionary InUniqueIdToCategory, string[] InGlobalView, Guid[] InGlobalExcludedCategories, bool bInGlobalProjectOnly, string[] InWorkspaceView, Guid[] InWorkspaceIncludedCategories, Guid[] InWorkspaceExcludedCategories, bool? bInWorkspaceProjectOnly) + public SyncFilter(Dictionary InUniqueIdToCategory, string[] InGlobalView, Guid[] InGlobalExcludedCategories, bool bInGlobalProjectOnly, bool bInGlobalIncludeAllProjectsInSolution, string[] InWorkspaceView, Guid[] InWorkspaceIncludedCategories, Guid[] InWorkspaceExcludedCategories, bool? bInWorkspaceProjectOnly, bool? bInWorkspaceIncludeAllProjectsInSolution) { InitializeComponent(); @@ -31,21 +33,26 @@ namespace UnrealGameSync GlobalExcludedCategories = InGlobalExcludedCategories; GlobalView = InGlobalView; bGlobalSyncAllProjects = bInGlobalProjectOnly; + bGlobalIncludeAllProjectsInSolution = bInGlobalIncludeAllProjectsInSolution; WorkspaceIncludedCategories = InWorkspaceIncludedCategories; WorkspaceExcludedCategories = InWorkspaceExcludedCategories; WorkspaceView = InWorkspaceView; - bWorkspaceSyncAllProjects = bInWorkspaceProjectOnly ?? bGlobalSyncAllProjects; + bWorkspaceSyncAllProjects = bInWorkspaceProjectOnly; + bWorkspaceIncludeAllProjectsInSolution = bInWorkspaceIncludeAllProjectsInSolution; GlobalControl.SetView(GlobalView); SetExcludedCategories(GlobalControl.CategoriesCheckList, UniqueIdToCategory, GlobalExcludedCategories); GlobalControl.SyncAllProjects.Checked = bGlobalSyncAllProjects; + GlobalControl.IncludeAllProjectsInSolution.Checked = bGlobalIncludeAllProjectsInSolution; WorkspaceControl.SetView(WorkspaceView); SetExcludedCategories(WorkspaceControl.CategoriesCheckList, UniqueIdToCategory, UserSettings.GetEffectiveExcludedCategories(GlobalExcludedCategories, WorkspaceIncludedCategories, WorkspaceExcludedCategories)); WorkspaceControl.SyncAllProjects.Checked = bWorkspaceSyncAllProjects ?? bGlobalSyncAllProjects; + WorkspaceControl.IncludeAllProjectsInSolution.Checked = bWorkspaceIncludeAllProjectsInSolution ?? bGlobalIncludeAllProjectsInSolution; GlobalControl.CategoriesCheckList.ItemCheck += GlobalControl_CategoriesCheckList_ItemCheck; GlobalControl.SyncAllProjects.CheckStateChanged += GlobalControl_SyncAllProjects_CheckStateChanged; + GlobalControl.IncludeAllProjectsInSolution.CheckStateChanged += GlobalControl_IncludeAllProjectsInSolution_CheckStateChanged; } private void GlobalControl_CategoriesCheckList_ItemCheck(object sender, ItemCheckEventArgs e) @@ -58,6 +65,11 @@ namespace UnrealGameSync WorkspaceControl.SyncAllProjects.Checked = GlobalControl.SyncAllProjects.Checked; } + private void GlobalControl_IncludeAllProjectsInSolution_CheckStateChanged(object sender, EventArgs e) + { + WorkspaceControl.IncludeAllProjectsInSolution.Checked = GlobalControl.IncludeAllProjectsInSolution.Checked; + } + private static void SetExcludedCategories(CheckedListBox ListBox, Dictionary UniqueIdToFilter, Guid[] ExcludedCategories) { ListBox.Items.Clear(); @@ -124,9 +136,11 @@ namespace UnrealGameSync GlobalView = NewGlobalView; bGlobalSyncAllProjects = GlobalControl.SyncAllProjects.Checked; + bGlobalIncludeAllProjectsInSolution = GlobalControl.IncludeAllProjectsInSolution.Checked; WorkspaceView = NewWorkspaceView; bWorkspaceSyncAllProjects = (WorkspaceControl.SyncAllProjects.Checked == bGlobalSyncAllProjects)? (bool?)null : WorkspaceControl.SyncAllProjects.Checked; + bWorkspaceIncludeAllProjectsInSolution = (WorkspaceControl.IncludeAllProjectsInSolution.Checked == bGlobalIncludeAllProjectsInSolution)? (bool?)null : WorkspaceControl.IncludeAllProjectsInSolution.Checked; GetExcludedCategories(out GlobalExcludedCategories, out WorkspaceIncludedCategories, out WorkspaceExcludedCategories); diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/OutputAdapters.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/OutputAdapters.cs index dac7ed5d8ef9..257e5b63ae8a 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/OutputAdapters.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/OutputAdapters.cs @@ -53,6 +53,16 @@ namespace UnrealGameSync } } + class BufferedTextWriter : LineBasedTextWriter + { + public List Lines = new List(); + + protected override void FlushLine(string Line) + { + Lines.Add(Line); + } + } + class PrefixedTextWriter : LineBasedTextWriter { string Prefix; diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Perforce.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Perforce.cs index 99f41bc56fa4..1ca6ff3ee4c5 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Perforce.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Perforce.cs @@ -155,7 +155,6 @@ namespace UnrealGameSync public string UserName; public string HostName; public string ClientAddress; - public string ServerAddress; public TimeSpan ServerTimeZone; public PerforceInfoRecord(Dictionary Tags) @@ -163,7 +162,6 @@ namespace UnrealGameSync Tags.TryGetValue("userName", out UserName); Tags.TryGetValue("clientHost", out HostName); Tags.TryGetValue("clientAddress", out ClientAddress); - Tags.TryGetValue("serverAddress", out ServerAddress); string ServerDateTime; if(Tags.TryGetValue("serverDate", out ServerDateTime)) @@ -201,6 +199,7 @@ namespace UnrealGameSync public int Revision; public bool IsMapped; public bool Unmap; + public string Digest; public PerforceFileRecord(Dictionary Tags) { @@ -247,6 +246,8 @@ namespace UnrealGameSync { int.TryParse(RevisionString, out Revision); } + + Tags.TryGetValue("digest", out Digest); } } @@ -470,11 +471,14 @@ namespace UnrealGameSync IgnoreFilesNotInClientViewError = 0x80, IgnoreEnterPassword = 0x100, IgnoreFilesNotOnClientError = 0x200, + IgnoreProtectedNamespaceError = 0x400, } delegate bool HandleRecordDelegate(Dictionary Tags); delegate bool HandleOutputDelegate(PerforceOutputLine Line); + public const string DefaultServerAndPort = "perforce:1666"; + public readonly string ServerAndPort; public readonly string UserName; public readonly string ClientName; @@ -506,7 +510,7 @@ namespace UnrealGameSync bIsLoggedIn = true; return true; } - else if(Lines[0].Channel == PerforceOutputChannel.Error && Lines[0].Text.Contains("P4PASSWD")) + else if(Lines[0].Channel == PerforceOutputChannel.Error && (Lines[0].Text.Contains("P4PASSWD") || Lines[0].Text.Contains("has expired"))) { bIsLoggedIn = false; return true; @@ -599,13 +603,13 @@ namespace UnrealGameSync public bool FindClients(string ForUserName, out List Clients, TextWriter Log) { - return RunCommand(String.Format("clients -u{0}", ForUserName), out Clients, CommandOptions.NoClient, Log); + return RunCommand(String.Format("clients -u \"{0}\"", ForUserName), out Clients, CommandOptions.NoClient, Log); } public bool FindClients(string ForUserName, out List ClientNames, TextWriter Log) { List Lines; - if(!RunCommand(String.Format("clients -u{0}", ForUserName), out Lines, CommandOptions.None, Log)) + if(!RunCommand(String.Format("clients -u \"{0}\"", ForUserName), out Lines, CommandOptions.None, Log)) { ClientNames = null; return false; @@ -685,9 +689,14 @@ namespace UnrealGameSync public bool FindFiles(string Filter, out List FileRecords, TextWriter Log) { - return RunCommand(String.Format("fstat \"{0}\"", Filter), out FileRecords, CommandOptions.None, Log); + bool bResult = RunCommand(String.Format("fstat \"{0}\"", Filter), out FileRecords, CommandOptions.None, Log); + if(bResult) + { + FileRecords.RemoveAll(x => x.Action != null && x.Action.Contains("delete")); + } + return bResult; } - + public bool Print(string DepotPath, out List Lines, TextWriter Log) { string TempFileName = Path.GetTempFileName(); @@ -731,9 +740,9 @@ namespace UnrealGameSync public bool FileExists(string Filter, out bool bExists, TextWriter Log) { List FileRecords; - if(RunCommand(String.Format("fstat \"{0}\"", Filter), out FileRecords, CommandOptions.IgnoreNoSuchFilesError | CommandOptions.IgnoreFilesNotInClientViewError, Log)) + if(RunCommand(String.Format("fstat \"{0}\"", Filter), out FileRecords, CommandOptions.IgnoreNoSuchFilesError | CommandOptions.IgnoreFilesNotInClientViewError | CommandOptions.IgnoreProtectedNamespaceError, Log)) { - bExists = (FileRecords.Count > 0); + bExists = (FileRecords.Exists(x => x.Action == null || !x.Action.Contains("delete"))); return true; } else @@ -1077,7 +1086,21 @@ namespace UnrealGameSync public bool Stat(string Filter, out List FileRecords, TextWriter Log) { - return RunCommand(String.Format("fstat \"{0}\"", Filter), out FileRecords, CommandOptions.IgnoreFilesNotOnClientError | CommandOptions.IgnoreNoSuchFilesError, Log); + return RunCommand(String.Format("fstat \"{0}\"", Filter), out FileRecords, CommandOptions.IgnoreFilesNotOnClientError | CommandOptions.IgnoreNoSuchFilesError | CommandOptions.IgnoreProtectedNamespaceError, Log); + } + + public bool Stat(string Options, List Files, out List FileRecords, TextWriter Log) + { + StringBuilder Arguments = new StringBuilder("fstat"); + if(!String.IsNullOrEmpty(Options)) + { + Arguments.AppendFormat(" {0}", Options); + } + foreach(string File in Files) + { + Arguments.AppendFormat(" \"{0}\"", File); + } + return RunCommand(Arguments.ToString(), out FileRecords, CommandOptions.IgnoreFilesNotOnClientError | CommandOptions.IgnoreNoSuchFilesError | CommandOptions.IgnoreProtectedNamespaceError, Log); } public bool Sync(string Filter, TextWriter Log) @@ -1442,6 +1465,10 @@ namespace UnrealGameSync { return true; } + else if(Options.HasFlag(CommandOptions.IgnoreProtectedNamespaceError) && Text.StartsWith("error: ") && Text.EndsWith(" - protected namespace - access denied.")) + { + return true; + } else if(Options.HasFlag(CommandOptions.IgnoreEnterPassword) && Text.StartsWith("Enter password:")) { return true; diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/PerforceModalTask.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/PerforceModalTask.cs index 9ec535a1e1b5..1cebde50fb8d 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/PerforceModalTask.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/PerforceModalTask.cs @@ -67,28 +67,24 @@ namespace UnrealGameSync } } + // If the server setting is still null, user the default + if(ServerAndPort == null) + { + ServerAndPort = PerforceConnection.DefaultServerAndPort; + } + // Update the server and username from the reported server info if it's not set - if(ServerAndPort == null || UserName == null) + if(UserName == null) { PerforceConnection Perforce = new PerforceConnection(UserName, null, ServerAndPort); PerforceInfoRecord PerforceInfo; - if(!Perforce.Info(out PerforceInfo, Log)) - { - return false; - } - if(ServerAndPort == null && !String.IsNullOrEmpty(PerforceInfo.ServerAddress)) - { - ServerAndPort = PerforceInfo.ServerAddress; - } - if(UserName == null && !String.IsNullOrEmpty(PerforceInfo.UserName)) - { - UserName = PerforceInfo.UserName; - } - if(ServerAndPort == null || UserName == null) + if(!Perforce.Info(out PerforceInfo, Log) || String.IsNullOrEmpty(PerforceInfo.UserName)) { return false; } + + UserName = PerforceInfo.UserName; } // Otherwise succeed diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/PerforceMonitor.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/PerforceMonitor.cs index d8860dbe5fba..11dc8e95802a 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/PerforceMonitor.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/PerforceMonitor.cs @@ -29,6 +29,8 @@ namespace UnrealGameSync } } + public int InitialMaxChangesValue = 100; + PerforceConnection Perforce; readonly string BranchClientPath; readonly string SelectedClientFileName; @@ -43,6 +45,7 @@ namespace UnrealGameSync BoundedLogWriter LogWriter; bool bIsEnterpriseProject; bool bDisposing; + string CacheFolder; List> LocalConfigFiles; public event Action OnUpdate; @@ -50,18 +53,24 @@ namespace UnrealGameSync public event Action OnStreamChange; public event Action OnLoginExpired; - public PerforceMonitor(PerforceConnection InPerforce, string InBranchClientPath, string InSelectedClientFileName, string InSelectedProjectIdentifier, string InLogPath, bool bInIsEnterpriseProject, ConfigFile InProjectConfigFile, List> InLocalConfigFiles) + public TimeSpan ServerTimeZone + { + get; + private set; + } + + public PerforceMonitor(PerforceConnection InPerforce, string InBranchClientPath, string InSelectedClientFileName, string InSelectedProjectIdentifier, string InLogPath, bool bInIsEnterpriseProject, ConfigFile InProjectConfigFile, string InCacheFolder, List> InLocalConfigFiles) { Perforce = InPerforce; BranchClientPath = InBranchClientPath; SelectedClientFileName = InSelectedClientFileName; SelectedProjectIdentifier = InSelectedProjectIdentifier; - PendingMaxChangesValue = 100; + PendingMaxChangesValue = InitialMaxChangesValue; LastChangeByCurrentUser = -1; LastCodeChangeByCurrentUser = -1; - OtherStreamNames = new List(); bIsEnterpriseProject = bInIsEnterpriseProject; LatestProjectConfigFile = InProjectConfigFile; + CacheFolder = InCacheFolder; LocalConfigFiles = InLocalConfigFiles; LogWriter = new BoundedLogWriter(InLogPath); @@ -116,12 +125,6 @@ namespace UnrealGameSync set { lock(this){ if(value != PendingMaxChangesValue){ PendingMaxChangesValue = value; RefreshEvent.Set(); } } } } - public IReadOnlyList OtherStreamNames - { - get; - private set; - } - void PollForUpdates() { string StreamName; @@ -130,6 +133,13 @@ namespace UnrealGameSync StreamName = null; } + // Get the perforce server settings + PerforceInfoRecord PerforceInfo; + if(Perforce.Info(out PerforceInfo, LogWriter)) + { + ServerTimeZone = PerforceInfo.ServerTimeZone; + } + // Try to update the zipped binaries list before anything else, because it causes a state change in the UI UpdateZippedBinaries(); @@ -155,17 +165,6 @@ namespace UnrealGameSync OnStreamChange(); } - // Update the stream list - if(StreamName != null) - { - List NewOtherStreamNames; - if(!Perforce.FindStreams(PerforceUtils.GetClientOrDepotDirectoryName(StreamName) + "/*", out NewOtherStreamNames, LogWriter)) - { - NewOtherStreamNames = new List(); - } - OtherStreamNames = NewOtherStreamNames; - } - // Check for any p4 changes if(!UpdateChanges()) { @@ -365,6 +364,12 @@ namespace UnrealGameSync { string[] CodeExtensions = { ".cs", ".h", ".cpp", ".inl", ".usf", ".ush", ".uproject", ".uplugin" }; + // Skip this stuff if the user wants us to query for more changes + if(PendingMaxChanges > CurrentMaxChanges) + { + break; + } + // If there's something to check for, find all the content changes after this changelist PerforceDescribeRecord DescribeRecord; if(Perforce.Describe(QueryChangeNumber, out DescribeRecord, LogWriter)) @@ -517,57 +522,62 @@ namespace UnrealGameSync void UpdateProjectConfigFile() { LocalConfigFiles.Clear(); - LatestProjectConfigFile = ReadProjectConfigFile(Perforce, BranchClientPath, SelectedClientFileName, LocalConfigFiles, LogWriter); + LatestProjectConfigFile = ReadProjectConfigFile(Perforce, BranchClientPath, SelectedClientFileName, CacheFolder, LocalConfigFiles, LogWriter); } - public static ConfigFile ReadProjectConfigFile(PerforceConnection Perforce, string BranchClientPath, string SelectedClientFileName, List> LocalConfigFiles, TextWriter Log) + public static ConfigFile ReadProjectConfigFile(PerforceConnection Perforce, string BranchClientPath, string SelectedClientFileName, string CacheFolder, List> LocalConfigFiles, TextWriter Log) { List ConfigFilePaths = Utility.GetConfigFileLocations(BranchClientPath, SelectedClientFileName, '/'); - List OpenFiles; - Perforce.GetOpenFiles(String.Format("{0}/....ini", BranchClientPath), out OpenFiles, Log); - ConfigFile ProjectConfig = new ConfigFile(); - foreach(string ConfigFilePath in ConfigFilePaths) - { - List Lines = null; - // If this file is open for edit, read the local version - if(OpenFiles != null && OpenFiles.Any(x => x.ClientPath.Equals(ConfigFilePath, StringComparison.InvariantCultureIgnoreCase))) + List FileRecords; + if(Perforce.Stat("-Ol", ConfigFilePaths, out FileRecords, Log)) + { + foreach(PerforceFileRecord FileRecord in FileRecords) { - try + List Lines = null; + + // Skip file records which are still in the workspace, but were synced from a different branch. For these files, the action seems to be empty, so filter against that. + if(FileRecord.Action == null) { - string LocalFileName; - if(Perforce.ConvertToLocalPath(ConfigFilePath, out LocalFileName, Log)) + continue; + } + + // If this file is open for edit, read the local version + string LocalFileName = FileRecord.ClientPath; + if(LocalFileName != null && File.Exists(LocalFileName) && (File.GetAttributes(LocalFileName) & FileAttributes.ReadOnly) == 0) + { + try { DateTime LastModifiedTime = File.GetLastWriteTimeUtc(LocalFileName); LocalConfigFiles.Add(new KeyValuePair(LocalFileName, LastModifiedTime)); Lines = File.ReadAllLines(LocalFileName).ToList(); } + catch(Exception Ex) + { + Log.WriteLine("Failed to read local config file for {0}: {1}", LocalFileName, Ex.ToString()); + } } - catch(Exception Ex) - { - Log.WriteLine("Failed to read local config file for {0}: {1}", ConfigFilePath, Ex.ToString()); - } - } - // Otherwise try to get it from perforce - if(Lines == null) - { - Perforce.Print(ConfigFilePath, out Lines, Log); - } - - // Merge the text with the config file - if(Lines != null) - { - try + // Otherwise try to get it from perforce + if(Lines == null) { - ProjectConfig.Parse(Lines.ToArray()); - Log.WriteLine("Read config file from {0}", ConfigFilePath); + Utility.TryPrintFileUsingCache(Perforce, FileRecord.DepotPath, CacheFolder, FileRecord.Digest, out Lines, Log); } - catch(Exception Ex) + + // Merge the text with the config file + if(Lines != null) { - Log.WriteLine("Failed to read config file from {0}: {1}", ConfigFilePath, Ex.ToString()); + try + { + ProjectConfig.Parse(Lines.ToArray()); + Log.WriteLine("Read config file from {0}", FileRecord.DepotPath); + } + catch(Exception Ex) + { + Log.WriteLine("Failed to read config file from {0}: {1}", FileRecord.DepotPath, Ex.ToString()); + } } } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Program.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Program.cs index 59e3afa8e572..a3a7b77c4e07 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Program.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Program.cs @@ -15,14 +15,8 @@ using System.Windows.Forms; namespace UnrealGameSync { - static partial class Program + static class Program { - /// - /// SQL connection string used to connect to the database for telemetry and review data. The 'Program' class is a partial class, to allow an - /// opportunistically included C# source file in NotForLicensees/ProgramSettings.cs to override this value in a static constructor. - /// - public static readonly string ApiUrl = null; - public static string SyncVersion = null; [STAThread] @@ -92,18 +86,19 @@ namespace UnrealGameSync string DataFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "UnrealGameSync"); Directory.CreateDirectory(DataFolder); - using(TelemetryWriter Telemetry = new TelemetryWriter(ApiUrl, Path.Combine(DataFolder, "Telemetry.log"))) + using(TelemetryWriter Telemetry = new TelemetryWriter(DeploymentSettings.ApiUrl, Path.Combine(DataFolder, "Telemetry.log"))) { AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; using(UpdateMonitor UpdateMonitor = new UpdateMonitor(new PerforceConnection(UserName, null, ServerAndPort), UpdatePath)) { - ProgramApplicationContext Context = new ProgramApplicationContext(UpdateMonitor, ApiUrl, DataFolder, ActivateEvent, bRestoreState, UpdateSpawn, ProjectFileName, bUnstable); + ProgramApplicationContext Context = new ProgramApplicationContext(UpdateMonitor, DeploymentSettings.ApiUrl, DataFolder, ActivateEvent, bRestoreState, UpdateSpawn, ProjectFileName, bUnstable); Application.Run(Context); if(UpdateMonitor.IsUpdateAvailable && UpdateSpawn != null) { InstanceMutex.Close(); - Utility.SpawnProcess(UpdateSpawn, "-restorestate" + (bUnstable? " -unstable" : "")); + bool bLaunchUnstable = UpdateMonitor.RelaunchUnstable ?? bUnstable; + Utility.SpawnProcess(UpdateSpawn, "-restorestate" + (bLaunchUnstable? " -unstable" : "")); } } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ProgramApplicationContext.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ProgramApplicationContext.cs index 09fecbb7c5f2..f5d03f4a62e1 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ProgramApplicationContext.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/ProgramApplicationContext.cs @@ -21,6 +21,7 @@ namespace UnrealGameSync UpdateMonitor UpdateMonitor; string ApiUrl; string DataFolder; + string CacheFolder; bool bRestoreState; string UpdateSpawn; bool bUnstable; @@ -40,6 +41,7 @@ namespace UnrealGameSync ToolStripSeparator NotifyMenu_ExitSeparator; ToolStripMenuItem NotifyMenu_Exit; + List StartupLogs = new List(); DetectMultipleProjectSettingsTask DetectStartupProjectSettingsTask; ModalTaskWindow DetectStartupProjectSettingsWindow; MainWindow MainWindowInstance; @@ -49,10 +51,15 @@ namespace UnrealGameSync this.UpdateMonitor = UpdateMonitor; this.ApiUrl = ApiUrl; this.DataFolder = DataFolder; + this.CacheFolder = Path.Combine(DataFolder, "Cache"); this.bRestoreState = bRestoreState; this.UpdateSpawn = UpdateSpawn; this.bUnstable = bUnstable; + // Create the directories + Directory.CreateDirectory(DataFolder); + Directory.CreateDirectory(CacheFolder); + // Make sure a synchronization context is set. We spawn a bunch of threads (eg. UpdateMonitor) at startup, and need to make sure we can post messages // back to the main thread at any time. if(SynchronizationContext.Current == null) @@ -147,8 +154,11 @@ namespace UnrealGameSync List Tasks = new List(); foreach(UserSelectedProjectSettings OpenProject in Settings.OpenProjects) { - Log.WriteLine("Detecting settings for {0}", OpenProject); - Tasks.Add(new DetectProjectSettingsTask(OpenProject, DataFolder, new PrefixedTextWriter(" ", Log))); + BufferedTextWriter StartupLog = new BufferedTextWriter(); + StartupLog.WriteLine("Detecting settings for {0}", OpenProject); + StartupLogs.Add(StartupLog); + + Tasks.Add(new DetectProjectSettingsTask(OpenProject, DataFolder, CacheFolder, new TimestampLogWriter(new PrefixedTextWriter(" ", StartupLog)))); } // Detect settings for the project we want to open @@ -177,12 +187,23 @@ namespace UnrealGameSync DetectStartupProjectSettingsWindow.Close(); DetectStartupProjectSettingsWindow = null; - // Create the main window - MainWindowInstance = new MainWindow(ApiUrl, DataFolder, bRestoreState, UpdateSpawn ?? Assembly.GetExecutingAssembly().Location, DetectStartupProjectSettingsTask.Results, Log, Settings); - if(bUnstable) + // Copy all the logs to the main log + foreach(BufferedTextWriter StartupLog in StartupLogs) { - MainWindowInstance.Text += String.Format(" (UNSTABLE BUILD {0})", Assembly.GetExecutingAssembly().GetName().Version); + foreach(string Line in StartupLog.Lines) + { + Log.WriteLine("{0}", Line); + } } + + // Clear out the cache folder + Utility.ClearPrintCache(CacheFolder); + + // Get a list of all the valid projects to open + DetectProjectSettingsResult[] StartupProjects = DetectStartupProjectSettingsTask.Results.Where(x => x != null).ToArray(); + + // Create the main window + MainWindowInstance = new MainWindow(UpdateMonitor, ApiUrl, DataFolder, CacheFolder, bRestoreState, UpdateSpawn ?? Assembly.GetExecutingAssembly().Location, bUnstable, StartupProjects, Log, Settings); if(bVisible) { MainWindowInstance.Show(); @@ -205,12 +226,7 @@ namespace UnrealGameSync private void OnActivationListenerCallback() { - // Check if we're trying to reopen with the unstable version; if so, trigger an update to trigger a restart with the new executable - if(!bUnstable && (Control.ModifierKeys & Keys.Shift) != 0) - { - UpdateMonitor.TriggerUpdate(); - } - else if(MainWindowInstance != null) + if(MainWindowInstance != null) { MainWindowInstance.ShowAndActivate(); } @@ -221,18 +237,21 @@ namespace UnrealGameSync MainThreadSynchronizationContext.Post((o) => OnActivationListenerCallback(), null); } - private void OnUpdateAvailable() + private void OnUpdateAvailable(UpdateType Type) { - if(MainWindowInstance != null && !bIsClosing && MainWindowInstance.CanPerformUpdate()) + if(MainWindowInstance != null && !bIsClosing) { - bIsClosing = true; - MainWindowInstance.ForceClose(); + if(Type == UpdateType.UserInitiated || MainWindowInstance.CanPerformUpdate()) + { + bIsClosing = true; + MainWindowInstance.ForceClose(); + } } } - private void OnUpdateAvailableCallback() + private void OnUpdateAvailableCallback(UpdateType Type) { - MainThreadSynchronizationContext.Post((o) => OnUpdateAvailable(), null); + MainThreadSynchronizationContext.Post((o) => OnUpdateAvailable(Type), null); } protected override void Dispose(bool bDisposing) diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Properties/AssemblyInfo.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Properties/AssemblyInfo.cs index 0c8149dd56b0..37c67b74b39a 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Properties/AssemblyInfo.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Properties/AssemblyInfo.cs @@ -34,5 +34,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.155.*")] -[assembly: AssemblyFileVersion("1.155.0.0")] +[assembly: AssemblyVersion("1.167.*")] +[assembly: AssemblyFileVersion("1.167.0.0")] diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UnrealGameSync.csproj b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UnrealGameSync.csproj index e3906e554daf..c339e54e76bd 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UnrealGameSync.csproj +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UnrealGameSync.csproj @@ -112,6 +112,7 @@ + @@ -149,6 +150,7 @@ Component + @@ -158,6 +160,12 @@ ArgumentsWindow.cs + + Form + + + AutomatedSyncWindow.cs + Form @@ -206,11 +214,17 @@ PasswordWindow.cs - + Form - - PerforceSettingsWindow.cs + + ApplicationSettingsWindow.cs + + + Form + + + PerforceSyncSettingsWindow.cs Form @@ -317,6 +331,7 @@ SyncFilter.cs + @@ -345,6 +360,9 @@ ArgumentsWindow.cs + + AutomatedSyncWindow.cs + CleanWorkspaceWindow.cs @@ -369,8 +387,11 @@ PasswordWindow.cs - - PerforceSettingsWindow.cs + + ApplicationSettingsWindow.cs + + + PerforceSyncSettingsWindow.cs ProgramsRunningWindow.cs @@ -441,11 +462,7 @@ - - - - - + diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UpdateMonitor.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UpdateMonitor.cs index e27e87f63453..b860ea98e5a2 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UpdateMonitor.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UpdateMonitor.cs @@ -10,14 +10,31 @@ using System.Threading.Tasks; namespace UnrealGameSync { + enum UpdateType + { + Background, + UserInitiated, + } + class UpdateMonitor : IDisposable { - PerforceConnection Perforce; string WatchPath; Thread WorkerThread; ManualResetEvent QuitEvent; - public event Action OnUpdateAvailable; + public event Action OnUpdateAvailable; + + public PerforceConnection Perforce + { + get; + private set; + } + + public bool? RelaunchUnstable + { + get; + private set; + } public UpdateMonitor(PerforceConnection InPerforce, string InWatchPath) { @@ -68,17 +85,18 @@ namespace UnrealGameSync List Changes; if(Perforce.FindChanges(WatchPath, 1, out Changes, Log) && Changes.Count > 0) { - TriggerUpdate(); + TriggerUpdate(UpdateType.Background, null); } } } - public void TriggerUpdate() + public void TriggerUpdate(UpdateType UpdateType, bool? RelaunchUnstable) { + this.RelaunchUnstable = RelaunchUnstable; IsUpdateAvailable = true; if(OnUpdateAvailable != null) { - OnUpdateAvailable(); + OnUpdateAvailable(UpdateType); } } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UserSettings.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UserSettings.cs index d80ef62db8a5..6ca2b8381d1b 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UserSettings.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/UserSettings.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Forms; namespace UnrealGameSync { @@ -39,6 +40,13 @@ namespace UnrealGameSync Local } + enum FilterType + { + None, + Code, + Content + } + class UserSelectedProjectSettings { public readonly string ServerAndPort; @@ -69,6 +77,12 @@ namespace UnrealGameSync ServerAndPort = null; } + // Fixup for code that was saving server host name rather than DNS entry + if(ServerAndPort != null && ServerAndPort.Equals("p4-nodeb.epicgames.net:1666", StringComparison.OrdinalIgnoreCase)) + { + ServerAndPort = "perforce:1666"; + } + string UserName = Object.GetValue("UserName", null); if(String.IsNullOrWhiteSpace(UserName)) { @@ -160,11 +174,14 @@ namespace UnrealGameSync public Guid[] SyncIncludedCategories; public Guid[] SyncExcludedCategories; public bool? bSyncAllProjects; + public bool? bIncludeAllProjectsInSolution; } class UserProjectSettings { public List BuildSteps = new List(); + public FilterType FilterType; + public HashSet FilterBadges = new HashSet(StringComparer.OrdinalIgnoreCase); } class UserSettings @@ -191,12 +208,15 @@ namespace UnrealGameSync public string[] SyncView; public Guid[] SyncExcludedCategories; public bool bSyncAllProjects; + public bool bIncludeAllProjectsInSolution; public LatestChangeType SyncType; public BuildConfig CompiledEditorBuildConfig; // NB: This assumes not using precompiled editor. See CurrentBuildConfig. public TabLabels TabLabels; // Window settings public bool bWindowVisible; + public FormWindowState WindowState; + public Rectangle? WindowBounds; // Schedule settings public bool bScheduleEnabled; @@ -292,6 +312,7 @@ namespace UnrealGameSync SyncView = ConfigFile.GetValues("General.SyncFilter", new string[0]); SyncExcludedCategories = ConfigFile.GetGuidValues("General.SyncExcludedCategories", new Guid[0]); bSyncAllProjects = ConfigFile.GetValue("General.SyncAllProjects", false); + bIncludeAllProjectsInSolution = ConfigFile.GetValue("General.IncludeAllProjectsInSolution", false); if(!Enum.TryParse(ConfigFile.GetValue("General.SyncType", ""), out SyncType)) { SyncType = LatestChangeType.Good; @@ -332,6 +353,11 @@ namespace UnrealGameSync // Window settings bWindowVisible = ConfigFile.GetValue("Window.Visible", true); + if(!Enum.TryParse(ConfigFile.GetValue("Window.State", ""), true, out WindowState)) + { + WindowState = FormWindowState.Normal; + } + WindowBounds = ParseRectangleValue(ConfigFile.GetValue("Window.Bounds", "")); // Schedule settings bScheduleEnabled = ConfigFile.GetValue("Schedule.Enabled", false); @@ -361,6 +387,37 @@ namespace UnrealGameSync } } + static Rectangle? ParseRectangleValue(string Text) + { + ConfigObject Object = new ConfigObject(Text); + + int X = Object.GetValue("X", -1); + int Y = Object.GetValue("Y", -1); + int W = Object.GetValue("W", -1); + int H = Object.GetValue("H", -1); + + if(X == -1 || Y == -1 || W == -1 || H == -1) + { + return null; + } + else + { + return new Rectangle(X, Y, W, H); + } + } + + static string FormatRectangleValue(Rectangle Value) + { + ConfigObject Object = new ConfigObject(); + + Object.SetValue("X", Value.X); + Object.SetValue("Y", Value.Y); + Object.SetValue("W", Value.Width); + Object.SetValue("H", Value.Height); + + return Object.ToString(); + } + public UserWorkspaceSettings FindOrAddWorkspace(string ClientBranchPath) { // Update the current workspace @@ -423,6 +480,7 @@ namespace UnrealGameSync CurrentWorkspace.SyncIncludedCategories = new Guid[0]; CurrentWorkspace.SyncExcludedCategories = new Guid[0]; CurrentWorkspace.bSyncAllProjects = null; + CurrentWorkspace.bIncludeAllProjectsInSolution = null; } else { @@ -458,6 +516,9 @@ namespace UnrealGameSync int SyncAllProjects = WorkspaceSection.GetValue("SyncAllProjects", -1); CurrentWorkspace.bSyncAllProjects = (SyncAllProjects == 0)? (bool?)false : (SyncAllProjects == 1)? (bool?)true : (bool?)null; + int IncludeAllProjectsInSolution = WorkspaceSection.GetValue("IncludeAllProjectsInSolution", -1); + CurrentWorkspace.bIncludeAllProjectsInSolution = (IncludeAllProjectsInSolution == 0)? (bool?)false : (IncludeAllProjectsInSolution == 1)? (bool?)true : (bool?)null; + string[] BisectEntries = WorkspaceSection.GetValues("Bisect", new string[0]); foreach(string BisectEntry in BisectEntries) { @@ -489,6 +550,11 @@ namespace UnrealGameSync ConfigSection ProjectSection = ConfigFile.FindOrAddSection(ClientProjectFileName); CurrentProject.BuildSteps.AddRange(ProjectSection.GetValues("BuildStep", new string[0]).Select(x => new ConfigObject(x))); + if(!Enum.TryParse(ProjectSection.GetValue("FilterType", ""), true, out CurrentProject.FilterType)) + { + CurrentProject.FilterType = FilterType.None; + } + CurrentProject.FilterBadges.UnionWith(ProjectSection.GetValues("FilterBadges", new string[0])); } return CurrentProject; } @@ -519,6 +585,7 @@ namespace UnrealGameSync GeneralSection.SetValues("SyncFilter", SyncView); GeneralSection.SetValues("SyncExcludedCategories", SyncExcludedCategories); GeneralSection.SetValue("SyncAllProjects", bSyncAllProjects); + GeneralSection.SetValue("IncludeAllProjectsInSolution", bIncludeAllProjectsInSolution); GeneralSection.SetValue("SyncType", SyncType.ToString()); // Build configuration @@ -549,6 +616,11 @@ namespace UnrealGameSync ConfigSection WindowSection = ConfigFile.FindOrAddSection("Window"); WindowSection.Clear(); WindowSection.SetValue("Visible", bWindowVisible); + WindowSection.SetValue("State", WindowState.ToString()); + if(WindowBounds != null) + { + WindowSection.SetValue("Bounds", FormatRectangleValue(WindowBounds.Value)); + } // Current workspace settings foreach(KeyValuePair Pair in WorkspaceKeyToSettings) @@ -585,6 +657,10 @@ namespace UnrealGameSync { WorkspaceSection.SetValue("SyncAllProjects", CurrentWorkspace.bSyncAllProjects.Value); } + if(CurrentWorkspace.bIncludeAllProjectsInSolution.HasValue) + { + WorkspaceSection.SetValue("IncludeAllProjectsInSolution", CurrentWorkspace.bIncludeAllProjectsInSolution.Value); + } List BisectEntryObjects = new List(); foreach(KeyValuePair BisectPair in CurrentWorkspace.ChangeNumberToBisectState) @@ -606,6 +682,11 @@ namespace UnrealGameSync ConfigSection ProjectSection = ConfigFile.FindOrAddSection(CurrentProjectKey); ProjectSection.Clear(); ProjectSection.SetValues("BuildStep", CurrentProject.BuildSteps.Select(x => x.ToString()).ToArray()); + if(CurrentProject.FilterType != FilterType.None) + { + ProjectSection.SetValue("FilterType", CurrentProject.FilterType.ToString()); + } + ProjectSection.SetValues("FilterBadges", CurrentProject.FilterBadges.ToArray()); } // Perforce settings diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Utility.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Utility.cs index 65ba38df9cba..b9fbd5b4ee3a 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Utility.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Utility.cs @@ -2,6 +2,7 @@ #define USE_NEW_PROCESS_JOBS +using Microsoft.Win32; using System; using System.Collections.Generic; using System.Diagnostics; @@ -11,6 +12,7 @@ using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using System.Web.Script.Serialization; +using System.Windows.Forms; namespace UnrealGameSync { @@ -289,5 +291,138 @@ namespace UnrealGameSync } return ProjectConfigFileNames; } + + public static void ReadGlobalPerforceSettings(ref string ServerAndPort, ref string UserName, ref string DepotPath) + { + using (RegistryKey Key = Registry.CurrentUser.OpenSubKey("SOFTWARE\\Epic Games\\UnrealGameSync", false)) + { + if(Key != null) + { + ServerAndPort = Key.GetValue("ServerAndPort", ServerAndPort) as string; + UserName = Key.GetValue("UserName", UserName) as string; + DepotPath = Key.GetValue("DepotPath", DepotPath) as string; + } + } + } + + public static void SaveGlobalPerforceSettings(string ServerAndPort, string UserName, string DepotPath) + { + try + { + using (RegistryKey Key = Registry.CurrentUser.CreateSubKey("SOFTWARE\\Epic Games\\UnrealGameSync")) + { + // Delete this legacy setting + try { Key.DeleteValue("Server"); } catch(Exception) { } + + if(String.IsNullOrEmpty(ServerAndPort)) + { + try { Key.DeleteValue("ServerAndPort"); } catch(Exception) { } + } + else + { + Key.SetValue("ServerAndPort", ServerAndPort); + } + + if(String.IsNullOrEmpty(UserName)) + { + try { Key.DeleteValue("UserName"); } catch(Exception) { } + } + else + { + Key.SetValue("UserName", UserName); + } + + if(String.IsNullOrEmpty(DepotPath) || (DeploymentSettings.DefaultDepotPath != null && String.Equals(DepotPath, DeploymentSettings.DefaultDepotPath, StringComparison.InvariantCultureIgnoreCase))) + { + try { Key.DeleteValue("DepotPath"); } catch(Exception) { } + } + else + { + Key.SetValue("DepotPath", DepotPath); + } + } + } + catch(Exception Ex) + { + MessageBox.Show("Unable to save settings.\n\n" + Ex.ToString()); + } + } + + public static bool TryPrintFileUsingCache(PerforceConnection Perforce, string DepotPath, string CacheFolder, string Digest, out List Lines, TextWriter Log) + { + if(Digest == null) + { + return Perforce.Print(DepotPath, out Lines, Log); + } + + string CacheFile = Path.Combine(CacheFolder, Digest); + if(File.Exists(CacheFile)) + { + Log.WriteLine("Reading cached copy of {0} from {1}", DepotPath, CacheFile); + Lines = new List(File.ReadAllLines(CacheFile)); + try + { + File.SetLastWriteTimeUtc(CacheFile, DateTime.UtcNow); + } + catch(Exception Ex) + { + Log.WriteLine("Exception touching cache file {0}: {1}", CacheFile, Ex.ToString()); + } + return true; + } + else + { + string TempFile = String.Format("{0}.{1}.temp", CacheFile, Guid.NewGuid()); + if(!Perforce.PrintToFile(DepotPath, TempFile, Log)) + { + Lines = null; + return false; + } + else + { + Lines = new List(File.ReadAllLines(TempFile)); + try + { + File.SetAttributes(TempFile, FileAttributes.Normal); + File.SetLastWriteTimeUtc(TempFile, DateTime.UtcNow); + File.Move(TempFile, CacheFile); + } + catch + { + try + { + File.Delete(TempFile); + } + catch + { + } + } + return true; + } + } + } + + public static void ClearPrintCache(string CacheFolder) + { + DirectoryInfo CacheDir = new DirectoryInfo(CacheFolder); + if(CacheDir.Exists) + { + DateTime DeleteTime = DateTime.UtcNow - TimeSpan.FromDays(5.0); + foreach(FileInfo CacheFile in CacheDir.EnumerateFiles()) + { + if(CacheFile.LastWriteTimeUtc < DeleteTime || CacheFile.Name.EndsWith(".temp", StringComparison.OrdinalIgnoreCase)) + { + try + { + CacheFile.Attributes = FileAttributes.Normal; + CacheFile.Delete(); + } + catch + { + } + } + } + } + } } } diff --git a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Workspace.cs b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Workspace.cs index 818440ebf56e..23ebedf634e1 100644 --- a/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Workspace.cs +++ b/Engine/Source/Programs/UnrealGameSync/UnrealGameSync/Workspace.cs @@ -30,6 +30,7 @@ namespace UnrealGameSync ContentOnly = 0x400, UpdateFilter = 0x800, SyncAllProjects = 0x1000, + IncludeAllProjectsInSolution = 0x2000, } enum WorkspaceUpdateResult @@ -697,8 +698,8 @@ namespace UnrealGameSync Log.WriteLine("Executing post-sync steps..."); Dictionary PostSyncVariables = new Dictionary(Context.Variables); - PostSyncVariables.Add("Change", PendingChangeNumber.ToString()); - PostSyncVariables.Add("CodeChange", VersionChangeNumber.ToString()); + PostSyncVariables["Change"] = PendingChangeNumber.ToString(); + PostSyncVariables["CodeChange"] = VersionChangeNumber.ToString(); foreach (string PostSyncStep in PostSyncSteps.Select(x => x.Trim())) { @@ -814,18 +815,21 @@ namespace UnrealGameSync { Progress.Set("Generating project files...", 0.0f); - string ProjectFileArgument = ""; - if(SelectedLocalFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase) && (Context.Options & WorkspaceUpdateOptions.SyncAllProjects) == 0) + StringBuilder CommandLine = new StringBuilder(); + CommandLine.AppendFormat("/C \"\"{0}\"", Path.Combine(LocalRootPath, "GenerateProjectFiles.bat")); + if((Context.Options & WorkspaceUpdateOptions.SyncAllProjects) == 0 && (Context.Options & WorkspaceUpdateOptions.IncludeAllProjectsInSolution) == 0) { - ProjectFileArgument = String.Format("\"{0}\" ", SelectedLocalFileName); + if(SelectedLocalFileName.EndsWith(".uproject", StringComparison.InvariantCultureIgnoreCase)) + { + CommandLine.AppendFormat(" \"{0}\"", SelectedLocalFileName); + } } - - string CommandLine = String.Format("/C \"\"{0}\" {1}-progress\"", Path.Combine(LocalRootPath, "GenerateProjectFiles.bat"), ProjectFileArgument); + CommandLine.Append(" -progress\""); Log.WriteLine("Generating project files..."); Log.WriteLine("gpf> Running {0} {1}", CmdExe, CommandLine); - int GenerateProjectFilesResult = Utility.ExecuteProcess(CmdExe, null, CommandLine, null, new ProgressTextWriter(Progress, new PrefixedTextWriter("gpf> ", Log))); + int GenerateProjectFilesResult = Utility.ExecuteProcess(CmdExe, null, CommandLine.ToString(), null, new ProgressTextWriter(Progress, new PrefixedTextWriter("gpf> ", Log))); if(GenerateProjectFilesResult != 0) { StatusMessage = String.Format("Failed to generate project files (exit code {0}).", GenerateProjectFilesResult); @@ -905,79 +909,82 @@ namespace UnrealGameSync Log.WriteLine(Step.StatusText); - switch(Step.Type) + if(Step.IsValid()) { - case BuildStepType.Compile: - using(TelemetryStopwatch StepStopwatch = new TelemetryStopwatch("Compile:" + Step.Target, TelemetryProjectPath)) - { - string CommandLine = String.Format("{0} {1} {2} {3} -NoHotReloadFromIDE", Step.Target, Step.Platform, Step.Configuration, Utility.ExpandVariables(Step.Arguments, Context.Variables)); - if(!Context.Options.HasFlag(WorkspaceUpdateOptions.UseIncrementalBuilds) || bForceClean) + switch(Step.Type) + { + case BuildStepType.Compile: + using(TelemetryStopwatch StepStopwatch = new TelemetryStopwatch("Compile:" + Step.Target, TelemetryProjectPath)) { - Log.WriteLine("ubt> Running {0} {1} -clean", UnrealBuildToolPath, CommandLine); - Utility.ExecuteProcess(UnrealBuildToolPath, null, CommandLine + " -clean", null, new ProgressTextWriter(Progress, new PrefixedTextWriter("ubt> ", Log))); - } + string CommandLine = String.Format("{0} {1} {2} {3} -NoHotReloadFromIDE", Step.Target, Step.Platform, Step.Configuration, Utility.ExpandVariables(Step.Arguments ?? "", Context.Variables)); + if(!Context.Options.HasFlag(WorkspaceUpdateOptions.UseIncrementalBuilds) || bForceClean) + { + Log.WriteLine("ubt> Running {0} {1} -clean", UnrealBuildToolPath, CommandLine); + Utility.ExecuteProcess(UnrealBuildToolPath, null, CommandLine + " -clean", null, new ProgressTextWriter(Progress, new PrefixedTextWriter("ubt> ", Log))); + } - Log.WriteLine("ubt> Running {0} {1} -progress", UnrealBuildToolPath, CommandLine); + Log.WriteLine("ubt> Running {0} {1} -progress", UnrealBuildToolPath, CommandLine); - int ResultFromBuild = Utility.ExecuteProcess(UnrealBuildToolPath, null, CommandLine + " -progress", null, new ProgressTextWriter(Progress, new PrefixedTextWriter("ubt> ", Log))); - if(ResultFromBuild != 0) - { - StepStopwatch.Stop("Failed"); - StatusMessage = String.Format("Failed to compile {0}.", Step.Target); - return (HasModifiedSourceFiles() || Context.UserBuildStepObjects.Count > 0)? WorkspaceUpdateResult.FailedToCompile : WorkspaceUpdateResult.FailedToCompileWithCleanWorkspace; - } - - StepStopwatch.Stop("Success"); - } - break; - case BuildStepType.Cook: - using(TelemetryStopwatch StepStopwatch = new TelemetryStopwatch("Cook/Launch: " + Path.GetFileNameWithoutExtension(Step.FileName), TelemetryProjectPath)) - { - string LocalRunUAT = Path.Combine(LocalRootPath, "Engine", "Build", "BatchFiles", "RunUAT.bat"); - string Arguments = String.Format("/C \"\"{0}\" -profile=\"{1}\"\"", LocalRunUAT, Path.Combine(LocalRootPath, Step.FileName)); - Log.WriteLine("uat> Running {0} {1}", LocalRunUAT, Arguments); - - int ResultFromUAT = Utility.ExecuteProcess(CmdExe, null, Arguments, null, new ProgressTextWriter(Progress, new PrefixedTextWriter("uat> ", Log))); - if(ResultFromUAT != 0) - { - StepStopwatch.Stop("Failed"); - StatusMessage = String.Format("Cook failed. ({0})", ResultFromUAT); - return WorkspaceUpdateResult.FailedToCompile; - } - - StepStopwatch.Stop("Success"); - } - break; - case BuildStepType.Other: - using(TelemetryStopwatch StepStopwatch = new TelemetryStopwatch("Custom: " + Path.GetFileNameWithoutExtension(Step.FileName), TelemetryProjectPath)) - { - string ToolFileName = Path.Combine(LocalRootPath, Utility.ExpandVariables(Step.FileName, Context.Variables)); - string ToolWorkingDir = String.IsNullOrWhiteSpace(Step.WorkingDir) ? Path.GetDirectoryName(ToolFileName) : Utility.ExpandVariables(Step.WorkingDir, Context.Variables); - string ToolArguments = Utility.ExpandVariables(Step.Arguments, Context.Variables); - Log.WriteLine("tool> Running {0} {1}", ToolFileName, ToolArguments); - - if(Step.bUseLogWindow) - { - int ResultFromTool = Utility.ExecuteProcess(ToolFileName, ToolWorkingDir, ToolArguments, null, new ProgressTextWriter(Progress, new PrefixedTextWriter("tool> ", Log))); - if(ResultFromTool != 0) + int ResultFromBuild = Utility.ExecuteProcess(UnrealBuildToolPath, null, CommandLine + " -progress", null, new ProgressTextWriter(Progress, new PrefixedTextWriter("ubt> ", Log))); + if(ResultFromBuild != 0) { StepStopwatch.Stop("Failed"); - StatusMessage = String.Format("Tool terminated with exit code {0}.", ResultFromTool); + StatusMessage = String.Format("Failed to compile {0}.", Step.Target); + return (HasModifiedSourceFiles() || Context.UserBuildStepObjects.Count > 0)? WorkspaceUpdateResult.FailedToCompile : WorkspaceUpdateResult.FailedToCompileWithCleanWorkspace; + } + + StepStopwatch.Stop("Success"); + } + break; + case BuildStepType.Cook: + using(TelemetryStopwatch StepStopwatch = new TelemetryStopwatch("Cook/Launch: " + Path.GetFileNameWithoutExtension(Step.FileName), TelemetryProjectPath)) + { + string LocalRunUAT = Path.Combine(LocalRootPath, "Engine", "Build", "BatchFiles", "RunUAT.bat"); + string Arguments = String.Format("/C \"\"{0}\" -profile=\"{1}\"\"", LocalRunUAT, Path.Combine(LocalRootPath, Step.FileName)); + Log.WriteLine("uat> Running {0} {1}", LocalRunUAT, Arguments); + + int ResultFromUAT = Utility.ExecuteProcess(CmdExe, null, Arguments, null, new ProgressTextWriter(Progress, new PrefixedTextWriter("uat> ", Log))); + if(ResultFromUAT != 0) + { + StepStopwatch.Stop("Failed"); + StatusMessage = String.Format("Cook failed. ({0})", ResultFromUAT); return WorkspaceUpdateResult.FailedToCompile; } - } - else - { - ProcessStartInfo StartInfo = new ProcessStartInfo(ToolFileName, ToolArguments); - StartInfo.WorkingDirectory = ToolWorkingDir; - using(Process.Start(StartInfo)) - { - } - } - StepStopwatch.Stop("Success"); - } - break; + StepStopwatch.Stop("Success"); + } + break; + case BuildStepType.Other: + using(TelemetryStopwatch StepStopwatch = new TelemetryStopwatch("Custom: " + Path.GetFileNameWithoutExtension(Step.FileName), TelemetryProjectPath)) + { + string ToolFileName = Path.Combine(LocalRootPath, Utility.ExpandVariables(Step.FileName, Context.Variables)); + string ToolWorkingDir = String.IsNullOrWhiteSpace(Step.WorkingDir) ? Path.GetDirectoryName(ToolFileName) : Utility.ExpandVariables(Step.WorkingDir, Context.Variables); + string ToolArguments = Utility.ExpandVariables(Step.Arguments ?? "", Context.Variables); + Log.WriteLine("tool> Running {0} {1}", ToolFileName, ToolArguments); + + if(Step.bUseLogWindow) + { + int ResultFromTool = Utility.ExecuteProcess(ToolFileName, ToolWorkingDir, ToolArguments, null, new ProgressTextWriter(Progress, new PrefixedTextWriter("tool> ", Log))); + if(ResultFromTool != 0) + { + StepStopwatch.Stop("Failed"); + StatusMessage = String.Format("Tool terminated with exit code {0}.", ResultFromTool); + return WorkspaceUpdateResult.FailedToCompile; + } + } + else + { + ProcessStartInfo StartInfo = new ProcessStartInfo(ToolFileName, ToolArguments); + StartInfo.WorkingDirectory = ToolWorkingDir; + using(Process.Start(StartInfo)) + { + } + } + + StepStopwatch.Stop("Success"); + } + break; + } } Log.WriteLine(); diff --git a/Engine/Source/Programs/UnrealHeaderTool/Private/HeaderParser.cpp b/Engine/Source/Programs/UnrealHeaderTool/Private/HeaderParser.cpp index e1797b76c944..139df58b7765 100644 --- a/Engine/Source/Programs/UnrealHeaderTool/Private/HeaderParser.cpp +++ b/Engine/Source/Programs/UnrealHeaderTool/Private/HeaderParser.cpp @@ -1010,9 +1010,46 @@ namespace { if (UFunction* Function = Cast(Field)) { - if (FHeaderParser::FindField(Function, *InValue, false) == nullptr) + // multiple entry parsing in the same format as eg SetParam. + TArray RawGroupings; + InValue.ParseIntoArray(RawGroupings, TEXT(","), false); + + UProperty* FirstInput = nullptr; + for (const FString& RawGroup : RawGroupings) { - UE_LOG_ERROR_UHT(TEXT("Function does not have a parameter named '%s'"), *InValue); + TArray IndividualEntries; + RawGroup.ParseIntoArray(IndividualEntries, TEXT("|")); + + for (const FString& Entry : IndividualEntries) + { + if (Entry.IsEmpty()) + { + continue; + } + + UField* FoundField = FHeaderParser::FindField(Function, *Entry, false); + if (!FoundField) + { + UE_LOG_ERROR_UHT(TEXT("Function does not have a parameter named '%s'"), *Entry); + } + else if (UProperty* Prop = Cast(FoundField)) + { + if (!Prop->HasAnyPropertyFlags(CPF_ReturnParm) && + + (!Prop->HasAnyPropertyFlags(CPF_OutParm) || + Prop->HasAnyPropertyFlags(CPF_ReferenceParm))) + { + if (!FirstInput) + { + FirstInput = Prop; + } + else + { + UE_LOG_ERROR_UHT(TEXT("Function already specified an ExpandEnumAsExec input (%s), but '%s' is also an input parameter. Only one is permitted."), *FirstInput->GetName(), *Entry); + } + } + } + } } } } diff --git a/Engine/Source/Runtime/AIModule/Classes/AIController.h b/Engine/Source/Runtime/AIModule/Classes/AIController.h index 21d39452c9ee..9c66b7b9642d 100644 --- a/Engine/Source/Runtime/AIModule/Classes/AIController.h +++ b/Engine/Source/Runtime/AIModule/Classes/AIController.h @@ -150,14 +150,6 @@ public: AAIController(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()); - /** Event called when PossessedPawn is possessed by this controller. */ - UFUNCTION(BlueprintImplementableEvent) - void OnPossess(APawn* PossessedPawn); - - /** Gets triggered after given pawn has been unpossesed */ - UFUNCTION(BlueprintImplementableEvent) - void OnUnpossess(APawn* UnpossessedPawn); - virtual void SetPawn(APawn* InPawn) override; /** Makes AI go toward specified Goal actor (destination will be continuously updated), aborts any active path following @@ -334,8 +326,11 @@ public: //~ End AActor Interface //~ Begin AController Interface - virtual void Possess(APawn* InPawn) override; - virtual void UnPossess() override; +protected: + virtual void OnPossess(APawn* InPawn) override; + virtual void OnUnPossess() override; + +public: virtual bool ShouldPostponePathUpdates() const override; virtual void DisplayDebug(UCanvas* Canvas, const FDebugDisplayInfo& DebugDisplay, float& YL, float& YPos) override; diff --git a/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/EnvQueryInstanceBlueprintWrapper.h b/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/EnvQueryInstanceBlueprintWrapper.h index 400d758e5560..ad32ee69cc75 100644 --- a/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/EnvQueryInstanceBlueprintWrapper.h +++ b/Engine/Source/Runtime/AIModule/Classes/EnvironmentQuery/EnvQueryInstanceBlueprintWrapper.h @@ -65,12 +65,12 @@ public: /** DEPRECATED! Use GetQueryResultsAsActors instead. Returns an array filled with resulting actors. Note that it makes sense only if ItemType is a EnvQueryItemType_ActorBase-derived type. */ UFUNCTION(BlueprintPure, Category = "AI|EQS", Meta=(DeprecatedFunction, DeprecationMessage="Use GetQueryResultsAsActors instead, which is more efficient itself and protects against very bad perf issues in some blueprints.")) - TArray GetResultsAsActors(); + TArray GetResultsAsActors() const; /** DEPRECATED! Use GetQueryResultsAsLocations instead. Returns an array of locations generated by the query. If the query generated Actors the the array is filled with their locations. */ UFUNCTION(BlueprintPure, Category = "AI|EQS", Meta=(DeprecatedFunction, DeprecationMessage="GetQueryResultsAsLocations instead, which is more efficient itself and protects against very bad perf issues in some blueprints.")) - TArray GetResultsAsLocations(); + TArray GetResultsAsLocations() const; UFUNCTION(BlueprintCallable, Category = "AI|EQS") void SetNamedParam(FName ParamName, float Value); diff --git a/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionComponent.h b/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionComponent.h index db7481b53cdd..0e41a8f6878b 100644 --- a/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionComponent.h +++ b/Engine/Source/Runtime/AIModule/Classes/Perception/AIPerceptionComponent.h @@ -165,7 +165,10 @@ class AIMODULE_API UAIPerceptionComponent : public UActorComponent static const int32 InitialStimuliToProcessArraySize; - typedef TMap TActorPerceptionContainer; + /** The uint64 is the address of the Actor. As we are using the address raw as a key to the TMap there's potential the actor it + * points to will be GCed in future (we aren't tagging PerceptualData UPROPERTY intentionally for optimization purposes). + */ + typedef TMap TActorPerceptionContainer; typedef TActorPerceptionContainer FActorPerceptionContainer; protected: @@ -188,6 +191,11 @@ protected: private: FPerceptionListenerID PerceptionListenerId; + /**@TODO there is a rare but possible issue here. Actors could be set to Endplay() and GCed between calls to RemoveDeadData, + * infact EndPlay and GC can occur in same frame. Either we need to take the hit and make PerceptualData a UPROPERTY or we need to come up + * with a different indexing scheme. Currently we could add an new Actor (at the same address as a GCed one), if we then add a new + * PerceptualData record we could end up merging the new results with the GCed Actor's record instead of making a fresh one. + */ FActorPerceptionContainer PerceptualData; protected: @@ -231,7 +239,7 @@ public: FORCEINLINE FPerceptionListenerID GetListenerId() const { return PerceptionListenerId; } FVector GetActorLocation(const AActor& Actor) const; - FORCEINLINE const FActorPerceptionInfo* GetActorInfo(const AActor& Actor) const { return PerceptualData.Find(&Actor); } + FORCEINLINE const FActorPerceptionInfo* GetActorInfo(const AActor& Actor) const { return PerceptualData.Find(reinterpret_cast(&Actor)); } FORCEINLINE FActorPerceptionContainer::TIterator GetPerceptualDataIterator() { return FActorPerceptionContainer::TIterator(PerceptualData); } FORCEINLINE FActorPerceptionContainer::TConstIterator GetPerceptualDataConstIterator() const { return FActorPerceptionContainer::TConstIterator(PerceptualData); } diff --git a/Engine/Source/Runtime/AIModule/Private/AIController.cpp b/Engine/Source/Runtime/AIModule/Private/AIController.cpp index f8b7cd3564b1..9d445a242120 100644 --- a/Engine/Source/Runtime/AIModule/Private/AIController.cpp +++ b/Engine/Source/Runtime/AIModule/Private/AIController.cpp @@ -454,7 +454,7 @@ void AAIController::UpdateControlRotation(float DeltaTime, bool bUpdatePawn) } -void AAIController::Possess(APawn* InPawn) +void AAIController::OnPossess(APawn* InPawn) { // don't even try possessing pending-kill pawns if (InPawn != nullptr && InPawn->IsPendingKill()) @@ -462,7 +462,7 @@ void AAIController::Possess(APawn* InPawn) return; } - Super::Possess(InPawn); + Super::OnPossess(InPawn); if (GetPawn() == nullptr || InPawn == nullptr) { @@ -501,15 +501,13 @@ void AAIController::Possess(APawn* InPawn) REDIRECT_OBJECT_TO_VLOG(CachedGameplayTasksComponent, this); } - - OnPossess(InPawn); } -void AAIController::UnPossess() +void AAIController::OnUnPossess() { APawn* CurrentPawn = GetPawn(); - Super::UnPossess(); + Super::OnUnPossess(); if (PathFollowingComponent) { @@ -529,8 +527,6 @@ void AAIController::UnPossess() CachedGameplayTasksComponent->OnClaimedResourcesChange.RemoveDynamic(this, &AAIController::OnGameplayTaskResourcesClaimed); CachedGameplayTasksComponent = nullptr; } - - OnUnpossess(CurrentPawn); } void AAIController::SetPawn(APawn* InPawn) diff --git a/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/EnvQueryInstanceBlueprintWrapper.cpp b/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/EnvQueryInstanceBlueprintWrapper.cpp index e53a01bfe592..9122f80ab00f 100644 --- a/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/EnvQueryInstanceBlueprintWrapper.cpp +++ b/Engine/Source/Runtime/AIModule/Private/EnvironmentQuery/EnvQueryInstanceBlueprintWrapper.cpp @@ -60,7 +60,7 @@ bool UEnvQueryInstanceBlueprintWrapper::GetQueryResultsAsActors(TArray& return false; } -TArray UEnvQueryInstanceBlueprintWrapper::GetResultsAsActors() +TArray UEnvQueryInstanceBlueprintWrapper::GetResultsAsActors() const { TArray Results; @@ -103,7 +103,7 @@ bool UEnvQueryInstanceBlueprintWrapper::GetQueryResultsAsLocations(TArray UEnvQueryInstanceBlueprintWrapper::GetResultsAsLocations() +TArray UEnvQueryInstanceBlueprintWrapper::GetResultsAsLocations() const { TArray Results; diff --git a/Engine/Source/Runtime/AIModule/Private/Navigation/PathFollowingComponent.cpp b/Engine/Source/Runtime/AIModule/Private/Navigation/PathFollowingComponent.cpp index 424f183617ac..bfd138dc06ad 100644 --- a/Engine/Source/Runtime/AIModule/Private/Navigation/PathFollowingComponent.cpp +++ b/Engine/Source/Runtime/AIModule/Private/Navigation/PathFollowingComponent.cpp @@ -612,7 +612,8 @@ void UPathFollowingComponent::Initialize() void UPathFollowingComponent::Cleanup() { - // empty in base class + SetMovementComponent(nullptr); + Reset(); } void UPathFollowingComponent::UpdateCachedComponents() diff --git a/Engine/Source/Runtime/AIModule/Private/Perception/AIPerceptionComponent.cpp b/Engine/Source/Runtime/AIModule/Private/Perception/AIPerceptionComponent.cpp index 5cf14ac535ef..4564804f5cec 100644 --- a/Engine/Source/Runtime/AIModule/Private/Perception/AIPerceptionComponent.cpp +++ b/Engine/Source/Runtime/AIModule/Private/Perception/AIPerceptionComponent.cpp @@ -220,6 +220,8 @@ void UAIPerceptionComponent::CleanUp() { if (bCleanedUp == false) { + ForgetAll(); + UAIPerceptionSystem* AIPerceptionSys = UAIPerceptionSystem::GetCurrent(GetWorld()); if (AIPerceptionSys != nullptr) { @@ -270,11 +272,13 @@ void UAIPerceptionComponent::GetHostileActors(TArray& OutActors) const OutActors.Reserve(PerceptualData.Num()); for (FActorPerceptionContainer::TConstIterator DataIt = GetPerceptualDataConstIterator(); DataIt; ++DataIt) { - if (DataIt->Value.bIsHostile && DataIt->Value.HasAnyKnownStimulus()) + const FActorPerceptionInfo& ActorPerceptionInfo = DataIt->Value; + + if (ActorPerceptionInfo.bIsHostile && ActorPerceptionInfo.HasAnyKnownStimulus()) { - if (DataIt->Value.Target.IsValid()) + if (ActorPerceptionInfo.Target.IsValid()) { - OutActors.Add(DataIt->Value.Target.Get()); + OutActors.Add(ActorPerceptionInfo.Target.Get()); } else { @@ -408,7 +412,9 @@ void UAIPerceptionComponent::ProcessStimuli() for (FStimulusToProcess& SourcedStimulus : StimuliToProcess) { - FActorPerceptionInfo* PerceptualInfo = PerceptualData.Find(SourcedStimulus.Source); + const uint64 SourceAddr = reinterpret_cast(SourcedStimulus.Source); + + FActorPerceptionInfo* PerceptualInfo = PerceptualData.Find(SourceAddr); if (PerceptualInfo == NULL) { @@ -421,7 +427,7 @@ void UAIPerceptionComponent::ProcessStimuli() else { // create an entry - PerceptualInfo = &PerceptualData.Add(SourcedStimulus.Source, FActorPerceptionInfo(SourcedStimulus.Source)); + PerceptualInfo = &PerceptualData.Add(SourceAddr, FActorPerceptionInfo(SourcedStimulus.Source)); // tell it what's our dominant sense PerceptualInfo->DominantSense = DominantSenseID; @@ -512,7 +518,7 @@ bool UAIPerceptionComponent::AgeStimuli(const float ConstPerceptionAgingRate) for (FAIStimulus& Stimulus : ActorPerceptionInfo.LastSensedStimuli) { // Age the stimulus. If it is active but has just expired, mark it as such - if (Stimulus.AgeStimulus(ConstPerceptionAgingRate) == false + if (Stimulus.AgeStimulus(ConstPerceptionAgingRate) == false && (Stimulus.IsActive() || Stimulus.WantsToNotifyOnlyOnPerceptionChange()) && Stimulus.IsExpired() == false) { @@ -540,7 +546,7 @@ void UAIPerceptionComponent::ForgetActor(AActor* ActorToForget) AIPerceptionSys->OnListenerForgetsActor(*this, *ActorToForget); } - PerceptualData.Remove(ActorToForget); + const int32 NumRemoved = PerceptualData.Remove(reinterpret_cast(ActorToForget)); } } diff --git a/Engine/Source/Runtime/Advertising/IOS/IOSAdvertising/Private/IOSAdvertising.cpp b/Engine/Source/Runtime/Advertising/IOS/IOSAdvertising/Private/IOSAdvertising.cpp index d472e07a465e..edca896d9659 100644 --- a/Engine/Source/Runtime/Advertising/IOS/IOSAdvertising/Private/IOSAdvertising.cpp +++ b/Engine/Source/Runtime/Advertising/IOS/IOSAdvertising/Private/IOSAdvertising.cpp @@ -6,13 +6,13 @@ #include "IOS/IOSAsyncTask.h" #include "IOS/IOSAppDelegate.h" -#import - DEFINE_LOG_CATEGORY_STATIC( LogAdvertising, Display, All ); IMPLEMENT_MODULE( FIOSAdvertisingProvider, IOSAdvertising ); #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_10_0 +#import + @interface IOSAdvertising : UIResponder /** iAd banner view, if open */ @property(retain) ADBannerView* BannerView; diff --git a/Engine/Source/Runtime/Android/AndroidRuntimeSettings/Classes/AndroidRuntimeSettings.h b/Engine/Source/Runtime/Android/AndroidRuntimeSettings/Classes/AndroidRuntimeSettings.h index 8a5ff53366c6..e64f149b5f0e 100644 --- a/Engine/Source/Runtime/Android/AndroidRuntimeSettings/Classes/AndroidRuntimeSettings.h +++ b/Engine/Source/Runtime/Android/AndroidRuntimeSettings/Classes/AndroidRuntimeSettings.h @@ -245,6 +245,10 @@ public: UPROPERTY(GlobalConfig, EditAnywhere, Category = "APK Packaging", Meta = (DisplayName = "Maximum supported aspect ratio.")) float MaxAspectRatio; + // Enables use of the display cutout area on Android 9+ + UPROPERTY(GlobalConfig, EditAnywhere, Category = "APK Packaging", Meta = (DisplayName = "Use display cutout region?")) + bool bUseDisplayCutout; + // Level of verbosity to use during packaging with Ant UPROPERTY(GlobalConfig, EditAnywhere, Category = "APK Packaging") TEnumAsByte AntVerbosity; diff --git a/Engine/Source/Runtime/AnimGraphRuntime/Public/AnimNodes/AnimNode_ApplyAdditive.h b/Engine/Source/Runtime/AnimGraphRuntime/Public/AnimNodes/AnimNode_ApplyAdditive.h index 092bfd90ae0b..1bb0299f00e0 100644 --- a/Engine/Source/Runtime/AnimGraphRuntime/Public/AnimNodes/AnimNode_ApplyAdditive.h +++ b/Engine/Source/Runtime/AnimGraphRuntime/Public/AnimNodes/AnimNode_ApplyAdditive.h @@ -34,7 +34,7 @@ struct ANIMGRAPHRUNTIME_API FAnimNode_ApplyAdditive : public FAnimNode_Base UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Performance, meta=(DisplayName="LOD Threshold")) int32 LODThreshold; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Alpha, meta = (DisplayName = "Blend Settings")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Alpha, meta = (DisplayName = "Blend Settings", DisplayAfter = "bAlphaBoolEnabled")) FInputAlphaBoolBlend AlphaBoolBlend; UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Alpha, meta = (PinShownByDefault)) @@ -45,10 +45,10 @@ struct ANIMGRAPHRUNTIME_API FAnimNode_ApplyAdditive : public FAnimNode_Base float ActualAlpha; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Alpha) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Alpha, meta = (DisplayAfter = "AlphaScaleBias")) EAnimAlphaInputType AlphaInputType; - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Alpha, meta = (PinShownByDefault, DisplayName = "bEnabled")) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Alpha, meta = (PinShownByDefault, DisplayName = "bEnabled", DisplayAfter = "AlphaInputType")) bool bAlphaBoolEnabled; public: diff --git a/Engine/Source/Runtime/AnimGraphRuntime/Public/AnimNodes/AnimNode_BlendBoneByChannel.h b/Engine/Source/Runtime/AnimGraphRuntime/Public/AnimNodes/AnimNode_BlendBoneByChannel.h index 2f7af9659197..d9808d431547 100644 --- a/Engine/Source/Runtime/AnimGraphRuntime/Public/AnimNodes/AnimNode_BlendBoneByChannel.h +++ b/Engine/Source/Runtime/AnimGraphRuntime/Public/AnimNodes/AnimNode_BlendBoneByChannel.h @@ -55,7 +55,7 @@ public: UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Links) FPoseLink B; - UPROPERTY(EditAnywhere, Category = Blend) + UPROPERTY(EditAnywhere, Category = Blend, meta = (DisplayAfter = "AlphaScaleBias")) TArray BoneDefinitions; private: @@ -64,7 +64,7 @@ private: TArray ValidBoneEntries; public: - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Settings, meta = (PinShownByDefault)) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Settings, meta = (PinShownByDefault, DisplayAfter = "B")) float Alpha; private: diff --git a/Engine/Source/Runtime/AnimGraphRuntime/Public/BoneControllers/AnimNode_AnimDynamics.h b/Engine/Source/Runtime/AnimGraphRuntime/Public/BoneControllers/AnimNode_AnimDynamics.h index d0c556ad1a00..eab96281cf97 100644 --- a/Engine/Source/Runtime/AnimGraphRuntime/Public/BoneControllers/AnimNode_AnimDynamics.h +++ b/Engine/Source/Runtime/AnimGraphRuntime/Public/BoneControllers/AnimNode_AnimDynamics.h @@ -140,7 +140,7 @@ struct FAnimPhysConstraintSetup AnimPhysTwistAxis TwistAxis; /** Axis on body1 to match to the angular target direction. */ - UPROPERTY(EditAnywhere, Category = Angular) + UPROPERTY(EditAnywhere, Category = Angular, meta=(DisplayAfter=AngularLimitsMax)) AnimPhysTwistAxis AngularTargetAxis; /** Angle to use when constraining using a cone */ @@ -232,11 +232,11 @@ struct ANIMGRAPHRUNTIME_API FAnimNode_AnimDynamics : public FAnimNode_SkeletalCo FAnimNode_AnimDynamics(); /** Overridden linear damping value */ - UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadWrite, Category = Setup, meta = (PinHiddenByDefault)) + UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadWrite, Category = Setup, meta = (PinHiddenByDefault, DisplayAfter="bOverrideLinearDamping")) float LinearDampingOverride; /** Overridden angular damping value */ - UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadWrite, Category = Setup, meta = (PinHiddenByDefault)) + UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadWrite, Category = Setup, meta = (PinHiddenByDefault, DisplayAfter="bOverrideAngularDamping")) float AngularDampingOverride; // Previous component & actor transforms, used to account for teleports @@ -244,11 +244,11 @@ struct ANIMGRAPHRUNTIME_API FAnimNode_AnimDynamics : public FAnimNode_SkeletalCo FTransform PreviousActorWorldSpaceTM; /** When in BoneRelative sim space, the simulation will use this bone as the origin */ - UPROPERTY(EditAnywhere, Category = Setup) + UPROPERTY(EditAnywhere, Category = Setup, meta=(DisplayAfter="SimulationSpace")) FBoneReference RelativeSpaceBone; /** The bone to attach the physics body to, if bChain is true this is the top of the chain */ - UPROPERTY(EditAnywhere, Category = Setup) + UPROPERTY(EditAnywhere, Category = Setup, meta=(DisplayAfter="bChain")) FBoneReference BoundBone; /** If bChain is true this is the bottom of the chain, otherwise ignored */ @@ -272,7 +272,7 @@ struct ANIMGRAPHRUNTIME_API FAnimNode_AnimDynamics : public FAnimNode_SkeletalCo FVector GravityOverride; /** Spring constant to use when calculating linear springs, higher values mean a stronger spring.*/ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Setup, meta = (PinHiddenByDefault)) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Setup, meta = (PinHiddenByDefault, DisplayAfter="bAngularSpring")) float LinearSpringConstant; /** Spring constant to use when calculating angular springs, higher values mean a stronger spring */ @@ -280,7 +280,7 @@ struct ANIMGRAPHRUNTIME_API FAnimNode_AnimDynamics : public FAnimNode_SkeletalCo float AngularSpringConstant; /** Scale to apply to calculated wind velocities in the solver */ - UPROPERTY(EditAnywhere, Category = Wind) + UPROPERTY(EditAnywhere, Category = Wind, meta=(DisplayAfter="bEnableWind")) float WindScale; /** When using non-world-space sim, this controls how much of the components world-space acceleration is passed on to the local-space simulation. */ @@ -300,11 +300,11 @@ struct ANIMGRAPHRUNTIME_API FAnimNode_AnimDynamics : public FAnimNode_SkeletalCo * in check. When using single-body systems sometimes angular forces will look like they are "catching-up" with * the mesh, if that's the case override this and push it towards 1.0f until it settles correctly */ - UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadWrite, Category = Setup, meta = (PinHiddenByDefault)) + UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadWrite, Category = Setup, meta = (PinHiddenByDefault, DisplayAfter="bOverrideAngularBias")) float AngularBiasOverride; /** Number of update passes on the linear and angular limits before we solve the position of the bodies recommended to be four times the value of NumSolverIterationsPostUpdate */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup) + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup, meta=(DisplayAfter="bDoEval")) int32 NumSolverIterationsPreUpdate; /** Number of update passes on the linear and angular limits after we solve the position of the bodies, recommended to be around a quarter of NumSolverIterationsPreUpdate */ @@ -316,11 +316,11 @@ struct ANIMGRAPHRUNTIME_API FAnimNode_AnimDynamics : public FAnimNode_SkeletalCo FAnimPhysConstraintSetup ConstraintSetup; /** List of available spherical limits for this node */ - UPROPERTY(EditAnywhere, Category = SphericalLimit) + UPROPERTY(EditAnywhere, Category = SphericalLimit, meta=(DisplayAfter="bUseSphericalLimits")) TArray SphericalLimits; /** Radius to use if CollisionType is set to CustomSphere */ - UPROPERTY(EditAnywhere, Category = Collision, meta = (UIMin = "1", ClampMin = "1")) + UPROPERTY(EditAnywhere, Category = Collision, meta = (UIMin = "1", ClampMin = "1", DisplayAfter="CollisionType")) float SphereCollisionRadius; /** An external force to apply to all bodies in the simulation when ticked, specified in world space */ @@ -328,7 +328,7 @@ struct ANIMGRAPHRUNTIME_API FAnimNode_AnimDynamics : public FAnimNode_SkeletalCo FVector ExternalForce; /** List of available planar limits for this node */ - UPROPERTY(EditAnywhere, Category=PlanarLimit) + UPROPERTY(EditAnywhere, Category=PlanarLimit, meta=(DisplayAfter="bUsePlanarLimit")) TArray PlanarLimits; /** Resolution method for planar limits */ @@ -336,7 +336,7 @@ struct ANIMGRAPHRUNTIME_API FAnimNode_AnimDynamics : public FAnimNode_SkeletalCo AnimPhysCollisionType CollisionType; /** The space used to run the simulation */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Setup, meta = (PinHiddenByDefault)) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Setup, meta = (PinHiddenByDefault, DisplayPriority=0)) AnimPhysSimSpaceType SimulationSpace; // Cached sim space that we last used @@ -355,7 +355,7 @@ struct ANIMGRAPHRUNTIME_API FAnimNode_AnimDynamics : public FAnimNode_SkeletalCo uint8 bUsePlanarLimit:1; /** If true we will perform physics update, otherwise skip - allows visualisation of the initial state of the bodies */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup) + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup, meta=(DisplayAfter="AngularBiasOverride")) uint8 bDoUpdate:1; /** If true we will perform bone transform evaluation, otherwise skip - allows visualisation of the initial anim state compared to the physics sim */ @@ -363,7 +363,7 @@ struct ANIMGRAPHRUNTIME_API FAnimNode_AnimDynamics : public FAnimNode_SkeletalCo uint8 bDoEval:1; /** If true, the override value will be used for linear damping */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup) + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup, meta=(DisplayAfter="AngularSpringConstraint")) uint8 bOverrideLinearDamping:1; /** If true, the override value will be used for the angular bias for bodies in this node. @@ -371,11 +371,11 @@ struct ANIMGRAPHRUNTIME_API FAnimNode_AnimDynamics : public FAnimNode_SkeletalCo * in check. When using single-body systems sometimes angular forces will look like they are "catching-up" with * the mesh, if that's the case override this and push it towards 1.0f until it settles correctly */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup) + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup, meta=(DisplayAfter="AngularDampingOverride")) uint8 bOverrideAngularBias:1; /** If true, the override value will be used for angular damping */ - UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup) + UPROPERTY(EditAnywhere, AdvancedDisplay, Category = Setup, meta=(DisplayAfter="LinearDampingOverride")) uint8 bOverrideAngularDamping:1; /** Whether or not wind is enabled for the bodies in this simulation */ @@ -389,15 +389,15 @@ struct ANIMGRAPHRUNTIME_API FAnimNode_AnimDynamics : public FAnimNode_SkeletalCo uint8 bUseGravityOverride:1; /** If true the body will attempt to spring back to its initial position */ - UPROPERTY(EditAnywhere, Category = Setup) + UPROPERTY(EditAnywhere, Category = Setup, meta=(DisplayAfter="GravityOverride")) uint8 bLinearSpring:1; /** If true the body will attempt to align itself with the specified angular target */ - UPROPERTY(EditAnywhere, Category = Setup) + UPROPERTY(EditAnywhere, Category = Setup, meta=(DisplayAfter="bLinearSpring")) uint8 bAngularSpring:1; /** Set to true to use the solver to simulate a connected chain */ - UPROPERTY(EditAnywhere, Category = Setup) + UPROPERTY(EditAnywhere, Category = Setup, meta=(DisplayAfter="RelativeSpaceBone")) uint8 bChain:1; // FAnimNode_SkeletalControlBase interface diff --git a/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalUAV.cpp b/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalUAV.cpp index 3ca34e8713e1..34e6ecde3bf7 100644 --- a/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalUAV.cpp +++ b/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalUAV.cpp @@ -851,32 +851,27 @@ void FMetalGPUFence::WriteInternal(mtlpp::CommandBuffer& CmdBuffer) void FMetalRHICommandContext::RHIEnqueueStagedRead(FStagingBufferRHIParamRef StagingBuffer, FGPUFenceRHIParamRef InFence, uint32 Offset, uint32 NumBytes) { @autoreleasepool { - check(StagingBuffer); - - FMetalStagingBuffer* StageBuffer = ResourceCast(StagingBuffer); - FMetalVertexBuffer* VertexBuffer = ResourceCast(StageBuffer->GetBackingBuffer()); - switch (VertexBuffer->Buffer.GetStorageMode()) + + FMetalStagingBuffer* MetalStagingBuffer = ResourceCast(StagingBuffer); + FMetalVertexBuffer* BackingBuffer = ResourceCast(MetalStagingBuffer->GetBackingBuffer()); + FMetalBuffer& ReadbackBuffer = MetalStagingBuffer->ReadbackStagingBuffer; + + // Need a shadow buffer for this read. If it hasn't been allocated in our FStagingBuffer or if + // it's not big enough to hold our readback we need to allocate. + if(!ReadbackBuffer || ReadbackBuffer.GetLength() < NumBytes) { - #if PLATFORM_MAC - case mtlpp::StorageMode::Managed: + if(ReadbackBuffer) { - GetMetalDeviceContext().SynchroniseResource(VertexBuffer->Buffer); - break; - } - #endif - case mtlpp::StorageMode::Private: - { - VertexBuffer->Alloc(VertexBuffer->Buffer.GetLength(), RLM_ReadOnly); - GetMetalDeviceContext().CopyFromBufferToBuffer(VertexBuffer->Buffer, Offset, VertexBuffer->CPUBuffer, Offset, NumBytes); - break; - } - default: - { - break; + SafeReleaseMetalBuffer(ReadbackBuffer); } + FMetalPooledBufferArgs ArgsCPU(GetMetalDeviceContext().GetDevice(), NumBytes, mtlpp::StorageMode::Shared); + ReadbackBuffer = GetMetalDeviceContext().CreatePooledBuffer(ArgsCPU); } - + + // Inline copy from the actual buffer to the shadow + GetMetalDeviceContext().CopyFromBufferToBuffer(BackingBuffer->Buffer, Offset, ReadbackBuffer, 0, NumBytes); + if (InFence) { FMetalGPUFence* Fence = ResourceCast(InFence); diff --git a/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalVertexBuffer.cpp b/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalVertexBuffer.cpp index 18c5afb52278..7dd070423c23 100644 --- a/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalVertexBuffer.cpp +++ b/Engine/Source/Runtime/Apple/MetalRHI/Private/MetalVertexBuffer.cpp @@ -774,36 +774,25 @@ FStagingBufferRHIRef FMetalDynamicRHI::RHICreateStagingBuffer(FVertexBufferRHIPa return new FMetalStagingBuffer(VertexBuffer); } -// Call this to lock the vertex-buffer for the given mode. -// A read-only lock must have the same buffer used to call EnqueueStagedRead, and that fence must have passed or the behaviour is undefined. -// A write-only lock must not have had the EnqueueStagedRead function called and must supply the buffer. -void *FMetalStagingBuffer::Lock(uint32 Offset, uint32 NumBytes) +FMetalStagingBuffer::~FMetalStagingBuffer() { - check(BackingBuffer); - FMetalVertexBuffer* VertexBuffer = ResourceCast(BackingBuffer.GetReference()); - uint8* BytePtr = nullptr; - if (VertexBuffer->CPUBuffer) + if(ReadbackStagingBuffer) { - BytePtr = (uint8*)VertexBuffer->CPUBuffer.GetContents(); + SafeReleaseMetalBuffer(ReadbackStagingBuffer); } - else - { - check(VertexBuffer->Buffer.GetStorageMode() != mtlpp::StorageMode::Private); - BytePtr = (uint8*)VertexBuffer->Buffer.GetContents(); - } - BytePtr += Offset; - return BytePtr; } -// Releases the mapped memory for a lock. +// Returns the pointer to read the buffer. There is no locking; the buffer is always shared. +// If this was not fenced correctly it will not have the expected data. +void *FMetalStagingBuffer::Lock(uint32 Offset, uint32 NumBytes) +{ + check(ReadbackStagingBuffer); + + uint8* BackingPtr = (uint8*) ReadbackStagingBuffer.GetContents(); + return BackingPtr+Offset; +} + void FMetalStagingBuffer::Unlock() { - check(BackingBuffer); - FMetalVertexBuffer* VertexBuffer = ResourceCast(BackingBuffer.GetReference()); - if (VertexBuffer->CPUBuffer && (VertexBuffer->UsePrivateMemory())) - { - LLM_SCOPE(ELLMTag::VertexBuffer); - SafeReleaseMetalBuffer(VertexBuffer->CPUBuffer); - VertexBuffer->CPUBuffer = nil; - } + // does nothing in metal. } diff --git a/Engine/Source/Runtime/Apple/MetalRHI/Public/MetalResources.h b/Engine/Source/Runtime/Apple/MetalRHI/Public/MetalResources.h index f88e96bdcf50..d8d9f0d4922b 100644 --- a/Engine/Source/Runtime/Apple/MetalRHI/Public/MetalResources.h +++ b/Engine/Source/Runtime/Apple/MetalRHI/Public/MetalResources.h @@ -1048,10 +1048,14 @@ public: : FRHIStagingBuffer(InBuffer) { } + + ~FMetalStagingBuffer(); void *Lock(uint32 Offset, uint32 NumBytes); void Unlock(); + + FMetalBuffer ReadbackStagingBuffer; }; class FMetalShaderLibrary final : public FRHIShaderLibrary diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Android/AndroidInputInterface.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Android/AndroidInputInterface.cpp index 1fe0a09a7793..4a3ab4433f57 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Android/AndroidInputInterface.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Android/AndroidInputInterface.cpp @@ -88,6 +88,16 @@ FAndroidInputInterface::FAndroidInputInterface(const TSharedRef< FGenericApplica ButtonMapping[16] = AndroidKeyNames::Android_Back; // Technically just an alias for SpecialLeft ButtonMapping[17] = AndroidKeyNames::Android_Menu; // Technically just an alias for SpecialRight + // Virtual buttons + ButtonMapping[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 0] = FGamepadKeyNames::LeftStickLeft; + ButtonMapping[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 1] = FGamepadKeyNames::LeftStickRight; + ButtonMapping[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 2] = FGamepadKeyNames::LeftStickUp; + ButtonMapping[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 3] = FGamepadKeyNames::LeftStickDown; + ButtonMapping[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 4] = FGamepadKeyNames::RightStickLeft; + ButtonMapping[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 5] = FGamepadKeyNames::RightStickRight; + ButtonMapping[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 6] = FGamepadKeyNames::RightStickUp; + ButtonMapping[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 7] = FGamepadKeyNames::RightStickDown; + InitialButtonRepeatDelay = 0.2f; ButtonRepeatDelay = 0.1f; @@ -955,21 +965,29 @@ void FAndroidInputInterface::SendControllerEvents() if (NewControllerState.LXAnalog != OldControllerState.LXAnalog || FMath::Abs(NewControllerState.LXAnalog) >= RepeatDeadzone) { MessageHandler->OnControllerAnalog(FGamepadKeyNames::LeftAnalogX, NewControllerState.DeviceId, NewControllerState.LXAnalog); + NewControllerState.ButtonStates[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 1] = NewControllerState.LXAnalog >= RepeatDeadzone; + NewControllerState.ButtonStates[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 0] = NewControllerState.LXAnalog <= -RepeatDeadzone; } if (NewControllerState.LYAnalog != OldControllerState.LYAnalog || FMath::Abs(NewControllerState.LYAnalog) >= RepeatDeadzone) { //LOGD(" Sending updated LeftAnalogY value of %f", NewControllerState.LYAnalog); MessageHandler->OnControllerAnalog(FGamepadKeyNames::LeftAnalogY, NewControllerState.DeviceId, NewControllerState.LYAnalog); + NewControllerState.ButtonStates[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 2] = NewControllerState.LYAnalog >= RepeatDeadzone; + NewControllerState.ButtonStates[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 3] = NewControllerState.LYAnalog <= -RepeatDeadzone; } if (NewControllerState.RXAnalog != OldControllerState.RXAnalog || FMath::Abs(NewControllerState.RXAnalog) >= RepeatDeadzone) { //LOGD(" Sending updated RightAnalogX value of %f", NewControllerState.RXAnalog); MessageHandler->OnControllerAnalog(FGamepadKeyNames::RightAnalogX, NewControllerState.DeviceId, NewControllerState.RXAnalog); + NewControllerState.ButtonStates[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 5] = NewControllerState.RXAnalog >= RepeatDeadzone; + NewControllerState.ButtonStates[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 4] = NewControllerState.RXAnalog <= -RepeatDeadzone; } if (NewControllerState.RYAnalog != OldControllerState.RYAnalog || FMath::Abs(NewControllerState.RYAnalog) >= RepeatDeadzone) { //LOGD(" Sending updated RightAnalogY value of %f", NewControllerState.RYAnalog); MessageHandler->OnControllerAnalog(FGamepadKeyNames::RightAnalogY, NewControllerState.DeviceId, NewControllerState.RYAnalog); + NewControllerState.ButtonStates[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 6] = NewControllerState.RYAnalog >= RepeatDeadzone; + NewControllerState.ButtonStates[MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + 7] = NewControllerState.RYAnalog <= -RepeatDeadzone; } if (NewControllerState.LTAnalog != OldControllerState.LTAnalog) { diff --git a/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSInputInterface.cpp b/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSInputInterface.cpp index cb575bc980df..b6794303d4b7 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSInputInterface.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/IOS/IOSInputInterface.cpp @@ -338,15 +338,15 @@ void FIOSInputInterface::SendControllerEvents() const double CurrentTime = FPlatformTime::Seconds(); const float IniitialRepeatDelay = 0.2f; const float RepeatDelay = 0.1; - -#define HANDLE_BUTTON(Gamepad, GCButton, UEButton) \ -if ((Controller.Previous##Gamepad == nil && Gamepad.GCButton.pressed) || Controller.Previous##Gamepad.GCButton.pressed != Gamepad.GCButton.pressed) \ + +#define HANDLE_BUTTON_INTERNAL(Gamepad, bWasPressed, bIsPressed, UEButton) \ +if (bWasPressed != bIsPressed) \ { \ - NSLog(@"%@ button %s on controller %d", (ExtendedGamepad.GCButton.pressed) ? @"Pressed" : @"Released", TCHAR_TO_ANSI(*UEButton.ToString()), (int32)Cont.playerIndex); \ - (Gamepad.GCButton.pressed) ? MessageHandler->OnControllerButtonPressed(UEButton, Cont.playerIndex, false) : MessageHandler->OnControllerButtonReleased(UEButton, Cont.playerIndex, false); \ + NSLog(@"%@ button %s on controller %d", bIsPressed ? @"Pressed" : @"Released", TCHAR_TO_ANSI(*UEButton.ToString()), (int32)Cont.playerIndex); \ + bIsPressed ? MessageHandler->OnControllerButtonPressed(UEButton, Cont.playerIndex, false) : MessageHandler->OnControllerButtonReleased(UEButton, Cont.playerIndex, false); \ NextKeyRepeatTime.FindOrAdd(UEButton) = CurrentTime + IniitialRepeatDelay; \ } \ -else if(Gamepad.GCButton.pressed) \ +else if(bIsPressed) \ { \ double* NextRepeatTime = NextKeyRepeatTime.Find(UEButton); \ if(NextRepeatTime && *NextRepeatTime <= CurrentTime) \ @@ -359,6 +359,13 @@ else \ { \ NextKeyRepeatTime.Remove(UEButton); \ } + +#define HANDLE_BUTTON(Gamepad, GCButton, UEButton) \ +{ \ + const bool bWasPressed = Controller.Previous##Gamepad != nil && Controller.Previous##Gamepad.GCButton.pressed; \ + const bool bPressed = Gamepad.GCButton.pressed; \ + HANDLE_BUTTON_INTERNAL(Gamepad, bWasPressed, bPressed, UEButton); \ +} // Send controller events any time we are passed the given input threshold similarly to PC/Console (see: XInputInterface.cpp) const float RepeatDeadzone = 0.24; @@ -369,6 +376,16 @@ if ((Controller.Previous##Gamepad != nil && Gamepad.GCAxis.value != Controller.P NSLog(@"Axis %s is %f", TCHAR_TO_ANSI(*UEAxis.ToString()), Gamepad.GCAxis.value); \ MessageHandler->OnControllerAnalog(UEAxis, Cont.playerIndex, Gamepad.GCAxis.value); \ } + +#define HANDLE_ANALOG_VIRTUAL_BUTTONS(Gamepad, GCAxis, UEButtonNegative, UEButtonPositive) \ +{ \ + const bool bWasNegativePressed = Controller.Previous##Gamepad != nil && Controller.Previous##Gamepad.GCAxis.value <= -RepeatDeadzone; \ + const bool bNegativePressed = Gamepad.GCAxis.value <= -RepeatDeadzone; \ + HANDLE_BUTTON_INTERNAL(Gamepad, bWasNegativePressed, bNegativePressed, UEButtonNegative) \ + const bool bWasPositivePressed = Controller.Previous##Gamepad != nil && Controller.Previous##Gamepad.GCAxis.value >= RepeatDeadzone; \ + const bool bPositivePressed = Gamepad.GCAxis.value >= RepeatDeadzone; \ + HANDLE_BUTTON_INTERNAL(Gamepad, bWasPositivePressed, bPositivePressed, UEButtonPositive) \ +} if (ExtendedGamepad != nil) { @@ -392,6 +409,11 @@ if ((Controller.Previous##Gamepad != nil && Gamepad.GCAxis.value != Controller.P HANDLE_ANALOG(ExtendedGamepad, leftTrigger, FGamepadKeyNames::LeftTriggerAnalog); HANDLE_ANALOG(ExtendedGamepad, rightTrigger, FGamepadKeyNames::RightTriggerAnalog); + HANDLE_ANALOG_VIRTUAL_BUTTONS(ExtendedGamepad, leftThumbstick.xAxis, FGamepadKeyNames::LeftStickLeft, FGamepadKeyNames::LeftStickRight); + HANDLE_ANALOG_VIRTUAL_BUTTONS(ExtendedGamepad, leftThumbstick.yAxis, FGamepadKeyNames::LeftStickDown, FGamepadKeyNames::LeftStickUp); + HANDLE_ANALOG_VIRTUAL_BUTTONS(ExtendedGamepad, rightThumbstick.xAxis, FGamepadKeyNames::RightStickLeft, FGamepadKeyNames::RightStickRight); + HANDLE_ANALOG_VIRTUAL_BUTTONS(ExtendedGamepad, rightThumbstick.yAxis, FGamepadKeyNames::RightStickDown, FGamepadKeyNames::RightStickUp); + [Controller.PreviousExtendedGamepad release]; Controller.PreviousExtendedGamepad = [ExtendedGamepad saveSnapshot]; [Controller.PreviousExtendedGamepad retain]; diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/XInputInterface.cpp b/Engine/Source/Runtime/ApplicationCore/Private/Windows/XInputInterface.cpp index c8e4815cf307..dd585893cd8b 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Windows/XInputInterface.cpp +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/XInputInterface.cpp @@ -225,15 +225,21 @@ void XInputInterface::SendControllerEvents() } // apply force feedback - XINPUT_VIBRATION VibrationState; - + const float LargeValue = (ControllerState.ForceFeedback.LeftLarge > ControllerState.ForceFeedback.RightLarge ? ControllerState.ForceFeedback.LeftLarge : ControllerState.ForceFeedback.RightLarge); const float SmallValue = (ControllerState.ForceFeedback.LeftSmall > ControllerState.ForceFeedback.RightSmall ? ControllerState.ForceFeedback.LeftSmall : ControllerState.ForceFeedback.RightSmall); - VibrationState.wLeftMotorSpeed = ( ::WORD ) ( LargeValue * 65535.0f ); - VibrationState.wRightMotorSpeed = ( ::WORD ) ( SmallValue * 65535.0f ); + if (!FMath::IsNearlyEqual(LargeValue, ControllerState.LastLargeValue) || !FMath::IsNearlyEqual(SmallValue, ControllerState.LastSmallValue)) + { + XINPUT_VIBRATION VibrationState; + VibrationState.wLeftMotorSpeed = ( ::WORD ) ( LargeValue * 65535.0f ); + VibrationState.wRightMotorSpeed = ( ::WORD ) ( SmallValue * 65535.0f ); - XInputSetState( ( ::DWORD ) ControllerState.ControllerId, &VibrationState ); + XInputSetState( ( ::DWORD ) ControllerState.ControllerId, &VibrationState ); + + ControllerState.LastLargeValue = LargeValue; + ControllerState.LastSmallValue = SmallValue; + } } } diff --git a/Engine/Source/Runtime/ApplicationCore/Private/Windows/XInputInterface.h b/Engine/Source/Runtime/ApplicationCore/Private/Windows/XInputInterface.h index 381f0ec45248..2f7a749c2616 100644 --- a/Engine/Source/Runtime/ApplicationCore/Private/Windows/XInputInterface.h +++ b/Engine/Source/Runtime/ApplicationCore/Private/Windows/XInputInterface.h @@ -86,14 +86,17 @@ private: /** Right trigger analog value */ uint8 RightTriggerAnalog; - /** Id of the controller */ - int32 ControllerId; - /** If the controller is currently connected */ bool bIsConnected; - + + /** Id of the controller */ + int32 ControllerId; + /** Current force feedback values */ FForceFeedbackValues ForceFeedback; + + float LastLargeValue; + float LastSmallValue; }; /** If we've been notified by the system that the controller state may have changed */ diff --git a/Engine/Source/Runtime/ApplicationCore/Public/Android/AndroidInputInterface.h b/Engine/Source/Runtime/ApplicationCore/Public/Android/AndroidInputInterface.h index b2d5f6aeccd2..9b4e498edce5 100644 --- a/Engine/Source/Runtime/ApplicationCore/Public/Android/AndroidInputInterface.h +++ b/Engine/Source/Runtime/ApplicationCore/Public/Android/AndroidInputInterface.h @@ -153,7 +153,9 @@ struct TouchInput }; #define MAX_NUM_CONTROLLERS 8 // reasonable limit for now -#define MAX_NUM_CONTROLLER_BUTTONS 18 +#define MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS 18 +#define MAX_NUM_VIRTUAL_CONTROLLER_BUTTONS 8 +#define MAX_NUM_CONTROLLER_BUTTONS MAX_NUM_PHYSICAL_CONTROLLER_BUTTONS + MAX_NUM_VIRTUAL_CONTROLLER_BUTTONS #define MAX_DEFERRED_MESSAGE_QUEUE_SIZE 128 struct FAndroidControllerData diff --git a/Engine/Source/Runtime/AudioMixer/Private/AudioMixerBlueprintLibrary.cpp b/Engine/Source/Runtime/AudioMixer/Private/AudioMixerBlueprintLibrary.cpp index 5dd67fbf0f42..ecb1492216d7 100644 --- a/Engine/Source/Runtime/AudioMixer/Private/AudioMixerBlueprintLibrary.cpp +++ b/Engine/Source/Runtime/AudioMixer/Private/AudioMixerBlueprintLibrary.cpp @@ -5,6 +5,7 @@ #include "AudioDevice.h" #include "AudioMixerDevice.h" #include "CoreMinimal.h" +#include "DSP/SpectrumAnalyzer.h" // This is our global recording task: @@ -190,6 +191,56 @@ void UAudioMixerBlueprintLibrary::ResumeRecordingOutput(const UObject* WorldCont } } +void UAudioMixerBlueprintLibrary::StartAnalyzingOutput(const UObject* WorldContextObject, USoundSubmix* SubmixToAnalyze /*= nullptr*/, EFFTSize FFTSize /*= EFFTSize::Default*/, EFFTPeakInterpolationMethod InterpolationMethod /*= Linear*/, EFFTWindowType WindowType /*= EFFTWindowType::Hamming*/, float HopSize /*= 0*/) +{ + if (Audio::FMixerDevice* MixerDevice = GetAudioMixerDeviceFromWorldContext(WorldContextObject)) + { + Audio::FSpectrumAnalyzerSettings Settings = Audio::FSpectrumAnalyzerSettings(); + PopulateSpectrumAnalyzerSettings(FFTSize, InterpolationMethod, WindowType, HopSize, Settings); + MixerDevice->StartSpectrumAnalysis(SubmixToAnalyze, Settings); + } + else + { + UE_LOG(LogAudioMixer, Error, TEXT("Spectrum Analysis is an audio mixer only feature. Please run the game with -audiomixer to enable this feature.")); + } +} + +void UAudioMixerBlueprintLibrary::StopAnalyzingOutput(const UObject* WorldContextObject, USoundSubmix* SubmixToStopAnalyzing /*= nullptr*/) +{ + if (Audio::FMixerDevice* MixerDevice = GetAudioMixerDeviceFromWorldContext(WorldContextObject)) + { + MixerDevice->StopSpectrumAnalysis(SubmixToStopAnalyzing); + } + else + { + UE_LOG(LogAudioMixer, Error, TEXT("Spectrum Analysis is an audio mixer only feature. Please run the game with -audiomixer to enable this feature.")); + } +} + +void UAudioMixerBlueprintLibrary::GetMagnitudeForFrequencies(const UObject* WorldContextObject, const TArray& Frequencies, TArray& Magnitudes, USoundSubmix* SubmixToAnalyze /*= nullptr*/) +{ + if (Audio::FMixerDevice* MixerDevice = GetAudioMixerDeviceFromWorldContext(WorldContextObject)) + { + MixerDevice->GetMagnitudesForFrequencies(SubmixToAnalyze, Frequencies, Magnitudes); + } + else + { + UE_LOG(LogAudioMixer, Error, TEXT("Output recording is an audio mixer only feature. Please run the game with -audiomixer to enable this feature.")); + } +} + +void UAudioMixerBlueprintLibrary::GetPhaseForFrequencies(const UObject* WorldContextObject, const TArray& Frequencies, TArray& Phases, USoundSubmix* SubmixToAnalyze /*= nullptr*/) +{ + if (Audio::FMixerDevice* MixerDevice = GetAudioMixerDeviceFromWorldContext(WorldContextObject)) + { + MixerDevice->GetPhasesForFrequencies(SubmixToAnalyze, Frequencies, Phases); + } + else + { + UE_LOG(LogAudioMixer, Error, TEXT("Output recording is an audio mixer only feature. Please run the game with -audiomixer to enable this feature.")); + } +} + void UAudioMixerBlueprintLibrary::AddSourceEffectToPresetChain(const UObject* WorldContextObject, USoundEffectSourcePresetChain* PresetChain, FSourceEffectChainEntry Entry) { if (!PresetChain) @@ -295,3 +346,65 @@ int32 UAudioMixerBlueprintLibrary::GetNumberOfEntriesInSourceEffectChain(const U return 0; } + +void UAudioMixerBlueprintLibrary::PopulateSpectrumAnalyzerSettings(EFFTSize FFTSize, EFFTPeakInterpolationMethod InterpolationMethod, EFFTWindowType WindowType, float HopSize, Audio::FSpectrumAnalyzerSettings &OutSettings) +{ + switch (FFTSize) + { + case EFFTSize::DefaultSize: + OutSettings.FFTSize = Audio::FSpectrumAnalyzerSettings::EFFTSize::Default; + break; + case EFFTSize::Min: + OutSettings.FFTSize = Audio::FSpectrumAnalyzerSettings::EFFTSize::Min_64; + break; + case EFFTSize::Small: + OutSettings.FFTSize = Audio::FSpectrumAnalyzerSettings::EFFTSize::Small_256; + break; + case EFFTSize::Medium: + OutSettings.FFTSize = Audio::FSpectrumAnalyzerSettings::EFFTSize::Medium_512; + break; + case EFFTSize::Large: + OutSettings.FFTSize = Audio::FSpectrumAnalyzerSettings::EFFTSize::Large_1024; + break; + case EFFTSize::Max: + OutSettings.FFTSize = Audio::FSpectrumAnalyzerSettings::EFFTSize::TestLarge_4096; + break; + default: + break; + } + + switch (InterpolationMethod) + { + case EFFTPeakInterpolationMethod::NearestNeighbor: + OutSettings.InterpolationMethod = Audio::FSpectrumAnalyzerSettings::EPeakInterpolationMethod::NearestNeighbor; + break; + case EFFTPeakInterpolationMethod::Linear: + OutSettings.InterpolationMethod = Audio::FSpectrumAnalyzerSettings::EPeakInterpolationMethod::Linear; + break; + case EFFTPeakInterpolationMethod::Quadratic: + OutSettings.InterpolationMethod = Audio::FSpectrumAnalyzerSettings::EPeakInterpolationMethod::Quadratic; + break; + default: + break; + } + + switch (WindowType) + { + case EFFTWindowType::None: + OutSettings.WindowType = Audio::EWindowType::None; + break; + case EFFTWindowType::Hamming: + OutSettings.WindowType = Audio::EWindowType::Hamming; + break; + case EFFTWindowType::Hann: + OutSettings.WindowType = Audio::EWindowType::Hann; + break; + case EFFTWindowType::Blackman: + OutSettings.WindowType = Audio::EWindowType::Blackman; + break; + default: + break; + } + + OutSettings.HopSize = HopSize; +} diff --git a/Engine/Source/Runtime/AudioMixer/Private/AudioMixerDevice.cpp b/Engine/Source/Runtime/AudioMixer/Private/AudioMixerDevice.cpp index be7608abddae..656317dd3662 100644 --- a/Engine/Source/Runtime/AudioMixer/Private/AudioMixerDevice.cpp +++ b/Engine/Source/Runtime/AudioMixer/Private/AudioMixerDevice.cpp @@ -1361,6 +1361,74 @@ namespace Audio } + void FMixerDevice::StartSpectrumAnalysis(USoundSubmix* InSubmix, const Audio::FSpectrumAnalyzerSettings& InSettings) + { + Audio::FMixerSubmixPtr* FoundSubmix = Submixes.Find(InSubmix); + if (FoundSubmix) + { + (*FoundSubmix)->StartSpectrumAnalysis(InSettings); + } + else + { + FMixerSubmixWeakPtr MasterSubmix = GetMasterSubmix(); + FMixerSubmixPtr MasterSubmixPtr = MasterSubmix.Pin(); + check(MasterSubmixPtr.IsValid()); + + MasterSubmixPtr->StartSpectrumAnalysis(InSettings); + } + } + + void FMixerDevice::StopSpectrumAnalysis(USoundSubmix* InSubmix) + { + Audio::FMixerSubmixPtr* FoundSubmix = Submixes.Find(InSubmix); + if (FoundSubmix) + { + (*FoundSubmix)->StopSpectrumAnalysis(); + } + else + { + FMixerSubmixWeakPtr MasterSubmix = GetMasterSubmix(); + FMixerSubmixPtr MasterSubmixPtr = MasterSubmix.Pin(); + check(MasterSubmixPtr.IsValid()); + + MasterSubmixPtr->StopSpectrumAnalysis(); + } + } + + void FMixerDevice::GetMagnitudesForFrequencies(USoundSubmix* InSubmix, const TArray& InFrequencies, TArray& OutMagnitudes) + { + Audio::FMixerSubmixPtr* FoundSubmix = Submixes.Find(InSubmix); + if (FoundSubmix) + { + (*FoundSubmix)->GetMagnitudeForFrequencies(InFrequencies, OutMagnitudes); + } + else + { + FMixerSubmixWeakPtr MasterSubmix = GetMasterSubmix(); + FMixerSubmixPtr MasterSubmixPtr = MasterSubmix.Pin(); + check(MasterSubmixPtr.IsValid()); + + MasterSubmixPtr->GetMagnitudeForFrequencies(InFrequencies, OutMagnitudes); + } + } + + void FMixerDevice::GetPhasesForFrequencies(USoundSubmix* InSubmix, const TArray& InFrequencies, TArray& OutPhases) + { + Audio::FMixerSubmixPtr* FoundSubmix = Submixes.Find(InSubmix); + if (FoundSubmix) + { + (*FoundSubmix)->GetPhaseForFrequencies(InFrequencies, OutPhases); + } + else + { + FMixerSubmixWeakPtr MasterSubmix = GetMasterSubmix(); + FMixerSubmixPtr MasterSubmixPtr = MasterSubmix.Pin(); + check(MasterSubmixPtr.IsValid()); + + MasterSubmixPtr->GetPhaseForFrequencies(InFrequencies, OutPhases); + } + } + void FMixerDevice::RegisterSubmixBufferListener(ISubmixBufferListener* InSubmixBufferListener, USoundSubmix* InSubmix) { Audio::FMixerSubmixPtr* FoundSubmix = Submixes.Find(InSubmix); diff --git a/Engine/Source/Runtime/AudioMixer/Private/AudioMixerSubmix.cpp b/Engine/Source/Runtime/AudioMixer/Private/AudioMixerSubmix.cpp index d6e2497b130a..3bde1f424308 100644 --- a/Engine/Source/Runtime/AudioMixer/Private/AudioMixerSubmix.cpp +++ b/Engine/Source/Runtime/AudioMixer/Private/AudioMixerSubmix.cpp @@ -360,6 +360,29 @@ namespace Audio } } + void FMixerSubmix::MixBufferDownToMono(const AlignedFloatBuffer& InBuffer, int32 NumInputChannels, AlignedFloatBuffer& OutBuffer) + { + check(NumInputChannels > 0); + + int32 NumFrames = InBuffer.Num() / NumInputChannels; + OutBuffer.Reset(); + OutBuffer.AddZeroed(NumFrames); + + const float* InData = InBuffer.GetData(); + float* OutData = OutBuffer.GetData(); + + const float GainFactor = 1.0f / FMath::Sqrt((float) NumInputChannels); + + for (int32 FrameIndex = 0; FrameIndex < NumFrames; FrameIndex++) + { + for (int32 ChannelIndex = 0; ChannelIndex < NumInputChannels; ChannelIndex++) + { + const int32 InputIndex = FrameIndex * NumInputChannels + ChannelIndex; + OutData[FrameIndex] += InData[InputIndex] * GainFactor; + } + } + } + void FMixerSubmix::SetUpAmbisonicsEncoder() { check(AmbisonicsMixer.IsValid()); @@ -665,6 +688,15 @@ namespace Audio } } + // If spectrum analysis is enabled for this submix, downmix the resulting audio + // and push it to the spectrum analyzer. + if (SpectrumAnalyzer.IsValid()) + { + MixBufferDownToMono(InputBuffer, NumChannels, MonoMixBuffer); + SpectrumAnalyzer->PushAudio(MonoMixBuffer.GetData(), MonoMixBuffer.Num()); + SpectrumAnalyzer->PerformAnalysisIfPossible(true, true); + } + // If the channel types match, just do a copy if (ChannelFormat != ParentChannelType || SubmixAmbisonicsDecoderID != INDEX_NONE) { @@ -844,6 +876,56 @@ namespace Audio OnSubmixEnvelope.AddUnique(OnSubmixEnvelopeBP); } + void FMixerSubmix::StartSpectrumAnalysis(const FSpectrumAnalyzerSettings& InSettings) + { + SpectrumAnalyzer.Reset(new FSpectrumAnalyzer(InSettings, MixerDevice->GetSampleRate())); + } + + void FMixerSubmix::StopSpectrumAnalysis() + { + SpectrumAnalyzer.Reset(); + } + + void FMixerSubmix::GetMagnitudeForFrequencies(const TArray& InFrequencies, TArray& OutMagnitudes) + { + if (SpectrumAnalyzer.IsValid()) + { + OutMagnitudes.Reset(); + OutMagnitudes.AddUninitialized(InFrequencies.Num()); + + SpectrumAnalyzer->LockOutputBuffer(); + for (int32 Index = 0; Index < InFrequencies.Num(); Index++) + { + OutMagnitudes[Index] = SpectrumAnalyzer->GetMagnitudeForFrequency(InFrequencies[Index]); + } + SpectrumAnalyzer->UnlockOutputBuffer(); + } + else + { + UE_LOG(LogAudioMixer, Warning, TEXT("Call StartSpectrumAnalysis before calling GetMagnitudeForFrequencies.")); + } + } + + void FMixerSubmix::GetPhaseForFrequencies(const TArray& InFrequencies, TArray& OutPhases) + { + if (SpectrumAnalyzer.IsValid()) + { + OutPhases.Reset(); + OutPhases.AddUninitialized(InFrequencies.Num()); + + SpectrumAnalyzer->LockOutputBuffer(); + for (int32 Index = 0; Index < InFrequencies.Num(); Index++) + { + OutPhases[Index] = SpectrumAnalyzer->GetPhaseForFrequency(InFrequencies[Index]); + } + SpectrumAnalyzer->UnlockOutputBuffer(); + } + else + { + UE_LOG(LogAudioMixer, Warning, TEXT("Call StartSpectrumAnalysis before calling GetMagnitudeForFrequencies.")); + } + } + void FMixerSubmix::BroadcastEnvelope() { diff --git a/Engine/Source/Runtime/AudioMixer/Private/DSP/AudioFFT.cpp b/Engine/Source/Runtime/AudioMixer/Private/DSP/AudioFFT.cpp index edc66533f6fc..59c91723ded3 100644 --- a/Engine/Source/Runtime/AudioMixer/Private/DSP/AudioFFT.cpp +++ b/Engine/Source/Runtime/AudioMixer/Private/DSP/AudioFFT.cpp @@ -21,7 +21,7 @@ namespace Audio { const int32 N = bIsPeriodic ? NumFrames : NumFrames - 1; const float PhaseDelta = 2.0f * PI / N; - float Phase = 0; + float Phase = 0.0f; for (int32 FrameIndex = 0; FrameIndex < NumFrames; FrameIndex++) { @@ -104,59 +104,6 @@ namespace Audio } } - FWindow::FWindow(EWindowType InType, int32 InNumFrames, int32 InNumChannels, bool bIsPeriodic) - : WindowType(InType) - , NumSamples(InNumFrames * InNumChannels) - { - checkf(NumSamples % 4 == 0, TEXT("For performance reasons, this window's length should be a multiple of 4.")); - Generate(InNumFrames, InNumChannels, bIsPeriodic); - } - - FWindow::~FWindow() - { - - } - - void FWindow::Generate(int32 NumFrames, int32 NumChannels, bool bIsPeriodic) - { - if (WindowType == EWindowType::None) - { - return; - } - - WindowBuffer.Reset(); - WindowBuffer.AddZeroed(NumSamples); - - switch (WindowType) - { - case EWindowType::Hann: - { - GenerateHannWindow(WindowBuffer.GetData(), NumFrames, NumChannels, bIsPeriodic); - break; - } - case EWindowType::Blackman: - { - GenerateBlackmanWindow(WindowBuffer.GetData(), NumFrames, NumChannels, bIsPeriodic); - break; - } - default: - { - break; - } - } - } - - void FWindow::ApplyToBuffer(float* InBuffer) - { - if (WindowType == EWindowType::None) - { - return; - } - - check(IsAligned(InBuffer, 4)); - MultiplyBuffersInPlace(WindowBuffer.GetData(), InBuffer, NumSamples); - } - namespace FFTIntrinsics { // Fast bit reversal helper function. Can be used if N is a power of 2. Not well exercised. diff --git a/Engine/Source/Runtime/AudioMixer/Private/DSP/SpectrumAnalyzer.cpp b/Engine/Source/Runtime/AudioMixer/Private/DSP/SpectrumAnalyzer.cpp index fc2c3c9627b3..f15e3bba826f 100644 --- a/Engine/Source/Runtime/AudioMixer/Private/DSP/SpectrumAnalyzer.cpp +++ b/Engine/Source/Runtime/AudioMixer/Private/DSP/SpectrumAnalyzer.cpp @@ -6,8 +6,9 @@ namespace Audio { FSpectrumAnalyzer::FSpectrumAnalyzer() - : CurrentSettings(SpectrumAnalyzerSettings::FSettings()) + : CurrentSettings(FSpectrumAnalyzerSettings()) , bSettingsWereUpdated(false) + , bIsInitialized(false) , SampleRate(0.0f) , Window(CurrentSettings.WindowType, (int32)CurrentSettings.FFTSize, 1, false) , InputQueue(FMath::Max((int32)CurrentSettings.FFTSize * 4, 4096)) @@ -16,9 +17,10 @@ namespace Audio { } - FSpectrumAnalyzer::FSpectrumAnalyzer(const SpectrumAnalyzerSettings::FSettings& InSettings, float InSampleRate) + FSpectrumAnalyzer::FSpectrumAnalyzer(const FSpectrumAnalyzerSettings& InSettings, float InSampleRate) : CurrentSettings(InSettings) , bSettingsWereUpdated(false) + , bIsInitialized(true) , SampleRate(InSampleRate) , Window(InSettings.WindowType, (int32)InSettings.FFTSize, 1, false) , InputQueue(FMath::Max((int32)CurrentSettings.FFTSize * 4, 4096)) @@ -29,8 +31,9 @@ namespace Audio } FSpectrumAnalyzer::FSpectrumAnalyzer(float InSampleRate) - : CurrentSettings(SpectrumAnalyzerSettings::FSettings()) + : CurrentSettings(FSpectrumAnalyzerSettings()) , bSettingsWereUpdated(false) + , bIsInitialized(true) , SampleRate(InSampleRate) , Window(CurrentSettings.WindowType, (int32)CurrentSettings.FFTSize, 1, false) , InputQueue(FMath::Max((int32)CurrentSettings.FFTSize * 4, 4096)) @@ -40,13 +43,21 @@ namespace Audio ResetSettings(); } + FSpectrumAnalyzer::~FSpectrumAnalyzer() + { + if (AsyncAnalysisTask.IsValid()) + { + AsyncAnalysisTask->EnsureCompletion(false); + } + } + void FSpectrumAnalyzer::Init(float InSampleRate) { - SpectrumAnalyzerSettings::FSettings DefaultSettings = SpectrumAnalyzerSettings::FSettings(); + FSpectrumAnalyzerSettings DefaultSettings = FSpectrumAnalyzerSettings(); Init(DefaultSettings, InSampleRate); } - void FSpectrumAnalyzer::Init(const SpectrumAnalyzerSettings::FSettings& InSettings, float InSampleRate) + void FSpectrumAnalyzer::Init(const FSpectrumAnalyzerSettings& InSettings, float InSampleRate) { CurrentSettings = InSettings; bSettingsWereUpdated = false; @@ -54,6 +65,8 @@ namespace Audio InputQueue.SetCapacity(FMath::Max((int32)CurrentSettings.FFTSize * 4, 4096)); FrequencyBuffer.Reset(CurrentSettings); ResetSettings(); + + bIsInitialized = true; } void FSpectrumAnalyzer::ResetSettings() @@ -67,7 +80,15 @@ namespace Audio Window = FWindow(CurrentSettings.WindowType, (int32)CurrentSettings.FFTSize, 1, false); FFTSize = (int32) CurrentSettings.FFTSize; - HopInSamples = GetCOLAHopSizeForWindow(CurrentSettings.WindowType, (uint32)CurrentSettings.FFTSize); + + if (FMath::IsNearlyZero(CurrentSettings.HopSize)) + { + HopInSamples = GetCOLAHopSizeForWindow(CurrentSettings.WindowType, (uint32)CurrentSettings.FFTSize); + } + else + { + HopInSamples = FMath::FloorToInt((float)CurrentSettings.FFTSize * CurrentSettings.HopSize); + } AnalysisTimeDomainBuffer.Reset(); AnalysisTimeDomainBuffer.AddZeroed(FFTSize); @@ -76,7 +97,7 @@ namespace Audio bSettingsWereUpdated = false; } - void FSpectrumAnalyzer::PerformInterpolation(const FSpectrumAnalyzerFrequencyVector* InFrequencies, SpectrumAnalyzerSettings::EPeakInterpolationMethod InMethod, const float InFreq, float& OutReal, float& OutImag) + void FSpectrumAnalyzer::PerformInterpolation(const FSpectrumAnalyzerFrequencyVector* InFrequencies, FSpectrumAnalyzerSettings::EPeakInterpolationMethod InMethod, const float InFreq, float& OutReal, float& OutImag) { const int32 VectorLength = InFrequencies->RealFrequencies.Num(); const float NyquistPosition = VectorLength / 2; @@ -93,7 +114,7 @@ namespace Audio switch (InMethod) { - case Audio::SpectrumAnalyzerSettings::EPeakInterpolationMethod::NearestNeighbor: + case Audio::FSpectrumAnalyzerSettings::EPeakInterpolationMethod::NearestNeighbor: { int32 Index = FMath::RoundToInt(Position); OutReal = InFrequencies->RealFrequencies[Index]; @@ -101,7 +122,7 @@ namespace Audio break; } - case Audio::SpectrumAnalyzerSettings::EPeakInterpolationMethod::Linear: + case Audio::FSpectrumAnalyzerSettings::EPeakInterpolationMethod::Linear: { const int32 LowerIndex = FMath::FloorToInt(Position); const int32 UpperIndex = FMath::CeilToInt(Position); @@ -116,7 +137,7 @@ namespace Audio OutImag = FMath::Lerp(y1Imag, y2Imag, PositionFraction); break; } - case Audio::SpectrumAnalyzerSettings::EPeakInterpolationMethod::Quadratic: + case Audio::FSpectrumAnalyzerSettings::EPeakInterpolationMethod::Quadratic: { const int32 MidIndex = FMath::RoundToInt(Position); const int32 LowerIndex = FMath::Max(0, MidIndex - 1); @@ -145,19 +166,24 @@ namespace Audio } } - void FSpectrumAnalyzer::SetSettings(const SpectrumAnalyzerSettings::FSettings& InSettings) + void FSpectrumAnalyzer::SetSettings(const FSpectrumAnalyzerSettings& InSettings) { CurrentSettings = InSettings; bSettingsWereUpdated = true; } - void FSpectrumAnalyzer::GetSettings(SpectrumAnalyzerSettings::FSettings& OutSettings) + void FSpectrumAnalyzer::GetSettings(FSpectrumAnalyzerSettings& OutSettings) { OutSettings = CurrentSettings; } float FSpectrumAnalyzer::GetMagnitudeForFrequency(float InFrequency) { + if (!bIsInitialized) + { + return false; + } + const FSpectrumAnalyzerFrequencyVector* OutVector; bool bShouldUnlockBuffer = true; @@ -195,6 +221,11 @@ namespace Audio float FSpectrumAnalyzer::GetPhaseForFrequency(float InFrequency) { + if (!bIsInitialized) + { + return false; + } + const FSpectrumAnalyzerFrequencyVector* OutVector; bool bShouldUnlockBuffer = true; @@ -232,6 +263,11 @@ namespace Audio void FSpectrumAnalyzer::LockOutputBuffer() { + if (!bIsInitialized) + { + return; + } + if (LockedFrequencyVector != nullptr) { FrequencyBuffer.UnlockBuffer(); @@ -242,6 +278,11 @@ namespace Audio void FSpectrumAnalyzer::UnlockOutputBuffer() { + if (!bIsInitialized) + { + return; + } + if (LockedFrequencyVector != nullptr) { FrequencyBuffer.UnlockBuffer(); @@ -260,8 +301,29 @@ namespace Audio return InputQueue.Push(InBuffer, NumSamples) > 0; } - bool FSpectrumAnalyzer::PerformAnalysisIfPossible() + bool FSpectrumAnalyzer::PerformAnalysisIfPossible(bool bUseLatestAudio, bool bAsync) { + if (!bIsInitialized) + { + return false; + } + + if (bAsync) + { + // if bAsync is true, kick off a new task if one isn't in flight already, and return. + if (!AsyncAnalysisTask.IsValid()) + { + AsyncAnalysisTask.Reset(new FSpectrumAnalyzerTask(this, bUseLatestAudio)); + AsyncAnalysisTask->StartBackgroundTask(); + } + else if (AsyncAnalysisTask->IsDone()) + { + AsyncAnalysisTask->StartBackgroundTask(); + } + + return true; + } + // If settings were updated, perform resizing and parameter updates here: if (bSettingsWereUpdated) { @@ -276,6 +338,12 @@ namespace Audio { float* TimeDomainBuffer = AnalysisTimeDomainBuffer.GetData(); + if (bUseLatestAudio) + { + // If we are only using the latest audio, scrap the oldest audio in the InputQueue: + InputQueue.SetNum((uint32)FFTSize); + } + // Perform pop/peek here based on FFT size and hop amount. const int32 PeekAmount = FFTSize - HopInSamples; InputQueue.Pop(TimeDomainBuffer, HopInSamples); @@ -293,7 +361,6 @@ namespace Audio OutputParams.OutReal = OutputVector->RealFrequencies.GetData(); OutputParams.OutImag = OutputVector->ImagFrequencies.GetData(); - PerformFFT(InputParams, OutputParams); // We're done, so unlock this vector. @@ -307,7 +374,12 @@ namespace Audio } } - static const int32 SpectrumAnalyzerBufferSize = 3; + bool FSpectrumAnalyzer::IsInitialized() + { + return bIsInitialized; + } + + static const int32 SpectrumAnalyzerBufferSize = 4; FSpectrumAnalyzerBuffer::FSpectrumAnalyzerBuffer() : OutputIndex(0) @@ -315,12 +387,12 @@ namespace Audio { } - FSpectrumAnalyzerBuffer::FSpectrumAnalyzerBuffer(const SpectrumAnalyzerSettings::FSettings& InSettings) + FSpectrumAnalyzerBuffer::FSpectrumAnalyzerBuffer(const FSpectrumAnalyzerSettings& InSettings) { Reset(InSettings); } - void FSpectrumAnalyzerBuffer::Reset(const SpectrumAnalyzerSettings::FSettings& InSettings) + void FSpectrumAnalyzerBuffer::Reset(const FSpectrumAnalyzerSettings& InSettings) { FScopeLock ScopeLock(&BufferIndicesCriticalSection); @@ -391,4 +463,10 @@ namespace Audio ImagFrequencies.Reset(); ImagFrequencies.AddZeroed(InFFTSize); } + + void FSpectrumAnalysisAsyncWorker::DoWork() + { + Analyzer->PerformAnalysisIfPossible(bUseLatestAudio, false); + } + } diff --git a/Engine/Source/Runtime/AudioMixer/Public/AudioMixerBlueprintLibrary.h b/Engine/Source/Runtime/AudioMixer/Public/AudioMixerBlueprintLibrary.h index 71118dd1fea3..4eb216f808fe 100644 --- a/Engine/Source/Runtime/AudioMixer/Public/AudioMixerBlueprintLibrary.h +++ b/Engine/Source/Runtime/AudioMixer/Public/AudioMixerBlueprintLibrary.h @@ -7,10 +7,57 @@ #include "SubmixEffects/AudioMixerSubmixEffectDynamicsProcessor.h" #include "Sound/SoundEffectSource.h" #include "Sound/SampleBuffer.h" +#include "DSP/SpectrumAnalyzer.h" #include "AudioMixerBlueprintLibrary.generated.h" class USoundSubmix; +UENUM(BlueprintType) +enum class EFFTSize : uint8 +{ + // 512 + DefaultSize, + + // 64 + Min, + + // 256 + Small, + + // 512 + Medium, + + // 1024 + Large, + + // 4096 + Max, +}; + +UENUM() +enum class EFFTPeakInterpolationMethod : uint8 +{ + NearestNeighbor, + Linear, + Quadratic +}; + +UENUM() +enum class EFFTWindowType : uint8 +{ + // No window is applied. Technically a boxcar window. + None, + + // Mainlobe width of -3 dB and sidelove attenuation of ~-40 dB. Good for COLA. + Hamming, + + // Mainlobe width of -3 dB and sidelobe attenuation of ~-30dB. Good for COLA. + Hann, + + // Mainlobe width of -3 dB and sidelobe attenuation of ~-60db. Tricky for COLA. + Blackman +}; + UCLASS(meta=(ScriptName="AudioMixerLibrary")) class AUDIOMIXER_API UAudioMixerBlueprintLibrary : public UBlueprintFunctionLibrary { @@ -42,10 +89,26 @@ public: UFUNCTION(BlueprintCallable, Category = "Audio", meta = (WorldContext = "WorldContextObject", AdvancedDisplay = 1)) static void PauseRecordingOutput(const UObject* WorldContextObject, USoundSubmix* SubmixToPause = nullptr); - /** Resume recording audio after pausing. By leaving the Submix To Record field blank, you can record the master output of the game. */ + /** Resume recording audio after pausing. By leaving the Submix To Pause field blank, you can record the master output of the game. */ UFUNCTION(BlueprintCallable, Category = "Audio", meta = (WorldContext = "WorldContextObject", AdvancedDisplay = 1)) static void ResumeRecordingOutput(const UObject* WorldContextObject, USoundSubmix* SubmixToPause = nullptr); + /** Start spectrum analysis of the audio output. By leaving the Submix To Analyze blank, you can analyze the master output of the game. */ + UFUNCTION(BlueprintCallable, Category = "Audio|Analysis", meta = (WorldContext = "WorldContextObject", AdvancedDisplay = 1)) + static void StartAnalyzingOutput(const UObject* WorldContextObject, USoundSubmix* SubmixToAnalyze = nullptr, EFFTSize FFTSize = EFFTSize::DefaultSize, EFFTPeakInterpolationMethod InterpolationMethod = EFFTPeakInterpolationMethod::Linear, EFFTWindowType WindowType = EFFTWindowType::Hann, float HopSize = 0); + + /** Start spectrum analysis of the audio output. By leaving the Submix To Stop Analyzing blank, you can analyze the master output of the game. */ + UFUNCTION(BlueprintCallable, Category = "Audio|Analysis", meta = (WorldContext = "WorldContextObject", AdvancedDisplay = 1)) + static void StopAnalyzingOutput(const UObject* WorldContextObject, USoundSubmix* SubmixToStopAnalyzing = nullptr); + + /** Start spectrum analysis of the audio output. By leaving the Submix To Analyze blank, you can analyze the master output of the game. */ + UFUNCTION(BlueprintCallable, Category = "Audio|Analysis", meta = (WorldContext = "WorldContextObject", AdvancedDisplay = 3)) + static void GetMagnitudeForFrequencies(const UObject* WorldContextObject, const TArray& Frequencies, TArray& Magnitudes, USoundSubmix* SubmixToAnalyze = nullptr); + + /** Start spectrum analysis of the audio output. By leaving the Submix To Analyze blank, you can analyze the master output of the game. */ + UFUNCTION(BlueprintCallable, Category = "Audio|Analysis", meta = (WorldContext = "WorldContextObject", AdvancedDisplay = 3)) + static void GetPhaseForFrequencies(const UObject* WorldContextObject, const TArray& Frequencies, TArray& Phases, USoundSubmix* SubmixToAnalyze = nullptr); + /** Adds source effect entry to preset chain. Only effects the instance of the preset chain */ UFUNCTION(BlueprintCallable, Category = "Audio|Effects", meta = (WorldContext = "WorldContextObject")) static void AddSourceEffectToPresetChain(const UObject* WorldContextObject, USoundEffectSourcePresetChain* PresetChain, FSourceEffectChainEntry Entry); @@ -62,4 +125,7 @@ public: UFUNCTION(BlueprintCallable, Category = "Audio|Effects", meta = (WorldContext = "WorldContextObject")) static int32 GetNumberOfEntriesInSourceEffectChain(const UObject* WorldContextObject, USoundEffectSourcePresetChain* PresetChain); + +private: + static void PopulateSpectrumAnalyzerSettings(EFFTSize FFTSize, EFFTPeakInterpolationMethod InterpolationMethod, EFFTWindowType WindowType, float HopSize, Audio::FSpectrumAnalyzerSettings &OutSettings); }; \ No newline at end of file diff --git a/Engine/Source/Runtime/AudioMixer/Public/AudioMixerDevice.h b/Engine/Source/Runtime/AudioMixer/Public/AudioMixerDevice.h index 5a37d5154319..8e25986220c9 100644 --- a/Engine/Source/Runtime/AudioMixer/Public/AudioMixerDevice.h +++ b/Engine/Source/Runtime/AudioMixer/Public/AudioMixerDevice.h @@ -120,6 +120,12 @@ namespace Audio virtual void StopEnvelopeFollowing(USoundSubmix* InSubmix) override; virtual void AddEnvelopeFollowerDelegate(USoundSubmix* InSubmix, const FOnSubmixEnvelopeBP& OnSubmixEnvelopeBP) override; + /** Submix Spectrum Analysis */ + virtual void StartSpectrumAnalysis(USoundSubmix* InSubmix, const Audio::FSpectrumAnalyzerSettings& InSettings) override; + virtual void StopSpectrumAnalysis(USoundSubmix* InSubmix) override; + virtual void GetMagnitudesForFrequencies(USoundSubmix* InSubmix, const TArray& InFrequencies, TArray& OutMagnitudes); + virtual void GetPhasesForFrequencies(USoundSubmix* InSubmix, const TArray& InFrequencies, TArray& OutPhases); + // Submix buffer listener callbacks virtual void RegisterSubmixBufferListener(ISubmixBufferListener* InSubmixBufferListener, USoundSubmix* InSubmix = nullptr) override; virtual void UnregisterSubmixBufferListener(ISubmixBufferListener* InSubmixBufferListener, USoundSubmix* InSubmix = nullptr) override; diff --git a/Engine/Source/Runtime/AudioMixer/Public/AudioMixerSubmix.h b/Engine/Source/Runtime/AudioMixer/Public/AudioMixerSubmix.h index 9fd1bc6f5e8a..56db03fe02f2 100644 --- a/Engine/Source/Runtime/AudioMixer/Public/AudioMixerSubmix.h +++ b/Engine/Source/Runtime/AudioMixer/Public/AudioMixerSubmix.h @@ -6,6 +6,7 @@ #include "Sound/SoundSubmix.h" #include "Sound/SampleBuffer.h" #include "DSP/EnvelopeFollower.h" +#include "DSP/SpectrumAnalyzer.h" class USoundEffectSubmix; @@ -134,6 +135,20 @@ namespace Audio // Adds an envelope follower delegate void AddEnvelopeFollowerDelegate(const FOnSubmixEnvelopeBP& OnSubmixEnvelopeBP); + // Initializes a new FFT analyzer for this submix and immediately begins feeding audio to it. + void StartSpectrumAnalysis(const FSpectrumAnalyzerSettings& InSettings); + + // Terminates whatever FFT Analyzer is being used for this submix. + void StopSpectrumAnalysis(); + + // Gets the most recent magnitude values for each corresponding value in InFrequencies (in Hz). + // This requires StartSpectrumAnalysis to be called first. + void GetMagnitudeForFrequencies(const TArray& InFrequencies, TArray& OutMagnitudes); + + // Gets the most recent phase values for each corresponding value in InFrequencies (in Hz). + // This requires StartSpectrumAnalysis to be called first. + void GetPhaseForFrequencies(const TArray& InFrequencies, TArray& OutPhases); + // Broadcast the envelope value on the game thread void BroadcastEnvelope(); @@ -141,6 +156,8 @@ namespace Audio // Down mix the given buffer to the desired down mix channel count void FormatChangeBuffer(const ESubmixChannelFormat NewChannelType, AlignedFloatBuffer& InBuffer, AlignedFloatBuffer& OutNewBuffer); + void MixBufferDownToMono(const AlignedFloatBuffer& InBuffer, int32 NumInputChannels, AlignedFloatBuffer& OutBuffer); + // Set up ambisonics encoder. Called when ambisonics settings are changed. void SetUpAmbisonicsEncoder(); @@ -238,6 +255,12 @@ namespace Audio int32 EnvelopeNumChannels; FCriticalSection EnvelopeCriticalSection; + // Spectrum analyzer: + TUniquePtr SpectrumAnalyzer; + + // This buffer is used to downmix the submix output to mono before submitting it to the SpectrumAnalyzer. + AlignedFloatBuffer MonoMixBuffer; + // This buffer is encoded into for each source, then summed into the ambisonics buffer. AlignedFloatBuffer InputAmbisonicsBuffer; diff --git a/Engine/Source/Runtime/AudioMixer/Public/DSP/AudioFFT.h b/Engine/Source/Runtime/AudioMixer/Public/DSP/AudioFFT.h index a5e249ce7c27..eba651297e67 100644 --- a/Engine/Source/Runtime/AudioMixer/Public/DSP/AudioFFT.h +++ b/Engine/Source/Runtime/AudioMixer/Public/DSP/AudioFFT.h @@ -40,25 +40,75 @@ namespace Audio * Generally, set this to false if using this window with an STFT, but use true * if this window will be used on an entire, self-contained signal. */ - FWindow(EWindowType InType, int32 InNumFrames, int32 InNumChannels, bool bIsPeriodic); + FWindow(EWindowType InType, int32 InNumFrames, int32 InNumChannels, bool bIsPeriodic) + : WindowType(InType) + , NumSamples(InNumFrames * InNumChannels) + { + checkf(NumSamples % 4 == 0, TEXT("For performance reasons, this window's length should be a multiple of 4.")); + Generate(InNumFrames, InNumChannels, bIsPeriodic); + } // Destructor. Releases memory used for window. - ~FWindow(); + ~FWindow() + { + } // Apply this window to InBuffer, which is expected to be an interleaved buffer with the same amount of frames // and channels this window was constructed with. - void ApplyToBuffer(float* InBuffer); + void ApplyToBuffer(float* InBuffer) + { + if (WindowType == EWindowType::None) + { + return; + } + + check(IsAligned(InBuffer, 4)); + MultiplyBuffersInPlace(WindowBuffer.GetData(), InBuffer, NumSamples); + } private: + EWindowType WindowType; + AlignedFloatBuffer WindowBuffer; + int32 NumSamples; + // Purposefully hidden constructor. FWindow(); // Generate the window. Called on constructor. - void Generate(int32 NumFrames, int32 NumChannels, bool bIsPeriodic); + void Generate(int32 NumFrames, int32 NumChannels, bool bIsPeriodic) + { + if (WindowType == EWindowType::None) + { + return; + } - EWindowType WindowType; - AlignedFloatBuffer WindowBuffer; - int32 NumSamples; + WindowBuffer.Reset(); + WindowBuffer.AddZeroed(NumSamples); + + switch (WindowType) + { + case EWindowType::Hann: + { + GenerateHannWindow(WindowBuffer.GetData(), NumFrames, NumChannels, bIsPeriodic); + break; + } + case EWindowType::Hamming: + { + GenerateHammingWindow(WindowBuffer.GetData(), NumFrames, NumChannels, bIsPeriodic); + break; + } + case EWindowType::Blackman: + { + GenerateBlackmanWindow(WindowBuffer.GetData(), NumFrames, NumChannels, bIsPeriodic); + break; + } + default: + { + checkf(false, TEXT("Unknown window type!")); + break; + } + } + } }; struct FFTTimeDomainData diff --git a/Engine/Source/Runtime/AudioMixer/Public/DSP/Dsp.h b/Engine/Source/Runtime/AudioMixer/Public/DSP/Dsp.h index 42487b6ee2c0..458fb296ff02 100644 --- a/Engine/Source/Runtime/AudioMixer/Public/DSP/Dsp.h +++ b/Engine/Source/Runtime/AudioMixer/Public/DSP/Dsp.h @@ -553,6 +553,29 @@ namespace Audio return ((int32)CurrentSlack) - NumSamples; } + // When called, seeks the read or write cursor to only retain either the NumSamples latest data + // (if bRetainOldestSamples is false) or the NumSamples oldest data (if bRetainOldestSamples is true) + // in the buffer. Cannot be used to increase the capacity of this buffer. + void SetNum(uint32 NumSamples, bool bRetainOldestSamples = false) + { + check(NumSamples < Capacity); + + if (bRetainOldestSamples) + { + WriteCounter.Set((ReadCounter.GetValue() + NumSamples) % Capacity); + } + else + { + int64 ReadCounterNum = ((int32)WriteCounter.GetValue()) - ((int32) NumSamples); + if (ReadCounterNum < 0) + { + ReadCounterNum = Capacity + ReadCounterNum; + } + + ReadCounter.Set(ReadCounterNum); + } + } + // Get number of samples that can be popped off of the buffer. uint32 Num() { diff --git a/Engine/Source/Runtime/AudioMixer/Public/DSP/SpectrumAnalyzer.h b/Engine/Source/Runtime/AudioMixer/Public/DSP/SpectrumAnalyzer.h index b65a6f000bee..5e21377e2903 100644 --- a/Engine/Source/Runtime/AudioMixer/Public/DSP/SpectrumAnalyzer.h +++ b/Engine/Source/Runtime/AudioMixer/Public/DSP/SpectrumAnalyzer.h @@ -4,13 +4,15 @@ #include "CoreMinimal.h" #include "DSP/Dsp.h" +#include "DSP/BufferVectorOperations.h" #include "DSP/AudioFFT.h" #include "Sound/SampleBuffer.h" namespace Audio { - - namespace SpectrumAnalyzerSettings + class FSpectrumAnalyzer; + + struct AUDIOMIXER_API FSpectrumAnalyzerSettings { // Actual FFT size used. For FSpectrumAnalyzer, we never zero pad the input buffer. enum class EFFTSize : uint16 @@ -33,28 +35,25 @@ namespace Audio Quadratic }; - struct FSettings - { - EWindowType WindowType; - EFFTSize FFTSize; - EPeakInterpolationMethod InterpolationMethod; + EWindowType WindowType; + EFFTSize FFTSize; + EPeakInterpolationMethod InterpolationMethod; - /** - * Hop size as a percentage of FFTSize. - * 1.0 indicates a full hop. - * Keeping this as 0.0 will use whatever hop size - * can be used for WindowType to maintain COLA. - */ - float HopSize; + /** + * Hop size as a percentage of FFTSize. + * 1.0 indicates a full hop. + * Keeping this as 0.0 will use whatever hop size + * can be used for WindowType to maintain COLA. + */ + float HopSize; - FSettings() - : WindowType(EWindowType::Hann) - , FFTSize(EFFTSize::Default) - , InterpolationMethod(EPeakInterpolationMethod::Linear) - , HopSize(0.0f) - {} - }; - } + FSpectrumAnalyzerSettings() + : WindowType(EWindowType::Hann) + , FFTSize(EFFTSize::Default) + , InterpolationMethod(EPeakInterpolationMethod::Linear) + , HopSize(0.0f) + {} + }; /** * This struct contains the output results from a singular FFT operation. @@ -79,9 +78,9 @@ namespace Audio { public: FSpectrumAnalyzerBuffer(); - FSpectrumAnalyzerBuffer(const SpectrumAnalyzerSettings::FSettings& InSettings); + FSpectrumAnalyzerBuffer(const FSpectrumAnalyzerSettings& InSettings); - void Reset(const SpectrumAnalyzerSettings::FSettings& InSettings); + void Reset(const FSpectrumAnalyzerSettings& InSettings); // Input. Used on analysis thread to lock a buffer to write to. FSpectrumAnalyzerFrequencyVector* StartWorkOnBuffer(); @@ -106,6 +105,31 @@ namespace Audio FCriticalSection BufferIndicesCriticalSection; }; + class FSpectrumAnalysisAsyncWorker : public FNonAbandonableTask + { + protected: + FSpectrumAnalyzer* Analyzer; + bool bUseLatestAudio; + + public: + FSpectrumAnalysisAsyncWorker(FSpectrumAnalyzer* InAnalyzer, bool bInUseLatestAudio) + : Analyzer(InAnalyzer) + , bUseLatestAudio(bInUseLatestAudio) + {} + + FORCEINLINE TStatId GetStatId() const + { + RETURN_QUICK_DECLARE_CYCLE_STAT(FSpectrumAnalysisAsyncWorker, STATGROUP_ThreadPoolAsyncTasks); + } + + void DoWork(); + + private: + FSpectrumAnalysisAsyncWorker(); + }; + + typedef FAsyncTask FSpectrumAnalyzerTask; + /** * Class built to be a rolling spectrum analyzer for arbitrary, monaural audio data. * Class is meant to scale accuracy with CPU and memory budgets. @@ -115,20 +139,24 @@ namespace Audio class AUDIOMIXER_API FSpectrumAnalyzer { public: - // Default constructor needs to call Init before using + // If an instance is created using the default constructor, Init() must be called before it is used. FSpectrumAnalyzer(); + + // If an instance is created using either of these constructors, Init() is not neccessary. FSpectrumAnalyzer(float InSampleRate); - FSpectrumAnalyzer(const SpectrumAnalyzerSettings::FSettings& InSettings, float InSampleRate); + FSpectrumAnalyzer(const FSpectrumAnalyzerSettings& InSettings, float InSampleRate); + + ~FSpectrumAnalyzer(); // Initialize sample rate of analyzer if not known at time of construction void Init(float InSampleRate); - void Init(const SpectrumAnalyzerSettings::FSettings& InSettings, float InSampleRate); + void Init(const FSpectrumAnalyzerSettings& InSettings, float InSampleRate); // Update the settings used by this Spectrum Analyzer. Safe to call on any thread, but should not be called every tick. - void SetSettings(const SpectrumAnalyzerSettings::FSettings& InSettings); + void SetSettings(const FSpectrumAnalyzerSettings& InSettings); // Get the current settings used by this Spectrum Analyzer. - void GetSettings(SpectrumAnalyzerSettings::FSettings& OutSettings); + void GetSettings(FSpectrumAnalyzerSettings& OutSettings); // Samples magnitude (linearly) for a given frequency, in Hz. float GetMagnitudeForFrequency(float InFrequency); @@ -148,7 +176,14 @@ namespace Audio bool PushAudio(const float* InBuffer, int32 NumSamples); // Thread safe call to perform actual FFT. Returns true if it performed the FFT, false otherwise. - bool PerformAnalysisIfPossible(); + // If bAsync is true, this function will kick off an async task. + // If bUseLatestAudio is set to true, this function will flush the entire input buffer, potentially losing data. + // Otherwise it will only consume enough samples necessary to perform a single FFT. + bool PerformAnalysisIfPossible(bool bUseLatestAudio = false, bool bAsync = false); + + // Returns false if this instance of FSpectrumAnalyzer was constructed with the default constructor + // and Init() has not been called yet. + bool IsInitialized(); private: @@ -156,12 +191,14 @@ namespace Audio void ResetSettings(); // Called in GetMagnitudeForFrequency and GetPhaseForFrequency. - void PerformInterpolation(const FSpectrumAnalyzerFrequencyVector* InFrequencies, SpectrumAnalyzerSettings::EPeakInterpolationMethod InMethod, const float InFreq, float& OutReal, float& OutImag); + void PerformInterpolation(const FSpectrumAnalyzerFrequencyVector* InFrequencies, FSpectrumAnalyzerSettings::EPeakInterpolationMethod InMethod, const float InFreq, float& OutReal, float& OutImag); // Cached current settings. Only actually used in ResetSettings(). - SpectrumAnalyzerSettings::FSettings CurrentSettings; + FSpectrumAnalyzerSettings CurrentSettings; volatile bool bSettingsWereUpdated; + volatile bool bIsInitialized; + float SampleRate; // Cached window that is applied prior to running the FFT. @@ -175,5 +212,9 @@ namespace Audio // if non-null, owns pointer to locked frequency vector we're using. const FSpectrumAnalyzerFrequencyVector* LockedFrequencyVector; + + // This is used if PerformAnalysisIfPossible is called + // with bAsync = true. + TUniquePtr AsyncAnalysisTask; }; } diff --git a/Engine/Source/Runtime/Core/Private/Logging/LogMacros.cpp b/Engine/Source/Runtime/Core/Private/Logging/LogMacros.cpp index 39895cfda560..2a1d3d67b6c7 100644 --- a/Engine/Source/Runtime/Core/Private/Logging/LogMacros.cpp +++ b/Engine/Source/Runtime/Core/Private/Logging/LogMacros.cpp @@ -7,6 +7,7 @@ #include "Misc/FeedbackContext.h" #include "Misc/VarargsHelper.h" #include "Stats/Stats.h" +#include "ProfilingDebugging/CsvProfiler.h" void StaticFailDebug( const TCHAR* Error, const ANSICHAR* File, int32 Line, const TCHAR* Description, bool bIsEnsure, int32 NumStackFramesToIgnore ); @@ -15,6 +16,8 @@ static FCriticalSection MsgLogfStaticBufferGuard; /** Increased from 4096 to fix crashes in the renderthread without autoreporter. */ static TCHAR MsgLogfStaticBuffer[8192]; +CSV_DEFINE_CATEGORY(FMsgLogf, true); + void FMsg::LogfImpl(const ANSICHAR* File, int32 Line, const FName& Category, ELogVerbosity::Type Verbosity, const TCHAR* Fmt, ...) { #if !NO_LOGGING @@ -68,7 +71,8 @@ void FMsg::LogfImpl(const ANSICHAR* File, int32 Line, const FName& Category, ELo void FMsg::Logf_InternalImpl(const ANSICHAR* File, int32 Line, const FName& Category, ELogVerbosity::Type Verbosity, const TCHAR* Fmt, ...) { #if !NO_LOGGING - QUICK_SCOPE_CYCLE_COUNTER(STAT_FMsg_Logf); + QUICK_SCOPE_CYCLE_COUNTER(STAT_FMsgLogf); + CSV_CUSTOM_STAT(FMsgLogf, FMsgLogfCount, 1, ECsvCustomStatOp::Accumulate); if (Verbosity != ELogVerbosity::Fatal) { diff --git a/Engine/Source/Runtime/Core/Private/Misc/ConfigCacheIni.cpp b/Engine/Source/Runtime/Core/Private/Misc/ConfigCacheIni.cpp index 01bf89b7bfc3..9d11e753cf58 100644 --- a/Engine/Source/Runtime/Core/Private/Misc/ConfigCacheIni.cpp +++ b/Engine/Source/Runtime/Core/Private/Misc/ConfigCacheIni.cpp @@ -575,40 +575,7 @@ void FConfigFile::CombineFromBuffer(const FString& Buffer) // If this line is delimited by quotes if( *Value=='\"' ) { - Value++; - //epic moelfke: fixed handling of escaped characters in quoted string - while (*Value && *Value != '\"') - { - if (*Value != '\\') // unescaped character - { - ProcessedValue += *Value++; - } - else if (*++Value == '\\') // escaped forward slash "\\" - { - ProcessedValue += '\\'; - Value++; - } - else if (*Value == '\"') // escaped double quote "\"" - { - ProcessedValue += '\"'; - Value++; - } - else if ( *Value == TEXT('n') ) - { - ProcessedValue += TEXT('\n'); - Value++; - } - else if( *Value == TEXT('u') && Value[1] && Value[2] && Value[3] && Value[4] ) // \uXXXX - UNICODE code point - { - ProcessedValue += (TCHAR)(FParse::HexDigit(Value[1])*(1<<12) + FParse::HexDigit(Value[2])*(1<<8) + FParse::HexDigit(Value[3])*(1<<4) + FParse::HexDigit(Value[4])); - Value += 5; - } - else if( Value[1] ) // some other escape sequence, assume it's a hex character value - { - ProcessedValue += (TCHAR)(FParse::HexDigit(Value[0])*16 + FParse::HexDigit(Value[1])); - Value += 2; - } - } + FParse::QuotedString(Value, ProcessedValue); } else { diff --git a/Engine/Source/Runtime/Core/Private/Misc/DefaultValueHelper.cpp b/Engine/Source/Runtime/Core/Private/Misc/DefaultValueHelper.cpp index dab2e6662d48..9aad3f96fe5c 100644 --- a/Engine/Source/Runtime/Core/Private/Misc/DefaultValueHelper.cpp +++ b/Engine/Source/Runtime/Core/Private/Misc/DefaultValueHelper.cpp @@ -310,33 +310,8 @@ bool FDefaultValueHelper::IsStringValidFloat(const FString& Source) bool FDefaultValueHelper::IsStringValidVector(const FString& Source) { - const TCHAR* Start = StartOf(Source); - const TCHAR* FirstComma = FCString::Strstr( Start, TEXT(",") ); - if(!FirstComma) - { - return false; - } - - const TCHAR* SecondComma = FCString::Strstr( FirstComma + 1, TEXT(",") ); - if(!SecondComma) - { - return false; - } - - // there must be exactly 2 commas in the string - if( FCString::Strstr( SecondComma + 1, TEXT(",") ) ) - { - return false; - } - - const TCHAR* End = EndOf(Source); - if( !IsStringValidFloat( Start, FirstComma ) || - !IsStringValidFloat( FirstComma + 1, SecondComma ) || - !IsStringValidFloat( SecondComma + 1, End ) ) - { - return false; - } - return true; + FVector TempVector; + return FDefaultValueHelper::ParseVector(Source, TempVector); } @@ -348,39 +323,8 @@ bool FDefaultValueHelper::IsStringValidRotator(const FString& Source) bool FDefaultValueHelper::IsStringValidLinearColor(const FString& Source) { - if(Source.IsEmpty()) - { - return false; - } - - const TCHAR* Start = StartOf(Source); - const TCHAR* FirstComma = FCString::Strstr( Start, TEXT(",") ); - if(!FirstComma) - { - return false; - } - - const TCHAR* SecondComma = FCString::Strstr( FirstComma + 1, TEXT(",") ); - if(!SecondComma) - { - return false; - } - - const TCHAR* ThirdComma = FCString::Strstr( SecondComma + 1, TEXT(",") ); - const TCHAR* End = EndOf(Source); - if( ( NULL != ThirdComma ) && !IsStringValidFloat( ThirdComma + 1, End ) ) - { - return false; - } - - if( !IsStringValidFloat( Start, FirstComma ) || - !IsStringValidFloat( FirstComma + 1, SecondComma ) || - !IsStringValidFloat( SecondComma + 1, ( NULL != ThirdComma ) ? ThirdComma : End ) ) - { - return false; - } - - return true; + FLinearColor TempColor; + return ParseLinearColor(Source, TempColor); } @@ -510,7 +454,8 @@ bool FDefaultValueHelper::ParseVector(const FString& Source, FVector& OutVal) !IsStringValidFloat( FirstComma + 1, SecondComma ) || !IsStringValidFloat( SecondComma + 1, End ) ) { - return false; + // Fallback to x= format + return OutVal.InitFromString(Source); } OutVal = FVector( @@ -542,7 +487,8 @@ bool FDefaultValueHelper::ParseVector2D(const FString& Source, FVector2D& OutVal if( !IsStringValidFloat( Start, FirstComma ) || !IsStringValidFloat( FirstComma + 1, End ) ) { - return false; + // Fallback to x= format + return OutVal.InitFromString(Source); } OutVal = FVector2D( @@ -567,7 +513,8 @@ bool FDefaultValueHelper::ParseVector4(const FString& Source, FVector4& OutVal) return true; } - return false; + // Fallback to x= format + return OutVal.InitFromString(Source); } @@ -579,7 +526,9 @@ bool FDefaultValueHelper::ParseRotator(const FString& Source, FRotator& OutVal) OutVal = FRotator(Vector.X, Vector.Y, Vector.Z); return true; } - return false; + + // Fallback to x= format + return OutVal.InitFromString(Source); } @@ -671,7 +620,8 @@ bool FDefaultValueHelper::ParseLinearColor(const FString& Source, FLinearColor& !IsStringValidFloat( FirstComma + 1, SecondComma ) || !IsStringValidFloat( SecondComma + 1, ( NULL != ThirdComma ) ? ThirdComma : End ) ) { - return false; + // Fallback to x= format + return OutVal.InitFromString(Source); } const float Alpha = ( NULL != ThirdComma ) ? FCString::Atof( ThirdComma + 1 ) : 1.0f; @@ -718,7 +668,8 @@ bool FDefaultValueHelper::ParseColor(const FString& Source, FColor& OutVal) !IsStringValidInteger( FirstComma + 1, SecondComma ) || !IsStringValidInteger( SecondComma + 1, ( NULL != ThirdComma ) ? ThirdComma : End ) ) { - return false; + // Fallback to x= format + return OutVal.InitFromString(Source); } const uint8 Alpha = ( NULL != ThirdComma ) ? FCString::Atoi( ThirdComma + 1 ) : 255; diff --git a/Engine/Source/Runtime/Core/Public/Math/TransformCalculus2D.h b/Engine/Source/Runtime/Core/Public/Math/TransformCalculus2D.h index 9947888838d1..db2857169a64 100644 --- a/Engine/Source/Runtime/Core/Public/Math/TransformCalculus2D.h +++ b/Engine/Source/Runtime/Core/Public/Math/TransformCalculus2D.h @@ -657,6 +657,8 @@ public: /** Access to the translation */ const FVector2D& GetTranslation() const { return Trans; } + void SetTranslation(const FVector2D& InTrans) { Trans = InTrans; } + /** * Specialized function to determine if a transform is precisely the identity transform. Uses exact float comparison, so rounding error is not considered. */ diff --git a/Engine/Source/Runtime/Core/Public/Math/UnrealMathNeon.h b/Engine/Source/Runtime/Core/Public/Math/UnrealMathNeon.h index 037a3335dabb..fef247c12379 100644 --- a/Engine/Source/Runtime/Core/Public/Math/UnrealMathNeon.h +++ b/Engine/Source/Runtime/Core/Public/Math/UnrealMathNeon.h @@ -1423,7 +1423,7 @@ FORCEINLINE VectorRegisterInt VectorIntSelect(const VectorRegisterInt& Mask, con * @param Vec Vector to store * @param Ptr Memory pointer */ -#define VectorIntStore( Vec, Ptr ) vst1q_s32( (VectorRegisterInt*)(Ptr), Vec ) +#define VectorIntStore( Vec, Ptr ) vst1q_s32( (int32*)(Ptr), Vec ) /** * Loads 4 int32s from unaligned memory. diff --git a/Engine/Source/Runtime/Core/Public/Serialization/Archive.h b/Engine/Source/Runtime/Core/Public/Serialization/Archive.h index 68cd94d59835..e240487e4d4f 100644 --- a/Engine/Source/Runtime/Core/Public/Serialization/Archive.h +++ b/Engine/Source/Runtime/Core/Public/Serialization/Archive.h @@ -255,7 +255,7 @@ public: /** * Serializes soft object paths from or into this archive. * - * @param Value String asset reference to serialize. + * @param Value Soft object path to serialize. * @return This instance. */ virtual FArchive& operator<<(struct FSoftObjectPath& Value); diff --git a/Engine/Source/Runtime/Core/Public/UObject/FrameworkObjectVersion.h b/Engine/Source/Runtime/Core/Public/UObject/FrameworkObjectVersion.h index 7a3d4c8f0dc7..a0a5718a3199 100644 --- a/Engine/Source/Runtime/Core/Public/UObject/FrameworkObjectVersion.h +++ b/Engine/Source/Runtime/Core/Public/UObject/FrameworkObjectVersion.h @@ -119,6 +119,9 @@ struct CORE_API FFrameworkObjectVersion // Custom event and non-native interface event implementations add 'const' to reference parameters EditableEventsUseConstRefParameters, + // No longer serialize the legacy flag that indicates this state, as it is now implied since we don't serialize the skeleton CDO + BlueprintGeneratedClassIsAlwaysAuthoritative, + // ------------------------------------------------------ VersionPlusOne, LatestVersion = VersionPlusOne - 1 diff --git a/Engine/Source/Runtime/CoreUObject/Private/Blueprint/BlueprintSupport.cpp b/Engine/Source/Runtime/CoreUObject/Private/Blueprint/BlueprintSupport.cpp index 36696d416177..890b897097a9 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Blueprint/BlueprintSupport.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Blueprint/BlueprintSupport.cpp @@ -2041,7 +2041,7 @@ void FLinkerLoad::ResolveDeferredExports(UClass* LoadClass) for (int32 ExportIndex = 0; ExportIndex < ExportMap.Num(); ++ExportIndex) { FObjectExport& Export = ExportMap[ExportIndex]; - if((Export.ObjectFlags & RF_DefaultSubObject) != 0 && Export.OuterIndex.ToExport() == DeferredCDOIndex) + if((Export.ObjectFlags & RF_DefaultSubObject) != 0 && Export.OuterIndex.IsExport() && Export.OuterIndex.ToExport() == DeferredCDOIndex) { if (Export.Object == nullptr && Export.OuterIndex.IsExport()) { @@ -2067,7 +2067,6 @@ void FLinkerLoad::ResolveDeferredExports(UClass* LoadClass) if (ensure(PlaceholderExport)) { // replace the placeholder with the proper object instance - PlaceholderExport->SetLinker(nullptr, INDEX_NONE); Export.ResetObject(); UObject* ExportObj = CreateExport(ExportIndex); @@ -2834,12 +2833,11 @@ FObjectInitializer* FDeferredObjInitializationHelper::DeferObjectInitializerIfNe UObject* TargetObj = DeferringInitializer.GetObj(); if (TargetObj) { - FDeferredCdoInitializationTracker& CdoInitDeferalSys = FDeferredCdoInitializationTracker::Get(); - auto IsSuperCdoReadyToBeCopied = [&CdoInitDeferalSys](const UClass* LoadClass, const UObject* SuperCDO)->bool + auto IsSuperCdoReadyToBeCopied = [](FDeferredCdoInitializationTracker& InCdoInitDeferalSys, const UClass* LoadClass, const UObject* SuperCDO)->bool { // RF_WasLoaded indicates that this Super was loaded from disk (and hasn't been regenerated on load) // regenerated CDOs will not have the RF_LoadCompleted - const bool bSuperCdoLoadPending = CdoInitDeferalSys.IsInitializationDeferred(SuperCDO) || + const bool bSuperCdoLoadPending = InCdoInitDeferalSys.IsInitializationDeferred(SuperCDO) || SuperCDO->HasAnyFlags(RF_NeedLoad) || (SuperCDO->HasAnyFlags(RF_WasLoaded) && !SuperCDO->HasAnyFlags(RF_LoadCompleted)); if (bSuperCdoLoadPending) @@ -2870,8 +2868,9 @@ FObjectInitializer* FDeferredObjInitializationHelper::DeferObjectInitializerIfNe DEFERRED_DEPENDENCY_CHECK(SuperCDO && SuperCDO->HasAnyFlags(RF_ClassDefaultObject)); // use the ObjectArchetype for the super CDO because the SuperClass may have a REINST CDO cached currently SuperClass = SuperCDO->GetClass(); - - if (!IsSuperCdoReadyToBeCopied(CdoClass, SuperCDO)) + + FDeferredCdoInitializationTracker& CdoInitDeferalSys = FDeferredCdoInitializationTracker::Get(); + if (!IsSuperCdoReadyToBeCopied(CdoInitDeferalSys, CdoClass, SuperCDO)) { DeferredInitializerCopy = CdoInitDeferalSys.Add(SuperCDO, DeferringInitializer); } @@ -2896,7 +2895,8 @@ FObjectInitializer* FDeferredObjInitializationHelper::DeferObjectInitializerIfNe // // So if the super CDO isn't ready, we need to defer this sub-object const UObject* SuperCDO = SuperClass->ClassDefaultObject; - if (!IsSuperCdoReadyToBeCopied(OwnerClass, SuperCDO)) + FDeferredCdoInitializationTracker& CdoInitDeferalSys = FDeferredCdoInitializationTracker::Get(); + if (!IsSuperCdoReadyToBeCopied(CdoInitDeferalSys, OwnerClass, SuperCDO)) { FDeferredSubObjInitializationTracker& SubObjInitDeferalSys = FDeferredSubObjInitializationTracker::Get(); DeferredInitializerCopy = SubObjInitDeferalSys.Add(SuperCDO, DeferringInitializer); diff --git a/Engine/Source/Runtime/CoreUObject/Private/Misc/PackageName.cpp b/Engine/Source/Runtime/CoreUObject/Private/Misc/PackageName.cpp index 1bc1b55d91e5..b078157bbb23 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Misc/PackageName.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Misc/PackageName.cpp @@ -1034,12 +1034,12 @@ FString FPackageName::GetNormalizedObjectPath(const FString& ObjectPath) { FString LongPath; - UE_LOG(LogPackageName, Warning, TEXT("String asset reference \"%s\" is in short form, which is unsupported and -- even if valid -- resolving it will be really slow."), *ObjectPath); + UE_LOG(LogPackageName, Warning, TEXT("Asset path \"%s\" is in short form, which is unsupported and -- even if valid -- resolving it will be really slow."), *ObjectPath); UE_LOG(LogPackageName, Warning, TEXT("Please consider resaving package in order to speed-up loading.")); if (!FPackageName::TryConvertShortPackagePathToLongInObjectPath(ObjectPath, LongPath)) { - UE_LOG(LogPackageName, Warning, TEXT("String asset reference \"%s\" could not be resolved."), *ObjectPath); + UE_LOG(LogPackageName, Warning, TEXT("Asset path \"%s\" could not be resolved."), *ObjectPath); } return LongPath; diff --git a/Engine/Source/Runtime/CoreUObject/Private/Misc/RedirectCollector.cpp b/Engine/Source/Runtime/CoreUObject/Private/Misc/RedirectCollector.cpp index 823c81fce182..918f8d1783c9 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/Misc/RedirectCollector.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/Misc/RedirectCollector.cpp @@ -105,7 +105,7 @@ void FRedirectCollector::ResolveAllSoftObjectPaths(FName FilterPackage) if (ToLoad.Len() > 0 ) { - UE_LOG(LogRedirectors, Verbose, TEXT("String Asset Reference '%s'"), *ToLoad); + UE_LOG(LogRedirectors, Verbose, TEXT("Resolving Soft Object Path '%s'"), *ToLoad); UE_CLOG(RefFilenameAndProperty.GetProperty().ToString().Len(), LogRedirectors, Verbose, TEXT(" Referenced by '%s'"), *RefFilenameAndProperty.GetProperty().ToString()); int32 DotIndex = ToLoad.Find(TEXT(".")); @@ -131,7 +131,7 @@ void FRedirectCollector::ResolveAllSoftObjectPaths(FName FilterPackage) else { const FString Referencer = RefFilenameAndProperty.GetProperty().ToString().Len() ? RefFilenameAndProperty.GetProperty().ToString() : TEXT("Unknown"); - UE_LOG(LogRedirectors, Warning, TEXT("String Asset Reference '%s' was not found! (Referencer '%s')"), *ToLoad, *Referencer); + UE_LOG(LogRedirectors, Warning, TEXT("Soft Object Path '%s' was not found when resolving paths! (Referencer '%s')"), *ToLoad, *Referencer); } } } diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/CoreNet.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/CoreNet.cpp index 0b3874bf60aa..bb8ae9da441a 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/CoreNet.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/CoreNet.cpp @@ -415,18 +415,18 @@ FArchive& FNetBitWriter::operator<<( UObject*& Object ) FArchive& FNetBitWriter::operator<<(FSoftObjectPath& Value) { + // It's more efficient to serialize as a string then name+string FString Path = Value.ToString(); - *this << Path; - if (IsLoading()) - { - Value.SetPath(MoveTemp(Path)); - } - return *this; } +FArchive& FNetBitWriter::operator<<(FSoftObjectPtr& Value) +{ + return FArchiveUObject::SerializeSoftObjectPtr(*this, Value); +} + FArchive& FNetBitWriter::operator<<(struct FWeakObjectPtr& WeakObjectPtr) { return FArchiveUObject::SerializeWeakObjectPtr(*this, WeakObjectPtr); @@ -470,18 +470,19 @@ FArchive& FNetBitReader::operator<<( class FName& N ) FArchive& FNetBitReader::operator<<(FSoftObjectPath& Value) { - FString Path = Value.ToString(); - + FString Path; *this << Path; - if (IsLoading()) - { - Value.SetPath(MoveTemp(Path)); - } + Value.SetPath(MoveTemp(Path)); return *this; } +FArchive& FNetBitReader::operator<<(FSoftObjectPtr& Value) +{ + return FArchiveUObject::SerializeSoftObjectPtr(*this, Value); +} + FArchive& FNetBitReader::operator<<(struct FWeakObjectPtr& WeakObjectPtr) { return FArchiveUObject::SerializeWeakObjectPtr(*this, WeakObjectPtr); diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerLoad.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerLoad.cpp index b53325f7428e..9dc1ffc545d2 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerLoad.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/LinkerLoad.cpp @@ -2725,6 +2725,14 @@ bool FLinkerLoad::VerifyImportInner(const int32 ImportIndex, FString& WarningSuf if (!bWasFullyLoaded) { Import.SourceLinker = GetPackageLinker( TmpPkg, NULL, InternalLoadFlags, NULL, NULL ); +#if WITH_EDITORONLY_DATA + if (Import.SourceLinker && !TmpPkg->HasAnyFlags(RF_WasLoaded)) + { + // If we didn't fully load, make sure our metadata is loaded before using this + // We need this case for user defined structs due to the LOAD_DeferDependencyLoads code above + Import.SourceLinker->LoadMetaDataFromExportMap(false); + } +#endif } } else @@ -3008,6 +3016,10 @@ bool FLinkerLoad::VerifyImportInner(const int32 ImportIndex, FString& WarningSuf SafeReplace = true; } } + else + { + SafeReplace = true; + } if (!Import.XObject && !SafeReplace) { diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertySoftObjectPtr.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertySoftObjectPtr.cpp index e6d9af15711f..31cb569f95b3 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertySoftObjectPtr.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/PropertySoftObjectPtr.cpp @@ -69,6 +69,14 @@ void USoftObjectProperty::SerializeItem( FStructuredArchive::FSlot Slot, void* V } } +bool USoftObjectProperty::NetSerializeItem(FArchive& Ar, UPackageMap* Map, void* Data, TArray* MetaData) const +{ + // Serialize directly, will use FBitWriter/Reader + Ar << *(FSoftObjectPtr*)Data; + + return true; +} + void USoftObjectProperty::ExportTextItem( FString& ValueStr, const void* PropertyValue, const void* DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope ) const { FSoftObjectPtr& SoftObjectPtr = *(FSoftObjectPtr*)PropertyValue; diff --git a/Engine/Source/Runtime/CoreUObject/Private/UObject/SavePackage.cpp b/Engine/Source/Runtime/CoreUObject/Private/UObject/SavePackage.cpp index 8c039af95fa2..c40c97ac059a 100644 --- a/Engine/Source/Runtime/CoreUObject/Private/UObject/SavePackage.cpp +++ b/Engine/Source/Runtime/CoreUObject/Private/UObject/SavePackage.cpp @@ -50,7 +50,6 @@ #include "ProfilingDebugging/CookStats.h" #include "UObject/DebugSerializationFlags.h" #include "UObject/EnumProperty.h" -#include "Blueprint/BlueprintSupport.h" #include "HAL/IConsoleManager.h" #include "Serialization/ArchiveStackTrace.h" #include "UObject/CoreRedirects.h" @@ -1357,6 +1356,15 @@ FArchive& FArchiveSaveTagImports::operator<<( UObject*& Obj ) { *this << Parent; } + + // For things with a BP-created class we need to recurse into that class so the import ClassPackage will load properly + // We don't do this for native classes to avoid bloating the import table + UClass* ObjClass = Obj->GetClass(); + + if (!ObjClass->IsNative()) + { + *this << ObjClass; + } } } } diff --git a/Engine/Source/Runtime/CoreUObject/Public/Misc/AssetRegistryInterface.h b/Engine/Source/Runtime/CoreUObject/Public/Misc/AssetRegistryInterface.h index 2aa5a9cc6e75..c8c54f567ee4 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Misc/AssetRegistryInterface.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Misc/AssetRegistryInterface.h @@ -10,7 +10,7 @@ namespace EAssetRegistryDependencyType { enum Type { - // Dependencies which don't need to be loaded for the object to be used (i.e. string asset references) + // Dependencies which don't need to be loaded for the object to be used (i.e. soft object paths) Soft = 0x01, // Dependencies which are required for correct usage of the source asset, and must be loaded at the same time diff --git a/Engine/Source/Runtime/CoreUObject/Public/Misc/RedirectCollector.h b/Engine/Source/Runtime/CoreUObject/Public/Misc/RedirectCollector.h index abba736081a1..c02f1b6e11f6 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/Misc/RedirectCollector.h +++ b/Engine/Source/Runtime/CoreUObject/Public/Misc/RedirectCollector.h @@ -15,7 +15,7 @@ class COREUOBJECT_API FRedirectCollector { private: - /** Helper struct for string asset reference tracking */ + /** Helper struct for soft object path tracking */ struct FPackagePropertyPair { FPackagePropertyPair() : bReferencedByEditorOnlyProperty(false) {} @@ -129,7 +129,7 @@ private: /** A map of assets referenced by soft object paths, with the key being the asset being referenced and the value equal to the package with the reference */ TMultiMap SoftObjectPathMap; - /** When saving, apply this remapping to all string asset references */ + /** When saving, apply this remapping to all soft object paths */ TMap AssetPathRedirectionMap; /** For SoftObjectPackageMap map */ diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/CoreNet.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/CoreNet.h index 102ecd30c60c..2dd10320bb15 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/CoreNet.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/CoreNet.h @@ -293,6 +293,7 @@ public: virtual FArchive& operator<<(FName& Name) override; virtual FArchive& operator<<(UObject*& Object) override; virtual FArchive& operator<<(FSoftObjectPath& Value) override; + virtual FArchive& operator<<(FSoftObjectPtr& Value) override; virtual FArchive& operator<<(struct FWeakObjectPtr& Value) override; virtual void CountMemory(FArchive& Ar) const override; @@ -314,6 +315,7 @@ public: virtual FArchive& operator<<(FName& Name) override; virtual FArchive& operator<<(UObject*& Object) override; virtual FArchive& operator<<(FSoftObjectPath& Value) override; + virtual FArchive& operator<<(FSoftObjectPtr& Value) override; virtual FArchive& operator<<(struct FWeakObjectPtr& Value) override; virtual void CountMemory(FArchive& Ar) const override; diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/SoftObjectPath.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/SoftObjectPath.h index 1f8176172b31..28b37962e892 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/SoftObjectPath.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/SoftObjectPath.h @@ -154,10 +154,10 @@ struct COREUOBJECT_API FSoftObjectPath /** Serializes the internal path and also handles save/PIE fixups. Call this from the archiver overrides */ void SerializePath(FArchive& Ar); - /** Fixes up string asset reference for saving, call if saving with a method that skips SerializePath. This can modify the path, it will return true if it was modified */ + /** Fixes up path for saving, call if saving with a method that skips SerializePath. This can modify the path, it will return true if it was modified */ bool PreSavePath(bool* bReportSoftObjectPathRedirects = nullptr); - /** Handles when a string asset reference has been loaded, call if loading with a method that skips SerializePath. This does not modify path but might call callbacks */ + /** Handles when a path has been loaded, call if loading with a method that skips SerializePath. This does not modify path but might call callbacks */ void PostLoadPath() const; /** Fixes up this SoftObjectPath to add the PIE prefix depending on what is currently active, returns true if it was modified. The overload that takes an explicit PIE instance is preferred, if it's available. */ @@ -323,21 +323,21 @@ public: * Returns the current serialization options that were added using SerializationScope or LinkerLoad * * @param OutPackageName Package that this string asset belongs to - * @param OutPropertyName Property that this string asset reference belongs to + * @param OutPropertyName Property that this path belongs to * @param OutCollectType Type of collecting that should be done * @param Archive The FArchive that is serializing this path if known. If null it will check FUObjectThreadContext */ bool GetSerializationOptions(FName& OutPackageName, FName& OutPropertyName, ESoftObjectPathCollectType& OutCollectType, ESoftObjectPathSerializeType& OutSerializeType, FArchive* Archive = nullptr) const; }; -/** Helper class to set and restore serialization options for string asset references */ +/** Helper class to set and restore serialization options for soft object paths */ struct FSoftObjectPathSerializationScope { /** - * Create a new serialization scope, which affects the way that string asset references are saved + * Create a new serialization scope, which affects the way that soft object paths are saved * * @param SerializingPackageName Package that this string asset belongs to - * @param SerializingPropertyName Property that this string asset reference belongs to + * @param SerializingPropertyName Property that this path belongs to * @param CollectType Set type of collecting that should be done, can be used to disable tracking entirely */ FSoftObjectPathSerializationScope(FName SerializingPackageName, FName SerializingPropertyName, ESoftObjectPathCollectType CollectType, ESoftObjectPathSerializeType SerializeType) diff --git a/Engine/Source/Runtime/CoreUObject/Public/UObject/UnrealType.h b/Engine/Source/Runtime/CoreUObject/Public/UObject/UnrealType.h index 52d16ed28347..577cb756b9ef 100644 --- a/Engine/Source/Runtime/CoreUObject/Public/UObject/UnrealType.h +++ b/Engine/Source/Runtime/CoreUObject/Public/UObject/UnrealType.h @@ -2178,6 +2178,7 @@ class COREUOBJECT_API USoftObjectProperty : public TUObjectPropertyBase * MetaData = NULL) const override; virtual void ExportTextItem( FString& ValueStr, const void* PropertyValue, const void* DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope ) const override; virtual const TCHAR* ImportText_Internal( const TCHAR* Buffer, void* Data, int32 PortFlags, UObject* OwnerObject, FOutputDevice* ErrorText ) const override; virtual EConvertFromTypeResult ConvertFromType(const FPropertyTag& Tag, FStructuredArchive::FSlot Slot, uint8* Data, UStruct* DefaultsStruct) override; diff --git a/Engine/Source/Runtime/D3D12RHI/Private/D3D12Allocation.cpp b/Engine/Source/Runtime/D3D12RHI/Private/D3D12Allocation.cpp index 3d36406a5e2c..98b68c1d699e 100644 --- a/Engine/Source/Runtime/D3D12RHI/Private/D3D12Allocation.cpp +++ b/Engine/Source/Runtime/D3D12RHI/Private/D3D12Allocation.cpp @@ -1577,7 +1577,7 @@ FD3D12SegHeap* FD3D12SegList::CreateBackingHeap( VERIFYD3D12RESULT(Parent->GetDevice()->CreateHeap(&Desc, IID_PPV_ARGS(&D3DHeap))); - FD3D12SegHeap* Ret = new FD3D12SegHeap(Parent, VisibleNodeMask, D3DHeap, this, FreeHeaps.Num()); + FD3D12SegHeap* Ret = new FD3D12SegHeap(Parent, VisibleNodeMask, D3DHeap, HeapSize, this, FreeHeaps.Num()); FreeHeaps.Add(Ret); return Ret; } diff --git a/Engine/Source/Runtime/D3D12RHI/Private/D3D12Allocation.h b/Engine/Source/Runtime/D3D12RHI/Private/D3D12Allocation.h index dc54ca23382c..fd123560ae46 100644 --- a/Engine/Source/Runtime/D3D12RHI/Private/D3D12Allocation.h +++ b/Engine/Source/Runtime/D3D12RHI/Private/D3D12Allocation.h @@ -644,6 +644,7 @@ private: FD3D12Device* Parent, FRHIGPUMask VisibileNodeMask, ID3D12Heap* NewHeap, + uint64 HeapSize, FD3D12SegList* Owner, uint32 Idx) : FD3D12Heap(Parent, VisibileNodeMask), @@ -652,6 +653,7 @@ private: FirstFreeOffset(0) { this->SetHeap(NewHeap); + BeginTrackingResidency(HeapSize); } virtual ~FD3D12SegHeap() = default; diff --git a/Engine/Source/Runtime/Engine/Classes/Components/SkeletalMeshComponent.h b/Engine/Source/Runtime/Engine/Classes/Components/SkeletalMeshComponent.h index 113509fdaef1..d0d09c2f532e 100644 --- a/Engine/Source/Runtime/Engine/Classes/Components/SkeletalMeshComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Components/SkeletalMeshComponent.h @@ -1855,6 +1855,7 @@ public: public: bool IsAnimBlueprintInstanced() const; + void ClearAnimScriptInstance(); protected: bool NeedToSpawnAnimScriptInstance() const; @@ -1897,7 +1898,6 @@ private: bool DoAnyPhysicsBodiesHaveWeight() const; - void ClearAnimScriptInstance(); virtual void RefreshMorphTargets() override; void GetWindForCloth_GameThread(FVector& WindVector, float& WindAdaption) const; diff --git a/Engine/Source/Runtime/Engine/Classes/Components/WindDirectionalSourceComponent.h b/Engine/Source/Runtime/Engine/Classes/Components/WindDirectionalSourceComponent.h index a1912c7a1370..f2456feb619e 100644 --- a/Engine/Source/Runtime/Engine/Classes/Components/WindDirectionalSourceComponent.h +++ b/Engine/Source/Runtime/Engine/Classes/Components/WindDirectionalSourceComponent.h @@ -19,8 +19,8 @@ class FWindData; class FWindSourceSceneProxy; /** Component that provides a directional wind source. Only affects SpeedTree assets. */ -UCLASS(MinimalAPI,collapsecategories, hidecategories=(Object, Mobility), editinlinenew) -class UWindDirectionalSourceComponent : public USceneComponent +UCLASS(collapsecategories, hidecategories=(Object, Mobility), editinlinenew) +class ENGINE_API UWindDirectionalSourceComponent : public USceneComponent { GENERATED_UCLASS_BODY() @@ -77,7 +77,7 @@ public: void SetWindType(EWindSourceType InNewType); /** Calculate wind parameters from the data on this component, safe to call on game thread */ - ENGINE_API bool GetWindParameters(const FVector& EvaluatePosition, FWindData& OutData, float& Weight) const; + bool GetWindParameters(const FVector& EvaluatePosition, FWindData& OutData, float& Weight) const; protected: //~ Begin UActorComponent Interface. diff --git a/Engine/Source/Runtime/Engine/Classes/Curves/IndexedCurve.h b/Engine/Source/Runtime/Engine/Classes/Curves/IndexedCurve.h index ec87ffa0e4e6..5fbfab4e83c2 100644 --- a/Engine/Source/Runtime/Engine/Classes/Curves/IndexedCurve.h +++ b/Engine/Source/Runtime/Engine/Classes/Curves/IndexedCurve.h @@ -46,6 +46,9 @@ public: /** Get the time for the Key with the specified index. */ virtual float GetKeyTime(FKeyHandle KeyHandle) const PURE_VIRTUAL(FIndexedCurve::GetKeyTime, return 0.f;); + /** Allocates a duplicate of the curve */ + virtual FIndexedCurve* Duplicate() const PURE_VIRTUAL(FIndexedCurve::Duplicate, return nullptr;); + /** Checks to see if the key handle is valid for this curve. */ bool IsKeyHandleValid(FKeyHandle KeyHandle) const; diff --git a/Engine/Source/Runtime/Engine/Classes/Curves/IntegralCurve.h b/Engine/Source/Runtime/Engine/Classes/Curves/IntegralCurve.h index c32a3ee14787..4f0beba9a431 100644 --- a/Engine/Source/Runtime/Engine/Classes/Curves/IntegralCurve.h +++ b/Engine/Source/Runtime/Engine/Classes/Curves/IntegralCurve.h @@ -53,6 +53,9 @@ public: /** Get number of keys in curve. */ virtual int32 GetNumKeys() const override final { return Keys.Num(); } + /** Allocates a duplicate of the curve */ + virtual FIndexedCurve* Duplicate() const final { return new FIntegralCurve(*this); } + /** Evaluates the value of an array of keys at a time */ int32 Evaluate(float Time, int32 InDefaultValue = 0) const; diff --git a/Engine/Source/Runtime/Engine/Classes/Curves/NameCurve.h b/Engine/Source/Runtime/Engine/Classes/Curves/NameCurve.h index ff834d46e9d1..e108cf488699 100644 --- a/Engine/Source/Runtime/Engine/Classes/Curves/NameCurve.h +++ b/Engine/Source/Runtime/Engine/Classes/Curves/NameCurve.h @@ -163,6 +163,9 @@ public: virtual int32 GetNumKeys() const override final { return Keys.Num(); } + /** Allocates a duplicate of the curve */ + virtual FIndexedCurve* Duplicate() const final { return new FNameCurve(*this); } + public: /** Sorted array of keys */ diff --git a/Engine/Source/Runtime/Engine/Classes/Curves/RichCurve.h b/Engine/Source/Runtime/Engine/Classes/Curves/RichCurve.h index a48163ab3f09..0b2969fb5341 100644 --- a/Engine/Source/Runtime/Engine/Classes/Curves/RichCurve.h +++ b/Engine/Source/Runtime/Engine/Classes/Curves/RichCurve.h @@ -312,6 +312,9 @@ public: /** Compresses a rich curve for efficient runtime storage and evaluation */ void CompressCurve(struct FCompressedRichCurve& OutCurve, float ErrorThreshold = 0.0001f, float SampleRate = 120.0f) const; + /** Allocates a duplicate of the curve */ + virtual FIndexedCurve* Duplicate() const final { return new FRichCurve(*this); } + private: void RemoveRedundantKeysInternal(float Tolerance, int32 InStartKeepKey, int32 InEndKeepKey); virtual int32 GetKeyIndex(float KeyTime, float KeyTimeTolerance) const override final; diff --git a/Engine/Source/Runtime/Engine/Classes/Curves/SimpleCurve.h b/Engine/Source/Runtime/Engine/Classes/Curves/SimpleCurve.h index 610e14dd6872..dd2ba6d9a077 100644 --- a/Engine/Source/Runtime/Engine/Classes/Curves/SimpleCurve.h +++ b/Engine/Source/Runtime/Engine/Classes/Curves/SimpleCurve.h @@ -186,6 +186,9 @@ public: virtual void RemoveRedundantKeys(float Tolerance) final override; virtual void RemoveRedundantKeys(float Tolerance, float FirstKeyTime, float LastKeyTime) final override; + /** Allocates a duplicate of the curve */ + virtual FIndexedCurve* Duplicate() const final { return new FSimpleCurve(*this); } + protected: virtual int32 GetKeyIndex(float KeyTime, float KeyTimeTolerance) const override final; diff --git a/Engine/Source/Runtime/Engine/Classes/Curves/StringCurve.h b/Engine/Source/Runtime/Engine/Classes/Curves/StringCurve.h index 1f733fa7c5ca..6d15df722a60 100644 --- a/Engine/Source/Runtime/Engine/Classes/Curves/StringCurve.h +++ b/Engine/Source/Runtime/Engine/Classes/Curves/StringCurve.h @@ -212,6 +212,9 @@ public: virtual int32 GetNumKeys() const override final { return Keys.Num(); } + /** Allocates a duplicate of the curve */ + virtual FIndexedCurve* Duplicate() const final { return new FStringCurve(*this); } + public: /** Default value */ diff --git a/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphSchema.h b/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphSchema.h index 9225c4446b84..aa743e9ed297 100644 --- a/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphSchema.h +++ b/Engine/Source/Runtime/Engine/Classes/EdGraph/EdGraphSchema.h @@ -842,8 +842,15 @@ class ENGINE_API UEdGraphSchema : public UObject /** Handles double-clicking on a pin<->pin connection */ virtual void OnPinConnectionDoubleCicked(UEdGraphPin* PinA, UEdGraphPin* PinB, const FVector2D& GraphPosition) const { } - /** Break links on this pin and create links instead on MoveToPin */ - virtual FPinConnectionResponse MovePinLinks(UEdGraphPin& MoveFromPin, UEdGraphPin& MoveToPin, bool bIsIntermediateMove = false) const; + /** + * Break links on this pin and create links instead on MoveToPin + * + * @param MoveFromPin Pin we are breaking links from + * @param MoveToPin Pin we are copying links to + * @param bIsIntermediateMove Allows linking to transient pins, should only be true when called from utility functions + * @param bNotifyLinkedNodes If true, it will notify linked nodes if it fails to move connection, this allows type fixup + */ + virtual FPinConnectionResponse MovePinLinks(UEdGraphPin& MoveFromPin, UEdGraphPin& MoveToPin, bool bIsIntermediateMove = false, bool bNotifyLinkedNodes = false) const; /** Copies pin links from one pin to another without breaking the original links */ virtual FPinConnectionResponse CopyPinLinks(UEdGraphPin& CopyFromPin, UEdGraphPin& CopyToPin, bool bIsIntermediateCopy = false) const; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/Blueprint.h b/Engine/Source/Runtime/Engine/Classes/Engine/Blueprint.h index 6a518800877f..13e86527382d 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/Blueprint.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/Blueprint.h @@ -425,9 +425,6 @@ class ENGINE_API UBlueprint : public UBlueprintCore UPROPERTY() TSubclassOf ParentClass; - UPROPERTY(transient) - UObject* PRIVATE_InnermostPreviousCDO; - /** When the class generated by this blueprint is loaded, it will be recompiled the first time. After that initial recompile, subsequent loads will skip the regeneration step */ UPROPERTY(transient) uint32 bHasBeenRegenerated:1; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/BlueprintCore.h b/Engine/Source/Runtime/Engine/Classes/Engine/BlueprintCore.h index 51ed90431e98..ed88dcb2d48c 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/BlueprintCore.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/BlueprintCore.h @@ -29,25 +29,18 @@ class ENGINE_API UBlueprintCore bool bLegacyNeedToPurgeSkelRefs; private: - - /** BackCompat: Whether or not this blueprint's authoritative CDO data has been migrated from the SkeletonGeneratedClass CDO to the GeneratedClass CDO */ - UPROPERTY() - bool bLegacyGeneratedClassIsAuthoritative; - /** Blueprint Guid */ UPROPERTY() FGuid BlueprintGuid; public: + UE_DEPRECATED(4.22, "The minimum UE4 object version implies the Blueprint generated class is always authoritative. It is no longer necessary to explicitly set it.") + void SetLegacyGeneratedClassIsAuthoritative() {} - void SetLegacyGeneratedClassIsAuthoritative() - { - bLegacyGeneratedClassIsAuthoritative = true; - } - + UE_DEPRECATED(4.22, "The minimum UE4 object version implies the Blueprint generated class is always authoritative. It is no longer necessary to explicitly check it.") bool IsGeneratedClassAuthoritative() { - return bLegacyGeneratedClassIsAuthoritative; + return true; } virtual void Serialize( FArchive& Ar ) override; diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/ContentEncryptionConfig.h b/Engine/Source/Runtime/Engine/Classes/Engine/ContentEncryptionConfig.h index c6b48fbbac15..1763f5960180 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/ContentEncryptionConfig.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/ContentEncryptionConfig.h @@ -1,4 +1,4 @@ -// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #pragma once /** Project specific configuration for content encryption */ diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/StreamableManager.h b/Engine/Source/Runtime/Engine/Classes/Engine/StreamableManager.h index 9722bb207ce8..9a870a2b17c0 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/StreamableManager.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/StreamableManager.h @@ -16,13 +16,17 @@ DECLARE_DELEGATE_OneParam(FStreamableUpdateDelegate, TSharedRef { - /** If this request has finished loading, meaning all available assets were loaded and delegate was called. If assets failed to load they will still be missing */ + /** + * If this request has finished loading, meaning all available assets were loaded + * Any assets that failed to load will still be null + * This can be true before the completion callback has happened as it may be in the delayed callback queue + */ bool HasLoadCompleted() const { return bLoadCompleted; } - /** If this request was cancelled. Assets may still have been loaded, but delegate will not be called */ + /** If this request was cancelled. Assets may still have been loaded, but completion delegate was not called */ bool WasCanceled() const { return bCanceled; @@ -76,8 +80,8 @@ struct ENGINE_API FStreamableHandle : public TSharedFromThis AssociatedHandle = nullptr); + /** + * Calls a StreamableDelegate, this will add to the delayed callback queue depending on s.StreamableDelegateDelayFrames + * + * @param Delegate Primary delegate to execute + * @param AssociatedHandle Streamable handle associated with this delegate, may be null + * @param CancelDelegate If handle gets cancelled before primary delegate executes, this delegate will be called instead + */ + static void ExecuteDelegate(const FStreamableDelegate& Delegate, TSharedPtr AssociatedHandle = nullptr, const FStreamableDelegate& CancelDelegate = FStreamableDelegate()); /** Destructor */ ~FStreamableHandle(); @@ -297,7 +307,7 @@ struct ENGINE_API FStreamableManager : public FGCObject /** Returns true if all pending async loads have finished for this target */ bool IsAsyncLoadComplete(const FSoftObjectPath& Target) const; - /** This will release any managed active handles pointing to the target string asset reference, even if they include other requested assets in the same load */ + /** This will release any managed active handles pointing to the target soft object path, even if they include other requested assets in the same load */ void Unload(const FSoftObjectPath& Target); /** Checks for any redirectors that were previously loaded, and returns the redirected target if found. This will not handle redirects that it doesn't yet know about */ diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/TextureRenderTarget.h b/Engine/Source/Runtime/Engine/Classes/Engine/TextureRenderTarget.h index d21f8022565d..5f30f04cdaf5 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/TextureRenderTarget.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/TextureRenderTarget.h @@ -26,6 +26,9 @@ class UTextureRenderTarget : public UTexture /** If true, there will be two copies in memory - one for the texture and one for the render target. If false, they will share memory if possible. This is useful for scene capture textures that are used in the scene. */ uint32 bNeedsTwoCopies:1; + /** If true, it will be possible to create a FUnorderedAccessViewRHIRef using RHICreateUnorderedAccessView and the internal FTexture2DRHIRef. */ + uint32 bCanCreateUAV : 1; + /** * Render thread: Access the render target resource for this texture target object * @return pointer to resource or NULL if not initialized diff --git a/Engine/Source/Runtime/Engine/Classes/Engine/ViewportSplitScreen.h b/Engine/Source/Runtime/Engine/Classes/Engine/ViewportSplitScreen.h index f96551b7233b..4448db6b4f18 100644 --- a/Engine/Source/Runtime/Engine/Classes/Engine/ViewportSplitScreen.h +++ b/Engine/Source/Runtime/Engine/Classes/Engine/ViewportSplitScreen.h @@ -31,10 +31,14 @@ namespace ESplitScreenType ThreePlayer_FavorBottom, //3 Player vertical split ThreePlayer_Vertical, + //3 Player horizontal split + ThreePlayer_Horizontal, // 4 Player grid split FourPlayer_Grid, // 4 Player vertical split FourPlayer_Vertical, + // 4 Player horizontal split + FourPlayer_Horizontal, SplitTypeCount }; diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/Controller.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/Controller.h index 9510a5c1906d..f765a2b7e35b 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/Controller.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/Controller.h @@ -93,11 +93,13 @@ protected: * update only some components of the rotation). */ UPROPERTY(EditDefaultsOnly, AdvancedDisplay, Category="Controller|Transform") - uint32 bAttachToPawn:1; + uint8 bAttachToPawn:1; /** Whether this controller is a PlayerController. */ - UPROPERTY() - uint32 bIsPlayerController:1; + uint8 bIsPlayerController:1; + + /** Whether the controller must have authority to be able to call possess on a Pawn */ + uint8 bCanPossessWithoutAuthority:1; /** Ignores movement input. Stacked state storage, Use accessor function IgnoreMoveInput() */ uint8 IgnoreMoveInput; @@ -261,12 +263,28 @@ public: * @see HasAuthority() */ UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category=Pawn, meta=(Keywords="set controller")) - virtual void Possess(APawn* InPawn); + virtual void Possess(APawn* InPawn) final; // DEPRECATED(4.22, "Posssess is marked virtual final as you should now be overriding OnPossess instead") /** Called to unpossess our pawn for any reason that is not the pawn being destroyed (destruction handled by PawnDestroyed()). */ UFUNCTION(BlueprintCallable, Category=Pawn, meta=(Keywords="set controller")) - virtual void UnPossess(); + virtual void UnPossess() final; // DEPRECATED(4.22, "Posssess is marked virtual final as you should now be overriding OnUnPossess instead") +protected: + /** Blueprint implementable event to react to the controller possessing a pawn */ + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "On Possess")) + void ReceivePossess(APawn* PossessedPawn); + + /** Overridable native function for when this controller possesses a pawn. */ + virtual void OnPossess(APawn* InPawn); + + /** Blueprint implementable event to react to the controller unpossessing a pawn */ + UFUNCTION(BlueprintImplementableEvent, meta = (DisplayName = "On UnPossess")) + void ReceiveUnPossess(APawn* UnpossessedPawn); + + /** Overridable native function for when this controller unpossesses its pawn. */ + virtual void OnUnPossess(); + +public: /** * Called to unpossess our pawn because it is going to be destroyed. * (other unpossession handled by UnPossess()) diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/ForceFeedbackEffect.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/ForceFeedbackEffect.h index debbd25e7841..c26355e6ffa0 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/ForceFeedbackEffect.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/ForceFeedbackEffect.h @@ -39,6 +39,31 @@ struct FForceFeedbackChannelDetails } }; +/** This structure is used to pass arguments to ClientPlayForceFeedback() client RPC function */ +USTRUCT() +struct FForceFeedbackParameters +{ + GENERATED_BODY() + + FForceFeedbackParameters() + : bLooping(false) + , bIgnoreTimeDilation(false) + , bPlayWhilePaused(false) + {} + + UPROPERTY() + FName Tag; + + UPROPERTY() + bool bLooping; + + UPROPERTY() + bool bIgnoreTimeDilation; + + UPROPERTY() + bool bPlayWhilePaused; +}; + USTRUCT() struct ENGINE_API FActiveForceFeedbackEffect { @@ -47,25 +72,18 @@ struct ENGINE_API FActiveForceFeedbackEffect UPROPERTY() class UForceFeedbackEffect* ForceFeedbackEffect; - FName Tag; - uint32 bLooping:1; - uint32 bIgnoreTimeDilation:1; + FForceFeedbackParameters Parameters; float PlayTime; FActiveForceFeedbackEffect() : ForceFeedbackEffect(nullptr) - , Tag(NAME_None) - , bLooping(false) - , bIgnoreTimeDilation(false) , PlayTime(0.f) { } - FActiveForceFeedbackEffect(UForceFeedbackEffect* InEffect, const bool bInLooping, const bool bInIgnoreTimeDilation, const FName InTag) + FActiveForceFeedbackEffect(UForceFeedbackEffect* InEffect, FForceFeedbackParameters InParameters) : ForceFeedbackEffect(InEffect) - , Tag(InTag) - , bLooping(bInLooping) - , bIgnoreTimeDilation(bInIgnoreTimeDilation) + , Parameters(InParameters) , PlayTime(0.f) { } diff --git a/Engine/Source/Runtime/Engine/Classes/GameFramework/PlayerController.h b/Engine/Source/Runtime/Engine/Classes/GameFramework/PlayerController.h index 017fa3ec7490..526237bc62b7 100644 --- a/Engine/Source/Runtime/Engine/Classes/GameFramework/PlayerController.h +++ b/Engine/Source/Runtime/Engine/Classes/GameFramework/PlayerController.h @@ -1031,16 +1031,37 @@ public: * @param ForceFeedbackEffect The force feedback pattern to play * @param bLooping Whether the pattern should be played repeatedly or be a single one shot * @param bIgnoreTimeDilation Whether the pattern should ignore time dilation + * @param bPlayWhilePaused Whether the pattern should continue to play while the game is paused * @param Tag A tag that allows stopping of an effect. If another effect with this Tag is playing, it will be stopped and replaced */ - UFUNCTION(unreliable, client, BlueprintCallable, Category="Game|Feedback") + UFUNCTION(BlueprintCallable, Category="Game|Feedback", meta=(DisplayName="Client Play Force Feedback", AdvancedDisplay="bIgnoreTimeDilation,bPlayWhilePaused")) + void K2_ClientPlayForceFeedback(class UForceFeedbackEffect* ForceFeedbackEffect, FName Tag, bool bLooping, bool bIgnoreTimeDilation, bool bPlayWhilePaused); + +private: + /** + * Internal replicated version of client play force feedback event. + * Cannot be named ClientPlayForceFeedback as redirector for blueprint function version to K2_... does not work in that case + */ + UFUNCTION(unreliable, client) + void ClientPlayForceFeedback_Internal(class UForceFeedbackEffect* ForceFeedbackEffect, FForceFeedbackParameters Params = FForceFeedbackParameters()); + +public: + + /** + * Play a force feedback pattern on the player's controller + * @param ForceFeedbackEffect The force feedback pattern to play + * @param Params Parameter struct to customize playback behavior of the feedback effect + */ + void ClientPlayForceFeedback(class UForceFeedbackEffect* ForceFeedbackEffect, FForceFeedbackParameters Params = FForceFeedbackParameters()) + { + ClientPlayForceFeedback_Internal(ForceFeedbackEffect, Params); + } + + UE_DEPRECATED(4.22, "Use version that specifies parameters using a struct instead of a list of parameters") void ClientPlayForceFeedback(class UForceFeedbackEffect* ForceFeedbackEffect, bool bLooping, bool bIgnoreTimeDilation, FName Tag); - UE_DEPRECATED(4.18, "Use version that specifies whether to ignore time dilation or not") - void ClientPlayForceFeedback(class UForceFeedbackEffect* ForceFeedbackEffect, bool bLooping, FName Tag) - { - ClientPlayForceFeedback(ForceFeedbackEffect, bLooping, false, Tag); - } + UE_DEPRECATED(4.18, "Use version that specifies parameters using a struct instead of a list of parameters") + void ClientPlayForceFeedback(class UForceFeedbackEffect* ForceFeedbackEffect, bool bLooping, FName Tag); /** * Stops a playing force feedback pattern @@ -1503,8 +1524,10 @@ public: virtual bool IsNetRelevantFor(const AActor* RealViewer, const AActor* ViewTarget, const FVector& SrcLocation) const override; virtual void FellOutOfWorld(const class UDamageType& dmgType) override; virtual void Reset() override; - virtual void Possess(APawn* aPawn) override; - virtual void UnPossess() override; +protected: + virtual void OnPossess(APawn* aPawn) override; + virtual void OnUnPossess() override; +public: virtual void CleanupPlayerState() override; virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; virtual void Destroyed() override; diff --git a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetArrayLibrary.h b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetArrayLibrary.h index 3e22f52f439e..138a1845e225 100644 --- a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetArrayLibrary.h +++ b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetArrayLibrary.h @@ -46,6 +46,16 @@ class ENGINE_API UKismetArrayLibrary : public UBlueprintFunctionLibrary UFUNCTION(BlueprintCallable, CustomThunk, meta=(DisplayName = "Shuffle", CompactNodeTitle = "SHUFFLE", ArrayParm = "TargetArray"), Category="Utilities|Array") static void Array_Shuffle(const TArray& TargetArray); + /** + * Checks if two arrays are memberwise identical + * + * @param ArrayA One of the arrays to compare + * @param ArrayB The other array to compare + * @return Whether the two arrays are identical + */ + UFUNCTION(BlueprintPure, CustomThunk, meta=(DisplayName = "Identical", CompactNodeTitle = "==", ArrayParm = "ArrayA,ArrayB", ArrayTypeDependentParams = "ArrayB"), Category="Utilities|Array") + static bool Array_Identical(const TArray& ArrayA, const TArray& ArrayB); + /** *Append an array to another array * @@ -203,6 +213,7 @@ class ENGINE_API UKismetArrayLibrary : public UBlueprintFunctionLibrary static int32 GenericArray_Add(void* TargetArray, const UArrayProperty* ArrayProp, const void* NewItem); static int32 GenericArray_AddUnique(void* TargetArray, const UArrayProperty* ArrayProp, const void* NewItem); static void GenericArray_Shuffle(void* TargetArray, const UArrayProperty* ArrayProp); + static bool GenericArray_Identical(void* ArrayA, const UArrayProperty* ArrayAProp, void* ArrayB, const UArrayProperty* ArrayBProperty); static void GenericArray_Append(void* TargetArray, const UArrayProperty* TargetArrayProp, void* SourceArray, const UArrayProperty* SourceArrayProperty); static void GenericArray_Insert(void* TargetArray, const UArrayProperty* ArrayProp, const void* NewItem, int32 Index); static void GenericArray_Remove(void* TargetArray, const UArrayProperty* ArrayProp, int32 IndexToRemove); @@ -303,6 +314,35 @@ public: P_NATIVE_END; } + DECLARE_FUNCTION(execArray_Identical) + { + // Retrieve the first array + Stack.MostRecentProperty = nullptr; + Stack.StepCompiledIn(NULL); + void* ArrayAAddr = Stack.MostRecentPropertyAddress; + UArrayProperty* ArrayAProperty = Cast(Stack.MostRecentProperty); + if (!ArrayAProperty) + { + Stack.bArrayContextFailed = true; + return; + } + // Retrieve the second array + Stack.MostRecentProperty = nullptr; + Stack.StepCompiledIn(NULL); + void* ArrayBAddr = Stack.MostRecentPropertyAddress; + UArrayProperty* ArrayBProperty = Cast(Stack.MostRecentProperty); + if (!ArrayBProperty) + { + Stack.bArrayContextFailed = true; + return; + } + + P_FINISH; + P_NATIVE_BEGIN; + *(bool*)RESULT_PARAM = GenericArray_Identical(ArrayAAddr, ArrayAProperty, ArrayBAddr, ArrayBProperty); + P_NATIVE_END; + } + DECLARE_FUNCTION(execArray_Append) { // Retrieve the target array diff --git a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetSystemLibrary.h b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetSystemLibrary.h index 9de88c24bbc6..0bc5e9acc1ae 100644 --- a/Engine/Source/Runtime/Engine/Classes/Kismet/KismetSystemLibrary.h +++ b/Engine/Source/Runtime/Engine/Classes/Kismet/KismetSystemLibrary.h @@ -113,6 +113,10 @@ class ENGINE_API UKismetSystemLibrary : public UBlueprintFunctionLibrary UFUNCTION(BlueprintPure, Category = "Utilities", meta = (DisplayName = "Get Display Name")) static FString GetClassDisplayName(UClass* Class); + // Returns the outer object of an object. + UFUNCTION(BlueprintPure, Category = "Utilities") + static UObject* GetOuterObject(const UObject* Object); + // Engine build number, for displaying to end users. UFUNCTION(BlueprintPure, Category="Development", meta=(BlueprintThreadSafe)) static FString GetEngineVersion(); @@ -385,8 +389,8 @@ class ENGINE_API UKismetSystemLibrary : public UBlueprintFunctionLibrary * @param VariableName Name of the console variable to find. * @return The value if found, 0 otherwise. */ - UFUNCTION(BlueprintCallable, Category="Development",meta=(WorldContext="WorldContextObject")) - static float GetConsoleVariableFloatValue(UObject* WorldContextObject, const FString& VariableName); + UFUNCTION(BlueprintCallable, Category="Development") + static float GetConsoleVariableFloatValue(const FString& VariableName); /** * Attempts to retrieve the value of the specified integer console variable, if it exists. @@ -394,8 +398,17 @@ class ENGINE_API UKismetSystemLibrary : public UBlueprintFunctionLibrary * @param VariableName Name of the console variable to find. * @return The value if found, 0 otherwise. */ - UFUNCTION(BlueprintCallable, Category="Development",meta=(WorldContext="WorldContextObject")) - static int32 GetConsoleVariableIntValue(UObject* WorldContextObject, const FString& VariableName); + UFUNCTION(BlueprintCallable, Category="Development") + static int32 GetConsoleVariableIntValue(const FString& VariableName); + + /** + * Evaluates, if it exists, whether the specified integer console variable has a non-zero value (true) or not (false). + * + * @param VariableName Name of the console variable to find. + * @return True if found and has a non-zero value, false otherwise. + */ + UFUNCTION(BlueprintCallable, Category="Development") + static bool GetConsoleVariableBoolValue(const FString& VariableName); /** * Exit the current game diff --git a/Engine/Source/Runtime/Engine/Classes/VectorField/VectorFieldStatic.h b/Engine/Source/Runtime/Engine/Classes/VectorField/VectorFieldStatic.h index 008b1afac1fa..075af762cece 100644 --- a/Engine/Source/Runtime/Engine/Classes/VectorField/VectorFieldStatic.h +++ b/Engine/Source/Runtime/Engine/Classes/VectorField/VectorFieldStatic.h @@ -12,6 +12,7 @@ #include "VectorField/VectorField.h" #include "VectorFieldStatic.generated.h" +class FRHITexture; struct FPropertyChangedEvent; UCLASS(hidecategories=VectorFieldBounds, MinimalAPI) @@ -31,6 +32,9 @@ class UVectorFieldStatic : public UVectorField UPROPERTY(Category=VectorFieldStatic, VisibleAnywhere) int32 SizeZ; + /** Whether to keep vector field data accessible to the CPU. */ + UPROPERTY(Category=VectorFieldStatic, EditAnywhere) + bool bAllowCPUAccess; public: /** The resource for this vector field. */ @@ -39,6 +43,11 @@ public: /** Source vector data. */ FByteBulkData SourceData; + /** Local copy of the source vector data. */ + UPROPERTY(Transient) + TArray CPUData; + + #if WITH_EDITORONLY_DATA UPROPERTY() FString SourceFilePath_DEPRECATED; @@ -68,6 +77,17 @@ public: * Initialize resources. */ ENGINE_API void InitResource(); + + /** Takes a local copy of the source bulk data so that it is readable at runtime on the CPU. */ + ENGINE_API void UpdateCPUData(); + +#if WITH_EDITOR + /** Sets the bAllowCPUAccess flag and calls UpdateCPUData(). */ + ENGINE_API void SetCPUAccessEnabled(); +#endif // WITH_EDITOR + + /** Returns a reference to a 3D texture handle for the GPU data. */ + ENGINE_API FRHITexture* GetVolumeTextureRef(); private: /** Permit the factory class to update and release resources externally. */ @@ -84,5 +104,6 @@ private: */ ENGINE_API void ReleaseResource(); + }; diff --git a/Engine/Source/Runtime/Engine/Private/Actor.cpp b/Engine/Source/Runtime/Engine/Private/Actor.cpp index bcff0ca6e684..803ae4f347db 100644 --- a/Engine/Source/Runtime/Engine/Private/Actor.cpp +++ b/Engine/Source/Runtime/Engine/Private/Actor.cpp @@ -2715,7 +2715,10 @@ void AActor::ClearInstanceComponents(const bool bDestroyComponents) // Run in reverse to reduce memory churn when the components are removed from InstanceComponents for (int32 Index=CachedComponents.Num()-1; Index >= 0; --Index) { - CachedComponents[Index]->DestroyComponent(); + if (CachedComponents[Index]) + { + CachedComponents[Index]->DestroyComponent(); + } } } else @@ -3049,9 +3052,9 @@ void AActor::FinishSpawning(const FTransform& UserTransform, bool bIsDefaultTran } } - // should be fast and relatively rare - ValidateDeferredTransformCache(); - } + // should be fast and relatively rare + ValidateDeferredTransformCache(); + } FinalRootComponentTransform.GetLocation().DiagnosticCheckNaN(TEXT("AActor::FinishSpawning: FinalRootComponentTransform.GetLocation()")); FinalRootComponentTransform.GetRotation().DiagnosticCheckNaN(TEXT("AActor::FinishSpawning: FinalRootComponentTransform.GetRotation()")); diff --git a/Engine/Source/Runtime/Engine/Private/ActorConstruction.cpp b/Engine/Source/Runtime/Engine/Private/ActorConstruction.cpp index d9a4c695ac12..19b0cf17b2e4 100644 --- a/Engine/Source/Runtime/Engine/Private/ActorConstruction.cpp +++ b/Engine/Source/Runtime/Engine/Private/ActorConstruction.cpp @@ -1044,7 +1044,8 @@ UActorComponent* AActor::CreateComponentFromTemplateData(const FBlueprintCookedC UActorComponent* AActor::AddComponent(FName TemplateName, bool bManualAttachment, const FTransform& RelativeTransform, const UObject* ComponentTemplateContext) { - if (GetWorld()->bIsTearingDown) + UWorld* World = GetWorld(); + if (World->bIsTearingDown) { UE_LOG(LogActor, Warning, TEXT("AddComponent failed because we are in the process of tearing down the world")); return nullptr; @@ -1115,7 +1116,6 @@ UActorComponent* AActor::AddComponent(FName TemplateName, bool bManualAttachment NewActorComp->RegisterComponent(); } - UWorld* World = GetWorld(); if (!bRunningUserConstructionScript && World && bIsSceneComponent) { UPrimitiveComponent* NewPrimitiveComponent = Cast(NewActorComp); diff --git a/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp b/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp index 3d2c93082f92..0354dd0fcd1e 100644 --- a/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp +++ b/Engine/Source/Runtime/Engine/Private/Animation/AnimSequence.cpp @@ -468,6 +468,13 @@ void UAnimSequence::Serialize(FArchive& Ar) SourceFileTimestamp_DEPRECATED = TEXT(""); } + // Do this is serialize as if the default animation curve compression asset isn't loaded it will + // fire a warning if we try and load it in post load + if (CurveCompressionSettings == nullptr || !CurveCompressionSettings->AreSettingsValid()) + { + CurveCompressionSettings = FAnimationUtils::GetDefaultAnimationCurveCompressionSettings(); + } + #endif // WITH_EDITORONLY_DATA AddAnimLoadingDebugEntry(TEXT("PostSerialize")); @@ -867,7 +874,8 @@ void UAnimSequence::PostEditChangeProperty(FPropertyChangedEvent& PropertyChange PostProcessSequence(); } - if (PropertyChangedEvent.Property != nullptr && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UAnimSequence, CurveCompressionSettings)) + UProperty* Property = PropertyChangedEvent.Property; + if (Property != nullptr && Property->GetFName() == GET_MEMBER_NAME_CHECKED(UAnimSequence, CurveCompressionSettings)) { RequestSyncAnimRecompression(false); } diff --git a/Engine/Source/Runtime/Engine/Private/AssetManager.cpp b/Engine/Source/Runtime/Engine/Private/AssetManager.cpp index 5f35c16cc4df..8c238d5dfa01 100644 --- a/Engine/Source/Runtime/Engine/Private/AssetManager.cpp +++ b/Engine/Source/Runtime/Engine/Private/AssetManager.cpp @@ -3053,7 +3053,7 @@ const TMap& UAssetManager::GetChunkManagementMap( void UAssetManager::ApplyPrimaryAssetLabels() { - // Load all of them off disk. Turn off string asset reference tracking to avoid them getting cooked + // Load all of them off disk. Turn off soft object path tracking to avoid them getting cooked FSoftObjectPathSerializationScope SerializationScope(NAME_None, NAME_None, ESoftObjectPathCollectType::NeverCollect, ESoftObjectPathSerializeType::AlwaysSerialize); TSharedPtr Handle = LoadPrimaryAssetsWithType(PrimaryAssetLabelType); diff --git a/Engine/Source/Runtime/Engine/Private/AudioVolume.cpp b/Engine/Source/Runtime/Engine/Private/AudioVolume.cpp index 3912f390029b..57baa33b27ef 100644 --- a/Engine/Source/Runtime/Engine/Private/AudioVolume.cpp +++ b/Engine/Source/Runtime/Engine/Private/AudioVolume.cpp @@ -210,18 +210,19 @@ FAudioVolumeProxy::FAudioVolumeProxy(const AAudioVolume* AudioVolume) void AAudioVolume::AddProxy() const { - UWorld* World = GetWorld(); - - if (FAudioDevice* AudioDevice = World->GetAudioDevice()) + if (UWorld* World = GetWorld()) { - DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.AddAudioVolumeProxy"), STAT_AudioAddAudioVolumeProxy, STATGROUP_TaskGraphTasks); - - FAudioVolumeProxy Proxy(this); - - FAudioThread::RunCommandOnAudioThread([AudioDevice, Proxy]() + if (FAudioDevice* AudioDevice = World->GetAudioDevice()) { - AudioDevice->AddAudioVolumeProxy(Proxy); - }, GET_STATID(STAT_AudioAddAudioVolumeProxy)); + DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.AddAudioVolumeProxy"), STAT_AudioAddAudioVolumeProxy, STATGROUP_TaskGraphTasks); + + FAudioVolumeProxy Proxy(this); + + FAudioThread::RunCommandOnAudioThread([AudioDevice, Proxy]() + { + AudioDevice->AddAudioVolumeProxy(Proxy); + }, GET_STATID(STAT_AudioAddAudioVolumeProxy)); + } } } @@ -266,18 +267,19 @@ void FAudioDevice::RemoveAudioVolumeProxy(const uint32 AudioVolumeID) void AAudioVolume::UpdateProxy() const { - UWorld* World = GetWorld(); - - if (FAudioDevice* AudioDevice = World->GetAudioDevice()) + if (UWorld* World = GetWorld()) { - DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.UpdateAudioVolumeProxy"), STAT_AudioUpdateAudioVolumeProxy, STATGROUP_TaskGraphTasks); - - FAudioVolumeProxy Proxy(this); - - FAudioThread::RunCommandOnAudioThread([AudioDevice, Proxy]() + if (FAudioDevice* AudioDevice = World->GetAudioDevice()) { - AudioDevice->UpdateAudioVolumeProxy(Proxy); - }, GET_STATID(STAT_AudioUpdateAudioVolumeProxy)); + DECLARE_CYCLE_STAT(TEXT("FAudioThreadTask.UpdateAudioVolumeProxy"), STAT_AudioUpdateAudioVolumeProxy, STATGROUP_TaskGraphTasks); + + FAudioVolumeProxy Proxy(this); + + FAudioThread::RunCommandOnAudioThread([AudioDevice, Proxy]() + { + AudioDevice->UpdateAudioVolumeProxy(Proxy); + }, GET_STATID(STAT_AudioUpdateAudioVolumeProxy)); + } } } @@ -367,10 +369,13 @@ void AAudioVolume::SetPriority(const float NewPriority) if (NewPriority != Priority) { Priority = NewPriority; - GetWorld()->AudioVolumes.Sort([](const AAudioVolume& A, const AAudioVolume& B) { return (A.GetPriority() > B.GetPriority()); }); - if (bEnabled) + if (UWorld* World = GetWorld()) { - UpdateProxy(); + World->AudioVolumes.Sort([](const AAudioVolume& A, const AAudioVolume& B) { return (A.GetPriority() > B.GetPriority()); }); + if (bEnabled) + { + UpdateProxy(); + } } } } @@ -413,7 +418,10 @@ void AAudioVolume::PostEditChangeProperty(FPropertyChangedEvent& PropertyChanged if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AAudioVolume, Priority)) { - GetWorld()->AudioVolumes.Sort([](const AAudioVolume& A, const AAudioVolume& B) { return (A.GetPriority() > B.GetPriority()); }); + if (UWorld* World = GetWorld()) + { + World->AudioVolumes.Sort([](const AAudioVolume& A, const AAudioVolume& B) { return (A.GetPriority() > B.GetPriority()); }); + } } if (PropertyChangedEvent.Property && PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(AAudioVolume, bEnabled)) diff --git a/Engine/Source/Runtime/Engine/Private/Blueprint.cpp b/Engine/Source/Runtime/Engine/Private/Blueprint.cpp index 9b1aae30cc71..3e7abce50190 100644 --- a/Engine/Source/Runtime/Engine/Private/Blueprint.cpp +++ b/Engine/Source/Runtime/Engine/Private/Blueprint.cpp @@ -4,6 +4,7 @@ #include "Misc/CoreMisc.h" #include "Misc/ConfigCacheIni.h" #include "UObject/BlueprintsObjectVersion.h" +#include "UObject/FrameworkObjectVersion.h" #include "UObject/UObjectHash.h" #include "Serialization/PropertyLocalizationDataGathering.h" #include "UObject/UnrealType.h" @@ -285,7 +286,6 @@ UBlueprintCore::UBlueprintCore(const FObjectInitializer& ObjectInitializer) { static const FAutoRegisterLocalizationDataGatheringCallback AutomaticRegistrationOfLocalizationGatherer(UBlueprintCore::StaticClass(), &GatherBlueprintForLocalization); } #endif - bLegacyGeneratedClassIsAuthoritative = false; bLegacyNeedToPurgeSkelRefs = true; } @@ -295,9 +295,15 @@ void UBlueprintCore::Serialize(FArchive& Ar) #if WITH_EDITOR Ar.UsingCustomVersion(FBlueprintsObjectVersion::GUID); -#endif + Ar.UsingCustomVersion(FFrameworkObjectVersion::GUID); - Ar << bLegacyGeneratedClassIsAuthoritative; + if (Ar.IsLoading() && Ar.CustomVer(FFrameworkObjectVersion::GUID) < FFrameworkObjectVersion::BlueprintGeneratedClassIsAlwaysAuthoritative) + { + // No longer in use. + bool bLegacyGeneratedClassIsAuthoritative; + Ar << bLegacyGeneratedClassIsAuthoritative; + } +#endif if ((Ar.UE4Ver() < VER_UE4_BLUEPRINT_SKEL_CLASS_TRANSIENT_AGAIN) && (Ar.UE4Ver() != VER_UE4_BLUEPRINT_SKEL_TEMPORARY_TRANSIENT)) @@ -1331,10 +1337,24 @@ void UBlueprint::BeginCacheForCookedPlatformData(const ITargetPlatform *TargetPl if (bIsOwnerClassTargetedForReplacement) { - // Use the template's archetype for the delta serialization here; remaining properties will have already been set via native subobject instancing at runtime. - constexpr bool bUseTemplateArchetype = true; - FBlueprintEditorUtils::BuildComponentInstancingData(RecordIt->ComponentTemplate, RecordIt->CookedComponentInstancingData, bUseTemplateArchetype); - ++NumCookedComponents; + // EDL is required, because we need to enforce a preload dependency on the CDO (see UBlueprintGeneratedClass::GetDefaultObjectPreloadDependencies). This is + // difficult to support in the non-EDL case, because we have to enforce the dependency at runtime, which can lead to unpredictable results in a cooked build. + if (IsEventDrivenLoaderEnabledInCookedBuilds()) + { + // Use the template's archetype for the delta serialization here; remaining properties will have already been set via native subobject instancing at runtime. + constexpr bool bUseTemplateArchetype = true; + FBlueprintEditorUtils::BuildComponentInstancingData(RecordIt->ComponentTemplate, RecordIt->CookedComponentInstancingData, bUseTemplateArchetype); + ++NumCookedComponents; + } + else + { + UE_LOG(LogBlueprint, Error, TEXT("%s overrides component \'%s\' inherited from %s, which will be converted to C++. This requires Event-Driven Loading (EDL) to be enabled; otherwise, %s must be excluded from Blueprint nativization."), + *GetName(), + *RecordIt->ComponentKey.GetSCSVariableName().ToString(), + *ComponentTemplateOwnerClass->GetName(), + *ComponentTemplateOwnerClass->GetName() + ); + } } } } @@ -1368,6 +1388,20 @@ void UBlueprint::BeginCacheForCookedPlatformData(const ITargetPlatform *TargetPl default: break; } + + // EDL is required, because we need to enforce a preload dependency on the CDO (see UBlueprintGeneratedClass::GetDefaultObjectPreloadDependencies). This is + // difficult to support in the non-EDL case, because we have to enforce the dependency at runtime, which can lead to unpredictable results in a cooked build. + if(bResult && !IsEventDrivenLoaderEnabledInCookedBuilds()) + { + bResult = false; + + static bool bWarnOnEDLDisabled = true; + if (bWarnOnEDLDisabled) + { + UE_LOG(LogBlueprint, Warning, TEXT("Cannot cook Blueprint component data for faster instancing at runtime, because Event-Driven Loading (EDL) has been disabled. Re-enable EDL to support this feature, or disable the option to cook Blueprint component data.")); + bWarnOnEDLDisabled = false; + } + } return bResult; }; @@ -1642,28 +1676,40 @@ void UBlueprint::GetAllGraphs(TArray& Graphs) const for (int32 i = 0; i < FunctionGraphs.Num(); ++i) { UEdGraph* Graph = FunctionGraphs[i]; - Graphs.Add(Graph); - Graph->GetAllChildrenGraphs(Graphs); + if(Graph) + { + Graphs.Add(Graph); + Graph->GetAllChildrenGraphs(Graphs); + } } for (int32 i = 0; i < MacroGraphs.Num(); ++i) { UEdGraph* Graph = MacroGraphs[i]; - Graphs.Add(Graph); - Graph->GetAllChildrenGraphs(Graphs); + if(Graph) + { + Graphs.Add(Graph); + Graph->GetAllChildrenGraphs(Graphs); + } } for (int32 i = 0; i < UbergraphPages.Num(); ++i) { UEdGraph* Graph = UbergraphPages[i]; - Graphs.Add(Graph); - Graph->GetAllChildrenGraphs(Graphs); + if(Graph) + { + Graphs.Add(Graph); + Graph->GetAllChildrenGraphs(Graphs); + } } for (int32 i = 0; i < DelegateSignatureGraphs.Num(); ++i) { UEdGraph* Graph = DelegateSignatureGraphs[i]; - Graphs.Add(Graph); - Graph->GetAllChildrenGraphs(Graphs); + if(Graph) + { + Graphs.Add(Graph); + Graph->GetAllChildrenGraphs(Graphs); + } } for (int32 BPIdx=0; BPIdx& Graphs) const for (int32 GraphIdx = 0; GraphIdx < InterfaceDesc.Graphs.Num(); GraphIdx++) { UEdGraph* Graph = InterfaceDesc.Graphs[GraphIdx]; - Graphs.Add(Graph); - Graph->GetAllChildrenGraphs(Graphs); + if(Graph) + { + Graphs.Add(Graph); + Graph->GetAllChildrenGraphs(Graphs); + } } } #endif // WITH_EDITORONLY_DATA diff --git a/Engine/Source/Runtime/Engine/Private/Controller.cpp b/Engine/Source/Runtime/Engine/Private/Controller.cpp index 1c149e6ee10e..4ac45788036c 100644 --- a/Engine/Source/Runtime/Engine/Private/Controller.cpp +++ b/Engine/Source/Runtime/Engine/Private/Controller.cpp @@ -24,6 +24,7 @@ #include "GameFramework/PlayerState.h" +DEFINE_LOG_CATEGORY(LogController); DEFINE_LOG_CATEGORY(LogPath); #define LOCTEXT_NAMESPACE "Controller" @@ -46,6 +47,7 @@ AController::AController(const FObjectInitializer& ObjectInitializer) bCanBeDamaged = false; bAttachToPawn = false; bIsPlayerController = false; + bCanPossessWithoutAuthority = false; if (RootComponent) { @@ -274,17 +276,25 @@ void AController::PostInitializeComponents() void AController::Possess(APawn* InPawn) { - if (!HasAuthority()) + if (!bCanPossessWithoutAuthority && !HasAuthority()) { FMessageLog("PIE").Warning(FText::Format( LOCTEXT("ControllerPossessAuthorityOnly", "Possess function should only be used by the network authority for {0}"), FText::FromName(GetFName()) )); + UE_LOG(LogController, Warning, TEXT("Trying to possess %s without network authority! Request will be ignored."), *GetNameSafe(InPawn)); return; } REDIRECT_OBJECT_TO_VLOG(InPawn, this); + OnPossess(InPawn); + + ReceivePossess(InPawn); +} + +void AController::OnPossess(APawn* InPawn) +{ const bool bNewPawn = GetPawn() != InPawn; if (InPawn != NULL) @@ -315,6 +325,15 @@ void AController::Possess(APawn* InPawn) } void AController::UnPossess() +{ + APawn* CurrentPawn = GetPawn(); + + OnUnPossess(); + + ReceiveUnPossess(CurrentPawn); +} + +void AController::OnUnPossess() { if ( Pawn != NULL ) { @@ -323,12 +342,11 @@ void AController::UnPossess() } } - void AController::PawnPendingDestroy(APawn* inPawn) { if ( IsInState(NAME_Inactive) ) { - UE_LOG(LogPath, Log, TEXT("PawnPendingDestroy while inactive %s"), *GetName()); + UE_LOG(LogController, Log, TEXT("PawnPendingDestroy while inactive %s"), *GetName()); } if ( inPawn != Pawn ) diff --git a/Engine/Source/Runtime/Engine/Private/DataReplication.cpp b/Engine/Source/Runtime/Engine/Private/DataReplication.cpp index 7c2bda63d1f5..839a8ad83b1e 100644 --- a/Engine/Source/Runtime/Engine/Private/DataReplication.cpp +++ b/Engine/Source/Runtime/Engine/Private/DataReplication.cpp @@ -14,9 +14,9 @@ #include "Engine/PackageMapClient.h" #include "Net/RepLayout.h" #include "Engine/ActorChannel.h" -#include "Engine/DemoNetDriver.h" #include "ProfilingDebugging/CsvProfiler.h" #include "Engine/Engine.h" +#include "Engine/NetConnection.h" DECLARE_CYCLE_STAT(TEXT("Custom Delta Property Rep Time"), STAT_NetReplicateCustomDeltaPropTime, STATGROUP_Game); DECLARE_CYCLE_STAT(TEXT("ReceiveRPC"), STAT_NetReceiveRPC, STATGROUP_Game); diff --git a/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp b/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp index eded1c6a6bd2..5a88970a146c 100644 --- a/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp +++ b/Engine/Source/Runtime/Engine/Private/DemoNetDriver.cpp @@ -2594,7 +2594,7 @@ void UDemoNetDriver::TickDemoRecordFrame( float DeltaSeconds ) // Make sure we're under the desired recording time quota, if any. // See ReplicatePriorizeActor. - if (TotalPrioritizeActorsTime > RecordTimeLimit) + if (RecordTimeLimit > 0.0f && TotalPrioritizeActorsTime > RecordTimeLimit) { DemoNetDriverRecordingPrivate::LogDemoRecordTimeElapsed(TEXT("Exceeded maximum desired recording time (during Prioritization). Max: %.3fms, TimeSpent: %.3fms, Active Actors: %d, Prioritized Actors: %d"), MaxDesiredRecordTimeMS, TotalPrioritizeActorsTimeMS, NumActiveObjects, NumPrioritizedActors); diff --git a/Engine/Source/Runtime/Engine/Private/EdGraph/EdGraphSchema.cpp b/Engine/Source/Runtime/Engine/Private/EdGraph/EdGraphSchema.cpp index 2a346065e825..de72cd496f48 100644 --- a/Engine/Source/Runtime/Engine/Private/EdGraph/EdGraphSchema.cpp +++ b/Engine/Source/Runtime/Engine/Private/EdGraph/EdGraphSchema.cpp @@ -622,7 +622,7 @@ void UEdGraphSchema::BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* Tar #endif //#if WITH_EDITOR } -FPinConnectionResponse UEdGraphSchema::MovePinLinks(UEdGraphPin& MoveFromPin, UEdGraphPin& MoveToPin, bool bIsIntermediateMove) const +FPinConnectionResponse UEdGraphSchema::MovePinLinks(UEdGraphPin& MoveFromPin, UEdGraphPin& MoveToPin, bool bIsIntermediateMove, bool bNotifyLinkedNodes) const { #if WITH_EDITOR ensureMsgf(bIsIntermediateMove || !MoveToPin.GetOwningNode()->GetGraph()->HasAnyFlags(RF_Transient), @@ -633,7 +633,7 @@ FPinConnectionResponse UEdGraphSchema::MovePinLinks(UEdGraphPin& MoveFromPin, UE // First copy the current set of links TArray CurrentLinks = MoveFromPin.LinkedTo; // Then break all links at pin we are moving from - MoveFromPin.BreakAllPinLinks(); + MoveFromPin.BreakAllPinLinks(false); // Try and make each new connection for (int32 i=0; iGetOwningNodeUnchecked()) + { + LinkedToNode->PinConnectionListChanged(NewLink); + Response = CanCreateConnection(&MoveToPin, NewLink); + } + } +#endif if (Response.CanSafeConnect()) { MoveToPin.MakeLinkTo(NewLink); - } + } else - { + { FinalResponse = Response; } } diff --git a/Engine/Source/Runtime/Engine/Private/GameFramework/ForceFeedbackEffect.cpp b/Engine/Source/Runtime/Engine/Private/GameFramework/ForceFeedbackEffect.cpp index ffa9f2de505f..7687ea960937 100644 --- a/Engine/Source/Runtime/Engine/Private/GameFramework/ForceFeedbackEffect.cpp +++ b/Engine/Source/Runtime/Engine/Private/GameFramework/ForceFeedbackEffect.cpp @@ -92,9 +92,9 @@ bool FActiveForceFeedbackEffect::Update(const float DeltaTime, FForceFeedbackVal const float Duration = ForceFeedbackEffect->GetDuration(); - PlayTime += (bIgnoreTimeDilation ? FApp::GetDeltaTime() : DeltaTime); + PlayTime += (Parameters.bIgnoreTimeDilation ? FApp::GetDeltaTime() : DeltaTime); - if (PlayTime > Duration && (!bLooping || Duration == 0.f) ) + if (PlayTime > Duration && (!Parameters.bLooping || Duration == 0.f) ) { return false; } diff --git a/Engine/Source/Runtime/Engine/Private/GameFramework/SpringArmComponent.cpp b/Engine/Source/Runtime/Engine/Private/GameFramework/SpringArmComponent.cpp index 9b58ff8a5171..bd8b45029669 100644 --- a/Engine/Source/Runtime/Engine/Private/GameFramework/SpringArmComponent.cpp +++ b/Engine/Source/Runtime/Engine/Private/GameFramework/SpringArmComponent.cpp @@ -84,20 +84,18 @@ void USpringArmComponent::UpdateDesiredArmLocation(bool bDoTrace, bool bDoLocati { FRotator DesiredRot = GetTargetRotation(); - const float InverseCameraLagMaxTimeStep = (1.f / CameraLagMaxTimeStep); - // Apply 'lag' to rotation if desired if(bDoRotationLag) { if (bUseCameraLagSubstepping && DeltaTime > CameraLagMaxTimeStep && CameraRotationLagSpeed > 0.f) { - const FRotator ArmRotStep = (DesiredRot - PreviousDesiredRot).GetNormalized() * (CameraLagMaxTimeStep / DeltaTime); + const FRotator ArmRotStep = (DesiredRot - PreviousDesiredRot).GetNormalized() * (1.f / DeltaTime); FRotator LerpTarget = PreviousDesiredRot; float RemainingTime = DeltaTime; while (RemainingTime > KINDA_SMALL_NUMBER) { const float LerpAmount = FMath::Min(CameraLagMaxTimeStep, RemainingTime); - LerpTarget += ArmRotStep * (LerpAmount * InverseCameraLagMaxTimeStep); + LerpTarget += ArmRotStep * LerpAmount; RemainingTime -= LerpAmount; DesiredRot = FRotator(FMath::QInterpTo(FQuat(PreviousDesiredRot), FQuat(LerpTarget), LerpAmount, CameraRotationLagSpeed)); @@ -119,13 +117,14 @@ void USpringArmComponent::UpdateDesiredArmLocation(bool bDoTrace, bool bDoLocati { if (bUseCameraLagSubstepping && DeltaTime > CameraLagMaxTimeStep && CameraLagSpeed > 0.f) { - const FVector ArmMovementStep = (ArmOrigin - PreviousArmOrigin) * (CameraLagMaxTimeStep / DeltaTime); - FVector LerpTarget = PreviousArmOrigin; + const FVector ArmMovementStep = (DesiredLoc - PreviousDesiredLoc) * (1.f / DeltaTime); + FVector LerpTarget = PreviousDesiredLoc; + float RemainingTime = DeltaTime; while (RemainingTime > KINDA_SMALL_NUMBER) { const float LerpAmount = FMath::Min(CameraLagMaxTimeStep, RemainingTime); - LerpTarget += ArmMovementStep * (LerpAmount * InverseCameraLagMaxTimeStep); + LerpTarget += ArmMovementStep * LerpAmount; RemainingTime -= LerpAmount; DesiredLoc = FMath::VInterpTo(PreviousDesiredLoc, LerpTarget, LerpAmount, CameraLagSpeed); diff --git a/Engine/Source/Runtime/Engine/Private/GameMode.cpp b/Engine/Source/Runtime/Engine/Private/GameMode.cpp index 0f202e8263f5..92c4eb2ea545 100644 --- a/Engine/Source/Runtime/Engine/Private/GameMode.cpp +++ b/Engine/Source/Runtime/Engine/Private/GameMode.cpp @@ -72,7 +72,12 @@ void AGameMode::InitGame(const FString& MapName, const FString& Options, FString Super::InitGame(MapName, Options, ErrorMessage); SetMatchState(MatchState::EnteringMap); - if (!GameStateClass->IsChildOf()) + if (GameStateClass == nullptr) + { + UE_LOG(LogGameMode, Error, TEXT("GameStateClass is not set, falling back to AGameState.")); + GameStateClass = AGameState::StaticClass(); + } + else if (!GameStateClass->IsChildOf()) { UE_LOG(LogGameMode, Error, TEXT("Mixing AGameStateBase with AGameMode is not compatible. Change AGameStateBase subclass (%s) to derive from AGameState, or make both derive from Base"), *GameStateClass->GetName()); } diff --git a/Engine/Source/Runtime/Engine/Private/GameViewportClient.cpp b/Engine/Source/Runtime/Engine/Private/GameViewportClient.cpp index 316020d22ed2..2956c24bb8e8 100644 --- a/Engine/Source/Runtime/Engine/Private/GameViewportClient.cpp +++ b/Engine/Source/Runtime/Engine/Private/GameViewportClient.cpp @@ -187,6 +187,10 @@ UGameViewportClient::UGameViewportClient(const FObjectInitializer& ObjectInitial SplitscreenInfo[ESplitScreenType::ThreePlayer_Vertical].PlayerData.Add(FPerPlayerSplitscreenData(0.333f, 1.0f, 0.333f, 0.0f)); SplitscreenInfo[ESplitScreenType::ThreePlayer_Vertical].PlayerData.Add(FPerPlayerSplitscreenData(0.333f, 1.0f, 0.666f, 0.0f)); + SplitscreenInfo[ESplitScreenType::ThreePlayer_Horizontal].PlayerData.Add(FPerPlayerSplitscreenData(1.0f, 0.333f, 0.0f, 0.0f)); + SplitscreenInfo[ESplitScreenType::ThreePlayer_Horizontal].PlayerData.Add(FPerPlayerSplitscreenData(1.0f, 0.333f, 0.0f, 0.333f)); + SplitscreenInfo[ESplitScreenType::ThreePlayer_Horizontal].PlayerData.Add(FPerPlayerSplitscreenData(1.0f, 0.333f, 0.0f, 0.666f)); + SplitscreenInfo[ESplitScreenType::FourPlayer_Grid].PlayerData.Add(FPerPlayerSplitscreenData(0.5f, 0.5f, 0.0f, 0.0f)); SplitscreenInfo[ESplitScreenType::FourPlayer_Grid].PlayerData.Add(FPerPlayerSplitscreenData(0.5f, 0.5f, 0.5f, 0.0f)); SplitscreenInfo[ESplitScreenType::FourPlayer_Grid].PlayerData.Add(FPerPlayerSplitscreenData(0.5f, 0.5f, 0.0f, 0.5f)); @@ -197,6 +201,11 @@ UGameViewportClient::UGameViewportClient(const FObjectInitializer& ObjectInitial SplitscreenInfo[ESplitScreenType::FourPlayer_Vertical].PlayerData.Add(FPerPlayerSplitscreenData(0.25f, 1.0f, 0.5f, 0.0f)); SplitscreenInfo[ESplitScreenType::FourPlayer_Vertical].PlayerData.Add(FPerPlayerSplitscreenData(0.25f, 1.0f, 0.75f, 0.0f)); + SplitscreenInfo[ESplitScreenType::FourPlayer_Horizontal].PlayerData.Add(FPerPlayerSplitscreenData(1.f, 0.25f, 0.0f, 0.0f)); + SplitscreenInfo[ESplitScreenType::FourPlayer_Horizontal].PlayerData.Add(FPerPlayerSplitscreenData(1.f, 0.25f, 0.0f, 0.25f)); + SplitscreenInfo[ESplitScreenType::FourPlayer_Horizontal].PlayerData.Add(FPerPlayerSplitscreenData(1.f, 0.25f, 0.0f, 0.5f)); + SplitscreenInfo[ESplitScreenType::FourPlayer_Horizontal].PlayerData.Add(FPerPlayerSplitscreenData(1.f, 0.25f, 0.0f, 0.75f)); + MaxSplitscreenPlayers = 4; bSuppressTransitionMessage = true; @@ -2033,6 +2042,10 @@ void UGameViewportClient::UpdateActiveSplitscreenType() SplitType = ESplitScreenType::ThreePlayer_Vertical; break; + case EThreePlayerSplitScreenType::Horizontal: + SplitType = ESplitScreenType::ThreePlayer_Horizontal; + break; + default: check(0); } @@ -2050,6 +2063,10 @@ void UGameViewportClient::UpdateActiveSplitscreenType() SplitType = ESplitScreenType::FourPlayer_Vertical; break; + case EFourPlayerSplitScreenType::Horizontal: + SplitType = ESplitScreenType::FourPlayer_Horizontal; + break; + default: check(0); } @@ -2121,6 +2138,8 @@ bool UGameViewportClient::HasTopSafeZone( int32 LocalPlayerIndex ) case ESplitScreenType::TwoPlayer_Horizontal: case ESplitScreenType::ThreePlayer_FavorTop: + case ESplitScreenType::ThreePlayer_Horizontal: + case ESplitScreenType::FourPlayer_Horizontal: return (LocalPlayerIndex == 0); case ESplitScreenType::ThreePlayer_FavorBottom: @@ -2147,7 +2166,11 @@ bool UGameViewportClient::HasBottomSafeZone( int32 LocalPlayerIndex ) case ESplitScreenType::ThreePlayer_FavorBottom: case ESplitScreenType::FourPlayer_Grid: + case ESplitScreenType::ThreePlayer_Horizontal: return (LocalPlayerIndex > 1); + + case ESplitScreenType::FourPlayer_Horizontal: + return (LocalPlayerIndex > 2); } return false; @@ -2159,6 +2182,8 @@ bool UGameViewportClient::HasLeftSafeZone( int32 LocalPlayerIndex ) { case ESplitScreenType::None: case ESplitScreenType::TwoPlayer_Horizontal: + case ESplitScreenType::ThreePlayer_Horizontal: + case ESplitScreenType::FourPlayer_Horizontal: return true; case ESplitScreenType::TwoPlayer_Vertical: @@ -2183,6 +2208,8 @@ bool UGameViewportClient::HasRightSafeZone( int32 LocalPlayerIndex ) { case ESplitScreenType::None: case ESplitScreenType::TwoPlayer_Horizontal: + case ESplitScreenType::ThreePlayer_Horizontal: + case ESplitScreenType::FourPlayer_Horizontal: return true; case ESplitScreenType::TwoPlayer_Vertical: @@ -2248,6 +2275,10 @@ void UGameViewportClient::GetPixelSizeOfScreen( float& Width, float& Height, UCa Width = Canvas->ClipX * 3; Height = Canvas->ClipY; return; + case ESplitScreenType::ThreePlayer_Horizontal: + Width = Canvas->ClipX; + Height = Canvas->ClipY * 3; + return; case ESplitScreenType::FourPlayer_Grid: Width = Canvas->ClipX * 2; Height = Canvas->ClipY * 2; @@ -2255,6 +2286,11 @@ void UGameViewportClient::GetPixelSizeOfScreen( float& Width, float& Height, UCa case ESplitScreenType::FourPlayer_Vertical: Width = Canvas->ClipX * 4; Height = Canvas->ClipY; + return; + case ESplitScreenType::FourPlayer_Horizontal: + Width = Canvas->ClipX; + Height = Canvas->ClipY * 4; + return; } } diff --git a/Engine/Source/Runtime/Engine/Private/InheritableComponentHandler.cpp b/Engine/Source/Runtime/Engine/Private/InheritableComponentHandler.cpp index d8d8cab3e133..c9bc5b9e44f0 100644 --- a/Engine/Source/Runtime/Engine/Private/InheritableComponentHandler.cpp +++ b/Engine/Source/Runtime/Engine/Private/InheritableComponentHandler.cpp @@ -30,14 +30,12 @@ void UInheritableComponentHandler::PostLoad() #if WITH_EDITOR if (!GIsDuplicatingClassForReinstancing) -#endif { for (int32 Index = Records.Num() - 1; Index >= 0; --Index) { FComponentOverrideRecord& Record = Records[Index]; if (Record.ComponentTemplate) { -#if WITH_EDITOR if (GetLinkerCustomVersion(FBlueprintsObjectVersion::GUID) < FBlueprintsObjectVersion::SCSHasComponentTemplateClass) { // Fix up component class on load, if it's not already set. @@ -66,7 +64,6 @@ void UInheritableComponentHandler::PostLoad() FixComponentTemplateName(Record.ComponentTemplate, ExpectedTemplateName); } } -#endif if (!CastChecked(Record.ComponentTemplate->GetArchetype())->IsEditableWhenInherited()) { @@ -76,6 +73,7 @@ void UInheritableComponentHandler::PostLoad() } } } +#endif } #if WITH_EDITOR @@ -469,29 +467,27 @@ const FComponentOverrideRecord* UInheritableComponentHandler::FindRecord(const F void UInheritableComponentHandler::FixComponentTemplateName(UActorComponent* ComponentTemplate, const FString& NewName) { - // Override template names were not previously kept in sync w/ past node rename operations. Thus, we need to check for - // and correct other (stale) template names. Otherwise, these could collide with the one we're trying to correct here. - for (int32 Index = 0; Index < Records.Num(); ++Index) + // Look for a collision with the template we're trying to rename here. It's possible that names were swapped on the + // original component template objects that were inherited from the associated Blueprint's parent class, for example. + FComponentOverrideRecord* MatchingRecord = Records.FindByPredicate([ComponentTemplate, NewName](FComponentOverrideRecord& Record) { - FComponentOverrideRecord& Record = Records[Index]; if (Record.ComponentTemplate && Record.ComponentTemplate != ComponentTemplate && Record.ComponentTemplate->GetName() == NewName) { - if (UActorComponent* OriginalTemplate = Record.ComponentKey.GetOriginalTemplate()) - { - if (OriginalTemplate->GetName() != Record.ComponentTemplate->GetName()) - { - // Recursively fix up this record's component template name first to also match its original template, which will then free up the name. - FixComponentTemplateName(Record.ComponentTemplate, OriginalTemplate->GetName()); - } - } - - // There should only be at most one collision, so we'll stop looking now. - break; + const UActorComponent* OriginalTemplate = Record.ComponentKey.GetOriginalTemplate(); + return ensureMsgf(OriginalTemplate && OriginalTemplate->GetName() != Record.ComponentTemplate->GetName(), + TEXT("Found a collision with an existing override record, but its associated template object is either invalid or already matches its inherited template's name (%s). This is unexpected."), *NewName); } - } - // Precondition: There are no other objects in the same scope with this name. - check(!FindObjectWithOuter(ComponentTemplate->GetOuter(), nullptr, FName(*NewName))); + return false; + }); + + // If we found a collision, temporarily rename the associated template object to something unique so that it no longer + // collides with the one we're trying to correct here. This will be fixed up when we later encounter this record during + // PostLoad() validation and see that it still doesn't match its original template name. + if (MatchingRecord) + { + MatchingRecord->ComponentTemplate->Rename(nullptr, nullptr, REN_DontCreateRedirectors | REN_ForceNoResetLoaders); + } // Now that we're sure there are no collisions with other records, we can safely rename this one to its new name. ComponentTemplate->Rename(*NewName, nullptr, REN_DontCreateRedirectors | REN_ForceNoResetLoaders); diff --git a/Engine/Source/Runtime/Engine/Private/KismetArrayLibrary.cpp b/Engine/Source/Runtime/Engine/Private/KismetArrayLibrary.cpp index 73297058c323..32db0e1951ec 100644 --- a/Engine/Source/Runtime/Engine/Private/KismetArrayLibrary.cpp +++ b/Engine/Source/Runtime/Engine/Private/KismetArrayLibrary.cpp @@ -103,6 +103,38 @@ int32 UKismetArrayLibrary::GenericArray_AddUnique(void* TargetArray, const UArra return NewIndex; } +bool UKismetArrayLibrary::GenericArray_Identical(void* ArrayA, const UArrayProperty* ArrayAProp, void* ArrayB, const UArrayProperty* ArrayBProp) +{ + if (ArrayA && ArrayB) + { + UProperty* InnerAProp = ArrayAProp->Inner; + + if (InnerAProp->SameType(ArrayBProp->Inner)) + { + FScriptArrayHelper ArrayAHelper(ArrayAProp, ArrayA); + FScriptArrayHelper ArrayBHelper(ArrayBProp, ArrayB); + + const int32 ArrayANum = ArrayAHelper.Num(); + if (ArrayANum != ArrayBHelper.Num()) + { + return false; + } + + for (int32 Index = 0; Index < ArrayANum; ++Index) + { + if (!InnerAProp->Identical(ArrayAHelper.GetRawPtr(Index), ArrayBHelper.GetRawPtr(Index))) + { + return false; + } + } + + return true; + } + } + + return false; +} + void UKismetArrayLibrary::GenericArray_Append(void* TargetArray, const UArrayProperty* TargetArrayProp, void* SourceArray, const UArrayProperty* SourceArrayProperty) { if(TargetArray && SourceArray) diff --git a/Engine/Source/Runtime/Engine/Private/KismetSystemLibrary.cpp b/Engine/Source/Runtime/Engine/Private/KismetSystemLibrary.cpp index 0dda70992853..825924e0edad 100644 --- a/Engine/Source/Runtime/Engine/Private/KismetSystemLibrary.cpp +++ b/Engine/Source/Runtime/Engine/Private/KismetSystemLibrary.cpp @@ -83,6 +83,11 @@ FString UKismetSystemLibrary::GetClassDisplayName(UClass* Class) return Class ? Class->GetName() : FString(); } +UObject* UKismetSystemLibrary::GetOuterObject(const UObject* Object) +{ + return Object ? Object->GetOuter() : nullptr; +} + FString UKismetSystemLibrary::GetEngineVersion() { return FEngineVersion::Current().ToString(); @@ -303,7 +308,7 @@ void UKismetSystemLibrary::ExecuteConsoleCommand(UObject* WorldContextObject, co } } -float UKismetSystemLibrary::GetConsoleVariableFloatValue(UObject* WorldContextObject, const FString& VariableName) +float UKismetSystemLibrary::GetConsoleVariableFloatValue(const FString& VariableName) { float Value = 0.0f; @@ -320,7 +325,7 @@ float UKismetSystemLibrary::GetConsoleVariableFloatValue(UObject* WorldContextOb return Value; } -int32 UKismetSystemLibrary::GetConsoleVariableIntValue(UObject* WorldContextObject, const FString& VariableName) +int32 UKismetSystemLibrary::GetConsoleVariableIntValue(const FString& VariableName) { int32 Value = 0; @@ -337,7 +342,10 @@ int32 UKismetSystemLibrary::GetConsoleVariableIntValue(UObject* WorldContextObje return Value; } - +bool UKismetSystemLibrary::GetConsoleVariableBoolValue(const FString& VariableName) +{ + return (GetConsoleVariableIntValue(VariableName) != 0); +} void UKismetSystemLibrary::QuitGame(UObject* WorldContextObject, class APlayerController* SpecificPlayer, TEnumAsByte QuitPreference, bool bIgnorePlatformRestrictions) diff --git a/Engine/Source/Runtime/Engine/Private/LevelScriptBlueprint.cpp b/Engine/Source/Runtime/Engine/Private/LevelScriptBlueprint.cpp index e785beff0a26..183af2ab0b8d 100644 --- a/Engine/Source/Runtime/Engine/Private/LevelScriptBlueprint.cpp +++ b/Engine/Source/Runtime/Engine/Private/LevelScriptBlueprint.cpp @@ -50,12 +50,11 @@ FString ULevelScriptBlueprint::GetFriendlyName() const return UBlueprint::GetFriendlyName(); } -FString ULevelScriptBlueprint::CreateLevelScriptNameFromLevel (const ULevel* Level) +FString ULevelScriptBlueprint::CreateLevelScriptNameFromLevel(const ULevel* Level) { - // Since all maps are named "PersistentLevel," check to see if this level is the actual OwningWorld->PersistentLevel, or name it based on the map package. - check(Level && Level->OwningWorld); + // Since all maps are named "PersistentLevel" the level script name is based on the LevelPackage + check(Level); UObject* LevelPackage = Level->GetOutermost(); - return FPackageName::GetShortName(LevelPackage->GetFName().GetPlainNameString()); } diff --git a/Engine/Source/Runtime/Engine/Private/LevelStreaming.cpp b/Engine/Source/Runtime/Engine/Private/LevelStreaming.cpp index 51eac07dd7f8..17dc0cce7915 100644 --- a/Engine/Source/Runtime/Engine/Private/LevelStreaming.cpp +++ b/Engine/Source/Runtime/Engine/Private/LevelStreaming.cpp @@ -847,6 +847,38 @@ bool ULevelStreaming::RequestLevel(UWorld* PersistentWorld, bool bAllowLevelLoad return false; } + auto ValidateUniqueLevel = [this, PersistentWorld]() + { + for (ULevelStreaming* OtherLevel : PersistentWorld->GetStreamingLevels()) + { + if (OtherLevel == nullptr || OtherLevel == this) + { + continue; + } + + const ECurrentState OtherState = OtherLevel->GetCurrentState(); + if (OtherState == ECurrentState::FailedToLoad || OtherState == ECurrentState::Removed || (OtherState == ECurrentState::Unloaded && (OtherLevel->TargetState == ETargetState::Unloaded || OtherLevel->TargetState == ETargetState::UnloadedAndRemoved))) + { + // If the other level isn't loaded or in the process of being loaded we don't need to consider it + continue; + } + + if (OtherLevel->WorldAsset == WorldAsset) + { + UE_LOG(LogLevelStreaming, Warning, TEXT("Streaming Level '%s' uses same destination for level ('%s') as '%s'. Level cannot be loaded again and this StreamingLevel will be flagged as failed to load."), *GetPathName(), *WorldAsset.GetLongPackageName(), *OtherLevel->GetPathName()); + return false; + } + } + + return true; + }; + + if (!ValidateUniqueLevel()) + { + CurrentState = ECurrentState::FailedToLoad; + return false; + } + EPackageFlags PackageFlags = PKG_ContainsMap; int32 PIEInstanceID = INDEX_NONE; @@ -1607,7 +1639,7 @@ void ULevelStreamingDynamic::PostLoad() // Initialize startup state of the streaming level if ( GetWorld()->IsGameWorld() ) { - bShouldBeLoaded = bInitiallyLoaded; + SetShouldBeLoaded(bInitiallyLoaded); SetShouldBeVisible(bInitiallyVisible); } } diff --git a/Engine/Source/Runtime/Engine/Private/Materials/MaterialExpressions.cpp b/Engine/Source/Runtime/Engine/Private/Materials/MaterialExpressions.cpp index aeac758fadfc..2a40cbffc61b 100644 --- a/Engine/Source/Runtime/Engine/Private/Materials/MaterialExpressions.cpp +++ b/Engine/Source/Runtime/Engine/Private/Materials/MaterialExpressions.cpp @@ -10928,7 +10928,10 @@ FString FMaterialLayersFunctions::GetStaticPermutationString() const void FMaterialLayersFunctions::SerializeForDDC(FArchive& Ar) { - KeyString = GetStaticPermutationString(); + if (!Ar.IsCooking()) + { + KeyString = GetStaticPermutationString(); + } Ar << KeyString; } diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleComponents.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleComponents.cpp index d3517521b451..1ee7a1f493b7 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleComponents.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleComponents.cpp @@ -114,6 +114,10 @@ DECLARE_CYCLE_STAT(TEXT("ParticleComponent CreateRenderState Concurrent GT"), ST DECLARE_CYCLE_STAT(TEXT("PSys Comp Marshall Time GT"), STAT_UParticleSystemComponent_Marshall, STATGROUP_Particles); CSV_DECLARE_CATEGORY_MODULE_EXTERN(CORE_API, Basic); +DEFINE_STAT(STAT_ParticlesOverview_GT); +DEFINE_STAT(STAT_ParticlesOverview_GT_CNC); +DEFINE_STAT(STAT_ParticlesOverview_RT); +DEFINE_STAT(STAT_ParticlesOverview_RT_CNC); #include "InGamePerformanceTracker.h" @@ -3839,6 +3843,7 @@ void UParticleSystemComponent::OnUnregister() void UParticleSystemComponent::CreateRenderState_Concurrent() { SCOPE_CYCLE_COUNTER(STAT_ParticleSystemComponent_CreateRenderState_Concurrent); + SCOPE_CYCLE_COUNTER(STAT_ParticlesOverview_GT_CNC); ForceAsyncWorkCompletion(ENSURE_AND_STALL); check( GetWorld() ); @@ -3873,6 +3878,7 @@ void UParticleSystemComponent::CreateRenderState_Concurrent() void UParticleSystemComponent::SendRenderTransform_Concurrent() { SCOPE_CYCLE_COUNTER(STAT_ParticleSystemComponent_SendRenderTransform_Concurrent); + SCOPE_CYCLE_COUNTER(STAT_ParticlesOverview_GT_CNC); ForceAsyncWorkCompletion(ENSURE_AND_STALL); if (bIsActive) @@ -3890,6 +3896,7 @@ void UParticleSystemComponent::SendRenderTransform_Concurrent() void UParticleSystemComponent::SendRenderDynamicData_Concurrent() { SCOPE_CYCLE_COUNTER(STAT_ParticleSystemComponent_SendRenderDynamicData_Concurrent); + SCOPE_CYCLE_COUNTER(STAT_ParticlesOverview_GT_CNC); ForceAsyncWorkCompletion(ENSURE_AND_STALL); Super::SendRenderDynamicData_Concurrent(); @@ -3924,6 +3931,7 @@ void UParticleSystemComponent::SendRenderDynamicData_Concurrent() void UParticleSystemComponent::DestroyRenderState_Concurrent() { SCOPE_CYCLE_COUNTER(STAT_ParticleSystemComponent_DestroyRenderState_Concurrent); + SCOPE_CYCLE_COUNTER(STAT_ParticlesOverview_GT_CNC); ForceAsyncWorkCompletion(ENSURE_AND_STALL); check( GetWorld() ); @@ -4927,6 +4935,7 @@ void UParticleSystemComponent::TickComponent(float DeltaTime, enum ELevelTick Ti CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Effects); LLM_SCOPE(ELLMTag::Particles); FInGameScopedCycleCounter InGameCycleCounter(GetWorld(), EInGamePerfTrackers::VFXSignificance, EInGamePerfTrackerThreads::GameThread, bIsManagingSignificance); + SCOPE_CYCLE_COUNTER(STAT_ParticlesOverview_GT); if (Template == nullptr || Template->Emitters.Num() == 0) { @@ -5214,6 +5223,8 @@ void UParticleSystemComponent::ComputeTickComponent_Concurrent() SCOPE_CYCLE_COUNTER(STAT_ParticleComputeTickTime); FScopeCycleCounterUObject AdditionalScope(AdditionalStatObject(), GET_STATID(STAT_ParticleComputeTickTime)); + SCOPE_CYCLE_COUNTER(STAT_ParticlesOverview_GT_CNC); + // Tick Subemitters. int32 EmitterIndex; NumSignificantEmitters = 0; @@ -5287,6 +5298,7 @@ void UParticleSystemComponent::FinalizeTickComponent() FInGameScopedCycleCounter InGameCycleCounter(GetWorld(), EInGamePerfTrackers::VFXSignificance, IsInGameThread() ? EInGamePerfTrackerThreads::GameThread : EInGamePerfTrackerThreads::OtherThread, bIsManagingSignificance); SCOPE_CYCLE_COUNTER(STAT_ParticleFinalizeTickTime); + SCOPE_CYCLE_COUNTER(STAT_ParticlesOverview_GT); if(bAsyncDataCopyIsValid) { @@ -5786,6 +5798,7 @@ void UParticleSystemComponent::ActivateSystem(bool bFlagAsJustAttached) { CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Effects); SCOPE_CYCLE_COUNTER(STAT_ParticleActivateTime); + SCOPE_CYCLE_COUNTER(STAT_ParticlesOverview_GT); ForceAsyncWorkCompletion(STALL); if (IsTemplate() == true || !IsRegistered() || !FApp::CanEverRender()) @@ -6033,6 +6046,7 @@ void UParticleSystemComponent::DeactivateSystem() { UWorld* World = GetWorld(); FInGameScopedCycleCounter InGameCycleCounter(World, EInGamePerfTrackers::VFXSignificance, EInGamePerfTrackerThreads::GameThread, bIsManagingSignificance); + SCOPE_CYCLE_COUNTER(STAT_ParticlesOverview_GT); if (IsTemplate() == true) { diff --git a/Engine/Source/Runtime/Engine/Private/Particles/ParticleSystemRender.cpp b/Engine/Source/Runtime/Engine/Private/Particles/ParticleSystemRender.cpp index 534c6b10b87b..beb08d4695a2 100644 --- a/Engine/Source/Runtime/Engine/Private/Particles/ParticleSystemRender.cpp +++ b/Engine/Source/Runtime/Engine/Private/Particles/ParticleSystemRender.cpp @@ -1122,6 +1122,8 @@ void FDynamicSpriteEmitterData::GetDynamicMeshElementsEmitter(const FParticleSys [this, SourceData, View, Proxy, Allocation, DynamicParameterAllocation, bInstanced, bSort, ParticleCount, NumVerticesPerParticleInBuffer]() { SCOPE_CYCLE_COUNTER(STAT_FDynamicSpriteEmitterData_GetDynamicMeshElementsEmitter_Task); + SCOPE_CYCLE_COUNTER(STAT_ParticlesOverview_RT_CNC); + FMemMark Mark(FMemStack::Get()); FParticleOrder* ParticleOrder = NULL; if (bSort) @@ -2397,6 +2399,7 @@ void FDynamicMeshEmitterData::CalculateParticleTransform( void FDynamicMeshEmitterData::GetInstanceData(void* InstanceData, void* DynamicParameterData, void* PrevTransformBuffer, const FParticleSystemSceneProxy* Proxy, const FSceneView* View) const { SCOPE_CYCLE_COUNTER(STAT_ParticlePackingTime); + SCOPE_CYCLE_COUNTER(STAT_ParticlesOverview_RT_CNC); int32 SubImagesX = Source.SubImages_Horizontal; int32 SubImagesY = Source.SubImages_Vertical; @@ -6933,6 +6936,7 @@ void FParticleSystemSceneProxy::GetDynamicMeshElements(const TArrayTemplate == Template); check(!RetElem.PSC->IsPendingKill()); - // Rename the PSC to move it into the current PersistentLevel - it may have been spawned in one - // level but is now needed in another level. - // Use the REN_ForceNoResetLoaders flag to prevent the rename from potentially calling FlushAsyncLoading. - RetElem.PSC->Rename(nullptr, World, REN_ForceNoResetLoaders); + if (RetElem.PSC->GetWorld() != World) + { + // Rename the PSC to move it into the current PersistentLevel - it may have been spawned in one + // level but is now needed in another level. + // Use the REN_ForceNoResetLoaders flag to prevent the rename from potentially calling FlushAsyncLoading. + RetElem.PSC->Rename(nullptr, World, REN_ForceNoResetLoaders); + } } else { diff --git a/Engine/Source/Runtime/Engine/Private/PlayerController.cpp b/Engine/Source/Runtime/Engine/Private/PlayerController.cpp index b9ff05045a3c..0224c3ae82c8 100644 --- a/Engine/Source/Runtime/Engine/Private/PlayerController.cpp +++ b/Engine/Source/Runtime/Engine/Private/PlayerController.cpp @@ -747,18 +747,8 @@ void APlayerController::ClientRestart_Implementation(APawn* NewPawn) /// @endcond -void APlayerController::Possess(APawn* PawnToPossess) +void APlayerController::OnPossess(APawn* PawnToPossess) { - if (!HasAuthority()) - { - FMessageLog("PIE").Warning(FText::Format( - LOCTEXT("PlayerControllerPossessAuthorityOnly", "Possess function should only be used by the network authority for {0}"), - FText::FromName(GetFName()) - )); - UE_LOG(LogPlayerController, Warning, TEXT("Trying to possess %s without network authority! Request will be ignored."), *GetNameSafe(PawnToPossess)); - return; - } - if ( PawnToPossess != NULL && (PlayerState == NULL || !PlayerState->bOnlySpectator) ) { @@ -1218,7 +1208,7 @@ bool APlayerController::ServerAcknowledgePossession_Validate(APawn* P) /// @endcond -void APlayerController::UnPossess() +void APlayerController::OnUnPossess() { if (GetPawn() != NULL) { @@ -3061,9 +3051,9 @@ void APlayerController::DisplayDebug(class UCanvas* Canvas, const FDebugDisplayI const FActiveForceFeedbackEffect& LastActiveEffect = ForceFeedbackEffectHistoryEntries[i].LastActiveForceFeedbackEffect; const FString HistoryEntry = FString::Printf(TEXT("%s %s %f %s %f"), *LastActiveEffect.ForceFeedbackEffect->GetFName().ToString(), - *LastActiveEffect.Tag.ToString(), + *LastActiveEffect.Parameters.Tag.ToString(), LastActiveEffect.ForceFeedbackEffect->GetDuration(), - (LastActiveEffect.bLooping ? TEXT("true") : TEXT("false")), + (LastActiveEffect.Parameters.bLooping ? TEXT("true") : TEXT("false")), ForceFeedbackEffectHistoryEntries[i].TimeShown); DisplayDebugManager.DrawString(HistoryEntry); } @@ -3081,9 +3071,9 @@ void APlayerController::DisplayDebug(class UCanvas* Canvas, const FDebugDisplayI { const FString ActiveEntry = FString::Printf(TEXT("%s %s N/A %.2f %s %.2f - LL: %.2f LS: %.2f RL: %.2f RS: %.2f"), *ActiveEffect.ForceFeedbackEffect->GetFName().ToString(), - *ActiveEffect.Tag.ToString(), + *ActiveEffect.Parameters.Tag.ToString(), ActiveEffect.ForceFeedbackEffect->GetDuration(), - (ActiveEffect.bLooping ? TEXT("true") : TEXT("false")), + (ActiveEffect.Parameters.bLooping ? TEXT("true") : TEXT("false")), ActiveEffect.PlayTime, ActiveValues.LeftLarge, ActiveValues.LeftSmall, ActiveValues.RightLarge, ActiveValues.RightSmall); DisplayDebugManager.DrawString(ActiveEntry); @@ -3672,22 +3662,22 @@ void APlayerController::ClientPrestreamTextures_Implementation( AActor* ForcedAc } } -void APlayerController::ClientPlayForceFeedback_Implementation( UForceFeedbackEffect* ForceFeedbackEffect, bool bLooping, bool bIgnoreTimeDilation, FName Tag) +void APlayerController::ClientPlayForceFeedback_Internal_Implementation( UForceFeedbackEffect* ForceFeedbackEffect, FForceFeedbackParameters Params) { if (ForceFeedbackEffect) { - if (Tag != NAME_None) + if (Params.Tag != NAME_None) { for (int32 Index = ActiveForceFeedbackEffects.Num() - 1; Index >= 0; --Index) { - if (ActiveForceFeedbackEffects[Index].Tag == Tag) + if (ActiveForceFeedbackEffects[Index].Parameters.Tag == Params.Tag) { ActiveForceFeedbackEffects.RemoveAtSwap(Index); } } } - ActiveForceFeedbackEffects.Emplace(ForceFeedbackEffect, bLooping, bIgnoreTimeDilation, Tag); + ActiveForceFeedbackEffects.Emplace(ForceFeedbackEffect, Params); #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) ForceFeedbackEffectHistoryEntries.Emplace(ActiveForceFeedbackEffects.Last(), GetWorld()->GetTimeSeconds()); @@ -3695,6 +3685,33 @@ void APlayerController::ClientPlayForceFeedback_Implementation( UForceFeedbackEf } } +void APlayerController::K2_ClientPlayForceFeedback(class UForceFeedbackEffect* ForceFeedbackEffect, FName Tag, bool bLooping, bool bIgnoreTimeDilation, bool bPlayWhilePaused) +{ + FForceFeedbackParameters Params; + Params.Tag = Tag; + Params.bLooping = bLooping; + Params.bIgnoreTimeDilation = bIgnoreTimeDilation; + Params.bPlayWhilePaused = bPlayWhilePaused; + ClientPlayForceFeedback(ForceFeedbackEffect, Params); +} + +void APlayerController::ClientPlayForceFeedback(class UForceFeedbackEffect* ForceFeedbackEffect, bool bLooping, bool bIgnoreTimeDilation, FName Tag) +{ + FForceFeedbackParameters Params; + Params.Tag = Tag; + Params.bLooping = bLooping; + Params.bIgnoreTimeDilation = bIgnoreTimeDilation; + ClientPlayForceFeedback(ForceFeedbackEffect, Params); +} + +void APlayerController::ClientPlayForceFeedback(class UForceFeedbackEffect* ForceFeedbackEffect, bool bLooping, FName Tag) +{ + FForceFeedbackParameters Params; + Params.Tag = Tag; + Params.bLooping = bLooping; + ClientPlayForceFeedback(ForceFeedbackEffect, Params); +} + void APlayerController::ClientStopForceFeedback_Implementation( UForceFeedbackEffect* ForceFeedbackEffect, FName Tag) { if (ForceFeedbackEffect == NULL && Tag == NAME_None) @@ -3706,7 +3723,7 @@ void APlayerController::ClientStopForceFeedback_Implementation( UForceFeedbackEf for (int32 Index = ActiveForceFeedbackEffects.Num() - 1; Index >= 0; --Index) { if ( (ForceFeedbackEffect == NULL || ActiveForceFeedbackEffects[Index].ForceFeedbackEffect == ForceFeedbackEffect) - && (Tag == NAME_None || ActiveForceFeedbackEffects[Index].Tag == Tag) ) + && (Tag == NAME_None || ActiveForceFeedbackEffects[Index].Parameters.Tag == Tag) ) { ActiveForceFeedbackEffects.RemoveAtSwap(Index); } @@ -4057,7 +4074,9 @@ void APlayerController::ProcessForceFeedbackAndHaptics(const float DeltaTime, co bool bRightHapticsNeedUpdate = false; bool bGunHapticsNeedUpdate = false; - bool bProcessFeedback = !bGamePaused; + // Always process feedback by default, but if the game is paused then only static + // effects that are flagged to play while paused will play + bool bProcessFeedback = true; #if WITH_EDITOR if (bProcessFeedback) { @@ -4079,23 +4098,31 @@ void APlayerController::ProcessForceFeedbackAndHaptics(const float DeltaTime, co // --- Force Feedback -------------------------- for (int32 Index = ActiveForceFeedbackEffects.Num() - 1; Index >= 0; --Index) { - if (!ActiveForceFeedbackEffects[Index].Update(DeltaTime, ForceFeedbackValues)) + // If the game is paused, only tick force feedback effects that want to ignore time dilation + if (!bGamePaused || ActiveForceFeedbackEffects[Index].Parameters.bPlayWhilePaused) { - ActiveForceFeedbackEffects.RemoveAtSwap(Index); + if (!ActiveForceFeedbackEffects[Index].Update(DeltaTime, ForceFeedbackValues)) + { + ActiveForceFeedbackEffects.RemoveAtSwap(Index); + } } } - for (TSortedMap::TIterator It(DynamicForceFeedbacks.CreateIterator()); It; ++It) + const bool bProcessDynamicFeedback = !bGamePaused; + if (bProcessDynamicFeedback) { - if (!It.Value().Update(DeltaTime, ForceFeedbackValues)) + for (TSortedMap::TIterator It(DynamicForceFeedbacks.CreateIterator()); It; ++It) { - It.RemoveCurrent(); + if (!It.Value().Update(DeltaTime, ForceFeedbackValues)) + { + It.RemoveCurrent(); + } } - } - for (const TPair& DynamicEntry : LatentDynamicForceFeedbacks) - { - DynamicEntry.Value->Update(ForceFeedbackValues); + for (const TPair& DynamicEntry : LatentDynamicForceFeedbacks) + { + DynamicEntry.Value->Update(ForceFeedbackValues); + } } if (FForceFeedbackManager* ForceFeedbackManager = FForceFeedbackManager::Get(World)) @@ -4110,39 +4137,41 @@ void APlayerController::ProcessForceFeedbackAndHaptics(const float DeltaTime, co ForceFeedbackValues.RightSmall = FMath::Clamp(ForceFeedbackValues.RightSmall * ForceFeedbackScale, 0.f, 1.f); // --- Haptic Feedback ------------------------- - if (ActiveHapticEffect_Left.IsValid()) + if (bProcessDynamicFeedback) { - const bool bPlaying = ActiveHapticEffect_Left->Update(DeltaTime, LeftHaptics); - if (!bPlaying) + if (ActiveHapticEffect_Left.IsValid()) { - ActiveHapticEffect_Left->bLoop ? ActiveHapticEffect_Left->Restart() : ActiveHapticEffect_Left.Reset(); + const bool bPlaying = ActiveHapticEffect_Left->Update(DeltaTime, LeftHaptics); + if (!bPlaying) + { + ActiveHapticEffect_Left->bLoop ? ActiveHapticEffect_Left->Restart() : ActiveHapticEffect_Left.Reset(); + } + + bLeftHapticsNeedUpdate = true; } - bLeftHapticsNeedUpdate = true; - } - - if (ActiveHapticEffect_Right.IsValid()) - { - const bool bPlaying = ActiveHapticEffect_Right->Update(DeltaTime, RightHaptics); - if (!bPlaying) + if (ActiveHapticEffect_Right.IsValid()) { - ActiveHapticEffect_Right->bLoop ? ActiveHapticEffect_Right->Restart() : ActiveHapticEffect_Right.Reset(); + const bool bPlaying = ActiveHapticEffect_Right->Update(DeltaTime, RightHaptics); + if (!bPlaying) + { + ActiveHapticEffect_Right->bLoop ? ActiveHapticEffect_Right->Restart() : ActiveHapticEffect_Right.Reset(); + } + + bRightHapticsNeedUpdate = true; } - bRightHapticsNeedUpdate = true; - } - - if (ActiveHapticEffect_Gun.IsValid()) - { - const bool bPlaying = ActiveHapticEffect_Gun->Update(DeltaTime, GunHaptics); - if (!bPlaying) + if (ActiveHapticEffect_Gun.IsValid()) { - ActiveHapticEffect_Gun->bLoop ? ActiveHapticEffect_Gun->Restart() : ActiveHapticEffect_Gun.Reset(); + const bool bPlaying = ActiveHapticEffect_Gun->Update(DeltaTime, GunHaptics); + if (!bPlaying) + { + ActiveHapticEffect_Gun->bLoop ? ActiveHapticEffect_Gun->Restart() : ActiveHapticEffect_Gun.Reset(); + } + + bGunHapticsNeedUpdate = true; } - - bGunHapticsNeedUpdate = true; } - } if (FSlateApplication::IsInitialized()) diff --git a/Engine/Source/Runtime/Engine/Private/SimpleConstructionScript.cpp b/Engine/Source/Runtime/Engine/Private/SimpleConstructionScript.cpp index c4b660390085..4cb0288c9f46 100644 --- a/Engine/Source/Runtime/Engine/Private/SimpleConstructionScript.cpp +++ b/Engine/Source/Runtime/Engine/Private/SimpleConstructionScript.cpp @@ -1253,7 +1253,6 @@ USCS_Node* USimpleConstructionScript::CreateNode(UClass* NewComponentClass, FNam check(NewComponentClass->IsChildOf(UActorComponent::StaticClass())); ensure(Cast(Blueprint->GeneratedClass)); - // note that naming logic is duplicated in CreateNodeAndRenameComponent: NewComponentVariableName = GenerateNewComponentName(NewComponentClass, NewComponentVariableName); // At this point we should have a unique, explicit name to use for the template object. @@ -1277,8 +1276,16 @@ USCS_Node* USimpleConstructionScript::CreateNodeAndRenameComponent(UActorCompone { check(NewComponentTemplate); - // note that naming logic is duplicated in CreateNode: - FName NewComponentVariableName = GenerateNewComponentName(NewComponentTemplate->GetClass()); + // When copying and pasting we'd prefer to keep the component name + // However, the incoming template will have the template name suffix on it so + // acquire the desired name by stripping the suffix + FName DesiredName; + FString TemplateName = NewComponentTemplate->GetName(); + if (TemplateName.EndsWith(ComponentTemplateNameSuffix)) + { + DesiredName = *TemplateName.LeftChop(ComponentTemplateNameSuffix.Len()); + } + FName NewComponentVariableName = GenerateNewComponentName(NewComponentTemplate->GetClass(), DesiredName); // At this point we should have a unique, explicit name to use for the template object. check(NewComponentVariableName != NAME_None); diff --git a/Engine/Source/Runtime/Engine/Private/StaticMeshActor.cpp b/Engine/Source/Runtime/Engine/Private/StaticMeshActor.cpp index b5af05c0f9dc..176e2800fd89 100644 --- a/Engine/Source/Runtime/Engine/Private/StaticMeshActor.cpp +++ b/Engine/Source/Runtime/Engine/Private/StaticMeshActor.cpp @@ -176,16 +176,7 @@ void AStaticMeshActor::CheckForErrors() ->AddToken(FTextToken::Create(FText::Format(LOCTEXT( "MapCheck_Message_StaticMeshComponent", "Static mesh actor {ActorName} has NULL StaticMeshComponent property - please delete" ), Arguments))) ->AddToken(FMapErrorToken::Create(FMapErrors::StaticMeshComponent)); } - else if( StaticMeshComponent->GetStaticMesh() == nullptr ) - { - FFormatNamedArguments Arguments; - Arguments.Add(TEXT("ActorName"), FText::FromString(GetName())); - MapCheck.Warning() - ->AddToken(FUObjectToken::Create(this)) - ->AddToken(FTextToken::Create(FText::Format(LOCTEXT( "MapCheck_Message_StaticMeshNull", "Static mesh actor {ActorName} has NULL StaticMesh property" ), Arguments))) - ->AddToken(FMapErrorToken::Create(FMapErrors::StaticMeshNull)); - } - else + else if( StaticMeshComponent->GetStaticMesh() != nullptr ) { FCollisionQueryParams SphereParams(SCENE_QUERY_STAT(CheckForErrors), false, this); @@ -194,22 +185,18 @@ void AStaticMeshActor::CheckForErrors() for ( int32 OverlapIdx=0; OverlapIdx(Overlaps[OverlapIdx].GetActor()); + if ( A && (A != this) && (A->GetActorLocation() - GetActorLocation()).IsNearlyZero() && A->StaticMeshComponent + && (A->StaticMeshComponent->GetStaticMesh() == StaticMeshComponent->GetStaticMesh()) && (A->GetActorRotation() == GetActorRotation()) + && (A->StaticMeshComponent->RelativeScale3D == StaticMeshComponent->RelativeScale3D) ) { - AStaticMeshActor *A = Cast( CurTouchingActor ); - if ( A && (A != this) && (A->GetActorLocation() - GetActorLocation()).IsNearlyZero() && A->StaticMeshComponent - && (A->StaticMeshComponent->GetStaticMesh() == StaticMeshComponent->GetStaticMesh()) && (A->GetActorRotation() == GetActorRotation()) - && (A->StaticMeshComponent->RelativeScale3D == StaticMeshComponent->RelativeScale3D) ) - { - FFormatNamedArguments Arguments; - Arguments.Add(TEXT("ActorName0"), FText::FromString(GetName())); - Arguments.Add(TEXT("ActorName1"), FText::FromString(A->GetName())); - MapCheck.Warning() - ->AddToken(FUObjectToken::Create(this)) - ->AddToken(FTextToken::Create(FText::Format( LOCTEXT( "MapCheck_Message_SameLocation", "{ActorName0} is in the same location as {ActorName1}" ), Arguments ) )) - ->AddToken(FMapErrorToken::Create(FMapErrors::SameLocation)); - } + FFormatNamedArguments Arguments; + Arguments.Add(TEXT("ActorName0"), FText::FromString(GetName())); + Arguments.Add(TEXT("ActorName1"), FText::FromString(A->GetName())); + MapCheck.Warning() + ->AddToken(FUObjectToken::Create(this)) + ->AddToken(FTextToken::Create(FText::Format( LOCTEXT( "MapCheck_Message_SameLocation", "{ActorName0} is in the same location as {ActorName1}" ), Arguments ) )) + ->AddToken(FMapErrorToken::Create(FMapErrors::SameLocation)); } } diff --git a/Engine/Source/Runtime/Engine/Private/StreamableManager.cpp b/Engine/Source/Runtime/Engine/Private/StreamableManager.cpp index 3aecd63f6e35..8cb5860cbf42 100644 --- a/Engine/Source/Runtime/Engine/Private/StreamableManager.cpp +++ b/Engine/Source/Runtime/Engine/Private/StreamableManager.cpp @@ -25,11 +25,44 @@ class FStreamableDelegateDelayHelper : public FTickableGameObject public: /** Adds a delegate to deferred list */ - void AddDelegate(const FStreamableDelegate& Delegate, TSharedPtr AssociatedHandle) + void AddDelegate(const FStreamableDelegate& Delegate, const FStreamableDelegate& CancelDelegate, TSharedPtr AssociatedHandle) { DataLock.Lock(); - PendingDelegates.Emplace(Delegate, AssociatedHandle); + PendingDelegates.Emplace(Delegate, CancelDelegate, AssociatedHandle); + + DataLock.Unlock(); + } + + /** Cancels delegate for handle, this will either delete the delegate or replace with the cancel delegate */ + void CancelDelegatesForHandle(TSharedPtr AssociatedHandle) + { + if (!AssociatedHandle.IsValid()) + { + return; + } + + DataLock.Lock(); + + for (int32 DelegateIndex = 0; DelegateIndex < PendingDelegates.Num(); DelegateIndex++) + { + FPendingDelegate& PendingDelegate = PendingDelegates[DelegateIndex]; + if (PendingDelegate.RelatedHandle == AssociatedHandle) + { + if (PendingDelegate.CancelDelegate.IsBound()) + { + // Replace with cancel delegate + PendingDelegate.Delegate = PendingDelegate.CancelDelegate; + PendingDelegate.CancelDelegate.Unbind(); + } + else + { + // Remove entirely + PendingDelegates.RemoveAt(DelegateIndex); + DelegateIndex--; + } + } + } DataLock.Unlock(); } @@ -61,7 +94,7 @@ public: if (--PendingDelegates[DelegateIndex].DelayFrames <= 0) { // Add to call array and remove from tracking one - DelegatesToCall.Emplace(PendingDelegates[DelegateIndex].Delegate, PendingDelegates[DelegateIndex].RelatedHandle); + DelegatesToCall.Emplace(PendingDelegates[DelegateIndex].Delegate, FStreamableDelegate(), PendingDelegates[DelegateIndex].RelatedHandle); PendingDelegates.RemoveAt(DelegateIndex--); } } @@ -101,17 +134,21 @@ private: struct FPendingDelegate { - /** Delegate to call on next frame */ + /** Delegate to call when frames are up */ FStreamableDelegate Delegate; + /** Delegate to call if this gets cancelled early */ + FStreamableDelegate CancelDelegate; + /** Handle related to delegates, needs to keep these around to avoid things GCing before the user callback goes off. This may be null */ TSharedPtr RelatedHandle; /** Frames left to delay */ int32 DelayFrames; - FPendingDelegate(const FStreamableDelegate& InDelegate, TSharedPtr InHandle) + FPendingDelegate(const FStreamableDelegate& InDelegate, const FStreamableDelegate& InCancelDelegate, TSharedPtr InHandle) : Delegate(InDelegate) + , CancelDelegate(InCancelDelegate) , RelatedHandle(InHandle) , DelayFrames(GStreamableDelegateDelayFrames) {} @@ -347,22 +384,32 @@ void FStreamableHandle::CancelHandle() { check(IsInGameThread()); - if (bReleased || bCanceled || !OwningManager) + if (bCanceled || !OwningManager) { // Too late to cancel it return; } - if (bLoadCompleted) + TSharedRef SharedThis = AsShared(); + if (bLoadCompleted || bReleased) { - ReleaseHandle(); + // Cancel if it's in the pending queue + if (StreamableDelegateDelayHelper) + { + StreamableDelegateDelayHelper->CancelDelegatesForHandle(SharedThis); + } + + if (!bReleased) + { + ReleaseHandle(); + } + + bCanceled = true; return; } bCanceled = true; - TSharedRef SharedThis = AsShared(); - ExecuteDelegate(CancelDelegate, SharedThis); UnbindDelegates(); @@ -483,7 +530,7 @@ void FStreamableHandle::CompleteLoad() { bLoadCompleted = true; - ExecuteDelegate(CompleteDelegate, AsShared()); + ExecuteDelegate(CompleteDelegate, AsShared(), CancelDelegate); UnbindDelegates(); if (ParentHandles.Num() > 0) @@ -599,16 +646,25 @@ void FStreamableHandle::AsyncLoadCallbackWrapper(const FName& PackageName, UPack } } -void FStreamableHandle::ExecuteDelegate(const FStreamableDelegate& Delegate, TSharedPtr AssociatedHandle) +void FStreamableHandle::ExecuteDelegate(const FStreamableDelegate& Delegate, TSharedPtr AssociatedHandle, const FStreamableDelegate& CancelDelegate) { if (Delegate.IsBound()) { - if (!StreamableDelegateDelayHelper) + if (GStreamableDelegateDelayFrames == 0) { - StreamableDelegateDelayHelper = new FStreamableDelegateDelayHelper; + // Execute it immediately + Delegate.Execute(); } + else + { + // Add to execution queue for next tick + if (!StreamableDelegateDelayHelper) + { + StreamableDelegateDelayHelper = new FStreamableDelegateDelayHelper; + } - StreamableDelegateDelayHelper->AddDelegate(Delegate, AssociatedHandle); + StreamableDelegateDelayHelper->AddDelegate(Delegate, CancelDelegate, AssociatedHandle); + } } } diff --git a/Engine/Source/Runtime/Engine/Private/TextureRenderTarget.cpp b/Engine/Source/Runtime/Engine/Private/TextureRenderTarget.cpp index f6cad9a76170..8ad8ad3d809f 100644 --- a/Engine/Source/Runtime/Engine/Private/TextureRenderTarget.cpp +++ b/Engine/Source/Runtime/Engine/Private/TextureRenderTarget.cpp @@ -18,6 +18,7 @@ UTextureRenderTarget::UTextureRenderTarget(const FObjectInitializer& ObjectIniti SRGB = true; LODGroup = TEXTUREGROUP_RenderTarget; bNeedsTwoCopies = false; + bCanCreateUAV = false; #if WITH_EDITORONLY_DATA CompressionNone = true; #endif // #if WITH_EDITORONLY_DATA diff --git a/Engine/Source/Runtime/Engine/Private/TextureRenderTarget2D.cpp b/Engine/Source/Runtime/Engine/Private/TextureRenderTarget2D.cpp index 1b4045ab60d8..ceebf88adb2c 100644 --- a/Engine/Source/Runtime/Engine/Private/TextureRenderTarget2D.cpp +++ b/Engine/Source/Runtime/Engine/Private/TextureRenderTarget2D.cpp @@ -450,6 +450,11 @@ void FTextureRenderTarget2DResource::InitDynamicRHI() TexCreateFlags |= TexCreate_GenerateMipCapable; } + if (Owner->bCanCreateUAV) + { + TexCreateFlags |= TexCreate_UAV; + } + RHICreateTargetableShaderResource2D( Owner->SizeX, Owner->SizeY, diff --git a/Engine/Source/Runtime/Engine/Private/VectorField.cpp b/Engine/Source/Runtime/Engine/Private/VectorField.cpp index 21f30eb1c806..7bee165da365 100644 --- a/Engine/Source/Runtime/Engine/Private/VectorField.cpp +++ b/Engine/Source/Runtime/Engine/Private/VectorField.cpp @@ -244,19 +244,19 @@ public: FVectorFieldStaticResource*, Resource, this, FUpdateParams, UpdateParams, UpdateParams, { - // Free any existing volume data on the resource. - FMemory::Free(Resource->VolumeData); - - // Update settings on this resource. - Resource->SizeX = UpdateParams.SizeX; - Resource->SizeY = UpdateParams.SizeY; - Resource->SizeZ = UpdateParams.SizeZ; - Resource->Intensity = UpdateParams.Intensity; - Resource->LocalBounds = UpdateParams.Bounds; - Resource->VolumeData = UpdateParams.VolumeData; + // Free any existing volume data on the resource. + FMemory::Free(Resource->VolumeData); - // Update RHI resources. - Resource->UpdateRHI(); + // Update settings on this resource. + Resource->SizeX = UpdateParams.SizeX; + Resource->SizeY = UpdateParams.SizeY; + Resource->SizeZ = UpdateParams.SizeZ; + Resource->Intensity = UpdateParams.Intensity; + Resource->LocalBounds = UpdateParams.Bounds; + Resource->VolumeData = UpdateParams.VolumeData; + + // Update RHI resources. + Resource->UpdateRHI(); }); } @@ -268,6 +268,7 @@ private: UVectorFieldStatic::UVectorFieldStatic(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) + , bAllowCPUAccess(false) { } @@ -280,18 +281,100 @@ void UVectorFieldStatic::InitInstance(FVectorFieldInstance* Instance, bool bPrev void UVectorFieldStatic::InitResource() { check(Resource == NULL); - Resource = new FVectorFieldStaticResource( this ); - BeginInitResource( Resource ); + + // Loads and copies the bulk data into CPUData if bAllowCPUAccess is set, otherwise clear CPUData. + UpdateCPUData(); + + Resource = new FVectorFieldStaticResource(this); + BeginInitResource(Resource); } void UVectorFieldStatic::UpdateResource() { check(Resource != NULL); + + // Loads and copies the bulk data into CPUData if bAllowCPUAccess is set, otherwise clears CPUData. + UpdateCPUData(); + FVectorFieldStaticResource* StaticResource = (FVectorFieldStaticResource*)Resource; StaticResource->UpdateResource(this); } +#if WITH_EDITOR +ENGINE_API void UVectorFieldStatic::SetCPUAccessEnabled() +{ + bAllowCPUAccess = true; + UpdateCPUData(); +} +#endif // WITH_EDITOR + +void UVectorFieldStatic::UpdateCPUData() +{ + if (bAllowCPUAccess) + { + // Grab a copy of the bulk vector data. + // If the data is already loaded it makes a copy and discards the old content, + // otherwise it simply loads the data directly from file into the pointer after allocating. + FFloat16Color *Ptr = nullptr; + SourceData.GetCopy((void**)&Ptr, /* bDiscardInternalCopy */ true); + + // Make sure the data is actually valid. + if (!ensure(Ptr)) + { + UE_LOG(LogVectorField, Error, TEXT("Vector field data is not loaded.")); + return; + } + + // Make sure the size actually match what we expect + if (!ensure(SourceData.GetBulkDataSize() == (SizeX*SizeY*SizeZ) * sizeof(FFloat16Color))) + { + UE_LOG(LogVectorField, Error, TEXT("Vector field bulk data size is different than expected. Expected %d bytes, got %d."), SizeX*SizeY*SizeZ, SourceData.GetBulkDataSize()); + FMemory::Free(Ptr); + return; + } + + // GetCopy should free/unload the data. + if (SourceData.IsBulkDataLoaded()) + { + // NOTE(mv): This assertion will fail in the case where the bulk data is still available even though the bDiscardInternalCopy + // flag is toggled when FUntypedBulkData::CanLoadFromDisk() also fail. This happens when the user tries to allow + // CPU access to a newly imported file that isn't reloaded. We still have our valid data, so we just issue a + // warning and move on. See FUntypedBulkData::GetCopy() for more details. + UE_LOG(LogVectorField, Warning, TEXT("SourceData.GetCopy() is supposed to unload the data after copying, but it is still loaded.")); + } + + // Convert from 16-bit to 32-bit floats. + // Use vec4s instead of vec3s because of alignment, which in principle would be better for + // cache and automatic or manual vectorization, even if the memory usage is 33% larger. + // Need to profile to to make sure. + CPUData.SetNumUninitialized(SizeX*SizeY*SizeZ); + for (size_t i = 0; i < (size_t)(SizeX*SizeY*SizeZ); i++) + { + CPUData[i] = FVector4(float(Ptr[i].R), float(Ptr[i].G), float(Ptr[i].B), 0.0f); + } + + FMemory::Free(Ptr); + } + else + { + // If there's no need to access the CPU data just empty the array. + CPUData.Empty(); + } +} + +FRHITexture* UVectorFieldStatic::GetVolumeTextureRef() +{ + if (Resource) + { + return Resource->VolumeTextureRHI; + } + else + { + // Fallback to a global 1x1x1 black texture when no vector field is loaded or unavailable + return GBlackVolumeTexture->TextureRHI; + } +} void UVectorFieldStatic::ReleaseResource() { diff --git a/Engine/Source/Runtime/Engine/Private/VoiceConfig.cpp b/Engine/Source/Runtime/Engine/Private/VoiceConfig.cpp index 970e850ac3f9..2809171bb82d 100644 --- a/Engine/Source/Runtime/Engine/Private/VoiceConfig.cpp +++ b/Engine/Source/Runtime/Engine/Private/VoiceConfig.cpp @@ -35,13 +35,21 @@ int32 UVOIPStatics::GetVoiceSampleRate() } static FString DesiredSampleRateStr; - bRetrievedSampleRate = true; - + if (GConfig->GetString(TEXT("/Script/Engine.AudioSettings"), TEXT("VoiPSampleRate"), DesiredSampleRateStr, GEngineIni)) { - SampleRate = (int32)GetEnumValueFromString(TEXT("EVoiceSampleRate"), DesiredSampleRateStr); + if (DesiredSampleRateStr.Equals(TEXT("Low16000Hz"))) + { + SampleRate = (int32)EVoiceSampleRate::Low16000Hz; + } + else if (DesiredSampleRateStr.Equals(TEXT("Normal24000Hz"))) + { + SampleRate = (int32)EVoiceSampleRate::Normal24000Hz; + } + if (SampleRate > 0) { + bRetrievedSampleRate = true; return SampleRate; } } diff --git a/Engine/Source/Runtime/Engine/Private/World.cpp b/Engine/Source/Runtime/Engine/Private/World.cpp index 25bc785103cd..bdef41fa3f43 100644 --- a/Engine/Source/Runtime/Engine/Private/World.cpp +++ b/Engine/Source/Runtime/Engine/Private/World.cpp @@ -2093,12 +2093,6 @@ private: #endif // PERF_TRACK_DETAILED_ASYNC_STATS -static TAutoConsoleVariable CVarStripSubLevelClasses( - TEXT("level.StripSubLevelClasses"), - 0, - TEXT("0 - The classes specified in Game Maps Settings in sublevels will not be stripped in game worlds. ") - TEXT("1 - The classes specified in Game Maps Settings found in sublevels will be marked pending kill when the level is added to a game world. ")); - void UWorld::AddToWorld( ULevel* Level, const FTransform& LevelTransform, bool bConsiderTimeLimit ) { SCOPE_CYCLE_COUNTER(STAT_AddToWorldTime); @@ -2128,61 +2122,6 @@ void UWorld::AddToWorld( ULevel* Level, const FTransform& LevelTransform, bool b // Mark level as being the one in process of being made visible. CurrentLevelPendingVisibility = Level; - if (bIsGameWorld && CVarStripSubLevelClasses.GetValueOnGameThread() != 0) - { - QUICK_SCOPE_CYCLE_COUNTER(STAT_AddToWorldTime_StripSubLevelClasses); - - const TArray& ClassPathsToStrip = GetDefault()->SubLevelClassesToStrip; - if (ClassPathsToStrip.Num() > 0) - { - TArray ExactClassesToStrip; - TArray IsChildOfClassesToStrip; // not reserving as this is expected to be the infrequent usage - ExactClassesToStrip.Reserve(ClassPathsToStrip.Num()); - for (const FSubLevelStrippingInfo& StrippingInfo : ClassPathsToStrip) - { - if (UClass* ClassToStrip = StrippingInfo.ClassToStrip.ResolveClass()) - { - if (StrippingInfo.StripMode == ESubLevelStripMode::ExactClass) - { - ExactClassesToStrip.Add(ClassToStrip); - } - else //if (StrippingInfo.StripMode == ESubLevelStripMode::IsChildOf) - { - IsChildOfClassesToStrip.Add(ClassToStrip); - } - } - } - if (ExactClassesToStrip.Num() > 0 || IsChildOfClassesToStrip.Num() > 0) - { - for (AActor*& Actor : Level->Actors) - { - if (Actor) - { - if (ExactClassesToStrip.Contains(Actor->GetClass())) - { - UE_LOG(LogStreaming, Verbose, TEXT("Stripped sub level actor '%s'"), *Actor->GetFullName()); - Actor->MarkPendingKill(); // We do not need to go through DestroyActor lifecycle as these objects haven't been initialized yet - Actor = nullptr; - } - else - { - for (UClass* StripClass : IsChildOfClassesToStrip) - { - if (Actor->GetClass()->IsChildOf(StripClass)) - { - UE_LOG(LogStreaming, Verbose, TEXT("Stripped sub level actor '%s'"), *Actor->GetFullName()); - Actor->MarkPendingKill(); // We do not need to go through DestroyActor lifecycle as these objects haven't been initialized yet - Actor = nullptr; - break; - } - } - } - } - } - } - } - } - // Add to the UWorld's array of levels, which causes it to be rendered et al. Levels.AddUnique( Level ); @@ -2441,7 +2380,7 @@ void UWorld::AddToWorld( ULevel* Level, const FTransform& LevelTransform, bool b UE_LOG(LogStreaming, Display, TEXT("Initialize : %6.2f ms"), RouteActorInitializeTime * 1000 ); UE_LOG(LogStreaming, Display, TEXT("Cross Level Refs : %6.2f ms"), CrossLevelRefsTime * 1000 ); UE_LOG(LogStreaming, Display, TEXT("Sort Actor List : %6.2f ms"), SortActorListTime * 1000 ); - UE_LOG(LogStreaming, Display, TEXT("Perform Last Step : %6.2f ms"), SortActorListTime * 1000 ); + UE_LOG(LogStreaming, Display, TEXT("Perform Last Step : %6.2f ms"), PerformLastStepTime * 1000 ); } #endif // PERF_TRACK_DETAILED_ASYNC_STATS } @@ -2456,7 +2395,9 @@ void UWorld::RemoveFromWorld( ULevel* Level, bool bAllowIncrementalRemoval ) check(!Level->IsPendingKill()); check(!Level->IsUnreachable()); - if ( CurrentLevelPendingVisibility == nullptr && Level->bIsVisible ) + // To be removed from the world a world must be visible and not pending being made visible (this may be redundent, but for safety) + // If the level may be removed incrementally then there must also be no level pending visibility + if ( ((CurrentLevelPendingVisibility == nullptr) || (!bAllowIncrementalRemoval && (CurrentLevelPendingVisibility != Level))) && Level->bIsVisible ) { // Keep track of timing. double StartTime = FPlatformTime::Seconds(); diff --git a/Engine/Source/Runtime/Engine/Public/AudioDevice.h b/Engine/Source/Runtime/Engine/Public/AudioDevice.h index 4889db44077d..ddcba150c20c 100644 --- a/Engine/Source/Runtime/Engine/Public/AudioDevice.h +++ b/Engine/Source/Runtime/Engine/Public/AudioDevice.h @@ -15,6 +15,7 @@ #include "Sound/SoundSourceBus.h" #include "Sound/AudioSettings.h" #include "AudioDeviceManager.h" +#include "DSP/SpectrumAnalyzer.h" #include "EngineGlobals.h" class FAudioEffectsManager; @@ -1204,6 +1205,26 @@ public: UE_LOG(LogAudio, Error, TEXT("Envelope following submixes only works with the audio mixer. Please run using -audiomixer or set INI file to use submix recording.")); } + virtual void StartSpectrumAnalysis(USoundSubmix* InSubmix, const Audio::FSpectrumAnalyzerSettings& InSettings) + { + UE_LOG(LogAudio, Error, TEXT("Spectrum analysis of submixes only works with the audio mixer. Please run using -audiomixer or set INI file to use submix recording.")); + } + + virtual void StopSpectrumAnalysis(USoundSubmix* InSubmix) + { + UE_LOG(LogAudio, Error, TEXT("Spectrum analysis of submixes only works with the audio mixer. Please run using -audiomixer or set INI file to use submix recording.")); + } + + virtual void GetMagnitudesForFrequencies(USoundSubmix* InSubmix, const TArray& InFrequencies, TArray& OutMagnitudes) + { + UE_LOG(LogAudio, Error, TEXT("Spectrum analysis of submixes only works with the audio mixer. Please run using -audiomixer or set INI file to use submix recording.")); + } + + virtual void GetPhasesForFrequencies(USoundSubmix* InSubmix, const TArray& InFrequencies, TArray& OutPhases) + { + UE_LOG(LogAudio, Error, TEXT("Spectrum analysis of submixes only works with the audio mixer. Please run using -audiomixer or set INI file to use submix recording.")); + } + protected: friend class FSoundSource; diff --git a/Engine/Source/Runtime/Engine/Public/EngineLogs.h b/Engine/Source/Runtime/Engine/Public/EngineLogs.h index 15740e22fd43..a4e7de2a4625 100644 --- a/Engine/Source/Runtime/Engine/Public/EngineLogs.h +++ b/Engine/Source/Runtime/Engine/Public/EngineLogs.h @@ -9,6 +9,7 @@ class Error; ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogPath, Warning, All); +ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogController, Warning, All); ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogPhysics, Warning, All); ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogBlueprint, Warning, All); ENGINE_API DECLARE_LOG_CATEGORY_EXTERN(LogBlueprintUserMessages, Log, All); diff --git a/Engine/Source/Runtime/Engine/Public/Net/VoiceConfig.h b/Engine/Source/Runtime/Engine/Public/Net/VoiceConfig.h index 14eaecab8412..61e8822e7d0a 100644 --- a/Engine/Source/Runtime/Engine/Public/Net/VoiceConfig.h +++ b/Engine/Source/Runtime/Engine/Public/Net/VoiceConfig.h @@ -42,17 +42,6 @@ namespace MicSilenceDetectionConfig class USoundAttenuation; -template -static FORCEINLINE EnumType GetEnumValueFromString(const FString& EnumName, const FString& String) -{ - UEnum* Enum = FindObject((UObject*) ANY_PACKAGE, *EnumName, true); - if (!Enum) - { - return EnumType(0); - } - return (EnumType)Enum->GetValueByName(FName(*String)); -} - USTRUCT(BlueprintType) struct FVoiceSettings { diff --git a/Engine/Source/Runtime/Engine/Public/ParticleEmitterInstances.h b/Engine/Source/Runtime/Engine/Public/ParticleEmitterInstances.h index 2d4151be2ba5..d32189efb455 100644 --- a/Engine/Source/Runtime/Engine/Public/ParticleEmitterInstances.h +++ b/Engine/Source/Runtime/Engine/Public/ParticleEmitterInstances.h @@ -1122,6 +1122,10 @@ struct FParticleBeam2EmitterInstance : public FParticleEmitterInstance { SourceEmitter = NULL; } + if (TargetEmitter == Instance) + { + TargetEmitter = NULL; + } } protected: diff --git a/Engine/Source/Runtime/Engine/Public/ParticleHelper.h b/Engine/Source/Runtime/Engine/Public/ParticleHelper.h index 4f710a94b0e4..8cd1c8b5dc25 100644 --- a/Engine/Source/Runtime/Engine/Public/ParticleHelper.h +++ b/Engine/Source/Runtime/Engine/Public/ParticleHelper.h @@ -242,6 +242,13 @@ enum EParticleStates /*----------------------------------------------------------------------------- FParticlesStatGroup -----------------------------------------------------------------------------*/ + +DECLARE_STATS_GROUP(TEXT("ParticlesOverview"), STATGROUP_ParticlesOverview, STATCAT_Advanced); +DECLARE_CYCLE_STAT_EXTERN(TEXT("GT Total"), STAT_ParticlesOverview_GT, STATGROUP_ParticlesOverview, ); +DECLARE_CYCLE_STAT_EXTERN(TEXT("GT Concurrent Total"), STAT_ParticlesOverview_GT_CNC, STATGROUP_ParticlesOverview, ); +DECLARE_CYCLE_STAT_EXTERN(TEXT("RT Total"), STAT_ParticlesOverview_RT, STATGROUP_ParticlesOverview, ); +DECLARE_CYCLE_STAT_EXTERN(TEXT("RT Concurrent Total"), STAT_ParticlesOverview_RT_CNC, STATGROUP_ParticlesOverview, ); + DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Sprite Particles"),STAT_SpriteParticles,STATGROUP_Particles, ); DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Sprite Ptcls Spawned"),STAT_SpriteParticlesSpawned,STATGROUP_Particles, ); DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Sprite Ptcls Updated"),STAT_SpriteParticlesUpdated,STATGROUP_Particles, ); diff --git a/Engine/Source/Runtime/Engine/Public/Physics/Experimental/PhysInterface_Chaos.h b/Engine/Source/Runtime/Engine/Public/Physics/Experimental/PhysInterface_Chaos.h index d20ab497ddf5..c0a75bfb303a 100644 --- a/Engine/Source/Runtime/Engine/Public/Physics/Experimental/PhysInterface_Chaos.h +++ b/Engine/Source/Runtime/Engine/Public/Physics/Experimental/PhysInterface_Chaos.h @@ -593,6 +593,9 @@ public: ENGINE_API void DeferredAddCollisionDisableTable(uint32 SkelMeshCompID, TMap * CollisionDisableTable) {} ENGINE_API void DeferredRemoveCollisionDisableTable(uint32 SkelMeshCompID) {} + + void MarkForPreSimKinematicUpdate(USkeletalMeshComponent* InSkelComp, ETeleportType InTeleport, bool bNeedsSkinning) {} + void ClearPreSimKinematicUpdate(USkeletalMeshComponent* InSkelComp) {} void AddPendingOnConstraintBreak(FConstraintInstance* ConstraintInstance, int32 SceneType) {} void AddPendingSleepingEvent(FBodyInstance* BI, ESleepEvent SleepEventType, int32 SceneType) {} diff --git a/Engine/Source/Runtime/EngineSettings/Classes/GameMapsSettings.h b/Engine/Source/Runtime/EngineSettings/Classes/GameMapsSettings.h index 2857a907bb2e..7c70d00eefea 100644 --- a/Engine/Source/Runtime/EngineSettings/Classes/GameMapsSettings.h +++ b/Engine/Source/Runtime/EngineSettings/Classes/GameMapsSettings.h @@ -28,7 +28,8 @@ namespace EThreePlayerSplitScreenType { FavorTop, FavorBottom, - Vertical + Vertical, + Horizontal }; } @@ -36,7 +37,8 @@ UENUM() enum class EFourPlayerSplitScreenType : uint8 { Grid, - Vertical + Vertical, + Horizontal }; /** Helper structure, used to associate GameModes with shortcut names. */ @@ -64,20 +66,6 @@ enum class ESubLevelStripMode : uint8 IsChildOf }; -USTRUCT() -struct FSubLevelStrippingInfo -{ - GENERATED_BODY() - - // Actor class that should be removed from a level when it is added to a world as a sublevel - UPROPERTY(config, EditAnywhere, Category = LevelStreaming, meta = (MetaClass = "Actor")) - FSoftClassPath ClassToStrip; - - // Whether the specified class should be be stripped only if an exact class or for any child of that class - UPROPERTY(config, EditAnywhere, Category = LevelStreaming) - ESubLevelStripMode StripMode; -}; - UCLASS(config=Engine, defaultconfig) class ENGINESETTINGS_API UGameMapsSettings : public UObject @@ -172,10 +160,6 @@ public: UPROPERTY(config, noclear, EditAnywhere, Category=GameInstance, meta=(MetaClass="GameInstance")) FSoftClassPath GameInstanceClass; - /** A list of classes that should be stripped from a level's actor array when added to a game world as a sublevel. */ - UPROPERTY(config, EditAnywhere, Category=LevelStreaming, AdvancedDisplay) - TArray SubLevelClassesToStrip; - private: /** The map that will be loaded by default when no other map is loaded. */ diff --git a/Engine/Source/Runtime/Experimental/ChaosSolvers/Private/PBDRigidsSolver.cpp b/Engine/Source/Runtime/Experimental/ChaosSolvers/Private/PBDRigidsSolver.cpp index da3fedf555e0..2aa7ee07020e 100644 --- a/Engine/Source/Runtime/Experimental/ChaosSolvers/Private/PBDRigidsSolver.cpp +++ b/Engine/Source/Runtime/Experimental/ChaosSolvers/Private/PBDRigidsSolver.cpp @@ -31,8 +31,8 @@ namespace Chaos : MScene(Scene) , MDeltaTime(DeltaTime) , PrevLock(PrevFrameLock) - , PrevEvent(PrevFrameEvent) , CurrentLock(CurrentFrameLock) + , PrevEvent(PrevFrameEvent) , CurrentEvent(CurrentFrameEvent) { UE_LOG(LogPBDRigidsSolverSolver, Verbose, TEXT("AdvanceOneTimeStepTask::AdvanceOneTimeStepTask()")); @@ -99,9 +99,9 @@ namespace Chaos , bHasFloor(true) , bIsFloorAnalytic(false) , FloorHeight(0.f) - , MCurrentLock(nullptr) - , MCurrentEvent(nullptr) , MaxCollisionDataSize(1024) + , MCurrentEvent(nullptr) + , MCurrentLock(nullptr) , CollisionDataTimeWindow(0.1f) , DoCollisionDataSpatialHash(true) , CollisionDataSpatialHashRadius(15.f) diff --git a/Engine/Source/Runtime/GameplayTags/Classes/GameplayTagContainer.h b/Engine/Source/Runtime/GameplayTags/Classes/GameplayTagContainer.h index 022be0ab3f9d..e68303f1d7bf 100644 --- a/Engine/Source/Runtime/GameplayTags/Classes/GameplayTagContainer.h +++ b/Engine/Source/Runtime/GameplayTags/Classes/GameplayTagContainer.h @@ -59,13 +59,20 @@ struct GAMEPLAYTAGS_API FGameplayTag * Gets the FGameplayTag that corresponds to the TagName * * @param TagName The Name of the tag to search for - * * @param ErrorIfNotfound: ensure() that tag exists. - * * @return Will return the corresponding FGameplayTag or an empty one if not found. */ static FGameplayTag RequestGameplayTag(FName TagName, bool ErrorIfNotFound=true); + /** + * Returns true if this is a valid gameplay tag string (foo.bar.baz). If false, it will fill + * @param TagString String to check for validity + * @param OutError If non-null and string invalid, will fill in with an error message + * @param OutFixedString If non-null and string invalid, will attempt to fix. Will be empty if no fix is possible + * @return True if this can be added to the tag dictionary, false if there's a syntax error + */ + static bool IsValidGameplayTagString(const FString& TagString, FText* OutError = nullptr, FString* OutFixedString = nullptr); + /** Operators */ FORCEINLINE bool operator==(FGameplayTag const& Other) const { diff --git a/Engine/Source/Runtime/GameplayTags/Classes/GameplayTagsManager.h b/Engine/Source/Runtime/GameplayTags/Classes/GameplayTagsManager.h index f07a407d1643..2e3c39aa5161 100644 --- a/Engine/Source/Runtime/GameplayTags/Classes/GameplayTagsManager.h +++ b/Engine/Source/Runtime/GameplayTags/Classes/GameplayTagsManager.h @@ -13,6 +13,7 @@ #include "GameplayTagsManager.generated.h" class UGameplayTagsList; +struct FStreamableHandle; /** Simple struct for a table row in the gameplay tag table and element in the ini list */ USTRUCT() @@ -141,8 +142,8 @@ struct FGameplayTagNode GENERATED_USTRUCT_BODY() FGameplayTagNode(){}; - /** Simple constructor */ - FGameplayTagNode(FName InTag, TSharedPtr InParentNode, bool InIsExplicitTag, bool InIsRestrictedTag, bool InAllowNonRestrictedChildren); + /** Simple constructor, passing redundant data for performance */ + FGameplayTagNode(FName InTag, FName InFullTag, TSharedPtr InParentNode, bool InIsExplicitTag, bool InIsRestrictedTag, bool InAllowNonRestrictedChildren); /** Returns a correctly constructed container with only this tag, useful for doing container queries */ FORCEINLINE const FGameplayTagContainer& GetSingleTagContainer() const { return CompleteTagWithParents; } @@ -304,6 +305,15 @@ class GAMEPLAYTAGS_API UGameplayTagsManager : public UObject */ FGameplayTag RequestGameplayTag(FName TagName, bool ErrorIfNotFound=true) const; + /** + * Returns true if this is a valid gameplay tag string (foo.bar.baz). If false, it will fill + * @param TagString String to check for validity + * @param OutError If non-null and string invalid, will fill in with an error message + * @param OutFixedString If non-null and string invalid, will attempt to fix. Will be empty if no fix is possible + * @return True if this can be added to the tag dictionary, false if there's a syntax error + */ + bool IsValidGameplayTagString(const FString& TagString, FText* OutError = nullptr, FString* OutFixedString = nullptr); + /** * Searches for a gameplay tag given a partial string. This is slow and intended mainly for console commands/utilities to make * developer life's easier. This will attempt to match as best as it can. If you pass "A.b" it will match on "A.b." before it matches "a.b.c". @@ -413,7 +423,7 @@ class GAMEPLAYTAGS_API UGameplayTagsManager : public UObject } /** Loads the tag tables referenced in the GameplayTagSettings object */ - void LoadGameplayTagTables(); + void LoadGameplayTagTables(bool bAllowAsyncLoad = false); /** Helper function to construct the gameplay tag tree */ void ConstructGameplayTagTree(); @@ -509,8 +519,12 @@ class GAMEPLAYTAGS_API UGameplayTagsManager : public UObject /** Returns "Categories" meta property from given handle, used for filtering by tag widget */ FString GetCategoriesMetaFromPropertyHandle(TSharedPtr PropertyHandle) const; + /** Returns "Categories" meta property from given field, used for filtering by tag widget */ + FString GetCategoriesMetaFromField(UField* Field) const; + /** Returns "Categories" meta property from given struct, used for filtering by tag widget */ - FString GetCategoriesMetaFromStruct(UScriptStruct* Struct) const; + UE_DEPRECATED(4.22, "Please call GetCategoriesMetaFromField instead.") + FString GetCategoriesMetaFromStruct(UScriptStruct* Struct) const { return GetCategoriesMetaFromField(Struct); } /** Returns "GameplayTagFilter" meta property from given function, used for filtering by tag widget for any parameters of the function that end up as BP pins */ FString GetCategoriesMetaFromFunction(UFunction* Func) const; @@ -611,7 +625,8 @@ private: /** * Helper function to insert a tag into a tag node array * - * @param Tag Tag to insert + * @param Tag Short name of tag to insert + * @param FullTag Full tag, passed in for performance * @param ParentNode Parent node, if any, for the tag * @param NodeArray Node array to insert the new node into, if necessary (if the tag already exists, no insertion will occur) * @param SourceName File tag was added from @@ -622,7 +637,7 @@ private: * * @return Index of the node of the tag */ - int32 InsertTagIntoNodeArray(FName Tag, TSharedPtr ParentNode, TArray< TSharedPtr >& NodeArray, FName SourceName, const FString& DevComment, bool bIsExplicitTag, bool bIsRestrictedTag, bool bAllowNonRestrictedChildren); + int32 InsertTagIntoNodeArray(FName Tag, FName FullTag, TSharedPtr ParentNode, TArray< TSharedPtr >& NodeArray, FName SourceName, const FString& DevComment, bool bIsExplicitTag, bool bIsRestrictedTag, bool bAllowNonRestrictedChildren); /** Helper function to populate the tag tree from each table */ void PopulateTreeFromDataTable(class UDataTable* Table); @@ -674,6 +689,9 @@ private: /** True if native tags have all been added and flushed */ bool bDoneAddingNativeTags; + /** String with outlawed characters inside tags */ + FString InvalidTagCharacters; + #if WITH_EDITOR // This critical section is to handle an editor-only issue where tag requests come from another thread when async loading from a background thread in FGameplayTagContainer::Serialize. // This class is not generically threadsafe. @@ -686,9 +704,6 @@ private: /** Sorted list of nodes, used for network replication */ TArray> NetworkGameplayTagNodeIndex; - UPROPERTY() - TArray RestrictedGameplayTagTables; - /** Holds all of the valid gameplay-related tags that can be applied to assets */ UPROPERTY() TArray GameplayTagTables; diff --git a/Engine/Source/Runtime/GameplayTags/Classes/GameplayTagsSettings.h b/Engine/Source/Runtime/GameplayTags/Classes/GameplayTagsSettings.h index 2a3f4141ceba..4b5cbf094f73 100644 --- a/Engine/Source/Runtime/GameplayTags/Classes/GameplayTagsSettings.h +++ b/Engine/Source/Runtime/GameplayTags/Classes/GameplayTagsSettings.h @@ -126,17 +126,22 @@ class GAMEPLAYTAGS_API UGameplayTagsSettings : public UGameplayTagsList UPROPERTY(config, EditAnywhere, Category = GameplayTags) bool ImportTagsFromConfig; - /** If true, will give load warnings when reading invalid tags off disk */ + /** If true, will give load warnings when reading in saved tag references that are not in the dictionary */ UPROPERTY(config, EditAnywhere, Category = GameplayTags) bool WarnOnInvalidTags; - UPROPERTY(config, EditAnywhere, Category = GameplayTags) - TArray CategoryRemapping; - /** If true, will replicate gameplay tags by index instead of name. For this to work, tags must be identical on client and server */ UPROPERTY(config, EditAnywhere, Category = "Advanced Replication") bool FastReplication; + /** These characters cannot be used in gameplay tags, in addition to special ones like newline*/ + UPROPERTY(config, EditAnywhere, Category = GameplayTags) + FString InvalidTagCharacters; + + /** Category remapping. This allows base engine tag category meta data to remap to multiple project-specific categories. */ + UPROPERTY(config, EditAnywhere, Category = GameplayTags) + TArray CategoryRemapping; + /** List of data tables to load tags from */ UPROPERTY(config, EditAnywhere, Category = GameplayTags, meta = (AllowedClasses = "DataTable")) TArray GameplayTagTableList; @@ -165,6 +170,7 @@ class GAMEPLAYTAGS_API UGameplayTagsSettings : public UGameplayTagsList UPROPERTY(EditAnywhere, AdvancedDisplay, transient, Category = "Advanced Gameplay Tags") FString RestrictedTagList; #endif + #if WITH_EDITOR virtual void PreEditChange(UProperty* PropertyThatWillChange) override; virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; diff --git a/Engine/Source/Runtime/GameplayTags/Private/GameplayTagContainer.cpp b/Engine/Source/Runtime/GameplayTags/Private/GameplayTagContainer.cpp index 45350987b1f2..da2c192eeb82 100644 --- a/Engine/Source/Runtime/GameplayTags/Private/GameplayTagContainer.cpp +++ b/Engine/Source/Runtime/GameplayTags/Private/GameplayTagContainer.cpp @@ -1090,6 +1090,11 @@ FGameplayTag FGameplayTag::RequestGameplayTag(FName TagName, bool ErrorIfNotFoun return UGameplayTagsManager::Get().RequestGameplayTag(TagName, ErrorIfNotFound); } +bool FGameplayTag::IsValidGameplayTagString(const FString& TagString, FText* OutError, FString* OutFixedString) +{ + return UGameplayTagsManager::Get().IsValidGameplayTagString(TagString, OutError, OutFixedString); +} + FGameplayTagContainer FGameplayTag::GetGameplayTagParents() const { return UGameplayTagsManager::Get().RequestGameplayTagParents(*this); @@ -1350,7 +1355,6 @@ void FGameplayTag::FromExportString(const FString& ExportString) FGameplayTagNativeAdder::FGameplayTagNativeAdder() { - UE_LOG(LogGameplayTags, Display, TEXT("FGameplayTagNativeAdder::FGameplayTagNativeAdder")); UGameplayTagsManager::OnLastChanceToAddNativeTags().AddRaw(this, &FGameplayTagNativeAdder::AddTags); } diff --git a/Engine/Source/Runtime/GameplayTags/Private/GameplayTagsManager.cpp b/Engine/Source/Runtime/GameplayTags/Private/GameplayTagsManager.cpp index 2bcb5d870faa..d996d90f5634 100644 --- a/Engine/Source/Runtime/GameplayTags/Private/GameplayTagsManager.cpp +++ b/Engine/Source/Runtime/GameplayTags/Private/GameplayTagsManager.cpp @@ -1,6 +1,5 @@ // Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. - #include "GameplayTagsManager.h" #include "Engine/Engine.h" #include "HAL/PlatformFilemanager.h" @@ -46,12 +45,32 @@ UGameplayTagsManager::UGameplayTagsManager(const FObjectInitializer& ObjectIniti NumBitsForContainerSize = 6; } -void UGameplayTagsManager::LoadGameplayTagTables() +// Enable to turn on detailed startup logging +#define GAMEPLAYTAGS_VERBOSE 0 + +#if STATS && GAMEPLAYTAGS_VERBOSE +#define SCOPE_LOG_GAMEPLAYTAGS(Name) SCOPE_LOG_TIME_IN_SECONDS(Name, nullptr) +#else +#define SCOPE_LOG_GAMEPLAYTAGS(Name) +#endif + +void UGameplayTagsManager::LoadGameplayTagTables(bool bAllowAsyncLoad) { + UGameplayTagsSettings* MutableDefault = GetMutableDefault(); GameplayTagTables.Empty(); - UGameplayTagsSettings* MutableDefault = GetMutableDefault(); + // If we're a cooked build and in a safe spot, start an async load so we can pipeline it + if (bAllowAsyncLoad && !WITH_EDITOR && !IsLoading() && MutableDefault->GameplayTagTableList.Num() > 0) + { + for (FSoftObjectPath DataTablePath : MutableDefault->GameplayTagTableList) + { + LoadPackageAsync(DataTablePath.GetLongPackageName()); + } + return; + } + + SCOPE_LOG_GAMEPLAYTAGS(TEXT("UGameplayTagsManager::LoadGameplayTagTables")); for (FSoftObjectPath DataTablePath : MutableDefault->GameplayTagTableList) { UDataTable* TagTable = LoadObject(nullptr, *DataTablePath.ToString(), nullptr, LOAD_None, nullptr); @@ -84,21 +103,22 @@ struct FCompareFGameplayTagNodeByTag void UGameplayTagsManager::ConstructGameplayTagTree() { + SCOPE_LOG_GAMEPLAYTAGS(TEXT("UGameplayTagsManager::ConstructGameplayTagTree")); if (!GameplayRootTag.IsValid()) { GameplayRootTag = MakeShareable(new FGameplayTagNode()); UGameplayTagsSettings* MutableDefault = GetMutableDefault(); - FString DefaultEnginePath = FString::Printf(TEXT("%sDefaultEngine.ini"), *FPaths::SourceConfigDir()); TArray RestrictedGameplayTagSourceNames; + // Copy invalid characters, then add internal ones + InvalidTagCharacters = MutableDefault->InvalidTagCharacters; + InvalidTagCharacters.Append(TEXT("\r\n\t")); + // Add prefixes first if (ShouldImportTagsFromINI()) { -#if STATS - FString PerfMessage = FString::Printf(TEXT("UGameplayTagsManager::ConstructGameplayTagTree: ImportINI prefixes")); - SCOPE_LOG_TIME_IN_SECONDS(*PerfMessage, nullptr) -#endif + SCOPE_LOG_GAMEPLAYTAGS(TEXT("UGameplayTagsManager::ConstructGameplayTagTree: ImportINI prefixes")); TArray RestrictedGameplayTagFiles; GetRestrictedTagConfigFiles(RestrictedGameplayTagFiles); @@ -135,23 +155,28 @@ void UGameplayTagsManager::ConstructGameplayTagTree() } } - // Add native tags before other tags - for (FName TagToAdd : NativeTagsToAdd) { - AddTagTableRow(FGameplayTagTableRow(TagToAdd), FGameplayTagSource::GetNativeName()); + SCOPE_LOG_GAMEPLAYTAGS(TEXT("UGameplayTagsManager::ConstructGameplayTagTree: Add native tags")); + // Add native tags before other tags + for (FName TagToAdd : NativeTagsToAdd) + { + AddTagTableRow(FGameplayTagTableRow(TagToAdd), FGameplayTagSource::GetNativeName()); + } + } + + // If we didn't load any tables it might be async loading, so load again with a flush + if (GameplayTagTables.Num() == 0) + { + LoadGameplayTagTables(false); } { -#if STATS - FString PerfMessage = FString::Printf(TEXT("UGameplayTagsManager::ConstructGameplayTagTree: Construct from data asset")); - SCOPE_LOG_TIME_IN_SECONDS(*PerfMessage, nullptr) -#endif - - for (auto It(GameplayTagTables.CreateIterator()); It; It++) + SCOPE_LOG_GAMEPLAYTAGS(TEXT("UGameplayTagsManager::ConstructGameplayTagTree: Construct from data asset")); + for (UDataTable* DataTable : GameplayTagTables) { - if (*It) + if (DataTable) { - PopulateTreeFromDataTable(*It); + PopulateTreeFromDataTable(DataTable); } } } @@ -161,14 +186,12 @@ void UGameplayTagsManager::ConstructGameplayTagTree() if (ShouldImportTagsFromINI()) { -#if STATS - FString PerfMessage = FString::Printf(TEXT("UGameplayTagsManager::ConstructGameplayTagTree: ImportINI")); - SCOPE_LOG_TIME_IN_SECONDS(*PerfMessage, nullptr) -#endif + SCOPE_LOG_GAMEPLAYTAGS(TEXT("UGameplayTagsManager::ConstructGameplayTagTree: ImportINI tags")); + // Copy from deprecated list in DefaultEngine.ini TArray EngineConfigTags; - GConfig->GetArray(TEXT("/Script/GameplayTags.GameplayTagsSettings"), TEXT("+GameplayTags"), EngineConfigTags, DefaultEnginePath); - + GConfig->GetArray(TEXT("/Script/GameplayTags.GameplayTagsSettings"), TEXT("+GameplayTags"), EngineConfigTags, GEngineIni); + for (const FString& EngineConfigTag : EngineConfigTags) { MutableDefault->GameplayTagList.AddUnique(FGameplayTagTableRow(FName(*EngineConfigTag))); @@ -223,7 +246,7 @@ void UGameplayTagsManager::ConstructGameplayTagTree() FGameplayTagSource* FoundSource = FindOrAddTagSource(TagSource, EGameplayTagSourceType::TagList); - UE_LOG(LogGameplayTags, Display, TEXT("Loading Tag File: %s"), *FileName); + UE_CLOG(GAMEPLAYTAGS_VERBOSE, LogGameplayTags, Display, TEXT("Loading Tag File: %s"), *FileName); if (FoundSource && FoundSource->SourceTagList) { @@ -264,144 +287,143 @@ void UGameplayTagsManager::ConstructGameplayTagTree() AddTagTableRow(FGameplayTagTableRow(TransientTag), FGameplayTagSource::GetTransientEditorName()); } #endif - - // Grab the commonly replicated tags - CommonlyReplicatedTags.Empty(); - for (FName TagName : MutableDefault->CommonlyReplicatedTags) { - FGameplayTag Tag = RequestGameplayTag(TagName); - if (Tag.IsValid()) + SCOPE_LOG_GAMEPLAYTAGS(TEXT("UGameplayTagsManager::ConstructGameplayTagTree: Request common tags")); + // Grab the commonly replicated tags + CommonlyReplicatedTags.Empty(); + for (FName TagName : MutableDefault->CommonlyReplicatedTags) { - CommonlyReplicatedTags.Add(Tag); + FGameplayTag Tag = RequestGameplayTag(TagName); + if (Tag.IsValid()) + { + CommonlyReplicatedTags.Add(Tag); + } + else + { + UE_LOG(LogGameplayTags, Warning, TEXT("%s was found in the CommonlyReplicatedTags list but doesn't appear to be a valid tag!"), *TagName.ToString()); + } } - else - { - UE_LOG(LogGameplayTags, Warning, TEXT("%s was found in the CommonlyReplicatedTags list but doesn't appear to be a valid tag!"), *TagName.ToString()); - } - } - bUseFastReplication = MutableDefault->FastReplication; - bShouldWarnOnInvalidTags = MutableDefault->WarnOnInvalidTags; - NumBitsForContainerSize = MutableDefault->NumBitsForContainerSize; - NetIndexFirstBitSegment = MutableDefault->NetIndexFirstBitSegment; + bUseFastReplication = MutableDefault->FastReplication; + bShouldWarnOnInvalidTags = MutableDefault->WarnOnInvalidTags; + NumBitsForContainerSize = MutableDefault->NumBitsForContainerSize; + NetIndexFirstBitSegment = MutableDefault->NetIndexFirstBitSegment; + } if (ShouldUseFastReplication()) { -#if STATS - FString PerfMessage = FString::Printf(TEXT("UGameplayTagsManager::ConstructGameplayTagTree: Reconstruct NetIndex")); - SCOPE_LOG_TIME_IN_SECONDS(*PerfMessage, nullptr) -#endif + SCOPE_LOG_GAMEPLAYTAGS(TEXT("UGameplayTagsManager::ConstructGameplayTagTree: Reconstruct NetIndex")); ConstructNetIndex(); } { -#if STATS - FString PerfMessage = FString::Printf(TEXT("UGameplayTagsManager::ConstructGameplayTagTree: GameplayTagTreeChangedEvent.Broadcast")); - SCOPE_LOG_TIME_IN_SECONDS(*PerfMessage, nullptr) -#endif + SCOPE_LOG_GAMEPLAYTAGS(TEXT("UGameplayTagsManager::ConstructGameplayTagTree: GameplayTagTreeChangedEvent.Broadcast")); IGameplayTagsModule::OnGameplayTagTreeChanged.Broadcast(); } - // Update the TagRedirects map - TagRedirects.Empty(); - - // Check the deprecated location - bool bFoundDeprecated = false; - FConfigSection* PackageRedirects = GConfig->GetSectionPrivate(TEXT("/Script/Engine.Engine"), false, true, DefaultEnginePath); - - if (PackageRedirects) { - for (FConfigSection::TIterator It(*PackageRedirects); It; ++It) + SCOPE_LOG_GAMEPLAYTAGS(TEXT("UGameplayTagsManager::ConstructGameplayTagTree: Load redirects")); + // Update the TagRedirects map + TagRedirects.Empty(); + + // Check the deprecated location + bool bFoundDeprecated = false; + FConfigSection* PackageRedirects = GConfig->GetSectionPrivate(TEXT("/Script/Engine.Engine"), false, true, GEngineIni); + + if (PackageRedirects) { - if (It.Key() == TEXT("+GameplayTagRedirects")) + for (FConfigSection::TIterator It(*PackageRedirects); It; ++It) { - FName OldTagName = NAME_None; - FName NewTagName; - - if (FParse::Value(*It.Value().GetValue(), TEXT("OldTagName="), OldTagName)) + if (It.Key() == TEXT("+GameplayTagRedirects")) { - if (FParse::Value(*It.Value().GetValue(), TEXT("NewTagName="), NewTagName)) + FName OldTagName = NAME_None; + FName NewTagName; + + if (FParse::Value(*It.Value().GetValue(), TEXT("OldTagName="), OldTagName)) { - FGameplayTagRedirect Redirect; - Redirect.OldTagName = OldTagName; - Redirect.NewTagName = NewTagName; + if (FParse::Value(*It.Value().GetValue(), TEXT("NewTagName="), NewTagName)) + { + FGameplayTagRedirect Redirect; + Redirect.OldTagName = OldTagName; + Redirect.NewTagName = NewTagName; - MutableDefault->GameplayTagRedirects.AddUnique(Redirect); + MutableDefault->GameplayTagRedirects.AddUnique(Redirect); - bFoundDeprecated = true; + bFoundDeprecated = true; + } } } } } - } - if (bFoundDeprecated) - { - UE_LOG(LogGameplayTags, Log, TEXT("GameplayTagRedirects is in a deprecated location, after editing GameplayTags developer settings you must remove these manually")); - } - - // Check settings object - for (const FGameplayTagRedirect& Redirect : MutableDefault->GameplayTagRedirects) - { - FName OldTagName = Redirect.OldTagName; - FName NewTagName = Redirect.NewTagName; - - if (ensureMsgf(!TagRedirects.Contains(OldTagName), TEXT("Old tag %s is being redirected to more than one tag. Please remove all the redirections except for one."), *OldTagName.ToString())) + if (bFoundDeprecated) { - FGameplayTag OldTag = RequestGameplayTag(OldTagName, false); //< This only succeeds if OldTag is in the Table! - if (OldTag.IsValid()) + UE_LOG(LogGameplayTags, Log, TEXT("GameplayTagRedirects is in a deprecated location, after editing GameplayTags developer settings you must remove these manually")); + } + + // Check settings object + for (const FGameplayTagRedirect& Redirect : MutableDefault->GameplayTagRedirects) + { + FName OldTagName = Redirect.OldTagName; + FName NewTagName = Redirect.NewTagName; + + if (ensureMsgf(!TagRedirects.Contains(OldTagName), TEXT("Old tag %s is being redirected to more than one tag. Please remove all the redirections except for one."), *OldTagName.ToString())) { - FGameplayTagContainer MatchingChildren = RequestGameplayTagChildren(OldTag); - - FString Msg = FString::Printf(TEXT("Old tag (%s) which is being redirected still exists in the table! Generally you should " - TEXT("remove the old tags from the table when you are redirecting to new tags, or else users will ") - TEXT("still be able to add the old tags to containers.")), *OldTagName.ToString()); - - if (MatchingChildren.Num() == 0) + FGameplayTag OldTag = RequestGameplayTag(OldTagName, false); //< This only succeeds if OldTag is in the Table! + if (OldTag.IsValid()) { - UE_LOG(LogGameplayTags, Warning, TEXT("%s"), *Msg); - } - else - { - Msg += TEXT("\nSuppressed warning due to redirected tag being a single component that matched other hierarchy elements."); - UE_LOG(LogGameplayTags, Log, TEXT("%s"), *Msg); - } - } + FGameplayTagContainer MatchingChildren = RequestGameplayTagChildren(OldTag); - FGameplayTag NewTag = (NewTagName != NAME_None) ? RequestGameplayTag(NewTagName, false) : FGameplayTag(); + FString Msg = FString::Printf(TEXT("Old tag (%s) which is being redirected still exists in the table! Generally you should " + TEXT("remove the old tags from the table when you are redirecting to new tags, or else users will ") + TEXT("still be able to add the old tags to containers.")), *OldTagName.ToString()); - // Basic infinite recursion guard - int32 IterationsLeft = 10; - while (!NewTag.IsValid() && NewTagName != NAME_None) - { - bool bFoundRedirect = false; - - // See if it got redirected again - for (const FGameplayTagRedirect& SecondRedirect : MutableDefault->GameplayTagRedirects) - { - if (SecondRedirect.OldTagName == NewTagName) + if (MatchingChildren.Num() == 0) { - NewTagName = SecondRedirect.NewTagName; - NewTag = RequestGameplayTag(NewTagName, false); - bFoundRedirect = true; + UE_LOG(LogGameplayTags, Warning, TEXT("%s"), *Msg); + } + else + { + Msg += TEXT("\nSuppressed warning due to redirected tag being a single component that matched other hierarchy elements."); + UE_LOG(LogGameplayTags, Log, TEXT("%s"), *Msg); + } + } + + FGameplayTag NewTag = (NewTagName != NAME_None) ? RequestGameplayTag(NewTagName, false) : FGameplayTag(); + + // Basic infinite recursion guard + int32 IterationsLeft = 10; + while (!NewTag.IsValid() && NewTagName != NAME_None) + { + bool bFoundRedirect = false; + + // See if it got redirected again + for (const FGameplayTagRedirect& SecondRedirect : MutableDefault->GameplayTagRedirects) + { + if (SecondRedirect.OldTagName == NewTagName) + { + NewTagName = SecondRedirect.NewTagName; + NewTag = RequestGameplayTag(NewTagName, false); + bFoundRedirect = true; + break; + } + } + IterationsLeft--; + + if (!bFoundRedirect || IterationsLeft <= 0) + { + UE_LOG(LogGameplayTags, Warning, TEXT("Invalid new tag %s! Cannot replace old tag %s."), + *Redirect.NewTagName.ToString(), *Redirect.OldTagName.ToString()); break; } } - IterationsLeft--; - if (!bFoundRedirect || IterationsLeft <= 0) + if (NewTag.IsValid()) { - UE_LOG(LogGameplayTags, Warning, TEXT("Invalid new tag %s! Cannot replace old tag %s."), - *Redirect.NewTagName.ToString(), *Redirect.OldTagName.ToString()); - break; + // Populate the map + TagRedirects.Add(OldTagName, NewTag); } } - - if (NewTag.IsValid()) - { - // Populate the map - TagRedirects.Add(OldTagName, NewTag); - } } } } @@ -454,8 +476,6 @@ void UGameplayTagsManager::ConstructNetIndex() NetworkGameplayTagNodeIndex.SetNum(INVALID_TAGNETINDEX - 1); } - - UE_CLOG(PrintNetIndiceAssignment, LogGameplayTags, Display, TEXT("Assigning NetIndices to %d tags."), NetworkGameplayTagNodeIndex.Num() ); for (FGameplayTagNetIndex i = 0; i < NetworkGameplayTagNodeIndex.Num(); i++) @@ -499,11 +519,10 @@ FGameplayTagNetIndex UGameplayTagsManager::GetNetIndexFromTag(const FGameplayTag bool UGameplayTagsManager::ShouldImportTagsFromINI() const { UGameplayTagsSettings* MutableDefault = GetMutableDefault(); - FString DefaultEnginePath = FString::Printf(TEXT("%sDefaultEngine.ini"), *FPaths::SourceConfigDir()); - + // Deprecated path bool ImportFromINI = false; - if (GConfig->GetBool(TEXT("GameplayTags"), TEXT("ImportTagsFromConfig"), ImportFromINI, DefaultEnginePath)) + if (GConfig->GetBool(TEXT("GameplayTags"), TEXT("ImportTagsFromConfig"), ImportFromINI, GEngineIni)) { if (ImportFromINI) { @@ -662,27 +681,35 @@ bool UGameplayTagsManager::ImportSingleGameplayTag(FGameplayTag& Tag, FName Impo void UGameplayTagsManager::InitializeManager() { check(!SingletonManager); + SCOPE_LOG_TIME_IN_SECONDS(TEXT("UGameplayTagsManager::InitializeManager"), nullptr); SingletonManager = NewObject(GetTransientPackage(), NAME_None); SingletonManager->AddToRoot(); - UGameplayTagsSettings* MutableDefault = GetMutableDefault(); - FString DefaultEnginePath = FString::Printf(TEXT("%sDefaultEngine.ini"), *FPaths::SourceConfigDir()); - - TArray GameplayTagTables; - GConfig->GetArray(TEXT("GameplayTags"), TEXT("+GameplayTagTableList"), GameplayTagTables, DefaultEnginePath); - - // Report deprecation - if (GameplayTagTables.Num() > 0) + UGameplayTagsSettings* MutableDefault = nullptr; { - UE_LOG(LogGameplayTags, Log, TEXT("GameplayTagTableList is in a deprecated location, open and save GameplayTag settings to fix")); - for (const FString& DataTable : GameplayTagTables) + SCOPE_LOG_GAMEPLAYTAGS(TEXT("UGameplayTagsManager::InitializeManager: Load settings")); + MutableDefault = GetMutableDefault(); + } + + { + SCOPE_LOG_GAMEPLAYTAGS(TEXT("UGameplayTagsManager::InitializeManager: Load deprecated")); + + TArray GameplayTagTablePaths; + GConfig->GetArray(TEXT("GameplayTags"), TEXT("+GameplayTagTableList"), GameplayTagTablePaths, GEngineIni); + + // Report deprecation + if (GameplayTagTablePaths.Num() > 0) { - MutableDefault->GameplayTagTableList.AddUnique(DataTable); + UE_LOG(LogGameplayTags, Log, TEXT("GameplayTagTableList is in a deprecated location, open and save GameplayTag settings to fix")); + for (const FString& DataTable : GameplayTagTablePaths) + { + MutableDefault->GameplayTagTableList.AddUnique(DataTable); + } } } - SingletonManager->LoadGameplayTagTables(); + SingletonManager->LoadGameplayTagTables(true); SingletonManager->ConstructGameplayTagTree(); // Bind to end of engine init to be done adding native tags @@ -722,19 +749,73 @@ void UGameplayTagsManager::AddTagTableRow(const FGameplayTagTableRow& TagRow, FN bAllowNonRestrictedChildren = RestrictedTagRow->bAllowNonRestrictedChildren; } - // Split the tag text on the "." delimiter to establish tag depth and then insert each tag into the - // gameplay tag tree - TArray SubTags; - TagRow.Tag.ToString().ParseIntoArray(SubTags, TEXT("."), true); + // Split the tag text on the "." delimiter to establish tag depth and then insert each tag into the gameplay tag tree + // We try to avoid as many FString->FName conversions as possible as they are slow + FName OriginalTagName = TagRow.Tag; + FString FullTagString = OriginalTagName.ToString(); +#if WITH_EDITOR + { + // In editor builds, validate string + // These must get fixed up cooking to work properly + FText ErrorText; + FString FixedString; + + if (!IsValidGameplayTagString(FullTagString, &ErrorText, &FixedString)) + { + if (FixedString.IsEmpty()) + { + // No way to fix it + UE_LOG(LogGameplayTags, Error, TEXT("Invalid tag %s from source %s: %s!"), *FullTagString, *SourceName.ToString(), *ErrorText.ToString()); + return; + } + else + { + UE_LOG(LogGameplayTags, Error, TEXT("Invalid tag %s from source %s: %s! Replacing with %s, you may need to modify InvalidTagCharacters"), *FullTagString, *SourceName.ToString(), *ErrorText.ToString(), *FixedString); + FullTagString = FixedString; + OriginalTagName = FName(*FixedString); + } + } + } +#endif + + TArray SubTags; + FullTagString.ParseIntoArray(SubTags, TEXT("."), true); + + // We will build this back up as we go + FullTagString.Reset(); + + int32 NumSubTags = SubTags.Num(); bool bHasSeenConflict = false; - for (int32 SubTagIdx = 0; SubTagIdx < SubTags.Num(); ++SubTagIdx) + for (int32 SubTagIdx = 0; SubTagIdx < NumSubTags; ++SubTagIdx) { - TArray< TSharedPtr >& ChildTags = CurNode.Get()->GetChildTagNodes(); + bool bIsExplicitTag = (SubTagIdx == (NumSubTags - 1)); + FName ShortTagName = *SubTags[SubTagIdx]; + FName FullTagName; - bool bFromDictionary = (SubTagIdx == (SubTags.Num() - 1)); - int32 InsertionIdx = InsertTagIntoNodeArray(*SubTags[SubTagIdx], CurNode, ChildTags, SourceName, TagRow.DevComment, bFromDictionary, bIsRestrictedTag, bAllowNonRestrictedChildren); + if (bIsExplicitTag) + { + // We already know the final name + FullTagName = OriginalTagName; + } + else if (SubTagIdx == 0) + { + // Full tag is the same as short tag, and start building full tag string + FullTagName = ShortTagName; + FullTagString = SubTags[SubTagIdx]; + } + else + { + // Add .Tag and use that as full tag + FullTagString += TEXT("."); + FullTagString += SubTags[SubTagIdx]; + + FullTagName = FName(*FullTagString); + } + + TArray< TSharedPtr >& ChildTags = CurNode.Get()->GetChildTagNodes(); + int32 InsertionIdx = InsertTagIntoNodeArray(ShortTagName, FullTagName, CurNode, ChildTags, SourceName, TagRow.DevComment, bIsExplicitTag, bIsRestrictedTag, bAllowNonRestrictedChildren); CurNode = ChildTags[InsertionIdx]; @@ -745,7 +826,7 @@ void UGameplayTagsManager::AddTagTableRow(const FGameplayTagTableRow& TagRow, FN CurNode->bAncestorHasConflict = bHasSeenConflict; // If the sources don't match and the tag is explicit and we should've added the tag explicitly here, we have a conflict - if (CurNode->SourceName != SourceName && (CurNode->bIsExplicitTag && SubTagIdx == SubTags.Num() - 1)) + if (CurNode->SourceName != SourceName && (CurNode->bIsExplicitTag && bIsExplicitTag)) { // mark all ancestors as having a bad descendant for (TSharedPtr CurAncestorNode : AncestorNodes) @@ -811,20 +892,21 @@ bool UGameplayTagsManager::IsNativelyAddedTag(FGameplayTag Tag) const return NativeTagsToAdd.Contains(Tag.GetTagName()); } -int32 UGameplayTagsManager::InsertTagIntoNodeArray(FName Tag, TSharedPtr ParentNode, TArray< TSharedPtr >& NodeArray, FName SourceName, const FString& DevComment, bool bIsExplicitTag, bool bIsRestrictedTag, bool bAllowNonRestrictedChildren) +int32 UGameplayTagsManager::InsertTagIntoNodeArray(FName Tag, FName FullTag, TSharedPtr ParentNode, TArray< TSharedPtr >& NodeArray, FName SourceName, const FString& DevComment, bool bIsExplicitTag, bool bIsRestrictedTag, bool bAllowNonRestrictedChildren) { - int32 InsertionIdx = INDEX_NONE; + int32 FoundNodeIdx = INDEX_NONE; int32 WhereToInsert = INDEX_NONE; // See if the tag is already in the array for (int32 CurIdx = 0; CurIdx < NodeArray.Num(); ++CurIdx) { - if (NodeArray[CurIdx].IsValid()) + FGameplayTagNode* CurrNode = NodeArray[CurIdx].Get(); + if (CurrNode) { - FGameplayTagNode* CurrNode = NodeArray[CurIdx].Get(); - if (CurrNode->GetSimpleTagName() == Tag) + FName SimpleTagName = CurrNode->GetSimpleTagName(); + if (SimpleTagName == Tag) { - InsertionIdx = CurIdx; + FoundNodeIdx = CurIdx; #if WITH_EDITORONLY_DATA // If we are explicitly adding this tag then overwrite the existing children restrictions with whatever is in the ini // If we restrict children in the input data, make sure we restrict them in the existing node. This applies to explicit and implicitly defined nodes @@ -849,7 +931,7 @@ int32 UGameplayTagsManager::InsertTagIntoNodeArray(FName Tag, TSharedPtrGetSimpleTagName() > Tag && WhereToInsert == INDEX_NONE) + else if (SimpleTagName > Tag && WhereToInsert == INDEX_NONE) { // Insert new node before this WhereToInsert = CurIdx; @@ -857,7 +939,7 @@ int32 UGameplayTagsManager::InsertTagIntoNodeArray(FName Tag, TSharedPtr TagNode = MakeShareable(new FGameplayTagNode(Tag, ParentNode != GameplayRootTag ? ParentNode : nullptr, bIsExplicitTag, bIsRestrictedTag, bAllowNonRestrictedChildren)); + TSharedPtr TagNode = MakeShareable(new FGameplayTagNode(Tag, FullTag, ParentNode != GameplayRootTag ? ParentNode : nullptr, bIsExplicitTag, bIsRestrictedTag, bAllowNonRestrictedChildren)); // Add at the sorted location - InsertionIdx = NodeArray.Insert(TagNode, WhereToInsert); + FoundNodeIdx = NodeArray.Insert(TagNode, WhereToInsert); FGameplayTag GameplayTag = TagNode->GetCompleteTag(); + // These should always match + ensure(GameplayTag.GetTagName() == FullTag); + { #if WITH_EDITOR // This critical section is to handle an editor-only issue where tag requests come from another thread when async loading from a background thread in FGameplayTagContainer::Serialize. @@ -887,23 +972,23 @@ int32 UGameplayTagsManager::InsertTagIntoNodeArray(FName Tag, TSharedPtrSourceName.IsNone() && !SourceName.IsNone()) + if (NodeArray[FoundNodeIdx]->SourceName.IsNone() && !SourceName.IsNone()) { - NodeArray[InsertionIdx]->SourceName = SourceName; + NodeArray[FoundNodeIdx]->SourceName = SourceName; } else if (SourceName == NativeSourceName) { // Native overrides other types - NodeArray[InsertionIdx]->SourceName = SourceName; + NodeArray[FoundNodeIdx]->SourceName = SourceName; } - if (NodeArray[InsertionIdx]->DevComment.IsEmpty() && !DevComment.IsEmpty()) + if (NodeArray[FoundNodeIdx]->DevComment.IsEmpty() && !DevComment.IsEmpty()) { - NodeArray[InsertionIdx]->DevComment = DevComment; + NodeArray[FoundNodeIdx]->DevComment = DevComment; } #endif - return InsertionIdx; + return FoundNodeIdx; } void UGameplayTagsManager::PrintReplicationIndices() @@ -1118,12 +1203,12 @@ void UGameplayTagsManager::GetFilteredGameplayRootTags(const FString& InFilterSt } } -FString UGameplayTagsManager::GetCategoriesMetaFromStruct(UScriptStruct* Struct) const +FString UGameplayTagsManager::GetCategoriesMetaFromField(UField* Field) const { - check(Struct); - if (Struct->HasMetaData(NAME_Categories)) + check(Field); + if (Field->HasMetaData(NAME_Categories)) { - return Struct->GetMetaData(NAME_Categories); + return Field->GetMetaData(NAME_Categories); } return FString(); } @@ -1241,7 +1326,7 @@ bool UGameplayTagsManager::GetTagEditorData(FName TagName, FString& OutComment, void UGameplayTagsManager::EditorRefreshGameplayTagTree() { DestroyGameplayTagTree(); - LoadGameplayTagTables(); + LoadGameplayTagTables(false); ConstructGameplayTagTree(); OnEditorRefreshGameplayTagTree.Broadcast(); @@ -1438,6 +1523,75 @@ FGameplayTag UGameplayTagsManager::RequestGameplayTag(FName TagName, bool ErrorI return FGameplayTag(); } +bool UGameplayTagsManager::IsValidGameplayTagString(const FString& TagString, FText* OutError, FString* OutFixedString) +{ + bool bIsValid = true; + FString FixedString = TagString; + FText ErrorText; + + if (FixedString.IsEmpty()) + { + ErrorText = LOCTEXT("EmptyStringError", "Tag is empty"); + bIsValid = false; + } + + while (FixedString.StartsWith(TEXT("."), ESearchCase::CaseSensitive)) + { + ErrorText = LOCTEXT("StartWithPeriod", "Tag starts with ."); + FixedString.RemoveAt(0); + bIsValid = false; + } + + while (FixedString.EndsWith(TEXT("."), ESearchCase::CaseSensitive)) + { + ErrorText = LOCTEXT("EndWithPeriod", "Tag ends with ."); + FixedString.RemoveAt(FixedString.Len() - 1); + bIsValid = false; + } + + while (FixedString.StartsWith(TEXT(" "), ESearchCase::CaseSensitive)) + { + ErrorText = LOCTEXT("StartWithSpace", "Tag starts with space"); + FixedString.RemoveAt(0); + bIsValid = false; + } + + while (FixedString.EndsWith(TEXT(" "), ESearchCase::CaseSensitive)) + { + ErrorText = LOCTEXT("EndWithSpace", "Tag ends with space"); + FixedString.RemoveAt(FixedString.Len() - 1); + bIsValid = false; + } + + FText TagContext = LOCTEXT("GameplayTagContext", "Tag"); + if (!FName::IsValidXName(TagString, InvalidTagCharacters, &ErrorText, &TagContext)) + { + for (TCHAR& TestChar : FixedString) + { + for (TCHAR BadChar : InvalidTagCharacters) + { + if (TestChar == BadChar) + { + TestChar = TEXT('_'); + } + } + } + + bIsValid = false; + } + + if (OutError) + { + *OutError = ErrorText; + } + if (OutFixedString) + { + *OutFixedString = FixedString; + } + + return bIsValid; +} + FGameplayTag UGameplayTagsManager::FindGameplayTagFromPartialString_Slow(FString PartialString) const { #if WITH_EDITOR @@ -1533,7 +1687,7 @@ void UGameplayTagsManager::DoneAddingNativeTags() // is initialized (DoneAddingNativeTags is bound to PostEngineInit to cover anything that's skipped). if (GEngine && !bDoneAddingNativeTags) { - UE_LOG(LogGameplayTags, Display, TEXT("UGameplayTagsManager::DoneAddingNativeTags. DelegateIsBound: %d"), (int32)OnLastChanceToAddNativeTags().IsBound()); + UE_CLOG(GAMEPLAYTAGS_VERBOSE, LogGameplayTags, Display, TEXT("UGameplayTagsManager::DoneAddingNativeTags. DelegateIsBound: %d"), (int32)OnLastChanceToAddNativeTags().IsBound()); OnLastChanceToAddNativeTags().Broadcast(); bDoneAddingNativeTags = true; @@ -1771,33 +1925,23 @@ bool FRestrictedGameplayTagTableRow::operator!=(FRestrictedGameplayTagTableRow c return true; } -FGameplayTagNode::FGameplayTagNode(FName InTag, TSharedPtr InParentNode, bool InIsExplicitTag, bool InIsRestrictedTag, bool InAllowNonRestrictedChildren) +FGameplayTagNode::FGameplayTagNode(FName InTag, FName InFullTag, TSharedPtr InParentNode, bool InIsExplicitTag, bool InIsRestrictedTag, bool InAllowNonRestrictedChildren) : Tag(InTag) , ParentNode(InParentNode) , NetIndex(INVALID_TAGNETINDEX) -{ - TArray ParentCompleteTags; - - TSharedPtr CurNode = InParentNode; - - // Stop iterating at root node - while (CurNode.IsValid() && CurNode->GetSimpleTagName() != NAME_None) - { - ParentCompleteTags.Add(CurNode->GetCompleteTag()); - CurNode = CurNode->GetParentTagNode(); - } - - FString CompleteTagString = InTag.ToString(); - - if (ParentCompleteTags.Num() > 0) - { - // If we have a parent, add parent., which will includes all earlier parents - CompleteTagString = FString::Printf(TEXT("%s.%s"), *ParentCompleteTags[0].ToString(), *InTag.ToString()); - } - +{ // Manually construct the tag container as we want to bypass the safety checks - CompleteTagWithParents.GameplayTags.Add(FGameplayTag(FName(*CompleteTagString))); - CompleteTagWithParents.ParentTags = ParentCompleteTags; + CompleteTagWithParents.GameplayTags.Add(FGameplayTag(InFullTag)); + + FGameplayTagNode* RawParentNode = ParentNode.Get(); + if (RawParentNode && RawParentNode->GetSimpleTagName() != NAME_None) + { + // Our parent nodes are already constructed, and must have it's tag in GameplayTags[0] + const FGameplayTagContainer ParentContainer = RawParentNode->GetSingleTagContainer(); + + CompleteTagWithParents.ParentTags.Add(ParentContainer.GameplayTags[0]); + CompleteTagWithParents.ParentTags.Append(ParentContainer.ParentTags); + } #if WITH_EDITORONLY_DATA bIsExplicitTag = InIsExplicitTag; diff --git a/Engine/Source/Runtime/GameplayTags/Private/GameplayTagsSettings.cpp b/Engine/Source/Runtime/GameplayTags/Private/GameplayTagsSettings.cpp index ee9dae286bb7..9d3a7d7030d9 100644 --- a/Engine/Source/Runtime/GameplayTags/Private/GameplayTagsSettings.cpp +++ b/Engine/Source/Runtime/GameplayTags/Private/GameplayTagsSettings.cpp @@ -62,6 +62,7 @@ UGameplayTagsSettings::UGameplayTagsSettings(const FObjectInitializer& ObjectIni ImportTagsFromConfig = true; WarnOnInvalidTags = true; FastReplication = false; + InvalidTagCharacters = ("\"',"); NumBitsForContainerSize = 6; NetIndexFirstBitSegment = 16; } diff --git a/Engine/Source/Runtime/InputCore/Classes/InputCoreTypes.h b/Engine/Source/Runtime/InputCore/Classes/InputCoreTypes.h index 7d6d30cb0a52..5176d4dd0bd2 100644 --- a/Engine/Source/Runtime/InputCore/Classes/InputCoreTypes.h +++ b/Engine/Source/Runtime/InputCore/Classes/InputCoreTypes.h @@ -79,6 +79,7 @@ struct INPUTCORE_API FKey bool ExportTextItem(FString& ValueStr, FKey const& DefaultValue, UObject* Parent, int32 PortFlags, UObject* ExportRootScope) const; bool ImportTextItem(const TCHAR*& Buffer, int32 PortFlags, UObject* Parent, FOutputDevice* ErrorText); void PostSerialize(const FArchive& Ar); + void PostScriptConstruct(); friend bool operator==(const FKey& KeyA, const FKey& KeyB) { return KeyA.KeyName == KeyB.KeyName; } friend bool operator!=(const FKey& KeyA, const FKey& KeyB) { return KeyA.KeyName != KeyB.KeyName; } @@ -109,6 +110,7 @@ struct TStructOpsTypeTraits : public TStructOpsTypeTraitsBase2 WithExportTextItem = true, WithImportTextItem = true, WithPostSerialize = true, + WithPostScriptConstruct = true, WithCopy = true, // Necessary so that TSharedPtr Data is copied around }; }; diff --git a/Engine/Source/Runtime/InputCore/Private/InputCoreTypes.cpp b/Engine/Source/Runtime/InputCore/Private/InputCoreTypes.cpp index 1accddcaa0de..97f04bb31080 100644 --- a/Engine/Source/Runtime/InputCore/Private/InputCoreTypes.cpp +++ b/Engine/Source/Runtime/InputCore/Private/InputCoreTypes.cpp @@ -1102,6 +1102,11 @@ void FKey::PostSerialize(const FArchive& Ar) ResetKey(); } +void FKey::PostScriptConstruct() +{ + KeyDetails.Reset(); +} + void FKey::ResetKey() { KeyDetails.Reset(); diff --git a/Engine/Source/Runtime/JsonUtilities/Public/JsonDomBuilder.h b/Engine/Source/Runtime/JsonUtilities/Public/JsonDomBuilder.h index 50c411b4c2ca..67cd9eda98a2 100644 --- a/Engine/Source/Runtime/JsonUtilities/Public/JsonDomBuilder.h +++ b/Engine/Source/Runtime/JsonUtilities/Public/JsonDomBuilder.h @@ -4,6 +4,12 @@ #include "Dom/JsonValue.h" #include "Dom/JsonObject.h" +#include "Serialization/JsonSerializer.h" + +#include "Templates/IsFloatingPoint.h" +#include "Templates/IsIntegral.h" +#include "Templates/EnableIf.h" +#include "Templates/Invoke.h" /** * Helpers for creating TSharedPtr JSON trees @@ -40,20 +46,39 @@ public: return MakeShared(Object); } - FObject& Set(const FString& Key, const FArray& Arr) { Object->SetField(Key, Arr.AsJsonValue()); return *this; } - FObject& Set(const FString& Key, const FObject& Obj) { Object->SetField(Key, Obj.AsJsonValue()); return *this; } + template