You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#lockdown Nick.Penwarden
#rb none
==========================
MAJOR FEATURES + CHANGES
==========================
Change 3194900 on 2016/11/11 by Ryan.Rauschkolb
Fixed issue where Reroute node pins weren't mirroring data properly.
#jira UE-33717
Change 3195081 on 2016/11/11 by Dan.Oconnor
This @todo was addressed
Change 3196368 on 2016/11/14 by Maciej.Mroz
Results of FBlueprintNativeCodeGenModule::IsTargetedForReplacement are cashed - optimization (cooking time).
Change 3196369 on 2016/11/14 by Maciej.Mroz
CompileDisplaysBinaryBackend, CompileDisplaysTextBackend and bDisplaysLayout features (in [Kismet] in Engine.ini) are disabled in commandlets. They slow down BP compilation.
Change 3196398 on 2016/11/14 by Ben.Cosh
Reworked the tracking of latent and expansion event tracking in the blueprint compiler for use with the blueprint profiler.
#Jira - UE-37364 - Crash: PIE with instrumented PlayerPawn_Generic added to AITestbed scene
#Proj BlueprintProfiler, KismetCompiler. BlueprintGraph, Engine
Change 3196410 on 2016/11/14 by Maciej.Mroz
Fixed crash in UK2Node_Knot::PropagatePinTypeFromInput
Change 3196852 on 2016/11/14 by Maciej.Mroz
Fixed static analysis warning.
Change 3196874 on 2016/11/14 by Maciej.Mroz
#jira UE-37778
(the issue was already fixed, but it was reintroduced, when EDL support was added).
ObjectImport->XObject is not filled prematurelly, so CreateExport is properly called each dynamic class.
Change 3197469 on 2016/11/14 by Dan.Oconnor
Fix for being able to make Sets and Maps of user defined structs that contained unhashable types (e.g. Rotator)
Change 3197703 on 2016/11/14 by Dan.Oconnor
Updated documentation comment to reflect current behavior
Change 3198167 on 2016/11/15 by Maciej.Mroz
Merged 3196582 from Dev-Core
UE4 - Changed a check to a warning related to detaching linekrs twice. Seen in nativized BP version of platformer game.
Change 3198433 on 2016/11/15 by Ryan.Rauschkolb
Fixed Copy/pasting variable nodes hides them from a reference search
#UE-31606
Change 3198811 on 2016/11/15 by Maciej.Mroz
Fixed Knot node - it will use/propagate the type from input connection, if it's possible (intstead of the type from output connection).
Change 3198866 on 2016/11/15 by Maciej.Mroz
#jira UE-38578
Fixed infinite loading of DynamicClass in EDL.
Change 3199045 on 2016/11/15 by Phillip.Kavan
[UE-27402] Fix attached Actor-based Blueprint instance root component relative transform values after reconstruction.
change summary:
- modified FActorComponentInstanceData's ctor to exclude relative transform properties when caching root component values.
#jira UE-27402
Change 3200703 on 2016/11/16 by Mike.Beach
Marking the ease node explicitly as pure, which makes it so we can prune it from graphs when it is unused.
#jira UE-38453
Change 3201115 on 2016/11/16 by Maciej.Mroz
Nativization + EDL: "Dynamic" objects are processed by FAsyncPackage::PostLoadDeferredObjects, so the EInternalObjectFlags::AsyncLoading flag is properly cleared.
Change 3201749 on 2016/11/17 by Maciej.Mroz
In EDL a package containig a dynamic class has PKG_CompiledIn flag (the same like without EDL).
Change 3202577 on 2016/11/17 by Mike.Beach
Accounting for a change in our intermediate node mapping - the old list no longer maps expanded nodes to macro instances (instead it maps to the corresponding node in the macro graph), so we had to use a new mapping meant for this.
#jira UE-35609
Change 3204803 on 2016/11/18 by Phillip.Kavan
[UE-38607] Implicitly turn on Blueprint class nativization for dependencies.
change summary:
- added a UBlueprint::PostEditChangeProperty() override method to handle this.
#jira UE-38607
Change 3204812 on 2016/11/18 by Phillip.Kavan
[UE-38580] Implicitly turn on the "nativize" project setting when enabling nativize for any Blueprint class.
change summary:
- modified UBlueprint::PostEditChangeProperty() to update project packaging settings if necessary
#jira UE-38580
Change 3204818 on 2016/11/18 by Phillip.Kavan
[UE-38725] Interface class dependencies that are not enabled for nativization will now raise an error during nativized cooks.
change summary:
- modified FBlueprintNativeCodeGenModule::IsTargetedForReplacement() to check interface class dependencies in addition to parent class dependencies.
#jira UE-38725
Change 3204963 on 2016/11/18 by Dan.Oconnor
Create a transaction when using UBlueprintFunctionNodeSpawner* directly
#jira UE-36439
Change 3206510 on 2016/11/21 by Mike.Beach
Adding math-expression aliases for dot and cross functions ("dot" and "cross" respectively).
#jira UEBP-71
Change 3206547 on 2016/11/21 by Mike.Beach
Exposing GetReflectionVector() to Blueprints.
#jira UEBP-70
Change 3206658 on 2016/11/21 by Dan.Oconnor
Fix for compile error, digging out authorative class from trash class.
Mirror of 3206638 from Odin
#jira None
Change 3207579 on 2016/11/22 by Mike.Beach
No longer enforcing the requirement that game UFunctions have to have a category (making writing of game code easier).
#jira UE-18093
Change 3207956 on 2016/11/22 by Phillip.Kavan
[UE-38690] Fix a regression in which nested scene component subobjects would no longer be registered after construction of an instance-added component in IWCE.
change summary:
- modified the IWCE path in SSCSEditor::AddNewComponent() to ensure that any components added as a result of instancing the newly-added component are also registered.
- modified AActor::ExecuteConstruction() to ensure that non-scene, native nested component subobjects that might be created as a result of SCS execution are also registered (previously it was only considering non-scene components that were explicitly created by the SCS, or that inherited the creation method of the instance that created it).
#jira UE-38690
Change 3208217 on 2016/11/22 by Mike.Beach
Modified fix (originally from Ryan.Rauschkolb, CL 3186023):
Fixed unable to set struct variable name if name includes space
#jira UE-28435
Change 3208347 on 2016/11/22 by Mike.Beach
Merging //UE4/Dev-Main to Dev-Blueprints (//UE4/Dev-Blueprints)
Change 3208688 on 2016/11/23 by Ben.Cosh
Made a minor change that forces debugging references to the PIE world when the play in editor world is changed. This is intended to better handle mutliple game instance/world debugging scenarios.
#Jira UE-26386 - Can't hit breakpoints in blueprints for level script for server instances
#Proj Engine, UnrealEd
Change 3208712 on 2016/11/23 by Ben.Cosh
Improved handling of unwired transform struct terminal expression's in the blueprint VM compiler to remove an errant warning.
#Jira UE-32401 - "ImportText: Missing opening parenthesis" message, when a function returns Transform
#Proj KismetCompiler
Change 3209457 on 2016/11/23 by Phillip.Kavan
[UE-30479] Fix inability to edit the ISMC instance array on Actor instances when the ISMC is inherited from a Blueprint class.
change summary:
- added PPF_ForceTaggedSerialization as a means to override the CPF_SkipSerialization flag when explicit serialization of the property value is needed
- modified UProperty::ShouldSerializeValue() to check for and handle the PPF_ForceTaggedSerialization flag when the CPF_SkipSerialization flag is present
- modified UActorComponent::DetermineUCSModifiedProperties() to add the PPF_ForceTaggedSerialization flag to the custom FArchive impl
- modified FActorComponentInstanceData::FActorComponentInstanceData() to add the PPF_ForceTaggedSerialization flag to the custom FObjectWriter impl
- modified FActorComponentInstanceData::ApplyToComponent() to add the PPF_ForceTaggedSerialization flag to the custom FObjectReader impl
#jira UE-30479
Change 3209758 on 2016/11/24 by Maciej.Mroz
#jira UE-38979
Nativization: fixed error when a BP implements a native interface.
FBlueprintNativeCodeGenModule::IsTargetedForReplacement will return "DontReplace" for native class.
Change 3210376 on 2016/11/25 by Maciej.Mroz
#jira UE-39028
Fixed FBlueprintNativeCodeGenModule::FindReplacedNameAndOuter
Components in nativized BPCG SCS have replaced outer object and name while cooking.
Change 3210936 on 2016/11/28 by Phillip.Kavan
Minor revision to try and avoid a potentially expensive Contains() call when possible.
Change 3211527 on 2016/11/28 by Maciej.Mroz
Fixed map of names cooked in packages in nativized build.
Change 3211969 on 2016/11/28 by Mike.Beach
Merging //UE4/Dev-Main to Dev-Blueprints (//UE4/Dev-Blueprints)
Change 3212328 on 2016/11/28 by Dan.Oconnor
Enum, pointer and arithmetic specializations for THasGetTypeHash, as VC doesn't detect them properly.
TIsEnum moved to its own header. Submitted on behalf of steve.robb
Change 3212398 on 2016/11/28 by Dan.Oconnor
Build fix, this function is part of another change
Change 3212442 on 2016/11/28 by Dan.Oconnor
UHT now supports structs in TMap and TSet, misc. fixes to PropertyStruct's PropertyFlags detecting whether the struct type is hashable (all HasGetTypeHash flags are now computed from THasGetTypeHash). Various fixes for generating TMap/TSet code from blueprints
Change 3212578 on 2016/11/28 by Dan.Oconnor
Rename RegisterClass to avoid collsion with RegistClass macro in generated code
Change 3213668 on 2016/11/29 by Dan.Oconnor
Fix for missing CDO when instatiating some subobjects in nativized BPs
#jira UE-34980
Change 3213737 on 2016/11/29 by Dan.Oconnor
Added GetTypeHash implementation to UEnumProperty
#jira UE-39091
Change 3215225 on 2016/11/30 by Maciej.Mroz
Bunch of changes required for nativized Orion to work with EDL.
- ClientOnly, ServerOnly and EditorOnly assets are properly distinguished and handled
- Introduced FCompilerNativizationOptions
- fixed inproper references to BP instead of BPGC
- fixed generated delegate name
- hack for DefaultRootNode UE-39168
- improved NativeCodeGenrationTool
- various minor improvements
Change 3216363 on 2016/11/30 by Dan.Oconnor
Better fix for discrepency between UUserDefinedEnum::GetEnumText and UEnum::GetEnumText. Without meta data we could not look up display names, so I'm writing out the display names into a function in the BP cpp backend. This function could be generated by UHT if we wanted to correct this odd behavior for native enums
#jira UE-27735
Change 3217168 on 2016/12/01 by Maciej.Mroz
#jira UE-35390
Nativization:
Fixed compilation warning C4458: declaration of 'CurrentState' hides class member
Disabled warning C4996 (deprecated) in nativized code.
Change 3217320 on 2016/12/01 by Phillip.Kavan
[UE-38652] Selecting Blueprint assets for nativization is now integrated into the project's configuration.
change summary:
- added EProjectPackagingBlueprintNativizationMethod
- deprecated the 'bNativizeBlueprintAssets' and 'bNativizeOnlySelectedBlueprints' flags in favor of a new 'BlueprintNativizationMethod' config property in UProjectPackagingSettings
- added a new 'NativizeBlueprintAssets' config property to UProjectPackagingSettings to track/store the list of Blueprints to be nativized when the exclusive method (whitelist) is selected
- added a PostInitProperties() override to UProjectPackagingSettings; implemented to migrate from the deprecated config properties to the new method enum type
- updated FMainFrameActionCallbacks::PackageProject() to migrate to checking the enum type
- updated FBlueprintNativeCodeGenModule::IsTargetedForReplacement() to migrate to checking the enum type
- modified UProjectPackagingSettings::CanEditChange() to enable editing of the nativization whitelist only when the "exclusive" method is active
- modified UProjectPackagingSettings::PostEditChangeProperty() to add a new case for handling changes to the whitelist; changes are propagated to any Blueprint assets that are loaded
- added new Add/RemoveBlueprintAssetFromNativizationList() APIs to UProjectPackagingSettings for assisting with adding/removing Blueprint assets to/from the exclusive list (whitelist)
- deprecated the 'bNativize' flag in UBlueprint in favor of a new transient 'bSelectedForNativization' flag that is no longer serialized; this now caches whether or not the asset is present in the whitelist in the project config
- modified UBlueprint::Serialize() to both migrate from the deprecated flag to the project config/transient flag on load, as well as to propagate the value of the transient flag back to the project config on save. this means that if the user changes the value of the transient flag through the Details view, that change won't be reflected back to the project config until the Blueprint is actually saved (saving the value to the config rather than serializing to the asset)
- modified UBlueprint::PostEditChangeProperty() to remove code that was previously updating the project configuration at edit time. this functionality has been relocated to Serialize() (save time) instead.
notes:
- also completes UE-38636 (task: consolidate config options into a single drop-down)
#jira UE-38652
Change 3218124 on 2016/12/01 by Dan.Oconnor
CPF_HasGetValueTypeHash flag was not set on native UEnumProperties
#jira UE-39181
Change 3218168 on 2016/12/01 by Phillip.Kavan
Fix local var name that shadows a function param (CIS fix).
Change 3219117 on 2016/12/02 by Maciej.Mroz
#jira UE-39241 "warning C4458: declaration of XXX hides class member" In Nativized Code
Nativization:
Fixed compilation warning C4458: when local function variable hides a class variable. Names of local variables in funvtions have prefix "bpfv__".
Change 3219201 on 2016/12/02 by Mike.Beach
Keeping the "Select All Input Nodes" option from infinitely recurssing by blocking on nodes it has already selected.
#jira UE-38988
Change 3219247 on 2016/12/02 by Mike.Beach
Fixing CIS shadow variable warning from my last check in (CL 3219201).
Change 3219332 on 2016/12/02 by Maciej.Mroz
#jira HeaderParser: "private:" specifier is lost in FGameplayTag::TagName
Workaround for UE-38231
Change 3219381 on 2016/12/02 by Mike.Beach
Accounting for cyclic compile issues in cast-node's validate function, making it check the authoratative class instead of the current type. Also down grading some of the warnings to notes (suggesting the users don't need the cast).
#jira UE-39272
Change 3220224 on 2016/12/02 by Dan.Oconnor
Reduce font size for compact nodes
Change 3220476 on 2016/12/03 by Maciej.Mroz
#jira UE-35390
Better fix for UE-35390
Disabled deprecation warnings in nativized code.
Change 3221637 on 2016/12/05 by Maciej.Mroz
#jira UEBP-245
Nativization:
Forced ImportCreation while InitialLoad for DynamicClasses.
Change 3222306 on 2016/12/05 by Dan.Oconnor
Support for default values of TMap and TSet local variables
#jira UE-39239
Change 3222383 on 2016/12/05 by Dan.Oconnor
Fixed bug in Blueprint TMap function for getting values out of a TMap
Change 3222427 on 2016/12/05 by Mike.Beach
Preventing ChildActorTemplate fixups from occuring on component load, when they may instead be a placeholder object (a byproduct of deferred Blueprint loading - a guard against cyclic load problems).
#jira UE-39323
Change 3222679 on 2016/12/05 by Dan.Oconnor
Remove unused code. Had no sideeffects, other than potential for leak when struct with non-trivial dtor was allocated here.
Change 3222719 on 2016/12/05 by Dan.Oconnor
Earlier detection of invalid native map/set properties. These generate a compile error if any TMap/TSet functions are used, but will slip by undetected if not used. Working on static assert to catch them.
#jira UE-39338
Change 3224375 on 2016/12/06 by Dan.Oconnor
Add tags for testing of array diffing
Change 3224507 on 2016/12/07 by Phillip.Kavan
[UE-39055] Fix a crash caused by an object name collision that could occur when loading some older Blueprint assets.
change summary:
- added UInheritableComponentHandler::FixComponentTemplateName()
- modified UInheritableComponentHandler::PostLoad() to check for and correct stale records that cause a collision with the corrected name
- added a UInheritableComponentHandler::Serialize() override to ensure that UsingCustomVersion() is called for the asset containing the ICH (already happening in UBlueprint::Serialize(), but added for consistency with other usage)
- modified USimpleConstructionScript::Serialize() to ensure that UsingCustomVersion() is called for the asset containing the SCS (same reason as above)
#jira UE-39055
Change 3225572 on 2016/12/07 by Samuel.Proctor
Test assets for TSet/TMap testing. Includes new native class for testing containers. #rb none
Change 3225577 on 2016/12/07 by Samuel.Proctor
New test map for Array, TSet and Tmap testing.
Change 3226281 on 2016/12/07 by Dan.Oconnor
Container test asset, needs to be in p4 for diff tool tests.
Change 3226345 on 2016/12/07 by Dan.Oconnor
Another revision of test data
Change 3228496 on 2016/12/09 by Ben.Cosh
This change adds extra information to component template arrays so that the component class can be determined in builds that strip out objects of certain class types such as the editor dedicated server build.
#Jira UE-38842 - "LogBlueprint:Error: [Compiler BP_Skybox_World_RandomTrees_01] Error Can't connect pins ReturnValue and Target" after entering a lobby in a synced server
#Proj KismetCompiler, BlueprintGraph, UnrealEd, Core, Engine, Kismet, BlueprintCompilerCppBackend
Change 3230120 on 2016/12/09 by Dan.Oconnor
Merging //UE4/Dev-Main to Dev-Blueprints (//UE4/Dev-Blueprints)
Change 3230700 on 2016/12/12 by Samuel.Proctor
Removing some array test properties from container test class that were not needed. Also updated struct element to better reflect testing purpose. #rb none
Change 3230926 on 2016/12/12 by Samuel.Proctor
Missed a file on previous container test native QA asset checkin. #rb none
Change 3231246 on 2016/12/12 by Dan.Oconnor
PR #3003: New Feature: In-editor diff of arrays and structs now highlights diff. (Contributed by CA-ADuran).
I've added TSet and TMap support as well.
Change 3231311 on 2016/12/12 by Dan.Oconnor
Handle class load failure
#jira UE-39480
Change 3231387 on 2016/12/12 by Dan.Oconnor
Shadow variable fixes
Change 3231501 on 2016/12/12 by Dan.Oconnor
More shadow fixes
Change 3231584 on 2016/12/12 by Maciej.Mroz
#jira UE-39636
Replaced obsolate macro usage.
#fyi Dan.Oconnor
Change 3231685 on 2016/12/12 by Mike.Beach
PR #3032: Fixed category for IsValidIndex (Contributed by elFarto)
#jira UE-39660
Change 3231689 on 2016/12/12 by Maciej.Mroz
Nativization: Fixed Dependency list generation.
Change 3231765 on 2016/12/12 by Phillip.Kavan
[UE-38903] Auto-enable exclusive Blueprint nativization only after explicitly selecting the first asset.
change summary:
- fixed up the auto-enable logic on save in UBlueprint::Serialize()
#jira UE-38903
#fyi Maciej.Mroz
Change 3231837 on 2016/12/12 by Dan.Oconnor
Restore hack to keep objects referenced by bytecode alive while in editor
#jira UE-38486
Change 3232085 on 2016/12/13 by Phillip.Kavan
Compile fix.
Change 3232435 on 2016/12/13 by Ben.Cosh
Fix for a bug introduced in CL 3228496 that caused component templates to fail to be identified by name and resulted in blueprint compilation issues for add component nodes.
#Jira UE-39623 - Unknown template referenced by Add Component Node
#Proj BlueprintGraph, Engine
Change 3232437 on 2016/12/13 by Maciej.Mroz
#jira UE-33021
Remove an obsolete warning.
Change 3232564 on 2016/12/13 by Ben.Cosh
This adds extra component template renaming propagation and checking for the blueprint generated class and blueprint skeleton class.
#Jira UE-39623 - Unknown template referenced by Add Component Node
#Proj BlueprintGraph
- Implementing a bit of review feedback and some safety checking.
Change 3232598 on 2016/12/13 by Maciej.Mroz
Nativization:
stati functions cannot be const, so no workaound (_Inner function) specyfic to const functions is necessary
#jira UE-39518
Change 3232601 on 2016/12/13 by Phillip.Kavan
[UE-38975] Warn on BuildCookRun or a standalone cook when the -nativizeAssets flag is omitted from the command line for a nativization-enabled project.
change summary:
- added 'bWarnIfPackagedWithoutNativizationFlag' to UProjectPackagingSettings (default = true)
- modified UCookOnTheFlyServer::StartCookByTheBook() to check for the presence of the -nativizeAssets flag and emit a warning for unexpected omission from the command line
- modified UAT to include a warning for the BuildCookRun command when -build is specified with the same unexpected omission of the -nativizeAssets flag on the UAT command line
#jira UE-38975
Change 3232749 on 2016/12/13 by Mike.Beach
Moving Blueprint nativization out of the experimental category.
#jira UE-39358
Change 3233043 on 2016/12/13 by Dan.Oconnor
Various fixes for TSet/TMap nativization issues
#jira UE-39634
Change 3233086 on 2016/12/13 by Dan.Oconnor
Advanced Containers (TSet/TMap) no longer experimental
Change 3233175 on 2016/12/13 by Mike.Beach
Whitelising "Packaging" as an acceptable BP settings category (follow up to CL 3232749).
#jira UE-39358
Change 3233182 on 2016/12/13 by Mike.Beach
Exposing the editor setting "Show Action Menu Item Signatures" through the Blueprint Developer menu (for ease of access).
Change 3233662 on 2016/12/13 by Phillip.Kavan
[UE-39722] Fix a typo that led to a UAT runtime failure.
#jira UE-39722
Change 3233710 on 2016/12/13 by Dan.Oconnor
Clang template useage fix
#jira UE-39742
Change 3233895 on 2016/12/13 by Dan.Oconnor
Several fixes to crashes that occur when you delete a blueprint asset when its children are not loaded.
#jira UE-39558
Change 3234443 on 2016/12/14 by Phillip.Kavan
[UE-39354] Fix script VM crash on assignment to TSet/TMap variables.
change summary:
- modified FScriptBuilderBase::EmitDestinationExpression() to consider TSet/TMap value types in addition to TArray terms
#jira UE-39354
Change 3234581 on 2016/12/14 by Mike.Beach
Backing out fix for UE-38842 (CL 3228496/3232435/3232564) - mapping UBlueprintGeneratedClass's ComponentTemplates array to a new format was causing issues with deferred dependency loading during serialization (trying to extract type information from a placeholder object). We're opting for a smaller/simpler solution to UE-38842, which will be to store the component information on the node itself (not with the templates).
#jira UE-39707
Change 3234729 on 2016/12/14 by Mike.Beach
Making it so AddComponent nodes now track the component (class) type that they represent (in case the template cannot be spawned, like in -server w/ client-only components).
#jira UE-38842
Change 3234805 on 2016/12/14 by Mike.Beach
Fixing CIS shadowed variable warning.
Change 3234830 on 2016/12/14 by Nick.Atamas
Added extra debugging mechanisms to help track down duplicate item issues with TableViews.
Change 3235075 on 2016/12/14 by Mike.Beach
Creating a helper to better manage nested scope blocks added in generated code - on close, clears out cached local accessor variables that were added, so we don't use one that was declared inside the nested scope.
#jira UE-39769
Change 3235213 on 2016/12/14 by Phillip.Kavan
[UE-39790] Fix UAT compile issue after latest merge from Main.
change summary:
- migrated the BuildCookRun command's usage of the (deprecated) ConfigCacheIni to the new ConfigHierarchy API
#jira UE-39790
Change 3235384 on 2016/12/14 by Mike.Beach
Defaulting to excluding data-only Blueprints from nativization.
Change 3235675 on 2016/12/14 by Nick.Atamas
Hopefully fixed build.
Added OnEnteredBadState delegate that lets users add arbitrary logging info when the List/Tree enters a bad state.
Change 3235761 on 2016/12/14 by Mike.Beach
Hopefully resolving CIS mac/ps4 build failures in Dev-BP for 4.15 integration.
#jira UE-39800
Change 3235800 on 2016/12/14 by Mike.Beach
More hopeful CIS mac/ps4 fixes for 4.15 integration.
#jira UE-39800
[CL 3236017 by Mike Beach in Main branch]
3363 lines
128 KiB
C++
3363 lines
128 KiB
C++
// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "ScriptInstrumentationPlayback.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "K2Node.h"
|
|
#include "K2Node_Tunnel.h"
|
|
#include "Modules/ModuleManager.h"
|
|
|
|
#if WITH_EDITOR
|
|
#include "GameFramework/Actor.h"
|
|
#include "Editor.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "EdGraphSchema_K2.h"
|
|
#include "K2Node_Event.h"
|
|
#include "K2Node_CallFunction.h"
|
|
#include "K2Node_CallParentFunction.h"
|
|
#include "K2Node_Composite.h"
|
|
#include "K2Node_CustomEvent.h"
|
|
#include "K2Node_ExecutionSequence.h"
|
|
#include "K2Node_FunctionEntry.h"
|
|
#include "K2Node_FunctionResult.h"
|
|
#include "K2Node_InputKey.h"
|
|
#include "K2Node_InputTouch.h"
|
|
#include "K2Node_MacroInstance.h"
|
|
#include "GraphEditorSettings.h"
|
|
#endif // WITH_EDITOR
|
|
|
|
#include "BlueprintProfilerModule.h"
|
|
#include "ScriptInstrumentationCapture.h"
|
|
|
|
#include "BlueprintProfilerStats.h"
|
|
#include "Profiler/BlueprintProfilerSettings.h"
|
|
|
|
#include "EditorStyleSet.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "DelayAction.h"
|
|
#include "K2Node_ForEachElementInEnum.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "ScriptInstrumentationPlayback"
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("Statistic Update"), STAT_StatUpdateCost, STATGROUP_BlueprintProfiler);
|
|
DECLARE_CYCLE_STAT(TEXT("Node Lookup"), STAT_NodeLookupCost, STATGROUP_BlueprintProfiler);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FBlueprintExecutionContext
|
|
|
|
bool FBlueprintExecutionContext::InitialiseContext(const FString& BlueprintPath)
|
|
{
|
|
// Locate the blueprint from the path
|
|
if (UObject* ObjectPtr = FindObject<UObject>(nullptr, *BlueprintPath))
|
|
{
|
|
UBlueprintGeneratedClass* BPClass = Cast<UBlueprintGeneratedClass>(ObjectPtr);
|
|
if (BPClass && BPClass->bHasInstrumentation)
|
|
{
|
|
BlueprintClass = BPClass;
|
|
Blueprint = Cast<UBlueprint>(BPClass->ClassGeneratedBy);
|
|
}
|
|
}
|
|
if (Blueprint.IsValid() && BlueprintClass.IsValid())
|
|
{
|
|
// Create new blueprint exec node
|
|
FScriptExecNodeParams BlueprintParams;
|
|
BlueprintParams.SampleFrequency = 1;
|
|
BlueprintParams.NodeName = FName(*BlueprintPath);
|
|
BlueprintParams.ObservedObject = Blueprint.Get();
|
|
BlueprintParams.OwningGraphName = NAME_None;
|
|
BlueprintParams.DisplayName = FText::FromName(Blueprint.Get()->GetFName());
|
|
BlueprintParams.Tooltip = LOCTEXT("NavigateToBlueprintHyperlink_ToolTip", "Navigate to the Blueprint");
|
|
BlueprintParams.NodeFlags = EScriptExecutionNodeFlags::Class;
|
|
BlueprintParams.Icon = const_cast<FSlateBrush*>(FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPIcon_Normal")));
|
|
BlueprintParams.IconColor = FLinearColor(0.46f, 0.54f, 0.81f);
|
|
BlueprintNode = MakeShareable<FScriptExecutionBlueprint>(new FScriptExecutionBlueprint(BlueprintParams));
|
|
// Map the blueprint execution
|
|
bIsBlueprintMapped = MapBlueprintExecution();
|
|
}
|
|
return bIsBlueprintMapped;
|
|
}
|
|
|
|
void FBlueprintExecutionContext::RemoveMapping()
|
|
{
|
|
bIsBlueprintMapped = false;
|
|
BlueprintClass.Reset();
|
|
BlueprintNode.Reset();
|
|
FunctionContexts.Reset();
|
|
}
|
|
|
|
bool FBlueprintExecutionContext::IsEventMapped(const FName EventName) const
|
|
{
|
|
return EventFunctionContexts.Contains(EventName);
|
|
}
|
|
|
|
void FBlueprintExecutionContext::AddEventNode(TSharedPtr<FBlueprintFunctionContext> FunctionContext, TSharedPtr<FScriptExecutionNode> EventExecNode)
|
|
{
|
|
check(BlueprintNode.IsValid());
|
|
BlueprintNode->AddChildNode(EventExecNode);
|
|
EventFunctionContexts.Add(EventExecNode->GetName()) = FunctionContext;
|
|
}
|
|
|
|
void FBlueprintExecutionContext::RegisterEventContext(const FName EventName, TSharedPtr<FBlueprintFunctionContext> FunctionContext)
|
|
{
|
|
EventFunctionContexts.Add(EventName) = FunctionContext;
|
|
}
|
|
|
|
FName FBlueprintExecutionContext::MapBlueprintInstance(const FString& InstancePath)
|
|
{
|
|
FName InstanceName(*InstancePath);
|
|
TWeakObjectPtr<const UObject> Instance;
|
|
const bool bNewInstance = ResolveInstance(InstanceName, Instance);
|
|
const bool bBlueprintMatches = Instance.IsValid() ? (Instance->GetClass() == BlueprintClass) : false;
|
|
if (bBlueprintMatches)
|
|
{
|
|
if (bNewInstance)
|
|
{
|
|
// Create new instance node
|
|
FScriptExecNodeParams InstanceNodeParams;
|
|
InstanceNodeParams.SampleFrequency = 1;
|
|
InstanceNodeParams.NodeName = InstanceName;
|
|
InstanceNodeParams.ObservedObject = Instance.Get();
|
|
InstanceNodeParams.NodeFlags = EScriptExecutionNodeFlags::Instance;
|
|
const AActor* Actor = Cast<AActor>(Instance.Get());
|
|
InstanceNodeParams.DisplayName = Actor ? FText::FromString(Actor->GetActorLabel()) : FText::FromString(Instance.Get()->GetName());
|
|
InstanceNodeParams.Tooltip = LOCTEXT("NavigateToInstanceHyperlink_ToolTip", "Navigate to the Instance");
|
|
InstanceNodeParams.IconColor = FLinearColor(1.f, 1.f, 1.f, 1.f);
|
|
InstanceNodeParams.Icon = const_cast<FSlateBrush*>(FEditorStyle::GetBrush(TEXT("BlueprintProfiler.Actor")));
|
|
TSharedPtr<FScriptExecutionInstance> InstanceNode = MakeShareable<FScriptExecutionInstance>(new FScriptExecutionInstance(InstanceNodeParams));
|
|
// Link to parent blueprint entry
|
|
BlueprintNode->AddInstance(InstanceNode);
|
|
// Fill out events from the blueprint root node
|
|
for (int32 NodeIdx = 0; NodeIdx < BlueprintNode->GetNumChildren(); ++NodeIdx)
|
|
{
|
|
InstanceNode->AddChildNode(BlueprintNode->GetChildByIndex(NodeIdx));
|
|
}
|
|
// Broadcast change
|
|
IBlueprintProfilerInterface& ProfilerModule = FModuleManager::LoadModuleChecked<IBlueprintProfilerInterface>("BlueprintProfiler");
|
|
ProfilerModule.GetGraphLayoutChangedDelegate().Broadcast(Blueprint.Get());
|
|
}
|
|
else
|
|
{
|
|
TSharedPtr<FScriptExecutionInstance> InstanceNode = StaticCastSharedPtr<FScriptExecutionInstance>(BlueprintNode->GetInstanceByName(InstanceName));
|
|
if (InstanceNode.IsValid())
|
|
{
|
|
if (const FWorldContext* PIEWorldContext = GEditor->GetPIEWorldContext())
|
|
{
|
|
PIEActorInstances.Add(InstanceName) = InstanceNode->GetActiveObject();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return InstanceName;
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionInstance> FBlueprintExecutionContext::GetInstanceExecNode(const FName InstanceName)
|
|
{
|
|
TSharedPtr<FScriptExecutionInstance> Result;
|
|
if (BlueprintNode.IsValid())
|
|
{
|
|
FName RemappedInstanceName = RemapInstancePath(InstanceName);
|
|
Result = StaticCastSharedPtr<FScriptExecutionInstance>(BlueprintNode->GetInstanceByName(RemappedInstanceName));
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
bool FBlueprintExecutionContext::HasProfilerDataForInstance(const FName InstanceName) const
|
|
{
|
|
bool bHasInstanceData = false;
|
|
if (BlueprintNode.IsValid())
|
|
{
|
|
FName RemappedInstanceName = RemapInstancePath(InstanceName);
|
|
TSharedPtr<FScriptExecutionNode> Result = BlueprintNode->GetInstanceByName(RemappedInstanceName);
|
|
bHasInstanceData = Result.IsValid();
|
|
}
|
|
return bHasInstanceData;
|
|
}
|
|
|
|
FName FBlueprintExecutionContext::RemapInstancePath(const FName InstanceName) const
|
|
{
|
|
FName Result = InstanceName;
|
|
if (const FName* RemappedName = PIEInstanceNameMap.Find(InstanceName))
|
|
{
|
|
Result = *RemappedName;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
FName FBlueprintExecutionContext::GetActiveInstanceName() const
|
|
{
|
|
FName InstanceName = NAME_None;
|
|
if (BlueprintNode.IsValid())
|
|
{
|
|
InstanceName = BlueprintNode->GetActiveInstanceName();
|
|
}
|
|
return InstanceName;
|
|
}
|
|
|
|
bool FBlueprintExecutionContext::ResolveInstance(FName& InstanceNameInOut, TWeakObjectPtr<const UObject>& ObjectInOut)
|
|
{
|
|
bool bNewInstance = false;
|
|
FName CorrectedName = InstanceNameInOut;
|
|
if (const FName* SearchName = PIEInstanceNameMap.Find(InstanceNameInOut))
|
|
{
|
|
CorrectedName = *SearchName;
|
|
InstanceNameInOut = CorrectedName;
|
|
}
|
|
if (TWeakObjectPtr<const UObject>* SearchResult = EditorActorInstances.Find(CorrectedName))
|
|
{
|
|
ObjectInOut = *SearchResult;
|
|
}
|
|
else
|
|
{
|
|
// Attempt to locate the instance and map PIE objects to editor world objects
|
|
if (const UObject* ObjectPtr = FindObject<UObject>(nullptr, *InstanceNameInOut.ToString()))
|
|
{
|
|
if (ObjectPtr->GetClass() == BlueprintClass && !ObjectPtr->HasAnyFlags(RF_Transient))
|
|
{
|
|
// Get Outer world
|
|
if (UWorld* ObjectWorld = ObjectPtr->GetTypedOuter<UWorld>())
|
|
{
|
|
switch (ObjectWorld->WorldType)
|
|
{
|
|
case EWorldType::PIE:
|
|
case EWorldType::Game:
|
|
{
|
|
FWorldContext& EditorWorldContext = GEditor->GetEditorWorldContext();
|
|
ObjectInOut.Reset();
|
|
if (UWorld* EditorWorld = EditorWorldContext.World())
|
|
{
|
|
for (auto LevelIter : EditorWorld->GetLevels())
|
|
{
|
|
if (UObject* EditorObject = FindObject<UObject>(LevelIter, *ObjectPtr->GetName()))
|
|
{
|
|
if (EditorObject->GetClass() == BlueprintClass)
|
|
{
|
|
CorrectedName = FName(*EditorObject->GetPathName());
|
|
EditorActorInstances.Add(CorrectedName) = EditorObject;
|
|
PIEActorInstances.Add(CorrectedName) = ObjectPtr;
|
|
PIEInstanceNameMap.Add(InstanceNameInOut) = CorrectedName;
|
|
InstanceNameInOut = CorrectedName;
|
|
ObjectInOut = EditorObject;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!ObjectInOut.IsValid())
|
|
{
|
|
EditorActorInstances.Add(CorrectedName) = ObjectPtr;
|
|
ObjectInOut = ObjectPtr;
|
|
bNewInstance = true;
|
|
}
|
|
break;
|
|
}
|
|
case EWorldType::Editor:
|
|
{
|
|
EditorActorInstances.Add(InstanceNameInOut) = ObjectPtr;
|
|
ObjectInOut = ObjectPtr;
|
|
bNewInstance = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return bNewInstance;
|
|
}
|
|
|
|
TSharedPtr<FBlueprintFunctionContext> FBlueprintExecutionContext::GetFunctionContextForEventChecked(const FName ScopedEventName) const
|
|
{
|
|
TSharedPtr<FBlueprintFunctionContext> Result;
|
|
if (const TSharedPtr<FBlueprintFunctionContext>* SearchResult = EventFunctionContexts.Find(ScopedEventName))
|
|
{
|
|
Result = *SearchResult;
|
|
}
|
|
else
|
|
{
|
|
Result = GetFunctionContext(ScopedEventName);
|
|
}
|
|
check (Result.IsValid());
|
|
return Result;
|
|
}
|
|
|
|
TSharedPtr<FBlueprintFunctionContext> FBlueprintExecutionContext::GetFunctionContext(const FName ScopedFunctionName) const
|
|
{
|
|
TSharedPtr<FBlueprintFunctionContext> Result;
|
|
if (const TSharedPtr<FBlueprintFunctionContext>* SearchResult = FunctionContexts.Find(ScopedFunctionName))
|
|
{
|
|
Result = *SearchResult;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
TSharedPtr<FBlueprintFunctionContext> FBlueprintExecutionContext::GetFunctionContextFromGraph(const UEdGraph* Graph) const
|
|
{
|
|
TSharedPtr<FBlueprintFunctionContext> Result;
|
|
if (Graph)
|
|
{
|
|
const FName ScopedFunctionName = GetScopedFunctionNameFromGraph(Graph);
|
|
if (const TSharedPtr<FBlueprintFunctionContext>* SearchResult = FunctionContexts.Find(ScopedFunctionName))
|
|
{
|
|
Result = *SearchResult;
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
template<typename FunctionType> TSharedPtr<FunctionType> FBlueprintExecutionContext::CreateFunctionContext(const FName FunctionName, UEdGraph* Graph)
|
|
{
|
|
TSharedPtr<FBlueprintFunctionContext>& Result = FunctionContexts.FindOrAdd(FunctionName);
|
|
if (!Result.IsValid())
|
|
{
|
|
Result = MakeShareable(new FunctionType);
|
|
Result->InitialiseContextFromGraph(AsShared(), FunctionName, Graph);
|
|
}
|
|
check(Result.IsValid());
|
|
return StaticCastSharedPtr<FunctionType>(Result);
|
|
}
|
|
|
|
bool FBlueprintExecutionContext::HasProfilerDataForPin(const UEdGraphPin* GraphPin) const
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_NodeLookupCost);
|
|
const UEdGraph* OwningGraph = GraphPin ? FBlueprintFunctionContext::GetGraphFromNode(GraphPin->GetOwningNode()) : nullptr;
|
|
if (OwningGraph)
|
|
{
|
|
TSharedPtr<FBlueprintFunctionContext> FunctionContext = GetFunctionContextFromGraph(OwningGraph);
|
|
if (FunctionContext.IsValid())
|
|
{
|
|
const FName PinName(FunctionContext->GetUniquePinName(GraphPin));
|
|
return FunctionContext->HasProfilerDataForNode(PinName);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintExecutionContext::GetProfilerDataForPin(const UEdGraphPin* GraphPin)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_NodeLookupCost);
|
|
TSharedPtr<FScriptExecutionNode> Result;
|
|
const UEdGraph* OwningGraph = GraphPin ? FBlueprintFunctionContext::GetGraphFromNode(GraphPin->GetOwningNode()) : nullptr;
|
|
if (OwningGraph)
|
|
{
|
|
TSharedPtr<FBlueprintFunctionContext> FunctionContext = GetFunctionContextFromGraph(OwningGraph);
|
|
if (FunctionContext.IsValid())
|
|
{
|
|
const FName PinName(FunctionContext->GetUniquePinName(GraphPin));
|
|
const FName UniquePinName(*FString::Printf(TEXT("%s_%s"), *GraphPin->GetOwningNode()->GetFName().ToString(), *PinName.ToString()));
|
|
if (FunctionContext->HasProfilerDataForNode(PinName))
|
|
{
|
|
Result = FunctionContext->GetProfilerDataForNode(PinName);
|
|
}
|
|
else
|
|
{
|
|
const UEdGraphNode* OwningNode = GraphPin->GetOwningNode();
|
|
if (FunctionContext->HasProfilerDataForNode(OwningNode->GetFName()))
|
|
{
|
|
Result = FunctionContext->GetProfilerDataForNode(OwningNode->GetFName());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
bool FBlueprintExecutionContext::HasProfilerDataForNode(const UEdGraphNode* GraphNode) const
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_NodeLookupCost);
|
|
// Do a simple outer lookup to get the nodes graph, the function context will handle tunnel instances.
|
|
const UEdGraph* OwningGraph = GraphNode ? GraphNode->GetTypedOuter<UEdGraph>() : nullptr;
|
|
if (OwningGraph)
|
|
{
|
|
TSharedPtr<FBlueprintFunctionContext> FunctionContext = GetFunctionContextFromGraph(OwningGraph);
|
|
if (FunctionContext.IsValid())
|
|
{
|
|
return FunctionContext->HasProfilerDataForNode(GraphNode->GetFName());
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintExecutionContext::GetProfilerDataForNode(const UEdGraphNode* GraphNode)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_NodeLookupCost);
|
|
TSharedPtr<FScriptExecutionNode> Result;
|
|
// Do a simple outer lookup to get the nodes graph, the function context will handle tunnel instances.
|
|
const UEdGraph* OwningGraph = GraphNode ? GraphNode->GetTypedOuter<UEdGraph>() : nullptr;
|
|
if (OwningGraph)
|
|
{
|
|
TSharedPtr<FBlueprintFunctionContext> FunctionContext = GetFunctionContextFromGraph(OwningGraph);
|
|
if (FunctionContext.IsValid() && FunctionContext->HasProfilerDataForNode(GraphNode->GetFName()))
|
|
{
|
|
Result = FunctionContext->GetProfilerDataForNode(GraphNode->GetFName());
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
void FBlueprintExecutionContext::UpdateConnectedStats()
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_StatUpdateCost);
|
|
if (BlueprintNode.IsValid())
|
|
{
|
|
const UBlueprintProfilerSettings* ProfilerSettings = GetDefault<UBlueprintProfilerSettings>();
|
|
FName InstanceName = SPDN_Blueprint;
|
|
// Find active debug instance name.
|
|
if (ProfilerSettings->bDisplayByInstance && ProfilerSettings->bScopeToDebugInstance)
|
|
{
|
|
if (UBlueprint* OwnerBlueprint = Blueprint.Get())
|
|
{
|
|
if (UObject* DebugInstance = OwnerBlueprint->GetObjectBeingDebugged())
|
|
{
|
|
InstanceName = RemapInstancePath(FName(*DebugInstance->GetPathName()));
|
|
}
|
|
}
|
|
}
|
|
FTracePath InitialTracePath;
|
|
BlueprintNode->SetActiveInstanceName(InstanceName);
|
|
BlueprintNode->RefreshStats(InitialTracePath);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintExecutionContext::MapBlueprintExecution()
|
|
{
|
|
bool bMappingSuccessful = false;
|
|
UBlueprint* BlueprintToMap = Blueprint.Get();
|
|
UBlueprintGeneratedClass* BPGC = BlueprintClass.Get();
|
|
|
|
if (BlueprintToMap && BPGC)
|
|
{
|
|
// Grab all ancestral blueprints used to generate this class.
|
|
TArray<UBlueprint*> InheritedBlueprints;
|
|
BlueprintToMap->GetBlueprintHierarchyFromClass(BPGC, InheritedBlueprints);
|
|
// Locate all blueprint graphs and event entry points
|
|
TMap<FName, UEdGraph*> FunctionGraphs;
|
|
for (auto CurrBlueprint : InheritedBlueprints)
|
|
{
|
|
if (UBlueprintGeneratedClass* CurrBPGC = Cast<UBlueprintGeneratedClass>(CurrBlueprint->GeneratedClass))
|
|
{
|
|
TArray<UEdGraph*> Graphs;
|
|
CurrBlueprint->GetAllGraphs(Graphs);
|
|
for (auto Graph : Graphs)
|
|
{
|
|
const FName FunctionName = GetFunctionNameFromGraph(Graph);
|
|
if (UFunction* ScriptFunction = CurrBPGC->FindFunctionByName(FunctionName))
|
|
{
|
|
const FName ScopedFunctionName = GetScopedFunctionNameFromGraph(Graph);
|
|
if (!FunctionGraphs.Contains(ScopedFunctionName))
|
|
{
|
|
FunctionGraphs.Add(ScopedFunctionName) = Graph;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Create function stubs
|
|
TMap<UK2Node_Tunnel*, TSharedPtr<FBlueprintFunctionContext>> DiscoveredTunnels;
|
|
for (auto GraphEntry : FunctionGraphs)
|
|
{
|
|
TSharedPtr<FBlueprintFunctionContext> NewFunctionContext = CreateFunctionContext<FBlueprintFunctionContext>(GraphEntry.Key, GraphEntry.Value);
|
|
NewFunctionContext->DiscoverTunnels(GraphEntry.Value, DiscoveredTunnels);
|
|
}
|
|
// Create and map each tunnels instance as its own function context.
|
|
TArray<UK2Node_Tunnel*> Tunnels;
|
|
for (auto TunnelInstance : DiscoveredTunnels)
|
|
{
|
|
if (UEdGraph* TunnelGraph = FBlueprintFunctionContext::GetGraphFromNode(TunnelInstance.Key, false))
|
|
{
|
|
// Register dependent context
|
|
UBlueprint* TunnelBlueprint = TunnelGraph->GetTypedOuter<UBlueprint>();
|
|
if (TunnelBlueprint->BlueprintType == BPTYPE_MacroLibrary)
|
|
{
|
|
const FString ClassPath = TunnelBlueprint->GeneratedClass->GetPathName();
|
|
DependentUtilityContexts.AddUnique(ClassPath);
|
|
}
|
|
// Create the tunnel instance context.
|
|
Tunnels.Push(TunnelInstance.Key);
|
|
const FName TunnelInstanceFunctionName = TunnelInstance.Value->GetTunnelInstanceFunctionName(TunnelInstance.Key);
|
|
TSharedPtr<FBlueprintTunnelInstanceContext> NewTunnelInstanceContext = CreateFunctionContext<FBlueprintTunnelInstanceContext>(TunnelInstanceFunctionName, TunnelGraph);
|
|
NewTunnelInstanceContext->MapTunnelContext(TunnelInstance.Value, TunnelInstance.Value, Tunnels);
|
|
Tunnels.Pop();
|
|
TunnelInstance.Value->MapTunnelInstance(TunnelInstance.Key);
|
|
}
|
|
}
|
|
// Map the function context nodes
|
|
for (auto Context : FunctionContexts)
|
|
{
|
|
Context.Value->MapFunction();
|
|
}
|
|
// Sort the events
|
|
if (BlueprintNode.IsValid())
|
|
{
|
|
BlueprintNode->SortEvents();
|
|
}
|
|
bMappingSuccessful = true;
|
|
}
|
|
return bMappingSuccessful;
|
|
}
|
|
|
|
FName FBlueprintExecutionContext::GetFunctionNameFromGraph(const UEdGraph* Graph) const
|
|
{
|
|
FName FunctionName = NAME_None;
|
|
if (Graph)
|
|
{
|
|
UBlueprint* OwnerBlueprint = Graph->GetTypedOuter<UBlueprint>();
|
|
UBlueprintGeneratedClass* BPGC = OwnerBlueprint ? Cast<UBlueprintGeneratedClass>(OwnerBlueprint->GeneratedClass) : nullptr;
|
|
if (BPGC)
|
|
{
|
|
const bool bIsEventGraph = BPGC->UberGraphFunction && FBlueprintEditorUtils::IsEventGraph(Graph);
|
|
FunctionName = bIsEventGraph ? BPGC->UberGraphFunction->GetFName() : Graph->GetFName();
|
|
}
|
|
}
|
|
check (FunctionName != NAME_None);
|
|
return FunctionName;
|
|
}
|
|
|
|
FName FBlueprintExecutionContext::GetScopedFunctionNameFromGraph(const UEdGraph* Graph) const
|
|
{
|
|
FName ScopedFunctionName = NAME_None;
|
|
if (Graph)
|
|
{
|
|
UBlueprint* OwnerBlueprint = Graph->GetTypedOuter<UBlueprint>();
|
|
UBlueprintGeneratedClass* BPGC = OwnerBlueprint ? Cast<UBlueprintGeneratedClass>(OwnerBlueprint->GeneratedClass) : nullptr;
|
|
if (BPGC)
|
|
{
|
|
const bool bIsEventGraph = BPGC->UberGraphFunction && FBlueprintEditorUtils::IsEventGraph(Graph);
|
|
FName GraphName = bIsEventGraph ? BPGC->UberGraphFunction->GetFName() : Graph->GetFName();
|
|
ScopedFunctionName = FName(*FString::Printf(TEXT("%s::%s"), *BPGC->GetName(), *GraphName.ToString()));
|
|
}
|
|
}
|
|
check (ScopedFunctionName != NAME_None);
|
|
return ScopedFunctionName;
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintExecutionContext::FindPurePinNode(const UEdGraphPin* PurePin)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> Result;
|
|
if (TSharedPtr<FScriptExecutionNode>* SearchResult = PureNodeMap.Find(PurePin))
|
|
{
|
|
Result = *SearchResult;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// FBlueprintFunctionContext
|
|
|
|
void FBlueprintFunctionContext::InitialiseContextFromGraph(TSharedPtr<FBlueprintExecutionContext> BlueprintContextIn, const FName FunctionNameIn, UEdGraph* Graph)
|
|
{
|
|
if (Graph)
|
|
{
|
|
// Initialise Blueprint references
|
|
BlueprintContext = BlueprintContextIn;
|
|
UBlueprint* Blueprint = Graph->GetTypedOuter<UBlueprint>();
|
|
UBlueprintGeneratedClass* BPClass = Blueprint ? Cast<UBlueprintGeneratedClass>(Blueprint->GeneratedClass) : nullptr;
|
|
check (Blueprint && BPClass);
|
|
if (Blueprint && BPClass)
|
|
{
|
|
// Instantiate context
|
|
OwningBlueprint = Blueprint;
|
|
BlueprintClass = BPClass;
|
|
bIsInheritedContext = BlueprintContextIn->GetBlueprintClass() != BPClass;
|
|
const bool bEventGraph = Graph->GetFName() == UEdGraphSchema_K2::GN_EventGraph;
|
|
GraphName = Graph->GetFName();
|
|
FunctionName = FunctionNameIn;
|
|
// Find Function
|
|
const FName UFunctionName = BlueprintContextIn->GetFunctionNameFromGraph(Graph);
|
|
Function = BPClass->FindFunctionByName(UFunctionName);
|
|
Function = Function.IsValid() ? Function : BPClass->UberGraphFunction;
|
|
if (bEventGraph)
|
|
{
|
|
// Map events
|
|
TArray<UK2Node_Event*> GraphEventNodes;
|
|
for (auto EventGraph : Blueprint->UbergraphPages)
|
|
{
|
|
EventGraph->GetNodesOfClass<UK2Node_Event>(GraphEventNodes);
|
|
}
|
|
for (auto EventNode : GraphEventNodes)
|
|
{
|
|
if (EventNode->IsNodeEnabled())
|
|
{
|
|
const FName EventName = GetScopedEventName(EventNode->GetFunctionName());
|
|
FScriptExecNodeParams EventParams;
|
|
EventParams.SampleFrequency = 1;
|
|
EventParams.NodeName = EventName;
|
|
EventParams.ObservedObject = EventNode;
|
|
if (bIsInheritedContext)
|
|
{
|
|
EventParams.Tooltip = LOCTEXT("NavigateToInheritedEventLocationHyperlink_ToolTip", "Navigate to the Inherited Event");
|
|
EventParams.IconColor = GetDefault<UGraphEditorSettings>()->ParentFunctionCallNodeTitleColor;
|
|
EventParams.NodeFlags = EScriptExecutionNodeFlags::Event|EScriptExecutionNodeFlags::RuntimeEvent|EScriptExecutionNodeFlags::InheritedEvent;
|
|
EventParams.DisplayName = FText::FromString(FString::Printf(TEXT("%s (%s)"), *EventNode->GetNodeTitle(ENodeTitleType::MenuTitle).ToString(), *OwningBlueprint->GetName()));
|
|
}
|
|
else
|
|
{
|
|
EventParams.Tooltip = LOCTEXT("NavigateToEventLocationHyperlink_ToolTip", "Navigate to the Event");
|
|
EventParams.IconColor = GetDefault<UGraphEditorSettings>()->EventNodeTitleColor;
|
|
EventParams.NodeFlags = EScriptExecutionNodeFlags::Event|EScriptExecutionNodeFlags::RuntimeEvent;
|
|
EventParams.DisplayName = EventNode->GetNodeTitle(ENodeTitleType::MenuTitle);
|
|
}
|
|
GetNodeCustomizations(EventParams);
|
|
const FSlateBrush* EventIcon = EventNode->ShowPaletteIconOnNode() ? EventNode->GetIconAndTint(EventParams.IconColor).GetOptionalIcon() :
|
|
FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPNode"));
|
|
EventParams.Icon = const_cast<FSlateBrush*>(EventIcon);
|
|
TSharedPtr<FScriptExecutionNode> EventExecNode = CreateExecutionNode(EventParams);
|
|
AddEntryPoint(EventExecNode);
|
|
BlueprintContextIn->AddEventNode(AsShared(), EventExecNode);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Map execution paths for each function entry nodes
|
|
TArray<UK2Node_FunctionEntry*> FunctionEntryNodes;
|
|
Graph->GetNodesOfClass<UK2Node_FunctionEntry>(FunctionEntryNodes);
|
|
// Create function node
|
|
for (auto FunctionEntry : FunctionEntryNodes)
|
|
{
|
|
if (FunctionEntry->IsNodeEnabled())
|
|
{
|
|
FScriptExecNodeParams FunctionNodeParams;
|
|
FunctionNodeParams.SampleFrequency = 1;
|
|
FunctionNodeParams.NodeName = FunctionName;
|
|
FunctionNodeParams.ObservedObject = FunctionEntry;
|
|
const bool bConstructionScriptEntryPoint = GraphName == UEdGraphSchema_K2::FN_UserConstructionScript;
|
|
if (bIsInheritedContext)
|
|
{
|
|
FunctionNodeParams.Tooltip = LOCTEXT("NavigateToInheritedFunctionLocationHyperlink_ToolTip", "Navigate to the Inherited Function");
|
|
FunctionNodeParams.IconColor = GetDefault<UGraphEditorSettings>()->ParentFunctionCallNodeTitleColor;
|
|
FunctionNodeParams.NodeFlags = EScriptExecutionNodeFlags::Event|EScriptExecutionNodeFlags::InheritedEvent;
|
|
FunctionNodeParams.NodeFlags |= bConstructionScriptEntryPoint ? EScriptExecutionNodeFlags::ConstructionEvent : EScriptExecutionNodeFlags::RuntimeEvent;
|
|
FunctionNodeParams.DisplayName = FText::FromString(FString::Printf(TEXT("%s (%s)"), *GraphName.ToString(), *OwningBlueprint->GetName()));
|
|
}
|
|
else
|
|
{
|
|
FunctionNodeParams.Tooltip = LOCTEXT("NavigateToFunctionLocationHyperlink_ToolTip", "Navigate to the Function");
|
|
FunctionNodeParams.IconColor = GetDefault<UGraphEditorSettings>()->EventNodeTitleColor;
|
|
FunctionNodeParams.NodeFlags = EScriptExecutionNodeFlags::Event;
|
|
FunctionNodeParams.NodeFlags |= bConstructionScriptEntryPoint ? EScriptExecutionNodeFlags::ConstructionEvent : EScriptExecutionNodeFlags::RuntimeEvent;
|
|
FunctionNodeParams.DisplayName = FText::FromName(GraphName);
|
|
}
|
|
GetNodeCustomizations(FunctionNodeParams);
|
|
const FSlateBrush* Icon = FunctionEntry->ShowPaletteIconOnNode() ? FunctionEntry->GetIconAndTint(FunctionNodeParams.IconColor).GetOptionalIcon() :
|
|
FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPNode"));
|
|
FunctionNodeParams.Icon = const_cast<FSlateBrush*>(Icon);
|
|
TSharedPtr<FScriptExecutionNode> FunctionEntryNode = CreateExecutionNode(FunctionNodeParams);
|
|
AddEntryPoint(FunctionEntryNode);
|
|
// Add user construction scripts as events
|
|
if (bConstructionScriptEntryPoint)
|
|
{
|
|
BlueprintContextIn->AddEventNode(AsShared(), FunctionEntryNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintFunctionContext::DiscoverTunnels(UEdGraph* Graph, TMap<UK2Node_Tunnel*, TSharedPtr<FBlueprintFunctionContext>>& DiscoveredTunnels)
|
|
{
|
|
if (Graph)
|
|
{
|
|
TArray<UK2Node_Tunnel*> GraphTunnels;
|
|
Graph->GetNodesOfClass<UK2Node_Tunnel>(GraphTunnels);
|
|
// Map sub graphs / composites and macros
|
|
for (auto Tunnel : GraphTunnels)
|
|
{
|
|
if (UK2Node_MacroInstance* MacroInstance = Cast<UK2Node_MacroInstance>(Tunnel))
|
|
{
|
|
DiscoveredTunnels.Add(Tunnel, AsShared());
|
|
}
|
|
else if (UK2Node_Composite* CompositeInstance = Cast<UK2Node_Composite>(Tunnel))
|
|
{
|
|
DiscoveredTunnels.Add(Tunnel, AsShared());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintFunctionContext::MapFunction()
|
|
{
|
|
// Map all entry point execution paths
|
|
for (auto EntryPoint : EntryPoints)
|
|
{
|
|
if (const UEdGraphNode* EntryPointNode = Cast<UEdGraphNode>(EntryPoint->GetObservedObject()))
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> ExecutionNode = MapNodeExecution(const_cast<UEdGraphNode*>(EntryPointNode));
|
|
if (ExecutionNode.IsValid())
|
|
{
|
|
EntryPoint->AddChildNode(ExecutionNode);
|
|
}
|
|
}
|
|
else if (const UEdGraphPin* EntryPointPin = EntryPoint->GetObservedPin())
|
|
{
|
|
const int32 PinOffset = GetCodeLocationFromPin(EntryPointPin);
|
|
for (auto LinkedPin : EntryPointPin->LinkedTo)
|
|
{
|
|
UEdGraphNode* LinkedNode = LinkedPin->GetOwningNode();
|
|
const bool bTunnelBoundary = LinkedNode && LinkedNode->IsA<UK2Node_Tunnel>();
|
|
TSharedPtr<FScriptExecutionNode> ExecutionNode = bTunnelBoundary ? MapTunnelBoundary(LinkedPin) : MapNodeExecution(LinkedNode);
|
|
if (ExecutionNode.IsValid())
|
|
{
|
|
EntryPoint->AddChildNode(ExecutionNode);
|
|
}
|
|
}
|
|
}
|
|
// Check for Cyclic Linkage
|
|
TArray<TSharedPtr<FScriptExecutionNode>> Filter;
|
|
DetectCyclicLinks(EntryPoint, Filter);
|
|
}
|
|
if (IsUbergraphFunction())
|
|
{
|
|
// Create any compiler generated events
|
|
CreateDelegatePinEvents();
|
|
}
|
|
}
|
|
|
|
bool FBlueprintFunctionContext::DetectCyclicLinks(TSharedPtr<FScriptExecutionNode> ExecNode, TArray<TSharedPtr<FScriptExecutionNode>>& Filter)
|
|
{
|
|
if (ExecNode->HasFlags(EScriptExecutionNodeFlags::PureStats))
|
|
{
|
|
return false;
|
|
}
|
|
if (Filter.Contains(ExecNode))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (!ExecNode->CanLinkAsCyclicNode())
|
|
{
|
|
Filter.Add(ExecNode);
|
|
}
|
|
if (!ExecNode->IsTunnelEntry())
|
|
{
|
|
for (TSharedPtr<FScriptExecutionNode>& Child : ExecNode->GetChildNodes())
|
|
{
|
|
if (DetectCyclicLinks(Child, Filter))
|
|
{
|
|
// Replace child with cyclic link node.
|
|
FScriptExecNodeParams CycleLinkParams;
|
|
CycleLinkParams.SampleFrequency = 1;
|
|
const FString NodeName = FString::Printf(TEXT("CyclicLinkTo_%i_%s"), ExecutionNodes.Num(), *Child->GetName().ToString());
|
|
CycleLinkParams.NodeName = FName(*NodeName);
|
|
CycleLinkParams.ObservedObject = Child->GetObservedObject();
|
|
CycleLinkParams.DisplayName = Child->GetDisplayName();
|
|
CycleLinkParams.Tooltip = LOCTEXT("CyclicLink_ToolTip", "Cyclic Link");
|
|
CycleLinkParams.NodeFlags = EScriptExecutionNodeFlags::CyclicLinkage|Child->GetFlags();
|
|
CycleLinkParams.IconColor = Child->GetIconColor();
|
|
CycleLinkParams.IconColor.A = 0.15f;
|
|
const FSlateBrush* LinkIcon = Child->GetIcon();
|
|
CycleLinkParams.Icon = const_cast<FSlateBrush*>(LinkIcon);
|
|
Child = CreateExecutionNode(CycleLinkParams);
|
|
}
|
|
}
|
|
}
|
|
const int32 FilterWaterMark = Filter.Num();
|
|
for (auto LinkedNode : ExecNode->GetLinkedNodes())
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> LinkedExecNode = LinkedNode.Value;
|
|
if (LinkedExecNode->HasFlags(EScriptExecutionNodeFlags::TunnelInstance))
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> TunnelBoundary = LinkedExecNode->GetLinkedNodeByScriptOffset(LinkedNode.Key);
|
|
if (TunnelBoundary.IsValid())
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> TunnelExec = TunnelBoundary->GetLinkedNodeByScriptOffset(LinkedNode.Key);
|
|
if (TunnelExec.IsValid())
|
|
{
|
|
ExecNode = TunnelExec;
|
|
}
|
|
}
|
|
}
|
|
Filter.SetNum(FilterWaterMark);
|
|
if (DetectCyclicLinks(LinkedExecNode, Filter))
|
|
{
|
|
// Break links and flag cycle linkage.
|
|
FScriptExecNodeParams CycleLinkParams;
|
|
CycleLinkParams.SampleFrequency = 1;
|
|
const FString NodeName = FString::Printf(TEXT("CyclicLinkTo_%i_%s"), ExecutionNodes.Num(), *LinkedExecNode->GetName().ToString());
|
|
CycleLinkParams.NodeName = FName(*NodeName);
|
|
CycleLinkParams.ObservedObject = LinkedExecNode->GetObservedObject();
|
|
CycleLinkParams.DisplayName = LinkedExecNode->GetDisplayName();
|
|
CycleLinkParams.Tooltip = LOCTEXT("CyclicLink_ToolTip", "Cyclic Link");
|
|
CycleLinkParams.NodeFlags = EScriptExecutionNodeFlags::CyclicLinkage|EScriptExecutionNodeFlags::ExecPin|EScriptExecutionNodeFlags::InvalidTrace;
|
|
CycleLinkParams.IconColor = LinkedExecNode->GetIconColor();
|
|
CycleLinkParams.IconColor.A = 0.15f;
|
|
const FSlateBrush* LinkIcon = LinkedExecNode->GetIcon();
|
|
CycleLinkParams.Icon = const_cast<FSlateBrush*>(LinkIcon);
|
|
TSharedPtr<FScriptExecutionNode> NewLink = CreateExecutionNode(CycleLinkParams);
|
|
ExecNode->AddLinkedNode(LinkedNode.Key, NewLink);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintFunctionContext::AddChildFunctionContext(const FName FunctionNameIn, TSharedPtr<FBlueprintFunctionContext> ChildContext)
|
|
{
|
|
if (ChildContext.IsValid() && ChildContext->GetFunctionName() != FunctionName)
|
|
{
|
|
ChildFunctionContexts.Add(FunctionNameIn) = ChildContext;
|
|
}
|
|
}
|
|
|
|
void FBlueprintFunctionContext::CreateDelegatePinEvents()
|
|
{
|
|
struct FPinDelegateDesc
|
|
{
|
|
FPinDelegateDesc(const FName EventNameIn, UEdGraphPin* DelegatePinIn, const int32 ScriptOffsetIn, FNodeExecutionContext& ContextIn)
|
|
: EventName(EventNameIn)
|
|
, DelegatePin(DelegatePinIn)
|
|
, ScriptOffset(ScriptOffsetIn)
|
|
, Context(ContextIn)
|
|
{
|
|
}
|
|
|
|
FName EventName;
|
|
UEdGraphPin* DelegatePin;
|
|
const int32 ScriptOffset;
|
|
FNodeExecutionContext Context;
|
|
};
|
|
|
|
FBlueprintDebugData& DebugData = BlueprintClass.Get()->GetDebugData();
|
|
const TMap<int32, FName>& PinEvents = DebugData.GetEntryPoints();
|
|
if (PinEvents.Num())
|
|
{
|
|
TMap<const UEdGraphNode*, TArray<FPinDelegateDesc>> NodeEventDescs;
|
|
TSharedPtr<FBlueprintFunctionContext> FunctionContext = AsShared();
|
|
TSharedPtr<FBlueprintExecutionContext> ParentBlueprintContext = BlueprintContext.Pin();
|
|
// Build event contexts per node
|
|
for (auto PinEvent : PinEvents)
|
|
{
|
|
if (UEdGraphPin* DelegatePin = DebugData.FindSourcePinFromCodeLocation(BlueprintClass.Get()->UberGraphFunction, PinEvent.Key))
|
|
{
|
|
const UEdGraphNode* OwningNode = DelegatePin->GetOwningNode();
|
|
TArray<FPinDelegateDesc>& Events = NodeEventDescs.FindOrAdd(OwningNode);
|
|
const int32 ScriptOffset = GetCodeLocationFromPin(DelegatePin);
|
|
FNodeExecutionContext& NodeContext = GetNodeExecutionContext(ScriptOffset);
|
|
if (NodeContext.IsValid())
|
|
{
|
|
const FName ScopedEventName = NodeContext.ProfilerContext->GetScopedEventName(PinEvent.Value);
|
|
Events.Add(FPinDelegateDesc(ScopedEventName, DelegatePin, ScriptOffset, NodeContext));
|
|
}
|
|
else
|
|
{
|
|
NodeContext.ProfilerContext = AsShared();
|
|
NodeContext.GraphNode = OwningNode;
|
|
const FName ScopedEventName = GetScopedEventName(PinEvent.Value);
|
|
Events.Add(FPinDelegateDesc(ScopedEventName, DelegatePin, ScriptOffset, NodeContext));
|
|
}
|
|
}
|
|
}
|
|
// Generate the event exec nodes
|
|
for (auto NodeEvents : NodeEventDescs)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> EventExecNode;
|
|
// Create the events for the pins
|
|
|
|
for (FPinDelegateDesc& EventDesc : NodeEvents.Value)
|
|
{
|
|
if (EventDesc.Context.IsValid())
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> PinExecNode = EventDesc.Context.ProfilerNode->GetLinkedNodeByScriptOffset(EventDesc.ScriptOffset);
|
|
if (PinExecNode.IsValid())
|
|
{
|
|
// Modify the pin node to mark it as an event/delegate pin.
|
|
PinExecNode->AddFlags(EScriptExecutionNodeFlags::ExecPin | EScriptExecutionNodeFlags::EventPin);
|
|
PinExecNode->SetIconColor(GetDefault<UGraphEditorSettings>()->DelegatePinTypeColor);
|
|
// Register the function context as a handler for the event.
|
|
ParentBlueprintContext->RegisterEventContext(EventDesc.EventName, AsShared());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!EventExecNode.IsValid())
|
|
{
|
|
// Check if this node requires an event node creating.
|
|
bool bCreateEventNode = true;
|
|
for (auto Pin : NodeEvents.Key->Pins)
|
|
{
|
|
if (Pin->Direction == EGPD_Input && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)
|
|
{
|
|
bCreateEventNode = false;
|
|
break;
|
|
}
|
|
}
|
|
if (bCreateEventNode)
|
|
{
|
|
// Setup the basic exec node params.
|
|
FScriptExecNodeParams EventParams;
|
|
EventParams.SampleFrequency = 1;
|
|
EventParams.NodeName = FName(*NodeEvents.Key->GetName());
|
|
EventParams.ObservedObject = NodeEvents.Key;
|
|
EventParams.OwningGraphName = NAME_None;
|
|
EventParams.DisplayName = NodeEvents.Key->GetNodeTitle(ENodeTitleType::MenuTitle);
|
|
EventParams.Tooltip = LOCTEXT("NavigateToEventLocationHyperlink_ToolTip", "Navigate to the Event");
|
|
EventParams.NodeFlags = EventDesc.Context.ProfilerContext->IsInheritedContext() ? (EScriptExecutionNodeFlags::Event | EScriptExecutionNodeFlags::RuntimeEvent | EScriptExecutionNodeFlags::InheritedEvent) :
|
|
(EScriptExecutionNodeFlags::Event | EScriptExecutionNodeFlags::RuntimeEvent);
|
|
GetNodeCustomizations(EventParams);
|
|
const FSlateBrush* EventIcon = NodeEvents.Key->ShowPaletteIconOnNode() ? NodeEvents.Key->GetIconAndTint(EventParams.IconColor).GetOptionalIcon() :
|
|
FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPNode"));
|
|
EventParams.Icon = const_cast<FSlateBrush*>(EventIcon);
|
|
EventExecNode = CreateExecutionNode(EventParams);
|
|
// Add entry points.
|
|
AddEntryPoint(EventExecNode);
|
|
ExecutionNodes.Add(EventDesc.EventName) = EventExecNode;
|
|
ParentBlueprintContext->AddEventNode(EventDesc.Context.ProfilerContext, EventExecNode);
|
|
}
|
|
}
|
|
|
|
if (EventExecNode.IsValid())
|
|
{
|
|
// Create the events for the pins
|
|
FScriptExecNodeParams PinParams;
|
|
PinParams.SampleFrequency = 1;
|
|
PinParams.NodeName = GetUniquePinName(EventDesc.DelegatePin);
|
|
PinParams.ObservedPin = EventDesc.DelegatePin;
|
|
PinParams.ObservedObject = NodeEvents.Key;
|
|
PinParams.OwningGraphName = NAME_None;
|
|
PinParams.DisplayName = EventDesc.DelegatePin->GetDisplayName();
|
|
PinParams.Tooltip = LOCTEXT("ExecPin_ExpandExecutionPath_ToolTip", "Expand execution path");
|
|
PinParams.NodeFlags = EScriptExecutionNodeFlags::ExecPin | EScriptExecutionNodeFlags::EventPin;
|
|
PinParams.IconColor = GetDefault<UGraphEditorSettings>()->DelegatePinTypeColor;
|
|
const bool bPinLinked = EventDesc.DelegatePin->LinkedTo.Num() > 0;
|
|
const FSlateBrush* Icon = bPinLinked ? FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPPinConnected")) :
|
|
FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPPinDisconnected"));
|
|
PinParams.Icon = const_cast<FSlateBrush*>(Icon);
|
|
|
|
TSharedPtr<FScriptExecutionNode> PinExecNode = CreateExecutionNode(PinParams);
|
|
AddEntryPoint(PinExecNode);
|
|
EventExecNode->AddChildNode(PinExecNode);
|
|
if (bPinLinked)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> LinkedNode = MapNodeExecution(EventDesc.DelegatePin->LinkedTo[0]->GetOwningNode());
|
|
PinExecNode->AddLinkedNode(EventDesc.ScriptOffset, LinkedNode);
|
|
}
|
|
|
|
ParentBlueprintContext->RegisterEventContext(EventDesc.EventName, FunctionContext);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintFunctionContext::GetPinCustomizations(const UEdGraphPin* Pin, FScriptExecNodeParams& PinParams)
|
|
{
|
|
// Defaults
|
|
PinParams.IconColor = FLinearColor::White;
|
|
PinParams.Icon = const_cast<FSlateBrush*>(FEditorStyle::GetBrush(TEXT("Graph.Pin.Connected")));
|
|
|
|
if (Pin)
|
|
{
|
|
// Set Pin Color
|
|
const UEdGraphSchema* Schema = Pin->GetSchema();
|
|
PinParams.IconColor = Schema->GetPinTypeColor(Pin->PinType);
|
|
// Determine pin icon
|
|
if (Pin->PinType.bIsArray)
|
|
{
|
|
// Array pins
|
|
PinParams.Icon = const_cast<FSlateBrush*>(FEditorStyle::GetBrush(TEXT("Graph.ArrayPin.Connected")));
|
|
}
|
|
else if(Schema->IsDelegateCategory(Pin->PinType.PinCategory))
|
|
{
|
|
// Delegate pins
|
|
PinParams.Icon = const_cast<FSlateBrush*>(FEditorStyle::GetBrush(TEXT("Graph.DelegatePin.Connected")));
|
|
}
|
|
else if (Pin->bDisplayAsMutableRef || (Pin->PinType.bIsReference && !Pin->PinType.bIsConst))
|
|
{
|
|
// Mutable ref's
|
|
PinParams.Icon = const_cast<FSlateBrush*>(FEditorStyle::GetBrush(TEXT("Graph.RefPin.Connected")));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintFunctionContext::GetNodeCustomizations(FScriptExecNodeParams& ParamsInOut) const
|
|
{
|
|
// Pick a color based on flags.
|
|
if ((ParamsInOut.NodeFlags & (EScriptExecutionNodeFlags::InheritedEvent|EScriptExecutionNodeFlags::ParentFunctionCall)) != 0U)
|
|
{
|
|
// Inherited events and calls
|
|
ParamsInOut.IconColor = GetDefault<UGraphEditorSettings>()->ParentFunctionCallNodeTitleColor;
|
|
}
|
|
else if ((ParamsInOut.NodeFlags & EScriptExecutionNodeFlags::Event) != 0U)
|
|
{
|
|
// Events and custom events
|
|
ParamsInOut.IconColor = GetDefault<UGraphEditorSettings>()->EventNodeTitleColor;
|
|
}
|
|
else if ((ParamsInOut.NodeFlags & EScriptExecutionNodeFlags::FunctionCall) != 0U)
|
|
{
|
|
// Function calls
|
|
ParamsInOut.IconColor = GetDefault<UGraphEditorSettings>()->FunctionCallNodeTitleColor;
|
|
}
|
|
else
|
|
{
|
|
// Set as the default node color.
|
|
ParamsInOut.IconColor = FLinearColor(1.f, 1.f, 1.f, 0.8f);
|
|
// Check for any final specialisations
|
|
if ((ParamsInOut.NodeFlags & (EScriptExecutionNodeFlags::PureNode|EScriptExecutionNodeFlags::PureChain)) != 0U)
|
|
{
|
|
// Pure nodes
|
|
ParamsInOut.IconColor = GetDefault<UGraphEditorSettings>()->PureFunctionCallNodeTitleColor;
|
|
}
|
|
else if ((ParamsInOut.NodeFlags & EScriptExecutionNodeFlags::SequentialBranch) != 0U)
|
|
{
|
|
// Sequential branches
|
|
ParamsInOut.IconColor = GetDefault<UGraphEditorSettings>()->ExecSequenceNodeTitleColor;
|
|
}
|
|
else if ((ParamsInOut.NodeFlags & EScriptExecutionNodeFlags::ConditionalBranch) != 0U)
|
|
{
|
|
// Consditional branches
|
|
ParamsInOut.IconColor = GetDefault<UGraphEditorSettings>()->ExecBranchNodeTitleColor;
|
|
}
|
|
else if (ParamsInOut.ObservedObject->IsA<UK2Node_Event>() || ParamsInOut.ObservedObject->IsA<UK2Node_FunctionEntry>())
|
|
{
|
|
// Differentiate between execution path and entry node when they have the same name
|
|
FFormatNamedArguments Args;
|
|
Args.Add(TEXT("NodeName"), ParamsInOut.DisplayName);
|
|
Args.Add(TEXT("EntryNode"), LOCTEXT("EntryNode", "Entry Node"));
|
|
ParamsInOut.DisplayName = FText::Format(FText::FromString("{NodeName} ({EntryNode})"), Args);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintFunctionContext::DetermineGraphNodeCharacteristics(const UEdGraphNode* GraphNode, TArray<UEdGraphPin*>& InputPins, TArray<UEdGraphPin*>& ExecPins, FScriptExecNodeParams& NodeParams)
|
|
{
|
|
// Set the standard sample base
|
|
NodeParams.SampleFrequency = 1;
|
|
// Evaluate Execution and Input Pins
|
|
int32 ConnectedExecPins = 0;
|
|
for (auto Pin : GraphNode->Pins)
|
|
{
|
|
if (Pin->Direction == EGPD_Output && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)
|
|
{
|
|
ExecPins.Add(Pin);
|
|
if (Pin->LinkedTo.Num())
|
|
{
|
|
ConnectedExecPins++;
|
|
}
|
|
}
|
|
else if (Pin->Direction == EGPD_Input && Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec)
|
|
{
|
|
InputPins.Add(Pin);
|
|
}
|
|
}
|
|
// Identify branching types and pure nodes based on pin layout.
|
|
if (ExecPins.Num() == 0 && !GraphNode->IsA<UK2Node_FunctionResult>())
|
|
{
|
|
NodeParams.NodeFlags |= EScriptExecutionNodeFlags::PureNode;
|
|
}
|
|
else if (ExecPins.Num() > 1)
|
|
{
|
|
if (GraphNode->IsA<UK2Node_ExecutionSequence>())
|
|
{
|
|
// Update the node params with the expected base sample rate.
|
|
NodeParams.SampleFrequency = ConnectedExecPins;
|
|
NodeParams.NodeFlags |= EScriptExecutionNodeFlags::SequentialBranch;
|
|
}
|
|
else if (const UK2Node_ForEachElementInEnum* ForEachElementInEnumNode = Cast<UK2Node_ForEachElementInEnum>(GraphNode))
|
|
{
|
|
// This behaves like a ForLoop macro instance node, but w/o an actual macro expansion.
|
|
const UEdGraphPin* LoopBodyPin = ForEachElementInEnumNode->FindPin(UK2Node_ForEachElementInEnum::InsideLoopPinName);
|
|
if (LoopBodyPin && LoopBodyPin->LinkedTo.Num() > 0)
|
|
{
|
|
// Subtract 1, because we've already accounted for the first iteration pass through the link above.
|
|
NodeParams.SampleFrequency = ConnectedExecPins + ForEachElementInEnumNode->Enum->GetMaxEnumValue() - 1;
|
|
}
|
|
NodeParams.NodeFlags |= EScriptExecutionNodeFlags::SequentialBranch;
|
|
}
|
|
else
|
|
{
|
|
if (GraphNode->IsA<UK2Node_Event>() || GraphNode->IsA<UK2Node_InputKey>() || GraphNode->IsA<UK2Node_InputTouch>())
|
|
{
|
|
NodeParams.SampleFrequency = ConnectedExecPins;
|
|
}
|
|
NodeParams.NodeFlags |= EScriptExecutionNodeFlags::ConditionalBranch;
|
|
}
|
|
}
|
|
// Identify function calls and custom events
|
|
if (GraphNode->IsA<UK2Node_CallParentFunction>())
|
|
{
|
|
NodeParams.NodeFlags |= EScriptExecutionNodeFlags::ParentFunctionCall;
|
|
}
|
|
else if (GraphNode->IsA<UK2Node_CallFunction>())
|
|
{
|
|
NodeParams.NodeFlags |= EScriptExecutionNodeFlags::FunctionCall;
|
|
}
|
|
else if (GraphNode->IsA<UK2Node_CustomEvent>())
|
|
{
|
|
NodeParams.NodeFlags |= EScriptExecutionNodeFlags::CustomEvent;
|
|
}
|
|
// Create display name.
|
|
NodeParams.DisplayName = GraphNode->GetNodeTitle(ENodeTitleType::MenuTitle);
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintFunctionContext::MapNodeExecution(UEdGraphNode* NodeToMap)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> MappedNode;
|
|
if (NodeToMap)
|
|
{
|
|
// Lookup existing mapped node
|
|
MappedNode = GetProfilerDataForGraphNode(NodeToMap);
|
|
// Map if not existing.
|
|
if (!MappedNode.IsValid())
|
|
{
|
|
// Determine node characteristics
|
|
FScriptExecNodeParams NodeParams;
|
|
NodeParams.NodeFlags = EScriptExecutionNodeFlags::Node;
|
|
TArray<UEdGraphPin*> ExecPins;
|
|
TArray<UEdGraphPin*> InputPins;
|
|
DetermineGraphNodeCharacteristics(NodeToMap, InputPins, ExecPins, NodeParams);
|
|
NodeParams.NodeName = NodeToMap->GetFName();
|
|
NodeParams.ObservedObject = NodeToMap;
|
|
NodeParams.Tooltip = LOCTEXT("NavigateToNodeLocationHyperlink_ToolTip", "Navigate to the Node");
|
|
GetNodeCustomizations(NodeParams);
|
|
const FSlateBrush* NodeIcon = NodeToMap->ShowPaletteIconOnNode() ? NodeToMap->GetIconAndTint(NodeParams.IconColor).GetOptionalIcon() :
|
|
FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPNode"));
|
|
NodeParams.Icon = const_cast<FSlateBrush*>(NodeIcon);
|
|
MappedNode = CreateExecutionNode(NodeParams);
|
|
// Discover pure node script ranges
|
|
if (!MappedNode->IsPureNode())
|
|
{
|
|
MappedNode->SetPureNodeScriptCodeRange(GetPureNodeScriptCodeRange(NodeToMap));
|
|
}
|
|
// Evaluate non-exec input pins (pure node execution chains)
|
|
if (InputPins.Num())
|
|
{
|
|
MapInputPins(MappedNode, InputPins);
|
|
}
|
|
// Evaluate exec output pins (execution chains)
|
|
if (ExecPins.Num())
|
|
{
|
|
MapExecPins(MappedNode, ExecPins);
|
|
}
|
|
// Evaluate Children for call sites
|
|
if (MappedNode->IsFunctionCallSite()||MappedNode->IsParentFunctionCallSite())
|
|
{
|
|
if (UK2Node_CallFunction* FunctionCallSite = Cast<UK2Node_CallFunction>(NodeToMap))
|
|
{
|
|
const UEdGraphNode* FunctionNode = nullptr;
|
|
if (UEdGraph* CalledGraph = FunctionCallSite->GetFunctionGraph(FunctionNode))
|
|
{
|
|
// Update Exec node
|
|
const bool bEventCall = FunctionNode ? FunctionNode->IsA<UK2Node_Event>() : false;
|
|
MappedNode->SetToolTipText(LOCTEXT("NavigateToFunctionCallsiteHyperlink_ToolTip", "Navigate to the Function Callsite"));
|
|
// Don't add entry points for events.
|
|
if (!bEventCall)
|
|
{
|
|
// Update the function context
|
|
TSharedPtr<FBlueprintFunctionContext> NewFunctionContext = BlueprintContext.Pin()->GetFunctionContextFromGraph(CalledGraph);
|
|
if (NewFunctionContext.IsValid())
|
|
{
|
|
NewFunctionContext->AddCallSiteEntryPointsToNode(MappedNode);
|
|
AddChildFunctionContext(NewFunctionContext->GetFunctionName(), NewFunctionContext);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return MappedNode;
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintFunctionContext::MapPureNodeExecution(const UEdGraphPin* LinkedPin)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> MappedNode;
|
|
UK2Node* LinkedNode = LinkedPin ? Cast<UK2Node>(LinkedPin->GetOwningNode()) : nullptr;
|
|
if (LinkedNode)
|
|
{
|
|
// Determine what type of mapping is required
|
|
if (LinkedNode->IsNodePure())
|
|
{
|
|
// Lookup existing mapped pin node
|
|
MappedNode = BlueprintContext.Pin()->FindPurePinNode(LinkedPin);
|
|
if (!MappedNode.IsValid())
|
|
{
|
|
// Multi pure pin i/o registers multiple pins per node so lookup the node by name.
|
|
MappedNode = GetProfilerDataForNode(LinkedNode->GetFName());
|
|
}
|
|
// Map if not existing.
|
|
if (!MappedNode.IsValid())
|
|
{
|
|
// Create a normal execution node.
|
|
FScriptExecNodeParams NodeParams;
|
|
NodeParams.NodeFlags = EScriptExecutionNodeFlags::Node;
|
|
TArray<UEdGraphPin*> ExecPins;
|
|
TArray<UEdGraphPin*> PurePins;
|
|
DetermineGraphNodeCharacteristics(LinkedNode, PurePins, ExecPins, NodeParams);
|
|
NodeParams.NodeName = LinkedNode->GetFName();
|
|
NodeParams.ObservedObject = LinkedNode;
|
|
NodeParams.Tooltip = LOCTEXT("NavigateToNodeLocationHyperlink_ToolTip", "Navigate to the Node");
|
|
GetNodeCustomizations(NodeParams);
|
|
const FSlateBrush* NodeIcon = LinkedNode->ShowPaletteIconOnNode() ? LinkedNode->GetIconAndTint(NodeParams.IconColor).GetOptionalIcon() :
|
|
FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPNode"));
|
|
NodeParams.Icon = const_cast<FSlateBrush*>(NodeIcon);
|
|
// Create execution node
|
|
MappedNode = CreateExecutionNode(NodeParams);
|
|
// Evaluate non-exec input pins (pure node execution chains)
|
|
if (PurePins.Num())
|
|
{
|
|
MapInputPins(MappedNode, PurePins);
|
|
}
|
|
}
|
|
// Register Pure Node
|
|
BlueprintContext.Pin()->RegisterPurePinNode(LinkedPin, MappedNode);
|
|
}
|
|
else
|
|
{
|
|
// Lookup existing mapped pin node
|
|
MappedNode = BlueprintContext.Pin()->FindPurePinNode(LinkedPin);
|
|
if (!MappedNode.IsValid())
|
|
{
|
|
// Multi pure pin i/o registers multiple pins per node so lookup the node by name.
|
|
MappedNode = GetProfilerDataForNode(GetUniquePinName(LinkedPin));
|
|
}
|
|
// Map if not existing.
|
|
if (!MappedNode.IsValid())
|
|
{
|
|
// Create a pure node pin entry for this pin located on an impure node.
|
|
FScriptExecNodeParams PinParams;
|
|
PinParams.SampleFrequency = 1;
|
|
PinParams.NodeFlags = EScriptExecutionNodeFlags::PureNode;
|
|
PinParams.NodeName = GetUniquePinName(LinkedPin);
|
|
PinParams.ObservedObject = LinkedNode;
|
|
PinParams.ObservedPin = LinkedPin;
|
|
PinParams.Tooltip = LOCTEXT("NavigateToPinLocationHyperlink_ToolTip", "Navigate to the Pure Pin");
|
|
FFormatNamedArguments Args;
|
|
Args.Add("NodeDisplayName", LinkedNode->GetNodeTitle(ENodeTitleType::MenuTitle));
|
|
Args.Add("PinDisplayName", LinkedPin->PinFriendlyName);
|
|
FText Format = LinkedPin->PinFriendlyName.IsEmpty() ? LOCTEXT("PureNodeDisplay_Text", "{NodeDisplayName}") : LOCTEXT("PurePinDisplay_Text", "{NodeDisplayName} - {PinDisplayName}");
|
|
PinParams.DisplayName = FText::Format(Format, Args);
|
|
GetPinCustomizations(LinkedPin, PinParams);
|
|
const FSlateBrush* NodeIcon = LinkedNode->ShowPaletteIconOnNode() ? LinkedNode->GetIconAndTint(PinParams.IconColor).GetOptionalIcon() :
|
|
FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPNode"));
|
|
PinParams.Icon = const_cast<FSlateBrush*>(NodeIcon);
|
|
MappedNode = CreateExecutionNode(PinParams);
|
|
}
|
|
// Register Pure Node
|
|
BlueprintContext.Pin()->RegisterPurePinNode(LinkedPin, MappedNode);
|
|
}
|
|
}
|
|
return MappedNode;
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintFunctionContext::FindOrCreatePureChainRoot(TSharedPtr<FScriptExecutionNode> ExecNode)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> PureChainRootNode;
|
|
// Add a pure chain container node as the root, if it's not already in place.
|
|
if (ExecNode.IsValid() && !ExecNode->IsPureNode())
|
|
{
|
|
// Try to find the root first
|
|
PureChainRootNode = ExecNode->GetPureChainNode();
|
|
// Otherwise create one
|
|
if (!PureChainRootNode.IsValid())
|
|
{
|
|
static const FString PureChainNodeNameSuffix = TEXT("__PROFILER_InputPureTime");
|
|
const FName PureChainNodeName = FName(*(ExecNode->GetName().ToString() + PureChainNodeNameSuffix));
|
|
FScriptExecNodeParams PureChainParams;
|
|
PureChainParams.SampleFrequency = 1;
|
|
PureChainParams.NodeName = PureChainNodeName;
|
|
PureChainParams.ObservedObject = ExecNode->GetObservedObject();
|
|
PureChainParams.DisplayName = LOCTEXT("PureChain_DisplayName", "Pure Time");
|
|
PureChainParams.Tooltip = LOCTEXT("PureChain_ToolTip", "Expand pure node timing");
|
|
PureChainParams.NodeFlags = EScriptExecutionNodeFlags::PureChain;
|
|
GetNodeCustomizations(PureChainParams);
|
|
const FSlateBrush* Icon = FEditorStyle::GetBrush(TEXT("BlueprintProfiler.PureNode"));
|
|
PureChainParams.Icon = const_cast<FSlateBrush*>(Icon);
|
|
PureChainRootNode = CreateTypedExecutionNode<FScriptExecutionPureChainNode>(PureChainParams);
|
|
PureChainRootNode->SetPureNodeScriptCodeRange(ExecNode->GetPureNodeScriptCodeRange());
|
|
ExecNode->AddChildNode(PureChainRootNode);
|
|
}
|
|
}
|
|
return PureChainRootNode;
|
|
}
|
|
|
|
void FBlueprintFunctionContext::MapInputPins(TSharedPtr<FScriptExecutionNode> ExecNode, const TArray<UEdGraphPin*>& Pins)
|
|
{
|
|
TArray<int32> PinScriptCodeOffsets;
|
|
TSharedPtr<FScriptExecutionNode> PureChainRootNode = ExecNode;
|
|
|
|
for (auto InputPin : Pins)
|
|
{
|
|
// If this input pin is linked to a pure node in the source graph, create and map all known execution paths for it.
|
|
for (auto LinkedPin : InputPin->LinkedTo)
|
|
{
|
|
// Pass through non-relevant (e.g. reroute) nodes.
|
|
LinkedPin = FBlueprintEditorUtils::FindFirstCompilerRelevantLinkedPin(LinkedPin);
|
|
if (LinkedPin)
|
|
{
|
|
UK2Node* OwningNode = Cast<UK2Node>(LinkedPin->GetOwningNode());
|
|
|
|
// If this is a tunnel node - we need to map through the tunnel
|
|
if (OwningNode)
|
|
{
|
|
if (OwningNode->IsA<UK2Node_Tunnel>() && FBlueprintEditorUtils::IsTunnelInstanceNode(OwningNode))
|
|
{
|
|
// Find the tunnel instance context
|
|
const FName ScopedTunnelContextName = GetTunnelInstanceFunctionName(OwningNode);
|
|
TSharedPtr<FBlueprintTunnelInstanceContext> TunnelContext = StaticCastSharedPtr<FBlueprintTunnelInstanceContext>(BlueprintContext.Pin()->GetFunctionContext(ScopedTunnelContextName));
|
|
check (TunnelContext.IsValid());
|
|
TSharedPtr<FScriptExecutionNode> TunnelPureNode = BlueprintContext.Pin()->FindPurePinNode(LinkedPin);
|
|
// Add the linked pure nodes
|
|
if (TunnelPureNode.IsValid() && TunnelPureNode->IsPureChain())
|
|
{
|
|
// Grab the pure chain root.
|
|
PureChainRootNode = FindOrCreatePureChainRoot(ExecNode);
|
|
if (!PureChainRootNode.IsValid())
|
|
{
|
|
PureChainRootNode = ExecNode;
|
|
}
|
|
// Link in the tunnel pure boundary.
|
|
PureChainRootNode->AddLinkedNode(INDEX_NONE, TunnelPureNode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Note: Intermediate pure nodes can have output pins that masquerade as impure node output pins when links are "moved" from the source graph (thus
|
|
// resulting in a false association here with one or more script code offsets), so we must first ensure that the link is really to a pure node output.
|
|
GetAllCodeLocationsFromPin(LinkedPin, PinScriptCodeOffsets);
|
|
if (PinScriptCodeOffsets.Num() > 0)
|
|
{
|
|
// Add a pure chain container node as the root, if it's not already in place.
|
|
if (!ExecNode->HasFlags(EScriptExecutionNodeFlags::PureStats))
|
|
{
|
|
PureChainRootNode = FindOrCreatePureChainRoot(ExecNode);
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> PureNode = MapPureNodeExecution(LinkedPin);
|
|
for (int32 i = 0; i < PinScriptCodeOffsets.Num(); ++i)
|
|
{
|
|
PureChainRootNode->AddLinkedNode(PinScriptCodeOffsets[i], PureNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintFunctionContext::MapExecPins(TSharedPtr<FScriptExecutionNode> ExecNode, const TArray<UEdGraphPin*>& Pins)
|
|
{
|
|
const bool bBranchedExecution = ExecNode->IsBranch();
|
|
int32 NumUnwiredPins = 0;
|
|
const UEdGraphPin* FinalExecPin = nullptr;
|
|
for (auto Pin : Pins)
|
|
{
|
|
const FName PinName = GetUniquePinName(Pin);
|
|
int32 PinScriptCodeOffset = GetCodeLocationFromPin(Pin);
|
|
const bool bInvalidTrace = PinScriptCodeOffset == INDEX_NONE;
|
|
PinScriptCodeOffset = bInvalidTrace ? NumUnwiredPins++ : PinScriptCodeOffset;
|
|
TSharedPtr<FScriptExecutionNode> PinExecNode;
|
|
// Note: Pass through non-relevant (e.g. reroute) nodes here as they're not compiled and thus do not need to be mapped for profiling.
|
|
const UEdGraphPin* LinkedPin = Pin->LinkedTo.Num() > 0 ? FBlueprintEditorUtils::FindFirstCompilerRelevantLinkedPin(Pin->LinkedTo[0]) : nullptr;
|
|
UEdGraphNode* LinkedPinNode = LinkedPin ? LinkedPin->GetOwningNode() : nullptr;
|
|
const bool bTunnelBoundary = LinkedPinNode ? LinkedPinNode->IsA<UK2Node_Tunnel>() : false;
|
|
// Try locate an already mapped pin.
|
|
if (TSharedPtr<FScriptExecutionNode>* MappedPin = ExecutionNodes.Find(PinName))
|
|
{
|
|
PinExecNode = *MappedPin;
|
|
ExecNode->AddLinkedNode(PinScriptCodeOffset, PinExecNode);
|
|
continue;
|
|
}
|
|
// Create any neccesary dummy pins for branches
|
|
if (bBranchedExecution)
|
|
{
|
|
FScriptExecNodeParams LinkNodeParams;
|
|
LinkNodeParams.SampleFrequency = 1;
|
|
LinkNodeParams.NodeName = PinName;
|
|
LinkNodeParams.ObservedObject = Pin->GetOwningNode();
|
|
LinkNodeParams.DisplayName = Pin->GetDisplayName();
|
|
LinkNodeParams.ObservedPin = Pin;
|
|
LinkNodeParams.Tooltip = LOCTEXT("ExecPin_ExpandExecutionPath_ToolTip", "Expand execution path");
|
|
LinkNodeParams.NodeFlags = !bInvalidTrace ? EScriptExecutionNodeFlags::ExecPin : (EScriptExecutionNodeFlags::ExecPin|EScriptExecutionNodeFlags::InvalidTrace);
|
|
LinkNodeParams.IconColor = FLinearColor(1.f, 1.f, 1.f, 0.8f);
|
|
const FSlateBrush* Icon = LinkedPin ? FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPPinConnected")) :
|
|
FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPPinDisconnected"));
|
|
LinkNodeParams.Icon = const_cast<FSlateBrush*>(Icon);
|
|
PinExecNode = CreateExecutionNode(LinkNodeParams);
|
|
ExecNode->AddLinkedNode(PinScriptCodeOffset, PinExecNode);
|
|
}
|
|
else if (!PinExecNode.IsValid())
|
|
{
|
|
PinExecNode = ExecNode;
|
|
}
|
|
// Continue mapping forward.
|
|
TSharedPtr<FScriptExecutionNode> LinkedPinExecNode = bTunnelBoundary ? MapTunnelBoundary(LinkedPin) : MapNodeExecution(LinkedPinNode);
|
|
if (LinkedPinExecNode.IsValid())
|
|
{
|
|
if (bBranchedExecution)
|
|
{
|
|
PinExecNode->AddChildNode(LinkedPinExecNode);
|
|
}
|
|
else
|
|
{
|
|
PinExecNode->AddLinkedNode(PinScriptCodeOffset, LinkedPinExecNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintFunctionContext::GetTunnelBoundaryNode(const UEdGraphPin* TunnelPin)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> Result;
|
|
if (const UK2Node_Tunnel* TunnelNode = Cast<UK2Node_Tunnel>(TunnelPin->GetOwningNode()))
|
|
{
|
|
if (FBlueprintEditorUtils::IsTunnelInstanceNode(TunnelNode))
|
|
{
|
|
// Lookup external tunnel boundary.
|
|
const FName TunnelInstanceFunctionName = GetTunnelInstanceFunctionName(TunnelNode);
|
|
TSharedPtr<FBlueprintFunctionContext> TunnelContext = BlueprintContext.Pin()->GetFunctionContext(TunnelInstanceFunctionName);
|
|
if (TunnelContext.IsValid())
|
|
{
|
|
Result = TunnelContext->GetProfilerDataForNode(GetTunnelBoundaryName(TunnelPin));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Internal boundary lookup, faster path.
|
|
Result = GetProfilerDataForNode(GetPinName(TunnelPin));
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintFunctionContext::GetTunnelBoundaryNodeChecked(const UEdGraphPin* TunnelPin)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> Result = GetTunnelBoundaryNode(TunnelPin);
|
|
check(Result.IsValid());
|
|
return Result;
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintFunctionContext::MapTunnelBoundary(const UEdGraphPin* TunnelPin)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> TunnelBoundaryNode;
|
|
if (TunnelPin)
|
|
{
|
|
TunnelBoundaryNode = GetTunnelBoundaryNode(TunnelPin);
|
|
if (TunnelBoundaryNode.IsValid() && TunnelBoundaryNode->IsTunnelEntry())
|
|
{
|
|
TSharedPtr<FScriptExecutionTunnelEntry> TunnelEntryInstance = StaticCastSharedPtr<FScriptExecutionTunnelEntry>(TunnelBoundaryNode);
|
|
for (auto ExitSite : TunnelEntryInstance->GetLinkedNodes())
|
|
{
|
|
if (ExitSite.Value->IsTunnelExit())
|
|
{
|
|
TSharedPtr<FScriptExecutionTunnelExit> TunnelExit = TunnelEntryInstance->GetExitSite(ExitSite.Key);
|
|
const UEdGraphPin* TunnelInstanceExitPin = TunnelExit->GetExternalPin();
|
|
for (auto LinkedPin : TunnelInstanceExitPin->LinkedTo)
|
|
{
|
|
// Pass through non-relevant (e.g. reroute) nodes.
|
|
LinkedPin = FBlueprintEditorUtils::FindFirstCompilerRelevantLinkedPin(LinkedPin);
|
|
if (LinkedPin)
|
|
{
|
|
UK2Node* LinkedNode = Cast<UK2Node>(LinkedPin->GetOwningNode());
|
|
TSharedPtr<FScriptExecutionNode> LinkedExecNode;
|
|
// Need to be careful here because a tunnel instance exit site can link to a tunnel Boundary too.
|
|
if (LinkedNode->IsA<UK2Node_Tunnel>())
|
|
{
|
|
LinkedExecNode = MapTunnelBoundary(LinkedPin);
|
|
}
|
|
else
|
|
{
|
|
LinkedExecNode = GetProfilerDataForNode(LinkedNode->GetFName());
|
|
}
|
|
if (!LinkedExecNode.IsValid())
|
|
{
|
|
LinkedExecNode = MapNodeExecution(LinkedNode);
|
|
}
|
|
TunnelExit->AddLinkedNode(ExitSite.Key, LinkedExecNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return TunnelBoundaryNode;
|
|
}
|
|
|
|
FName FBlueprintFunctionContext::GetPinName(const UEdGraphPin* Pin)
|
|
{
|
|
FName PinName(NAME_None);
|
|
if (Pin)
|
|
{
|
|
FString PinString = Pin->PinName;
|
|
if (PinString.IsEmpty())
|
|
{
|
|
int32 PinTypeIndex = INDEX_NONE;
|
|
for (auto NodePin : Pin->GetOwningNode()->Pins)
|
|
{
|
|
if (NodePin->Direction == Pin->Direction && NodePin->PinType.PinCategory == Pin->PinType.PinCategory)
|
|
{
|
|
PinTypeIndex++;
|
|
}
|
|
}
|
|
PinString = PinTypeIndex > 0 ? FString::Printf(TEXT("%s%i"), *Pin->PinType.PinCategory, PinTypeIndex) : Pin->PinType.PinCategory;
|
|
}
|
|
UEdGraphNode* OwningNode = Pin->GetOwningNode();
|
|
if (OwningNode->IsA<UK2Node_Tunnel>())
|
|
{
|
|
// Tunnel pins have to be unique so we need the node name in addition to pin name.
|
|
UEdGraph* TunnelGraph = FBlueprintFunctionContext::GetGraphFromNode(OwningNode, false);
|
|
TunnelGraph = TunnelGraph ? TunnelGraph : OwningNode->GetTypedOuter<UEdGraph>();
|
|
FName TunnelName = TunnelGraph->GetFName();
|
|
PinString = FString::Printf(TEXT("%s_%s"), *TunnelName.ToString(), *PinString);
|
|
}
|
|
PinName = FName(*PinString);
|
|
}
|
|
check (PinName != NAME_None);
|
|
return PinName;
|
|
}
|
|
|
|
FName FBlueprintFunctionContext::GetUniquePinName(const UEdGraphPin* Pin)
|
|
{
|
|
FName PinName = GetPinName(Pin);
|
|
if (Pin)
|
|
{
|
|
PinName = FName(*FString::Printf(TEXT("%s_%s"), *Pin->GetOwningNode()->GetFName().ToString(), *PinName.ToString()));
|
|
}
|
|
check (PinName != NAME_None);
|
|
return PinName;
|
|
}
|
|
|
|
FName FBlueprintFunctionContext::GetTunnelBoundaryName(const UEdGraphPin* Pin)
|
|
{
|
|
FName PinName(NAME_None);
|
|
if (Pin)
|
|
{
|
|
UEdGraphNode* OwningNode = Pin->GetOwningNode();
|
|
const FString PinString = Pin->PinName.IsEmpty() ? Pin->PinType.PinCategory : Pin->PinName;
|
|
PinName = FName(*FString::Printf(TEXT("%s_%s"), *OwningNode->GetFName().ToString(), *PinString));
|
|
}
|
|
check (PinName != NAME_None);
|
|
return PinName;
|
|
}
|
|
|
|
UEdGraphPin* FBlueprintFunctionContext::FindMatchingPin(const UEdGraphNode* NodeToSearch, const UEdGraphPin* PinToFind, const bool bIgnoreDirection)
|
|
{
|
|
UEdGraphPin* MatchingPin = nullptr;
|
|
if (NodeToSearch && PinToFind)
|
|
{
|
|
// Quick simple lookup, works the bulk of the time.
|
|
MatchingPin = NodeToSearch->FindPin(PinToFind->PinName);
|
|
// More exhaustive search, for Boundary cases
|
|
if (!MatchingPin)
|
|
{
|
|
const FName ForcedPinName = GetPinName(PinToFind);
|
|
for (auto SearchPin : NodeToSearch->Pins)
|
|
{
|
|
if (PinToFind->PinName == SearchPin->PinName && PinToFind->PinType.PinCategory == SearchPin->PinType.PinCategory)
|
|
{
|
|
if (bIgnoreDirection || PinToFind->Direction == SearchPin->Direction)
|
|
{
|
|
MatchingPin = SearchPin;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return MatchingPin;
|
|
}
|
|
|
|
UEdGraph* FBlueprintFunctionContext::GetGraphFromNode(const UEdGraphNode* GraphNode, const bool bAllowNonTunnel)
|
|
{
|
|
UEdGraph* OwningGraph = nullptr;
|
|
if (GraphNode)
|
|
{
|
|
if (const UK2Node_MacroInstance* MacroInstance = Cast<UK2Node_MacroInstance>(GraphNode))
|
|
{
|
|
OwningGraph = MacroInstance->GetMacroGraph();
|
|
}
|
|
else if (const UK2Node_Composite* CompositeNode = Cast<UK2Node_Composite>(GraphNode))
|
|
{
|
|
OwningGraph = CompositeNode->BoundGraph;
|
|
}
|
|
else if (bAllowNonTunnel)
|
|
{
|
|
OwningGraph = GraphNode->GetTypedOuter<UEdGraph>();
|
|
}
|
|
}
|
|
return OwningGraph;
|
|
}
|
|
|
|
FName FBlueprintFunctionContext::GetGraphNameFromNode(const UEdGraphNode* GraphNode)
|
|
{
|
|
FName NodeGraphName = NAME_None;
|
|
UEdGraph* NodeGraph = GetGraphFromNode(GraphNode, true);
|
|
if (NodeGraph)
|
|
{
|
|
NodeGraphName = FBlueprintEditorUtils::IsEventGraph(NodeGraph) ? UEdGraphSchema_K2::GN_EventGraph : NodeGraph->GetFName();
|
|
}
|
|
check (NodeGraphName != NAME_None);
|
|
return NodeGraphName;
|
|
}
|
|
|
|
FName FBlueprintFunctionContext::GetTunnelInstanceFunctionName(const UEdGraphNode* GraphNode) const
|
|
{
|
|
FName ScopedFunctionName = NAME_None;
|
|
UEdGraph* OuterGraph = GraphNode ? GraphNode->GetTypedOuter<UEdGraph>() : nullptr;
|
|
if (OuterGraph)
|
|
{
|
|
UBlueprint* OwnerBlueprint = FBlueprintEditorUtils::FindBlueprintForGraph(OuterGraph);
|
|
if (OwnerBlueprint)
|
|
{
|
|
UBlueprintGeneratedClass* BPGC = Cast<UBlueprintGeneratedClass>(OwnerBlueprint->GeneratedClass);
|
|
if (BPGC)
|
|
{
|
|
// To identify instances exactly, we need the owning blueprint name, owning graph name, and the node name to avoid collisions.
|
|
ScopedFunctionName = FName(*FString::Printf(TEXT("%s::%s"), *FunctionName.ToString(), *GraphNode->GetName()));
|
|
}
|
|
}
|
|
}
|
|
check (ScopedFunctionName != NAME_None);
|
|
return ScopedFunctionName;
|
|
}
|
|
|
|
FName FBlueprintFunctionContext::GetScopedFunctionNameFromNode(const UEdGraphNode* GraphNode) const
|
|
{
|
|
FName ScopedFunctionName = NAME_None;
|
|
if (GraphNode)
|
|
{
|
|
if (const UEdGraphNode* TunnelNode = GetTunnelNodeFromGraphNode(GraphNode))
|
|
{
|
|
ScopedFunctionName = GetTunnelInstanceFunctionName(TunnelNode);
|
|
}
|
|
else if (const UEdGraph* Graph = GetGraphFromNode(GraphNode))
|
|
{
|
|
UBlueprint* OwnerBlueprint = Graph->GetTypedOuter<UBlueprint>();
|
|
UBlueprintGeneratedClass* BPGC = OwnerBlueprint ? Cast<UBlueprintGeneratedClass>(OwnerBlueprint->GeneratedClass) : nullptr;
|
|
if (BPGC)
|
|
{
|
|
const bool bIsEventGraph = BPGC->UberGraphFunction && FBlueprintEditorUtils::IsEventGraph(Graph);
|
|
FName NodeGraphName = bIsEventGraph ? BPGC->UberGraphFunction->GetFName() : Graph->GetFName();
|
|
ScopedFunctionName = FName(*FString::Printf(TEXT("%s::%s"), *BPGC->GetName(), *NodeGraphName.ToString()));
|
|
}
|
|
}
|
|
}
|
|
check (ScopedFunctionName != NAME_None);
|
|
return ScopedFunctionName;
|
|
}
|
|
|
|
void FBlueprintFunctionContext::MapTunnelExits(TSharedPtr<FScriptExecutionTunnelEntry> TunnelEntryPoint)
|
|
{
|
|
if (TunnelEntryPoint.IsValid())
|
|
{
|
|
for (auto ExitSite : TunnelEntryPoint->GetLinkedNodes())
|
|
{
|
|
if (const UEdGraphPin* ExitPin = ExitSite.Value->GetObservedPin())
|
|
{
|
|
for (UEdGraphPin* LinkedPin : ExitPin->LinkedTo)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> LinkedExecNode = MapNodeExecution(LinkedPin->GetOwningNode());
|
|
ExitSite.Value->AddChildNode(LinkedExecNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintFunctionContext::HasProfilerDataForNode(const FName NodeName) const
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> Result = GetProfilerDataForNode(NodeName);
|
|
return Result.IsValid();
|
|
}
|
|
|
|
bool FBlueprintFunctionContext::HasProfilerDataForGraphNode(const UEdGraphNode* Node) const
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> Result = GetProfilerDataForGraphNode(Node);
|
|
return Result.IsValid();
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintFunctionContext::GetProfilerDataForNode(const FName NodeName) const
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> Result;
|
|
if (const TSharedPtr<FScriptExecutionNode>* SearchResult = ExecutionNodes.Find(NodeName))
|
|
{
|
|
Result = *SearchResult;
|
|
}
|
|
else
|
|
{
|
|
// Check child function contexts
|
|
for (auto ChildFunctionContext : ChildFunctionContexts)
|
|
{
|
|
if (ChildFunctionContext.Value.IsValid())
|
|
{
|
|
Result = ChildFunctionContext.Value.Pin()->GetProfilerDataForNode(NodeName);
|
|
if (Result.IsValid())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintFunctionContext::GetProfilerDataForNodeChecked(const FName NodeName) const
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> Result = GetProfilerDataForNode(NodeName);
|
|
check (Result.IsValid());
|
|
return Result;
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintFunctionContext::GetProfilerDataForGraphNode(const UEdGraphNode* Node) const
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> Result;
|
|
if (Node)
|
|
{
|
|
// Do a local lookup, this could be a tunnel/macro node and the graph wouldn't match.
|
|
if (const TSharedPtr<FScriptExecutionNode>* SearchResult = ExecutionNodes.Find(Node->GetFName()))
|
|
{
|
|
Result = *SearchResult;
|
|
}
|
|
// Check objects match
|
|
if (Result.IsValid())
|
|
{
|
|
if (Node != Result->GetObservedObject())
|
|
{
|
|
// If the observed object doesn't match we need to look elsewhere.
|
|
Result.Reset();
|
|
}
|
|
}
|
|
// Perform a more exhaustive search.
|
|
if (!Result.IsValid())
|
|
{
|
|
const FName ObjectGraphName = GetGraphNameFromNode(Node);
|
|
if (ObjectGraphName != GraphName)
|
|
{
|
|
TSharedPtr<FBlueprintFunctionContext> OwningFunction = BlueprintContext.Pin()->GetFunctionContext(GetScopedFunctionNameFromNode(Node));
|
|
if (OwningFunction.IsValid())
|
|
{
|
|
Result = OwningFunction->GetProfilerDataForGraphNode(Node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
template<typename ExecNodeType> TSharedPtr<ExecNodeType> FBlueprintFunctionContext::GetTypedProfilerDataForNode(const FName NodeName)
|
|
{
|
|
TSharedPtr<ExecNodeType> Result;
|
|
TSharedPtr<FScriptExecutionNode> SearchResult = GetProfilerDataForNode(NodeName);
|
|
if (SearchResult.IsValid())
|
|
{
|
|
Result = StaticCastSharedPtr<ExecNodeType>(SearchResult);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
template<typename ExecNodeType> TSharedPtr<ExecNodeType> FBlueprintFunctionContext::GetTypedProfilerDataForGraphNode(const UEdGraphNode* Node)
|
|
{
|
|
TSharedPtr<ExecNodeType> Result;
|
|
TSharedPtr<FScriptExecutionNode> SearchResult = GetProfilerDataForGraphNode(Node);
|
|
if (SearchResult.IsValid())
|
|
{
|
|
Result = StaticCastSharedPtr<ExecNodeType>(SearchResult);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
bool FBlueprintFunctionContext::GetProfilerContextFromScriptOffset(const int32 ScriptOffset, FNodeExecutionContext& ExecContextOut)
|
|
{
|
|
// Locate the correct context
|
|
if (const UEdGraphNode* GraphNode = GetNodeFromCodeLocation(ScriptOffset))
|
|
{
|
|
// get the fully scoped function name
|
|
const FName FunctionContextName = GetScopedFunctionNameFromNode(GraphNode);
|
|
if (FunctionContextName == FunctionName)
|
|
{
|
|
ExecContextOut.ProfilerNode = GetProfilerDataForNode(GraphNode->GetFName());
|
|
ExecContextOut.ProfilerContext = AsShared();
|
|
}
|
|
else
|
|
{
|
|
TSharedPtr<FBlueprintFunctionContext> OwningFunction = BlueprintContext.Pin()->GetFunctionContext(FunctionContextName);
|
|
if (OwningFunction.IsValid())
|
|
{
|
|
OwningFunction->GetProfilerContextFromScriptOffset(ScriptOffset, ExecContextOut);
|
|
}
|
|
}
|
|
}
|
|
return ExecContextOut.IsValid();
|
|
}
|
|
|
|
bool FBlueprintFunctionContext::IsUbergraphFunction() const
|
|
{
|
|
bool bResult = false;
|
|
if (UBlueprintGeneratedClass* BPGC = Cast<UBlueprintGeneratedClass>(BlueprintClass.Get()))
|
|
{
|
|
bResult = Function.Get() == BPGC->UberGraphFunction;
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
const UEdGraphNode* FBlueprintFunctionContext::GetNodeFromCodeLocation(const int32 ScriptOffset)
|
|
{
|
|
TWeakObjectPtr<const UEdGraphNode>& Result = ScriptOffsetToNodes.FindOrAdd(ScriptOffset);
|
|
if (!Result.IsValid() && BlueprintClass.IsValid())
|
|
{
|
|
// First pass lookup, if inside a tunnel this will return the tunnel instance node.
|
|
Result = BlueprintClass->GetDebugData().FindSourceNodeFromCodeLocation(Function.Get(), ScriptOffset, true);
|
|
if (const UEdGraphNode* PotentialTunnelNode = Result.Get())
|
|
{
|
|
// Check for tunnel nodes
|
|
if (FBlueprintEditorUtils::IsTunnelInstanceNode(PotentialTunnelNode))
|
|
{
|
|
// Find the true source node.
|
|
if (const UEdGraphNode* TrueGraphNode = BlueprintClass->GetDebugData().FindMacroSourceNodeFromCodeLocation(Function.Get(), ScriptOffset))
|
|
{
|
|
// Cache the tunnel node.
|
|
if (TrueGraphNode != PotentialTunnelNode || FBlueprintEditorUtils::IsTunnelInstanceNode(TrueGraphNode))
|
|
{
|
|
NodeToTunnelNode.Add(TrueGraphNode) = PotentialTunnelNode;
|
|
Result = TrueGraphNode;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Result.Get();
|
|
}
|
|
|
|
const UEdGraphNode* FBlueprintFunctionContext::GetTunnelNodeFromCodeLocation(const int32 ScriptOffset)
|
|
{
|
|
TWeakObjectPtr<const UEdGraphNode> Result;
|
|
if (TWeakObjectPtr<const UEdGraphNode>* NodeSearch = ScriptOffsetToNodes.Find(ScriptOffset))
|
|
{
|
|
if (TWeakObjectPtr<const UEdGraphNode>* TunnelNodeSearch = NodeToTunnelNode.Find(*NodeSearch))
|
|
{
|
|
Result = *TunnelNodeSearch;
|
|
}
|
|
}
|
|
return Result.Get();
|
|
}
|
|
|
|
const UEdGraphNode* FBlueprintFunctionContext::GetTunnelNodeFromGraphNode(const UEdGraphNode* GraphNode) const
|
|
{
|
|
TWeakObjectPtr<const UEdGraphNode> Result;
|
|
if (const TWeakObjectPtr<const UEdGraphNode>* SearchResult = NodeToTunnelNode.Find(GraphNode))
|
|
{
|
|
Result = *SearchResult;
|
|
}
|
|
return Result.Get();
|
|
}
|
|
|
|
bool FBlueprintFunctionContext::IsNodeFromTunnelGraph(const UEdGraphNode* Node) const
|
|
{
|
|
return NodeToTunnelNode.Contains(Node);
|
|
}
|
|
|
|
const UEdGraphPin* FBlueprintFunctionContext::GetPinFromCodeLocation(const int32 ScriptOffset)
|
|
{
|
|
FEdGraphPinReference& Result = ScriptOffsetToPins.FindOrAdd(ScriptOffset);
|
|
if (Result.Get() == nullptr && BlueprintClass.IsValid())
|
|
{
|
|
if (const UEdGraphPin* GraphPin = BlueprintClass->GetDebugData().FindSourcePinFromCodeLocation(Function.Get(), ScriptOffset))
|
|
{
|
|
Result = GraphPin;
|
|
}
|
|
}
|
|
return Result.Get();
|
|
}
|
|
|
|
const int32 FBlueprintFunctionContext::GetCodeLocationFromPin(const UEdGraphPin* Pin)
|
|
{
|
|
if (BlueprintClass.IsValid())
|
|
{
|
|
return BlueprintClass.Get()->GetDebugData().FindCodeLocationFromSourcePin(Pin, Function.Get());
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
void FBlueprintFunctionContext::GetAllCodeLocationsFromPin(const UEdGraphPin* Pin, TArray<int32>& OutCodeLocations) const
|
|
{
|
|
if (BlueprintClass.IsValid())
|
|
{
|
|
BlueprintClass.Get()->GetDebugData().FindAllCodeLocationsFromSourcePin(Pin, Function.Get(), OutCodeLocations);
|
|
}
|
|
}
|
|
|
|
void FBlueprintFunctionContext::FindAllTunnelsAtCodeLocation(const int32 CodeLocation, TArray<UEdGraphNode*>& MacrosAtLocation) const
|
|
{
|
|
if (BlueprintClass.IsValid() && Function.IsValid())
|
|
{
|
|
BlueprintClass.Get()->GetDebugData().FindMacroInstanceNodesFromCodeLocation(Function.Get(), CodeLocation, MacrosAtLocation);
|
|
}
|
|
}
|
|
|
|
FInt32Range FBlueprintFunctionContext::GetPureNodeScriptCodeRange(const UEdGraphNode* Node) const
|
|
{
|
|
if (BlueprintClass.IsValid())
|
|
{
|
|
return BlueprintClass->GetDebugData().FindPureNodeScriptCodeRangeFromSourceNode(Node, Function.Get());
|
|
}
|
|
|
|
return FInt32Range(INDEX_NONE);
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintFunctionContext::CreateExecutionNode(FScriptExecNodeParams& InitParams)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode>& ScriptExecNode = ExecutionNodes.FindOrAdd(InitParams.NodeName);
|
|
check(!ScriptExecNode.IsValid());
|
|
UEdGraph* OuterGraph = InitParams.ObservedObject.IsValid() ? InitParams.ObservedObject.Get()->GetTypedOuter<UEdGraph>() : nullptr;
|
|
InitParams.OwningGraphName = OuterGraph ? OuterGraph->GetFName() : NAME_None;
|
|
ScriptExecNode = MakeShareable(new FScriptExecutionNode(InitParams));
|
|
return ScriptExecNode;
|
|
}
|
|
|
|
template<typename ExecNodeType> TSharedPtr<ExecNodeType> FBlueprintFunctionContext::CreateTypedExecutionNode(FScriptExecNodeParams& InitParams)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode>& ScriptExecNode = ExecutionNodes.FindOrAdd(InitParams.NodeName);
|
|
check(!ScriptExecNode.IsValid());
|
|
UEdGraph* OuterGraph = InitParams.ObservedObject.IsValid() ? InitParams.ObservedObject.Get()->GetTypedOuter<UEdGraph>() : nullptr;
|
|
InitParams.OwningGraphName = OuterGraph ? OuterGraph->GetFName() : NAME_None;
|
|
TSharedPtr<ExecNodeType> NewTypedNode = MakeShareable(new ExecNodeType(InitParams));
|
|
ScriptExecNode = NewTypedNode;
|
|
return NewTypedNode;
|
|
}
|
|
|
|
void FBlueprintFunctionContext::MapTunnelInstance(UK2Node_Tunnel* TunnelInstance)
|
|
{
|
|
// Grab tunnel context from instance name
|
|
if (TunnelInstance)
|
|
{
|
|
const FName ScopedFunctionName = GetTunnelInstanceFunctionName(TunnelInstance);
|
|
TSharedPtr<FBlueprintFunctionContext> TunnelFunctionContext = BlueprintContext.Pin()->GetFunctionContext(ScopedFunctionName);
|
|
if (TunnelFunctionContext.IsValid())
|
|
{
|
|
AddChildFunctionContext(ScopedFunctionName, TunnelFunctionContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintFunctionContext::AddEntryPoint(TSharedPtr<FScriptExecutionNode> EntryPoint)
|
|
{
|
|
EntryPoints.Add(EntryPoint);
|
|
}
|
|
|
|
void FBlueprintFunctionContext::AddExitPoint(TSharedPtr<FScriptExecutionNode> ExitPoint)
|
|
{
|
|
ExitPoints.Add(ExitPoint);
|
|
}
|
|
|
|
FName FBlueprintFunctionContext::GetScopedEventName(const FName EventName) const
|
|
{
|
|
return FName(*FString::Printf(TEXT("%s::%s"), *BlueprintClass.Get()->GetName(), *EventName.ToString()));
|
|
}
|
|
|
|
void FBlueprintFunctionContext::AddCallSiteEntryPointsToNode(TSharedPtr<FScriptExecutionNode> CallingNode) const
|
|
{
|
|
if (CallingNode.IsValid())
|
|
{
|
|
for (auto EntryPoint : EntryPoints)
|
|
{
|
|
for (auto EntryPointChild : EntryPoint->GetChildNodes())
|
|
{
|
|
CallingNode->AddChildNode(EntryPointChild);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FNodeExecutionContext& FBlueprintFunctionContext::GetNodeExecutionContext(const int32 ScriptOffset)
|
|
{
|
|
FNodeExecutionContext& Context = ScriptOffsetToNodeContext.FindOrAdd(ScriptOffset);
|
|
if (!Context.IsValid())
|
|
{
|
|
const UEdGraphNode* GraphNode = GetNodeFromCodeLocation(ScriptOffset);
|
|
TSharedPtr<FBlueprintFunctionContext> SearchContext = AsShared();
|
|
TArray<FName> ScopedContextNames;
|
|
GraphNodeToContextName.MultiFind(GraphNode, ScopedContextNames);
|
|
if (ScopedContextNames.Num() > 0)
|
|
{
|
|
for (const FName ScopedContextName : ScopedContextNames)
|
|
{
|
|
SearchContext = BlueprintContext.Pin()->GetFunctionContext(ScopedContextName);
|
|
if (SearchContext.IsValid())
|
|
{
|
|
if (SearchContext->GetProfilerContextFromScriptOffset(ScriptOffset, Context))
|
|
{
|
|
Context.GraphNode = GraphNode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (SearchContext->GetProfilerContextFromScriptOffset(ScriptOffset, Context))
|
|
{
|
|
Context.GraphNode = GraphNode;
|
|
}
|
|
}
|
|
}
|
|
return Context;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// FBlueprintTunnelInstanceContext
|
|
|
|
void FBlueprintTunnelInstanceContext::InitialiseContextFromGraph(TSharedPtr<FBlueprintExecutionContext> BlueprintContextIn, const FName TunnelInstanceName, UEdGraph* TunnelGraph)
|
|
{
|
|
GraphName = TunnelGraph->GetFName();
|
|
FunctionName = TunnelInstanceName;
|
|
BlueprintContext = BlueprintContextIn;
|
|
}
|
|
|
|
void FBlueprintTunnelInstanceContext::SetParentContext(TSharedPtr<FBlueprintFunctionContext> ParentContextIn)
|
|
{
|
|
ParentTunnel = StaticCastSharedPtr<FBlueprintTunnelInstanceContext>(ParentContextIn);
|
|
}
|
|
|
|
void FBlueprintTunnelInstanceContext::MapTunnelContext(TSharedPtr<FBlueprintFunctionContext> RootFunctionContextIn, TSharedPtr<FBlueprintFunctionContext> CallingFunctionContext, TArray<UK2Node_Tunnel*> TunnelInstances)
|
|
{
|
|
if (!TunnelInstanceNode.IsValid())
|
|
{
|
|
// Set the mapping context.
|
|
RootFunctionContext = RootFunctionContextIn;
|
|
Function = CallingFunctionContext->GetUFunction();
|
|
BlueprintClass = CallingFunctionContext->GetBlueprintClass();
|
|
check (TunnelInstances.Num() > 0);
|
|
NestedTunnelInstanceStack = TunnelInstances;
|
|
UK2Node_Tunnel* TunnelInstance = TunnelInstances.Last();
|
|
TunnelInstanceNode = TunnelInstance;
|
|
// Find the function context that represents the graph and not the instance
|
|
UEdGraph* TunnelGraph = GetGraphFromNode(TunnelInstance, false);
|
|
// Map tunnel Input/Output, creating stubbed pure pin chain sites we can link up in nested tunnels.
|
|
TMap<UEdGraphPin*, UEdGraphPin*> PurePins;
|
|
MapTunnelIO(PurePins);
|
|
// Create tunnel instance node.
|
|
FScriptExecNodeParams TunnelInstanceParams;
|
|
TunnelInstanceParams.SampleFrequency = 1;
|
|
TunnelInstanceParams.NodeName = TunnelInstance->GetFName();
|
|
TunnelInstanceParams.ObservedObject = TunnelInstance;
|
|
TunnelInstanceParams.DisplayName = TunnelInstance->GetNodeTitle(ENodeTitleType::MenuTitle);
|
|
TunnelInstanceParams.Tooltip = LOCTEXT("NavigateToNodeLocationHyperlink_ToolTip", "Navigate to the Node");
|
|
TunnelInstanceParams.NodeFlags = EScriptExecutionNodeFlags::TunnelInstance;
|
|
TunnelInstanceParams.IconColor = FLinearColor(1.f, 1.f, 1.f, 0.8f);
|
|
if (TunnelInstance->IsA<UK2Node_Composite>())
|
|
{
|
|
const FSlateBrush* TunnelIcon = FEditorStyle::GetBrush(TEXT("GraphEditor.SubGraph_16x"));
|
|
TunnelInstanceParams.Icon = const_cast<FSlateBrush*>(TunnelIcon);
|
|
}
|
|
else
|
|
{
|
|
const FSlateBrush* TunnelIcon = TunnelInstance->ShowPaletteIconOnNode() ? TunnelInstance->GetIconAndTint(TunnelInstanceParams.IconColor).GetOptionalIcon() :
|
|
FEditorStyle::GetBrush(TEXT("GraphEditor.SubGraph_16x"));
|
|
TunnelInstanceParams.Icon = const_cast<FSlateBrush*>(TunnelIcon);
|
|
}
|
|
TSharedPtr<FScriptExecutionTunnelInstance> TunnelInstanceExecNode = CreateTypedExecutionNode<FScriptExecutionTunnelInstance>(TunnelInstanceParams);
|
|
// Map child tunnel instances now, because we require that the instance exec node is setup.
|
|
TMap<UK2Node_Tunnel*, TSharedPtr<FBlueprintFunctionContext>> ChildTunnels;
|
|
DiscoverTunnels(TunnelGraph, ChildTunnels);
|
|
|
|
for (auto NestedTunnelInstance : ChildTunnels)
|
|
{
|
|
if (UEdGraph* NestedTunnelGraph = FBlueprintFunctionContext::GetGraphFromNode(NestedTunnelInstance.Key, false))
|
|
{
|
|
// Map nested tunnel instances.
|
|
const FName ScopedFunctionName = FBlueprintFunctionContext::GetTunnelInstanceFunctionName(NestedTunnelInstance.Key);
|
|
TSharedPtr<FBlueprintTunnelInstanceContext> NestedContext = MakeShareable(new FBlueprintTunnelInstanceContext);
|
|
NestedContext->InitialiseContextFromGraph(BlueprintContext.Pin(), ScopedFunctionName, NestedTunnelGraph);
|
|
NestedContext->SetParentContext(AsShared());
|
|
TunnelInstances.Push(NestedTunnelInstance.Key);
|
|
NestedContext->MapTunnelContext(RootFunctionContext.Pin(), NestedTunnelInstance.Value, TunnelInstances);
|
|
TunnelInstances.Pop();
|
|
AddChildFunctionContext(NestedTunnelInstance.Key->GetFName(), NestedContext);
|
|
BlueprintContext.Pin()->AddFunctionContext(ScopedFunctionName, NestedContext);
|
|
}
|
|
}
|
|
// Find tunnel instance entry sites
|
|
TMap<FName, UEdGraphPin*> InstanceEntrySites;
|
|
for (auto Pin : TunnelInstance->Pins)
|
|
{
|
|
if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)
|
|
{
|
|
if (Pin->LinkedTo.Num())
|
|
{
|
|
const FName EntryPointName = GetPinName(Pin);
|
|
if (Pin->Direction == EGPD_Input)
|
|
{
|
|
InstanceEntrySites.Add(EntryPointName) = Pin;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Map tunnel entry site to exit sites
|
|
for (auto InstanceEntryPoint : InstanceEntrySites)
|
|
{
|
|
const int32 EntryPointScriptOffset = GetCodeLocationFromPin(InstanceEntryPoint.Value);
|
|
TSharedPtr<FScriptExecutionNode> EntryPoint = GetProfilerDataForNode(InstanceEntryPoint.Key);
|
|
if (EntryPoint.IsValid() && EntryPointScriptOffset != INDEX_NONE)
|
|
{
|
|
// Create custom exec node for each tunnel instance entry, this is so we can customise the appearance of the node.
|
|
FName BoundaryName = GetTunnelBoundaryName(InstanceEntryPoint.Value);
|
|
FScriptExecNodeParams TunnelEntryParams;
|
|
TunnelEntryParams.SampleFrequency = 1;
|
|
TunnelEntryParams.NodeName = BoundaryName;
|
|
TunnelEntryParams.ObservedObject = TunnelInstance;
|
|
TunnelEntryParams.ObservedPin = InstanceEntryPoint.Value;
|
|
TunnelEntryParams.DisplayName = TunnelInstance->GetNodeTitle(ENodeTitleType::MenuTitle);
|
|
TunnelEntryParams.Tooltip = LOCTEXT("NavigateToTunnelInstanceHyperlink_ToolTip", "Navigate to the Tunnel Instance");
|
|
TunnelEntryParams.NodeFlags = EScriptExecutionNodeFlags::TunnelEntryPinInstance;
|
|
TunnelEntryParams.IconColor = FLinearColor(1.f, 1.f, 1.f, 0.8f);
|
|
TunnelEntryParams.Icon = TunnelInstanceParams.Icon;
|
|
TSharedPtr<FScriptExecutionTunnelEntry> InstanceTunnelEntry = CreateTypedExecutionNode<FScriptExecutionTunnelEntry>(TunnelEntryParams);
|
|
// Add internal entrypoint as child.
|
|
InstanceTunnelEntry->AddChildNode(EntryPoint);
|
|
// Register the external entry point
|
|
TunnelInstanceExecNode->AddEntrySite(EntryPointScriptOffset, InstanceTunnelEntry);
|
|
// Set the current entry point we are mapping.
|
|
StagingEntryPoint = InstanceTunnelEntry;
|
|
// Map this tunnel entry
|
|
if (const UEdGraphPin* InternalEntryPointPin = EntryPoint->GetObservedPin())
|
|
{
|
|
for (auto LinkedPin : InternalEntryPointPin->LinkedTo)
|
|
{
|
|
// Pass through any non-relevant (e.g. reroute) nodes.
|
|
LinkedPin = FBlueprintEditorUtils::FindFirstCompilerRelevantLinkedPin(LinkedPin);
|
|
if (LinkedPin)
|
|
{
|
|
// Grab the owning node
|
|
UEdGraphNode* LinkedNode = LinkedPin->GetOwningNode();
|
|
// Check for linkage to another tunnel, this could be another tunnel or an exit site for this one.
|
|
bool bLinkedToTunnel = false;
|
|
bool bInternalTunnel = false;
|
|
if (const UK2Node_Tunnel* LinkedTunnelNode = Cast<UK2Node_Tunnel>(LinkedNode))
|
|
{
|
|
// Marked as linked to another tunnel.
|
|
bLinkedToTunnel = true;
|
|
// Check if the tunnel is a tunnel instance node or not.
|
|
bInternalTunnel = !FBlueprintEditorUtils::IsTunnelInstanceNode(LinkedTunnelNode);
|
|
}
|
|
// Map the linked pin/node whilst being aware of directly linked tunnel boundaries.
|
|
TSharedPtr<FScriptExecutionNode> ExecNode = bLinkedToTunnel ? MapTunnelBoundary(LinkedPin) : MapNodeExecution(LinkedNode);
|
|
// Only add the new node if it is not an internal tunnel.
|
|
if (!bInternalTunnel && ExecNode.IsValid())
|
|
{
|
|
EntryPoint->AddChildNode(ExecNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Add exit sites to instance
|
|
for (auto ExitSite : InstanceTunnelEntry->GetLinkedNodes())
|
|
{
|
|
if (ExitSite.Value->IsTunnelExit())
|
|
{
|
|
TunnelInstanceExecNode->AddExitSite(ExitSite.Key, StaticCastSharedPtr<FScriptExecutionTunnelExit>(ExitSite.Value));
|
|
}
|
|
}
|
|
// Reset staged entry point
|
|
StagingEntryPoint.Reset();
|
|
}
|
|
}
|
|
// Map pure node chains
|
|
for (auto PurePinSet : PurePins)
|
|
{
|
|
// Use the external pin if tunnel entry otherwise use the internal pin.
|
|
UEdGraphPin* PinToTrace = PurePinSet.Key->Direction == EGPD_Output ? PurePinSet.Value : PurePinSet.Key;
|
|
// Grab the pure chain we created earlier
|
|
TSharedPtr<FScriptExecutionNode> PureChainRootNode = BlueprintContext.Pin()->FindPurePinNode(PinToTrace);
|
|
TArray<UEdGraphPin*> InputPins;
|
|
InputPins.Add(PinToTrace);
|
|
MapInputPins(PureChainRootNode, InputPins);
|
|
}
|
|
// Map any composite events
|
|
if (TunnelInstance && TunnelInstanceNode->IsA<UK2Node_Composite>())
|
|
{
|
|
MapCompositeEvents(TunnelGraph);
|
|
}
|
|
// Detect any cyclic links
|
|
TArray<TSharedPtr<FScriptExecutionNode>> Filter;
|
|
for (auto EntryPoint : EntryPoints)
|
|
{
|
|
DetectCyclicLinks(EntryPoint, Filter);
|
|
}
|
|
// Register graph nodes to context and discover valid script offsets
|
|
TSet<const UEdGraphNode*> ProcessedNodes;
|
|
for (auto ExecNode : ExecutionNodes)
|
|
{
|
|
if (UEdGraphNode* GraphNode = const_cast<UEdGraphNode*>(ExecNode.Value->GetTypedObservedObject<UEdGraphNode>()))
|
|
{
|
|
if (!ProcessedNodes.Contains(GraphNode))
|
|
{
|
|
ProcessedNodes.Add(GraphNode);
|
|
// Register valid code locations.
|
|
TArray<int32> CodeLocations;
|
|
BlueprintClass.Get()->GetDebugData().FindAllCodeLocationsFromSourceNode(GraphNode, Function.Get(), CodeLocations);
|
|
for (const int32 CodeLocation : CodeLocations)
|
|
{
|
|
if (!ScriptOffsetToPins.Contains(CodeLocation))
|
|
{
|
|
if (IsCodeLocationValidForThisTunnel(CodeLocation))
|
|
{
|
|
ScriptOffsetToPins.Add(CodeLocation);
|
|
}
|
|
}
|
|
}
|
|
RootFunctionContext.Pin()->AddGraphNodeContextPath(GraphNode, FunctionName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintTunnelInstanceContext::MapCompositeEvents(UEdGraph* CompositeGraph)
|
|
{
|
|
// Map events
|
|
TArray<UK2Node_Event*> CompositeEventNodes;
|
|
CompositeGraph->GetNodesOfClass<UK2Node_Event>(CompositeEventNodes);
|
|
for (auto EventNode : CompositeEventNodes)
|
|
{
|
|
if (EventNode->IsNodeEnabled())
|
|
{
|
|
const FName EventName = GetScopedEventName(EventNode->GetFunctionName());
|
|
FScriptExecNodeParams EventParams;
|
|
EventParams.SampleFrequency = 1;
|
|
EventParams.NodeName = EventName;
|
|
EventParams.ObservedObject = EventNode;
|
|
if (bIsInheritedContext)
|
|
{
|
|
EventParams.Tooltip = LOCTEXT("NavigateToInheritedEventLocationHyperlink_ToolTip", "Navigate to the Inherited Event");
|
|
EventParams.IconColor = GetDefault<UGraphEditorSettings>()->ParentFunctionCallNodeTitleColor;
|
|
EventParams.NodeFlags = EScriptExecutionNodeFlags::Event|EScriptExecutionNodeFlags::RuntimeEvent|EScriptExecutionNodeFlags::InheritedEvent;
|
|
EventParams.DisplayName = FText::FromString(FString::Printf(TEXT("%s (%s)"), *EventNode->GetNodeTitle(ENodeTitleType::MenuTitle).ToString(), *BlueprintContext.Pin()->GetBlueprint()->GetName()));
|
|
}
|
|
else
|
|
{
|
|
EventParams.Tooltip = LOCTEXT("NavigateToEventLocationHyperlink_ToolTip", "Navigate to the Event");
|
|
EventParams.IconColor = GetDefault<UGraphEditorSettings>()->EventNodeTitleColor;
|
|
EventParams.NodeFlags = EScriptExecutionNodeFlags::Event|EScriptExecutionNodeFlags::RuntimeEvent;
|
|
EventParams.DisplayName = EventNode->GetNodeTitle(ENodeTitleType::MenuTitle);
|
|
}
|
|
GetNodeCustomizations(EventParams);
|
|
const FSlateBrush* EventIcon = EventNode->ShowPaletteIconOnNode() ? EventNode->GetIconAndTint(EventParams.IconColor).GetOptionalIcon() :
|
|
FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPNode"));
|
|
EventParams.Icon = const_cast<FSlateBrush*>(EventIcon);
|
|
TSharedPtr<FScriptExecutionNode> EventExecNode = CreateExecutionNode(EventParams);
|
|
AddEntryPoint(EventExecNode);
|
|
BlueprintContext.Pin()->AddEventNode(AsShared(), EventExecNode);
|
|
// Map the event
|
|
if (EventExecNode.IsValid())
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> EventEntryPoint = MapNodeExecution(EventNode);
|
|
EventExecNode->AddChildNode(EventEntryPoint);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintTunnelInstanceContext::MapTunnelIO(TMap<UEdGraphPin*, UEdGraphPin*>& PurePins)
|
|
{
|
|
if (UK2Node_Tunnel* TunnelInstanceGraphNode = TunnelInstanceNode.Get())
|
|
{
|
|
// Map internal tunnel pins to tunnel instance pins
|
|
TArray<UK2Node_Tunnel*> GraphTunnels;
|
|
UEdGraph* TunnelGraph = GetGraphFromNode(TunnelInstanceGraphNode, false);
|
|
TunnelGraph->GetNodesOfClass<UK2Node_Tunnel>(GraphTunnels);
|
|
// Build tunnel pin sets, retaining the pure pins for later mapping.
|
|
TMap<UEdGraphPin*, UEdGraphPin*> ExecPins;
|
|
for (auto Tunnel : GraphTunnels)
|
|
{
|
|
if (IsTunnelNodeInternal(Tunnel))
|
|
{
|
|
for (UEdGraphPin* InternalPin : Tunnel->Pins)
|
|
{
|
|
UEdGraphPin* TunnelInstancePin = FindMatchingPin(TunnelInstanceGraphNode, InternalPin);
|
|
if (InternalPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)
|
|
{
|
|
if (InternalPin->LinkedTo.Num())
|
|
{
|
|
ExecPins.Add(InternalPin) = TunnelInstancePin;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PurePins.Add(InternalPin) = TunnelInstancePin;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Create exec entry/exit sites
|
|
for (auto ExecPinSet : ExecPins)
|
|
{
|
|
// Create entry site
|
|
FScriptExecNodeParams LinkParams;
|
|
LinkParams.SampleFrequency = 1;
|
|
LinkParams.NodeName = GetPinName(ExecPinSet.Key);
|
|
LinkParams.ObservedObject = ExecPinSet.Key->GetOwningNode();
|
|
LinkParams.ObservedPin = ExecPinSet.Key;
|
|
LinkParams.DisplayName = ExecPinSet.Key->GetDisplayName();
|
|
LinkParams.IconColor = FLinearColor(1.f, 1.f, 1.f, 0.8f);
|
|
const bool bPinLinked = ExecPinSet.Key->LinkedTo.Num() > 0;
|
|
const FSlateBrush* Icon = bPinLinked ? FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPPinConnected")) :
|
|
FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPPinDisconnected"));
|
|
LinkParams.Icon = const_cast<FSlateBrush*>(Icon);
|
|
|
|
if (ExecPinSet.Key->Direction == EGPD_Output)
|
|
{
|
|
LinkParams.Tooltip = LOCTEXT("ExecPin_ExpandTunnelEntryPoint_ToolTip", "Expand tunnel entry point");
|
|
LinkParams.NodeFlags = EScriptExecutionNodeFlags::TunnelEntryPin|EScriptExecutionNodeFlags::InvalidTrace;
|
|
TSharedPtr<FScriptExecutionNode> EntryPoint = CreateExecutionNode(LinkParams);
|
|
AddEntryPoint(EntryPoint);
|
|
}
|
|
else
|
|
{
|
|
LinkParams.Tooltip = LOCTEXT("ExecPin_ExpandTunnelExitPoint_ToolTip", "Expand tunnel exit point");
|
|
LinkParams.NodeFlags = EScriptExecutionNodeFlags::TunnelExitPin;
|
|
TSharedPtr<FScriptExecutionTunnelExit> ExitPoint = CreateTypedExecutionNode<FScriptExecutionTunnelExit>(LinkParams);
|
|
// Add exit point under internal pin name for mapping.
|
|
ExitPoint->SetExternalPin(ExecPinSet.Value);
|
|
AddExitPoint(ExitPoint);
|
|
}
|
|
}
|
|
// Create pure chain sites, but don't map linkage yet because this causes problems in nested tunnels.
|
|
for (auto PurePinSet : PurePins)
|
|
{
|
|
// Use the external pin if tunnel entry otherwise use the internal pin.
|
|
const UEdGraphPin* PinToTrace = PurePinSet.Key->Direction == EGPD_Output ? PurePinSet.Value : PurePinSet.Key;
|
|
// Create a pure node chain for the pin
|
|
FScriptExecNodeParams PureChainParams;
|
|
static const FString PureChainNodeNameSuffix = TEXT("__PROFILER_InputPureTime");
|
|
FString PureChainNodeNameString = PinToTrace->GetName() + PureChainNodeNameSuffix;
|
|
PureChainParams.SampleFrequency = 1;
|
|
PureChainParams.NodeName = FName(*PureChainNodeNameString);
|
|
PureChainParams.ObservedPin = PinToTrace;
|
|
PureChainParams.DisplayName = LOCTEXT("PureChain_DisplayName", "Pure Time");
|
|
PureChainParams.Tooltip = LOCTEXT("PureChain_ToolTip", "Expand pure node timing");
|
|
PureChainParams.NodeFlags = EScriptExecutionNodeFlags::PureChain|EScriptExecutionNodeFlags::InvalidTrace;
|
|
GetNodeCustomizations(PureChainParams);
|
|
const FSlateBrush* Icon = FEditorStyle::GetBrush(TEXT("BlueprintProfiler.PureNode"));
|
|
PureChainParams.Icon = const_cast<FSlateBrush*>(Icon);
|
|
TSharedPtr<FScriptExecutionNode> PureChainRootNode = CreateTypedExecutionNode<FScriptExecutionPureChainNode>(PureChainParams);
|
|
// Resiter for both internal and external pins
|
|
BlueprintContext.Pin()->RegisterPurePinNode(PurePinSet.Key, PureChainRootNode);
|
|
BlueprintContext.Pin()->RegisterPurePinNode(PurePinSet.Value, PureChainRootNode);
|
|
PureLinkNodes.Add(PurePinSet.Key) = PureChainRootNode;
|
|
PureLinkNodes.Add(PurePinSet.Value) = PureChainRootNode;
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintTunnelInstanceContext::MapNodeExecution(UEdGraphNode* NodeToMap)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> MappedNode;
|
|
if (NodeToMap)
|
|
{
|
|
MappedNode = GetProfilerDataForGraphNode(NodeToMap);
|
|
if (!MappedNode.IsValid())
|
|
{
|
|
// First encounter, map execution.
|
|
MappedNode = FBlueprintFunctionContext::MapNodeExecution(NodeToMap);
|
|
}
|
|
else
|
|
{
|
|
// Discover already mapped exit sites
|
|
DiscoverExitSites(MappedNode);
|
|
}
|
|
}
|
|
return MappedNode;
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintTunnelInstanceContext::MapPureNodeExecution(const UEdGraphPin* LinkedPin)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> MappedNode;
|
|
UK2Node* LinkedNode = LinkedPin ? Cast<UK2Node>(LinkedPin->GetOwningNode()) : nullptr;
|
|
if (LinkedNode)
|
|
{
|
|
// Find the correct context
|
|
TSharedPtr<FBlueprintFunctionContext> MappingContext = FindContextByNodeGraph(LinkedNode);
|
|
// Determine what type of mapping is required
|
|
if (LinkedNode->IsNodePure())
|
|
{
|
|
// Lookup existing mapped pin node
|
|
MappedNode = BlueprintContext.Pin()->FindPurePinNode(LinkedPin);
|
|
if (!MappedNode.IsValid())
|
|
{
|
|
// Multi pure pin i/o registers multiple pins per node so lookup the node by name.
|
|
MappedNode = MappingContext->GetProfilerDataForNode(LinkedNode->GetFName());
|
|
}
|
|
// Map if not existing.
|
|
if (!MappedNode.IsValid())
|
|
{
|
|
// Create a normal execution node.
|
|
FScriptExecNodeParams NodeParams;
|
|
NodeParams.NodeFlags = EScriptExecutionNodeFlags::Node;
|
|
TArray<UEdGraphPin*> ExecPins;
|
|
TArray<UEdGraphPin*> PurePins;
|
|
DetermineGraphNodeCharacteristics(LinkedNode, PurePins, ExecPins, NodeParams);
|
|
NodeParams.NodeName = LinkedNode->GetFName();
|
|
NodeParams.ObservedObject = LinkedNode;
|
|
NodeParams.Tooltip = LOCTEXT("NavigateToNodeLocationHyperlink_ToolTip", "Navigate to the Node");
|
|
GetNodeCustomizations(NodeParams);
|
|
const FSlateBrush* NodeIcon = LinkedNode->ShowPaletteIconOnNode() ? LinkedNode->GetIconAndTint(NodeParams.IconColor).GetOptionalIcon() :
|
|
FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPNode"));
|
|
NodeParams.Icon = const_cast<FSlateBrush*>(NodeIcon);
|
|
// Create execution node
|
|
MappedNode = MappingContext->CreateExecutionNode(NodeParams);
|
|
// Evaluate non-exec input pins (pure node execution chains)
|
|
if (PurePins.Num())
|
|
{
|
|
MappingContext->MapInputPins(MappedNode, PurePins);
|
|
}
|
|
}
|
|
// Register Pure Node
|
|
BlueprintContext.Pin()->RegisterPurePinNode(LinkedPin, MappedNode);
|
|
}
|
|
else
|
|
{
|
|
// Lookup existing mapped pin node
|
|
MappedNode = BlueprintContext.Pin()->FindPurePinNode(LinkedPin);
|
|
if (!MappedNode.IsValid())
|
|
{
|
|
// Multi pure pin i/o registers multiple pins per node so lookup the node by name.
|
|
MappedNode = MappingContext->GetProfilerDataForNode(GetUniquePinName(LinkedPin));
|
|
}
|
|
// Map if not existing.
|
|
if (!MappedNode.IsValid())
|
|
{
|
|
// Create a pure node pin entry for this pin located on an impure node.
|
|
FScriptExecNodeParams PinParams;
|
|
PinParams.SampleFrequency = 1;
|
|
PinParams.NodeFlags = EScriptExecutionNodeFlags::PureNode;
|
|
PinParams.NodeName = GetUniquePinName(LinkedPin);
|
|
PinParams.ObservedObject = LinkedNode;
|
|
PinParams.ObservedPin = LinkedPin;
|
|
PinParams.Tooltip = LOCTEXT("NavigateToPinLocationHyperlink_ToolTip", "Navigate to the Pure Pin");
|
|
FFormatNamedArguments Args;
|
|
Args.Add("NodeDisplayName", LinkedNode->GetNodeTitle(ENodeTitleType::MenuTitle));
|
|
Args.Add("PinDisplayName", LinkedPin->PinFriendlyName);
|
|
FText Format = LinkedPin->PinFriendlyName.IsEmpty() ? LOCTEXT("PureNodeDisplay_Text", "{NodeDisplayName}") : LOCTEXT("PurePinDisplay_Text", "{NodeDisplayName} - {PinDisplayName}");
|
|
PinParams.DisplayName = FText::Format(Format, Args);
|
|
GetPinCustomizations(LinkedPin, PinParams);
|
|
const FSlateBrush* NodeIcon = LinkedNode->ShowPaletteIconOnNode() ? LinkedNode->GetIconAndTint(PinParams.IconColor).GetOptionalIcon() :
|
|
FEditorStyle::GetBrush(TEXT("BlueprintProfiler.BPNode"));
|
|
PinParams.Icon = const_cast<FSlateBrush*>(NodeIcon);
|
|
MappedNode = MappingContext->CreateExecutionNode(PinParams);
|
|
}
|
|
// Register Pure Node
|
|
BlueprintContext.Pin()->RegisterPurePinNode(LinkedPin, MappedNode);
|
|
}
|
|
}
|
|
return MappedNode;
|
|
}
|
|
|
|
TSharedPtr<FBlueprintFunctionContext> FBlueprintTunnelInstanceContext::FindContextByNodeGraph(const UEdGraphNode* GraphNode)
|
|
{
|
|
TSharedPtr<FBlueprintFunctionContext> Result;
|
|
// Check associated contexts so we prioritise any tunnel contexts before fully scoped functions.
|
|
const FName NodeGraphName = GetGraphNameFromNode(GraphNode);
|
|
if (NodeGraphName == GraphName)
|
|
{
|
|
Result = AsShared();
|
|
}
|
|
else if (ParentTunnel.IsValid() && ParentTunnel.Pin()->GetGraphName() == NodeGraphName)
|
|
{
|
|
Result = ParentTunnel.Pin();
|
|
}
|
|
else
|
|
{
|
|
for (auto ChildTunnelContext : ChildFunctionContexts)
|
|
{
|
|
if (ChildTunnelContext.Value.IsValid() && ChildTunnelContext.Value.Pin()->GetGraphName() == NodeGraphName)
|
|
{
|
|
Result = ChildTunnelContext.Value.Pin();
|
|
break;
|
|
}
|
|
}
|
|
// Fall back to scoped function name lookup.
|
|
Result = BlueprintContext.Pin()->GetFunctionContext(GetScopedFunctionNameFromNode(GraphNode));
|
|
}
|
|
check (Result.IsValid());
|
|
return Result;
|
|
}
|
|
|
|
void FBlueprintTunnelInstanceContext::MapInputPins(TSharedPtr<FScriptExecutionNode> ExecNode, const TArray<UEdGraphPin*>& Pins)
|
|
{
|
|
TArray<int32> PinScriptCodeOffsets;
|
|
TSharedPtr<FScriptExecutionNode> PureChainRootNode = ExecNode;
|
|
|
|
for (auto InputPin : Pins)
|
|
{
|
|
// If this input pin is linked to a pure node in the source graph, create and map all known execution paths for it.
|
|
for (auto LinkedPin : InputPin->LinkedTo)
|
|
{
|
|
// Pass through non-relevant (e.g. reroute) nodes.
|
|
LinkedPin = FBlueprintEditorUtils::FindFirstCompilerRelevantLinkedPin(LinkedPin);
|
|
if (LinkedPin)
|
|
{
|
|
UK2Node* OwningNode = Cast<UK2Node>(LinkedPin->GetOwningNode());
|
|
|
|
// If this is a tunnel node - we need to map through the tunnel
|
|
if (OwningNode)
|
|
{
|
|
if (OwningNode->IsA<UK2Node_Tunnel>())
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> TunnelPureNode = BlueprintContext.Pin()->FindPurePinNode(LinkedPin);
|
|
// Link up the pure nodes
|
|
if (TunnelPureNode.IsValid() && TunnelPureNode->IsPureChain())
|
|
{
|
|
// Grab the pure chain root.
|
|
PureChainRootNode = FindOrCreatePureChainRoot(ExecNode);
|
|
if (!PureChainRootNode.IsValid())
|
|
{
|
|
PureChainRootNode = ExecNode;
|
|
}
|
|
// Link in tunnel pure boundary
|
|
PureChainRootNode->AddLinkedNode(INDEX_NONE, TunnelPureNode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Note: Intermediate pure nodes can have output pins that masquerade as impure node output pins when links are "moved" from the source graph (thus
|
|
// resulting in a false association here with one or more script code offsets), so we must first ensure that the link is really to a pure node output.
|
|
GetAllCodeLocationsFromPin(LinkedPin, PinScriptCodeOffsets);
|
|
if (PinScriptCodeOffsets.Num() > 0)
|
|
{
|
|
// Add a pure chain container node as the root, if it's not already in place.
|
|
if (!ExecNode->HasFlags(EScriptExecutionNodeFlags::PureStats))
|
|
{
|
|
PureChainRootNode = FindOrCreatePureChainRoot(ExecNode);
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> PureNode = MapPureNodeExecution(LinkedPin);
|
|
for (int32 i = 0; i < PinScriptCodeOffsets.Num(); ++i)
|
|
{
|
|
if (IsCodeLocationValidForThisTunnel(PinScriptCodeOffsets[i]))
|
|
{
|
|
PureChainRootNode->AddLinkedNode(PinScriptCodeOffsets[i], PureNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintTunnelInstanceContext::GetTunnelBoundaryNode(const UEdGraphPin* TunnelPin)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> Result;
|
|
// Check tunnel node
|
|
if (const UK2Node_Tunnel* TunnelNode = Cast<UK2Node_Tunnel>(TunnelPin->GetOwningNode()))
|
|
{
|
|
if (FBlueprintEditorUtils::IsTunnelInstanceNode(TunnelNode))
|
|
{
|
|
const FName TunnelInstanceFunctionName = GetTunnelInstanceFunctionName(TunnelNode);
|
|
|
|
// Lookup internal tunnel boundary.
|
|
if (TWeakPtr<FBlueprintFunctionContext>* NestedTunnelContext = ChildFunctionContexts.Find(TunnelInstanceFunctionName))
|
|
{
|
|
Result = NestedTunnelContext->Pin()->GetProfilerDataForNode(GetTunnelBoundaryName(TunnelPin));
|
|
}
|
|
else
|
|
{
|
|
// Lookup external tunnel boundary.
|
|
TSharedPtr<FBlueprintFunctionContext> TunnelContext = BlueprintContext.Pin()->GetFunctionContext(TunnelInstanceFunctionName);
|
|
if (TunnelContext.IsValid())
|
|
{
|
|
Result = TunnelContext->GetProfilerDataForNode(GetTunnelBoundaryName(TunnelPin));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const FName NodeGraphName = GetGraphFromNode(TunnelNode, true)->GetFName();
|
|
if (NodeGraphName == GraphName)
|
|
{
|
|
// Internal boundary lookup, faster path.
|
|
Result = GetProfilerDataForNode(GetPinName(TunnelPin));
|
|
}
|
|
else
|
|
{
|
|
check (ParentTunnel.IsValid());
|
|
// Parent tunnel boundary lookup.
|
|
Result = ParentTunnel.Pin()->GetProfilerDataForNode(GetPinName(TunnelPin));
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
bool FBlueprintTunnelInstanceContext::IsTunnelNodeInternal(const UEdGraphNode* TunnelNode)
|
|
{
|
|
if (!FBlueprintEditorUtils::IsTunnelInstanceNode(TunnelNode))
|
|
{
|
|
return TunnelNode->GetTypedOuter<UEdGraph>()->GetFName() == GraphName;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FBlueprintTunnelInstanceContext::IsPinFromThisTunnel(const UEdGraphPin* TunnelPin) const
|
|
{
|
|
bool bIsFromThisTunnel = false;
|
|
const UEdGraphNode* PinNode = TunnelPin ? TunnelPin->GetOwningNode() : nullptr;
|
|
if (PinNode)
|
|
{
|
|
if (UEdGraph* PinGraph = GetGraphFromNode(PinNode))
|
|
{
|
|
bIsFromThisTunnel = GraphName == PinGraph->GetFName();
|
|
}
|
|
}
|
|
return bIsFromThisTunnel;
|
|
}
|
|
|
|
void FBlueprintTunnelInstanceContext::DiscoverExitSites(TSharedPtr<FScriptExecutionNode> MappedNode)
|
|
{
|
|
if (MappedNode->IsTunnelExit())
|
|
{
|
|
// Map tunnel exit because we are mapping inside a tunnel discovering exit pins.
|
|
if (StagingEntryPoint.IsValid())
|
|
{
|
|
TSharedPtr<FScriptExecutionTunnelExit> ExitNode = StaticCastSharedPtr<FScriptExecutionTunnelExit>(MappedNode);
|
|
if (UEdGraphPin* ExternalPin = ExitNode->GetExternalPin())
|
|
{
|
|
const int32 ScriptCodeOffset = GetCodeLocationFromPin(ExternalPin);
|
|
StagingEntryPoint->AddLinkedNode(ScriptCodeOffset, ExitNode);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Don't map inside tunnels during discovery!
|
|
if (!MappedNode->IsTunnelEntry())
|
|
{
|
|
for (auto ChildIter : MappedNode->GetChildNodes())
|
|
{
|
|
if (!ChildIter->IsPureChain())
|
|
{
|
|
DiscoverExitSites(ChildIter);
|
|
}
|
|
}
|
|
}
|
|
for (auto LinkIter : MappedNode->GetLinkedNodes())
|
|
{
|
|
DiscoverExitSites(LinkIter.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedPtr<FScriptExecutionNode> FBlueprintTunnelInstanceContext::MapTunnelBoundary(const UEdGraphPin* TunnelPin)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> TunnelBoundaryNode = GetTunnelBoundaryNodeChecked(TunnelPin);
|
|
if (TunnelBoundaryNode->IsTunnelExit())
|
|
{
|
|
// Map tunnel exit because we are mapping inside a tunnel discovering exit pins.
|
|
TSharedPtr<FScriptExecutionTunnelExit> ExitNode = StaticCastSharedPtr<FScriptExecutionTunnelExit>(TunnelBoundaryNode);
|
|
if (UEdGraphPin* ExternalPin = ExitNode->GetExternalPin())
|
|
{
|
|
const int32 ScriptCodeOffset = GetCodeLocationFromPin(ExternalPin);
|
|
StagingEntryPoint->AddLinkedNode(ScriptCodeOffset, ExitNode);
|
|
}
|
|
}
|
|
else if (TunnelBoundaryNode->IsTunnelEntry())
|
|
{
|
|
TSharedPtr<FScriptExecutionTunnelEntry> TunnelEntryInstance = StaticCastSharedPtr<FScriptExecutionTunnelEntry>(TunnelBoundaryNode);
|
|
for (auto ExitSite : TunnelEntryInstance->GetLinkedNodes())
|
|
{
|
|
if (ExitSite.Value->IsTunnelExit())
|
|
{
|
|
TSharedPtr<FScriptExecutionTunnelExit> TunnelExit = TunnelEntryInstance->GetExitSite(ExitSite.Key);
|
|
const UEdGraphPin* TunnelInstanceExitPin = TunnelExit->GetExternalPin();
|
|
for (auto LinkedPin : TunnelInstanceExitPin->LinkedTo)
|
|
{
|
|
// Pass through non-relevant (e.g. reroute) nodes.
|
|
LinkedPin = FBlueprintEditorUtils::FindFirstCompilerRelevantLinkedPin(LinkedPin);
|
|
if (LinkedPin)
|
|
{
|
|
UK2Node* LinkedNode = Cast<UK2Node>(LinkedPin->GetOwningNode());
|
|
TSharedPtr<FScriptExecutionNode> LinkedExecNode;
|
|
// Need to be careful here because a tunnel instance exit site can link to a tunnel Boundary too.
|
|
if (LinkedNode->IsA<UK2Node_Tunnel>())
|
|
{
|
|
LinkedExecNode = MapTunnelBoundary(LinkedPin);
|
|
}
|
|
else
|
|
{
|
|
LinkedExecNode = GetProfilerDataForNode(LinkedNode->GetFName());
|
|
}
|
|
if (!LinkedExecNode.IsValid())
|
|
{
|
|
LinkedExecNode = MapNodeExecution(LinkedNode);
|
|
}
|
|
TunnelExit->AddLinkedNode(ExitSite.Key, LinkedExecNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return TunnelBoundaryNode;
|
|
}
|
|
|
|
const int32 FBlueprintTunnelInstanceContext::GetCodeLocationFromPin(const UEdGraphPin* Pin)
|
|
{
|
|
int32 Result = INDEX_NONE;
|
|
if (const int32* ScriptOffset = ScriptOffsetToPins.FindKey(Pin))
|
|
{
|
|
Result = *ScriptOffset;
|
|
}
|
|
else
|
|
{
|
|
if (BlueprintClass.IsValid())
|
|
{
|
|
// Verify the correct code location for this tunnel ( in case of nested tunnels )
|
|
TArray<int32> CodeLocations;
|
|
BlueprintClass.Get()->GetDebugData().FindAllCodeLocationsFromSourcePin(Pin, Function.Get(), CodeLocations);
|
|
if (CodeLocations.Num() == 1)
|
|
{
|
|
Result = CodeLocations[0];
|
|
ScriptOffsetToPins.Add(Result) = Pin;
|
|
}
|
|
else if (CodeLocations.Num() > 1)
|
|
{
|
|
for (const int32 CodeLocation : CodeLocations)
|
|
{
|
|
if (IsCodeLocationValidForThisTunnel(CodeLocation))
|
|
{
|
|
// Cache the value, so we don't do this each time.
|
|
ScriptOffsetToPins.Add(CodeLocation) = Pin;
|
|
Result = CodeLocation;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
bool FBlueprintTunnelInstanceContext::IsCodeLocationValidForThisTunnel(const int32 CodeLocation) const
|
|
{
|
|
TArray<UEdGraphNode*> TunnelsAtLocation;
|
|
BlueprintClass.Get()->GetDebugData().FindMacroInstanceNodesFromCodeLocation(Function.Get(), CodeLocation, TunnelsAtLocation);
|
|
// Try and identify which code location we want.
|
|
for (auto Macro : NestedTunnelInstanceStack)
|
|
{
|
|
if (!TunnelsAtLocation.Contains(Macro))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
FName FBlueprintTunnelInstanceContext::GetTunnelInstanceFunctionName(const UEdGraphNode* GraphNode) const
|
|
{
|
|
FName ScopedFunctionName = NAME_None;
|
|
if (GraphNode)
|
|
{
|
|
UK2Node_Tunnel* MacroInstance = TunnelInstanceNode.Get();
|
|
if (MacroInstance == GraphNode)
|
|
{
|
|
ScopedFunctionName = FunctionName;
|
|
}
|
|
else
|
|
{
|
|
ScopedFunctionName = FName(*FString::Printf(TEXT("%s::%s"), *FunctionName.ToString(), *GraphNode->GetName()));
|
|
}
|
|
}
|
|
check (ScopedFunctionName != NAME_None);
|
|
return ScopedFunctionName;
|
|
}
|
|
|
|
bool FBlueprintTunnelInstanceContext::GetProfilerContextFromScriptOffset(const int32 ScriptOffset, FNodeExecutionContext& ExecContextOut)
|
|
{
|
|
// Check the script offset is valid for this context
|
|
if (ScriptOffsetToPins.Contains(ScriptOffset))
|
|
{
|
|
if (const UEdGraphNode* GraphNode = GetNodeFromCodeLocation(ScriptOffset))
|
|
{
|
|
ExecContextOut.ProfilerNode = GetProfilerDataForNode(GraphNode->GetFName());
|
|
ExecContextOut.ProfilerContext = AsShared();
|
|
}
|
|
}
|
|
return ExecContextOut.IsValid();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FScriptEventPlayback
|
|
|
|
bool FScriptEventPlayback::Process(const TArray<FScriptInstrumentedEvent>& SignalData, const int32 StartIdx, const int32 StopIdx)
|
|
{
|
|
const int32 NumEvents = (StopIdx+1) - StartIdx;
|
|
bool bProcessingSuccess = false;
|
|
const bool bEventIsResuming = SignalData[StartIdx].IsResumeEvent() ;
|
|
IBlueprintProfilerInterface* Profiler = FModuleManager::GetModulePtr<IBlueprintProfilerInterface>("BlueprintProfiler");
|
|
|
|
if (BlueprintContext.IsValid() && InstanceName != NAME_None)
|
|
{
|
|
check(SignalData[StartIdx].IsEvent());
|
|
EventName = bEventIsResuming ? EventName : SignalData[StartIdx].GetScopedFunctionName();
|
|
TSharedPtr<FBlueprintFunctionContext> FunctionContext = BlueprintContext->GetFunctionContextForEventChecked(EventName);
|
|
CurrentFunctionName = FunctionContext->GetFunctionName();
|
|
TSharedPtr<FScriptExecutionNode> EventNode = FunctionContext->GetProfilerDataForNode(EventName);
|
|
// Find Associated graph nodes and submit into a node map for later processing.
|
|
TMap<TSharedPtr<FScriptExecutionNode>, FNodeSignalHelper> CachedNodeInfo;
|
|
bProcessingSuccess = true;
|
|
int32 LastEventIdx = StartIdx;
|
|
const int32 EventStartOffset = SignalData[StartIdx].IsResumeEvent() ? 3 : 1;
|
|
LatentLinkId = bEventIsResuming ? INDEX_NONE : LatentLinkId;
|
|
// If we have sub calls into inherited events we don't have properly formed events, find the correct event now.
|
|
if (!EventNode.IsValid() && FunctionContext.IsValid())
|
|
{
|
|
const FScriptInstrumentedEvent& FirstValidSignal = SignalData[StartIdx + EventStartOffset];
|
|
if (const UK2Node_Event* EventGraphNode = Cast<UK2Node_Event>(FunctionContext->GetNodeFromCodeLocation(FirstValidSignal.GetScriptCodeOffset())))
|
|
{
|
|
EventName = FunctionContext->GetScopedEventName(EventGraphNode->GetFunctionName());
|
|
EventNode = FunctionContext->GetProfilerDataForNode(EventName);
|
|
}
|
|
}
|
|
|
|
for (int32 SignalIdx = StartIdx + EventStartOffset; SignalIdx < StopIdx; ++SignalIdx)
|
|
{
|
|
// Handle midstream context switches.
|
|
const FScriptInstrumentedEvent& CurrSignal = SignalData[SignalIdx];
|
|
const int ScriptCodeOffset = CurrSignal.GetScriptCodeOffset();
|
|
const UEdGraphPin* ValidPinTemp = FunctionContext->GetPinFromCodeLocation(ScriptCodeOffset);
|
|
if (CurrSignal.GetType() == EScriptInstrumentation::Class)
|
|
{
|
|
// Update the current mapped blueprint context.
|
|
BlueprintContext = Profiler->GetOrCreateBlueprintContext(CurrSignal.GetBlueprintClassPath());
|
|
|
|
// Skip to the next signal.
|
|
continue;
|
|
}
|
|
else if (CurrSignal.GetType() == EScriptInstrumentation::Instance)
|
|
{
|
|
// Update the current mapped instance name.
|
|
InstanceName = BlueprintContext->MapBlueprintInstance(CurrSignal.GetInstancePath());
|
|
|
|
// Skip to the next signal.
|
|
continue;
|
|
}
|
|
|
|
// Update script function.
|
|
if (CurrentFunctionName != CurrSignal.GetScopedFunctionName())
|
|
{
|
|
CurrentFunctionName = CurrSignal.GetScopedFunctionName();
|
|
FunctionContext = BlueprintContext->GetFunctionContext(CurrentFunctionName);
|
|
check(FunctionContext.IsValid());
|
|
}
|
|
// Grab the node execution context ( this is cached under the hood ).
|
|
FNodeExecutionContext& NodeExecContext = FunctionContext->GetNodeExecutionContext(CurrSignal.GetScriptCodeOffset());
|
|
if (const UEdGraphNode* GraphNode = NodeExecContext.GraphNode.Get())
|
|
{
|
|
// Initialise the current node context.
|
|
FNodeSignalHelper& CurrentNodeData = CachedNodeInfo.FindOrAdd(NodeExecContext.ProfilerNode);
|
|
if (!CurrentNodeData.IsValid())
|
|
{
|
|
CurrentNodeData.ImpureNode = NodeExecContext.ProfilerNode;
|
|
CurrentNodeData.FunctionContext = NodeExecContext.ProfilerContext;
|
|
}
|
|
// Check for tunnel boundries and process here
|
|
if (NodeExecContext.ProfilerNode->IsTunnelInstance())
|
|
{
|
|
ProcessTunnelBoundary(CurrentNodeData, CurrSignal);
|
|
continue;
|
|
}
|
|
// Process node data
|
|
switch (CurrSignal.GetType())
|
|
{
|
|
case EScriptInstrumentation::PureNodeEntry:
|
|
{
|
|
const int32 CodeOffset = CurrSignal.GetScriptCodeOffset();
|
|
if (!CurrentNodeData.PureNodes.Contains(CodeOffset))
|
|
{
|
|
if (const UEdGraphPin* Pin = FunctionContext->GetPinFromCodeLocation(CodeOffset))
|
|
{
|
|
// Use the impure nodes function context to look up the pure graph node because it may not be cached yet.
|
|
TSharedPtr<FScriptExecutionNode> PureNode = BlueprintContext->FindPurePinNode(Pin);
|
|
if (PureNode.IsValid() && PureNode->IsPureNode())
|
|
{
|
|
TracePath.AddExitPin(CodeOffset);
|
|
CurrentNodeData.PureNodes.Add(CodeOffset) = PureNode;
|
|
CurrentNodeData.InputTracePaths.Insert(TracePath, 0);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Fast path, already cached.
|
|
TracePath.AddExitPin(CodeOffset);
|
|
CurrentNodeData.InputTracePaths.Insert(TracePath, 0);
|
|
}
|
|
CurrentNodeData.AverageEvents.Add(CurrSignal);
|
|
break;
|
|
}
|
|
case EScriptInstrumentation::NodeEntry:
|
|
case EScriptInstrumentation::NodeDebugSite:
|
|
{
|
|
// Add node timings
|
|
CurrentNodeData.AverageTracePaths.Push(TracePath);
|
|
CurrentNodeData.AverageEvents.Add(CurrSignal);
|
|
AddToTraceHistory(CurrentNodeData.ImpureNode, CurrSignal);
|
|
break;
|
|
}
|
|
case EScriptInstrumentation::PushState:
|
|
{
|
|
TraceStack.Push(TracePath);
|
|
// Process execution sequence inclusive timing
|
|
if (GraphNode->IsA<UK2Node_ExecutionSequence>())
|
|
{
|
|
ProcessExecutionSequence(CurrentNodeData, CurrSignal);
|
|
}
|
|
break;
|
|
}
|
|
case EScriptInstrumentation::PopState:
|
|
{
|
|
if (TraceStack.Num())
|
|
{
|
|
TracePath = TraceStack.Pop();
|
|
// Process execution sequence inclusive timing
|
|
if (GraphNode->IsA<UK2Node_ExecutionSequence>())
|
|
{
|
|
ProcessExecutionSequence(CurrentNodeData, CurrSignal);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case EScriptInstrumentation::RestoreState:
|
|
{
|
|
check(TraceStack.Num());
|
|
TracePath = TraceStack.Last();
|
|
// Process execution sequence inclusive timing
|
|
if (GraphNode->IsA<UK2Node_ExecutionSequence>())
|
|
{
|
|
ProcessExecutionSequence(CurrentNodeData, CurrSignal);
|
|
}
|
|
// Process tunnel if present
|
|
TSharedPtr<FScriptExecutionTunnelEntry> TunnelEntry = TracePath.GetTunnel();
|
|
if (TunnelEntry.IsValid())
|
|
{
|
|
FNodeSignalHelper& TunnelNodeData = CachedNodeInfo.FindOrAdd(TunnelEntry->GetTunnelInstance());
|
|
FScriptInstrumentedEvent OverrideEvent(CurrSignal);
|
|
FTracePath TunnelTrace;
|
|
TracePath.GetTunnelTracePath(TunnelTrace);
|
|
OverrideEvent.OverrideType(EScriptInstrumentation::NodeEntry);
|
|
TunnelNodeData.AverageEvents.Add(OverrideEvent);
|
|
TunnelNodeData.AverageTracePaths.Add(TunnelTrace);
|
|
TunnelTraceStack.Push(TunnelTrace);
|
|
}
|
|
break;
|
|
}
|
|
case EScriptInstrumentation::SuspendState:
|
|
{
|
|
// Handle latent suspends - make use of the link id to match re-entry.
|
|
TSharedPtr<FScriptExecutionInstance> InstanceNode = BlueprintContext->GetInstanceExecNode(InstanceName);
|
|
if (UObject* InstanceObj = InstanceNode->GetActiveObject())
|
|
{
|
|
if (UWorld* WorldForEvent = InstanceObj->GetWorld())
|
|
{
|
|
LatentLinkId = CurrSignal.GetScriptCodeOffset();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case EScriptInstrumentation::NodeExit:
|
|
{
|
|
// Cleanup branching multiple exits and correct the tracepath
|
|
if (CurrentNodeData.AverageEvents.Num() && CurrentNodeData.AverageEvents.Last().GetType() == EScriptInstrumentation::NodeExit)
|
|
{
|
|
CurrentNodeData.AverageEvents.Pop();
|
|
if (CurrentNodeData.AverageTracePaths.Num())
|
|
{
|
|
TracePath = CurrentNodeData.AverageTracePaths.Last();
|
|
}
|
|
}
|
|
// Add Trace History
|
|
AddToTraceHistory(CurrentNodeData.ImpureNode, CurrSignal);
|
|
// Process node exit
|
|
const int32 ScriptCodeExit = CurrSignal.GetScriptCodeOffset();
|
|
const UEdGraphPin* ValidPin = FunctionContext->GetPinFromCodeLocation(ScriptCodeExit);
|
|
if (ValidPin && ValidPin->GetOwningNode() == GraphNode)
|
|
{
|
|
// Delegate/event pin entry points require the tracepath to be reset.
|
|
TSharedPtr<FScriptExecutionNode> Pin = FunctionContext->GetProfilerDataForNode(FBlueprintFunctionContext::GetUniquePinName(ValidPin));
|
|
if (Pin.IsValid() && Pin->IsEventPin())
|
|
{
|
|
TracePath.Reset();
|
|
}
|
|
// Update Tracepath.
|
|
TracePath.AddExitPin(ScriptCodeExit);
|
|
}
|
|
CurrentNodeData.AverageEvents.Add(CurrSignal);
|
|
// Process cyclic linkage - reset to root link tracepath
|
|
TSharedPtr<FScriptExecutionNode> NextLink = CurrentNodeData.ImpureNode->GetLinkedNodeByScriptOffset(ScriptCodeExit);
|
|
if (NextLink.IsValid() && NextLink->HasCyclicLinkage())
|
|
{
|
|
if (FNodeSignalHelper* LinkedEntry = CachedNodeInfo.Find(NextLink))
|
|
{
|
|
if (LinkedEntry->AverageTracePaths.Num())
|
|
{
|
|
TracePath = LinkedEntry->AverageTracePaths.Last();
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
CurrentNodeData.AverageEvents.Add(CurrSignal);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Process last event timing
|
|
if (EventNode.IsValid())
|
|
{
|
|
double* TimingData = bEventIsResuming ? EventTimings.Find(EventName) : nullptr;
|
|
if (TimingData)
|
|
{
|
|
*TimingData += SignalData[StopIdx].GetTime() - SignalData[LastEventIdx].GetTime();
|
|
}
|
|
else
|
|
{
|
|
EventTimings.Add(EventName) = SignalData[StopIdx].GetTime() - SignalData[LastEventIdx].GetTime();
|
|
}
|
|
}
|
|
// Process outstanding event timings, adding to previous timings if existing.
|
|
for (auto EventTiming : EventTimings)
|
|
{
|
|
FTracePath EventTracePath;
|
|
// The UCS, along with BP events declared as 'const' in native C++ code, are implemented as standalone functions, and not within the ubergraph function context.
|
|
FunctionContext = BlueprintContext->GetFunctionContextForEventChecked(EventTiming.Key);
|
|
EventNode = FunctionContext->GetProfilerDataForNode(EventTiming.Key);
|
|
check (EventNode.IsValid());
|
|
TSharedPtr<FScriptPerfData> PerfData = EventNode->GetOrAddPerfDataByInstanceAndTracePath(InstanceName, EventTracePath);
|
|
PerfData->AddEventTiming(EventTiming.Value);
|
|
EventNode->AddFlags(EScriptExecutionNodeFlags::RequiresRefresh);
|
|
}
|
|
EventTimings.Reset();
|
|
// Process Node map timings -- this can probably be rolled back into submission during the initial processing and lose this extra iteration.
|
|
for (auto CurrentNodeData : CachedNodeInfo)
|
|
{
|
|
TSharedPtr<FScriptExecutionNode> ExecNode = CurrentNodeData.Value.ImpureNode;
|
|
TSharedPtr<FScriptExecutionNode> PureNode;
|
|
TSharedPtr<FScriptExecutionNode> PureChainNode = ExecNode->GetPureChainNode();
|
|
TSharedPtr<FBlueprintFunctionContext> NodeFunctionContext = CurrentNodeData.Value.FunctionContext;
|
|
check(NodeFunctionContext.IsValid());
|
|
double PureNodeEntryTime = 0.0;
|
|
double PureChainEntryTime = 0.0;
|
|
double NodeEntryTime = 0.0;
|
|
int32 ExclTracePathIdx = 0;
|
|
int32 InclTracePathIdx = 0;
|
|
// Process exclusive events for this node
|
|
for (auto EventIter = CurrentNodeData.Value.AverageEvents.CreateIterator(); EventIter; ++EventIter)
|
|
{
|
|
switch(EventIter->GetType())
|
|
{
|
|
case EScriptInstrumentation::PureNodeEntry:
|
|
{
|
|
if (PureChainEntryTime == 0.0)
|
|
{
|
|
PureChainEntryTime = EventIter->GetTime();
|
|
}
|
|
else if (PureNode.IsValid())
|
|
{
|
|
TSharedPtr<FScriptPerfData> PerfData = PureNode->GetOrAddPerfDataByInstanceAndTracePath(InstanceName, CurrentNodeData.Value.InputTracePaths.Pop());
|
|
PerfData->AddEventTiming(EventIter->GetTime() - PureNodeEntryTime);
|
|
}
|
|
PureNode.Reset();
|
|
PureNodeEntryTime = EventIter->GetTime();
|
|
// Find new pure node
|
|
if (TSharedPtr<FScriptExecutionNode>* NewPureNode = CurrentNodeData.Value.PureNodes.Find(EventIter->GetScriptCodeOffset()))
|
|
{
|
|
PureNode = *NewPureNode;
|
|
}
|
|
break;
|
|
}
|
|
case EScriptInstrumentation::NodeEntry:
|
|
case EScriptInstrumentation::NodeDebugSite:
|
|
{
|
|
if (PureNode.IsValid())
|
|
{
|
|
TSharedPtr<FScriptPerfData> PerfData = PureNode->GetOrAddPerfDataByInstanceAndTracePath(InstanceName, CurrentNodeData.Value.InputTracePaths.Pop());
|
|
PerfData->AddEventTiming(EventIter->GetTime() - PureNodeEntryTime);
|
|
|
|
PureNode.Reset();
|
|
PureNodeEntryTime = 0.0;
|
|
}
|
|
if (NodeEntryTime == 0.0)
|
|
{
|
|
NodeEntryTime = EventIter->GetTime();
|
|
}
|
|
break;
|
|
}
|
|
case EScriptInstrumentation::NodeExit:
|
|
{
|
|
check(NodeEntryTime != 0.0);
|
|
const FTracePath NodeTracePath = CurrentNodeData.Value.AverageTracePaths[ExclTracePathIdx++];
|
|
TSharedPtr<FScriptPerfData> PerfData = ExecNode->GetOrAddPerfDataByInstanceAndTracePath(InstanceName, NodeTracePath);
|
|
double PureChainDuration = PureChainEntryTime != 0.0 ? (NodeEntryTime - PureChainEntryTime) : 0.0;
|
|
double NodeDuration = EventIter->GetTime() - NodeEntryTime;
|
|
PerfData->AddEventTiming(NodeDuration);
|
|
PerfData->AddInclusiveTiming(PureChainDuration, false);
|
|
if (PureChainNode.IsValid())
|
|
{
|
|
PerfData = PureChainNode->GetOrAddPerfDataByInstanceAndTracePath(InstanceName, NodeTracePath);
|
|
PerfData->AddEventTiming(PureChainDuration);
|
|
}
|
|
PureChainEntryTime = NodeEntryTime = 0.0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Process inclusive events for this node
|
|
if (CurrentNodeData.Value.InclusiveEvents.Num() > 0)
|
|
{
|
|
int32 EventDepth = 0;
|
|
TArray<FScriptInstrumentedEvent> ProcessQueue;
|
|
// Discard any nested timings, it's nasty that we have to do this extra processing.
|
|
for (auto EventIter : CurrentNodeData.Value.InclusiveEvents)
|
|
{
|
|
EventDepth = EventIter.IsNodeExit() ? (EventDepth-1) : EventDepth;
|
|
check (EventDepth>=0);
|
|
if (EventDepth == 0)
|
|
{
|
|
ProcessQueue.Add(EventIter);
|
|
}
|
|
EventDepth = EventIter.IsNodeEntry() ? (EventDepth+1) : EventDepth;
|
|
}
|
|
for (auto EventIter : ProcessQueue)
|
|
{
|
|
switch(EventIter.GetType())
|
|
{
|
|
case EScriptInstrumentation::NodeEntry:
|
|
case EScriptInstrumentation::NodeDebugSite:
|
|
{
|
|
if (NodeEntryTime == 0.0)
|
|
{
|
|
NodeEntryTime = EventIter.GetTime();
|
|
}
|
|
break;
|
|
}
|
|
case EScriptInstrumentation::NodeExit:
|
|
{
|
|
check(NodeEntryTime != 0.0 );
|
|
const FTracePath NodeTracePath = CurrentNodeData.Value.InclusiveTracePaths[InclTracePathIdx++];
|
|
TSharedPtr<FScriptPerfData> PerfData = ExecNode->GetOrAddPerfDataByInstanceAndTracePath(InstanceName, NodeTracePath);
|
|
double NodeDuration = EventIter.GetTime() - NodeEntryTime;
|
|
PerfData->AddInclusiveTiming(NodeDuration, false);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return bProcessingSuccess;
|
|
}
|
|
|
|
void FScriptEventPlayback::ProcessTunnelBoundary(FNodeSignalHelper& CurrentNodeData, const FScriptInstrumentedEvent& CurrSignal)
|
|
{
|
|
// Find grab the tunnel instance exec node.
|
|
TSharedPtr<FScriptExecutionTunnelInstance> TunnelInstance = StaticCastSharedPtr<FScriptExecutionTunnelInstance>(CurrentNodeData.ImpureNode);
|
|
// Then the Boundary node by script offset.
|
|
const int32 ScriptCodeOffset = CurrSignal.GetScriptCodeOffset();
|
|
TSharedPtr<FScriptExecutionNode> TunnelBoundary = TunnelInstance->FindBoundarySite(ScriptCodeOffset);
|
|
if (TunnelBoundary.IsValid())
|
|
{
|
|
if (TunnelBoundary->HasFlags(EScriptExecutionNodeFlags::TunnelEntry))
|
|
{
|
|
// Grab the tunnel entry
|
|
TSharedPtr<FScriptExecutionTunnelEntry> TunnelEntry = StaticCastSharedPtr<FScriptExecutionTunnelEntry>(TunnelBoundary);
|
|
// Add Tunnel Trace History
|
|
AddTunnelTraceHistory(TunnelBoundary, CurrSignal, TracePath, TracePath);
|
|
// Update the tunnel entry count so we can fixup stat samples later.
|
|
TunnelEntry->IncrementTunnelEntryCount(InstanceName, TracePath);
|
|
// Process tunnel entry sites
|
|
TunnelTraceStack.Push(TracePath);
|
|
TracePath = FTracePath(TracePath, TunnelEntry);
|
|
CurrentNodeData.AverageEvents.Add(CurrSignal);
|
|
}
|
|
else if (TunnelBoundary->HasFlags(EScriptExecutionNodeFlags::TunnelExit))
|
|
{
|
|
// Process tunel exit sites.
|
|
if (CurrentNodeData.AverageEvents.Num())
|
|
{
|
|
TSharedPtr<FScriptExecutionTunnelEntry> Tunnel = TracePath.GetTunnel();
|
|
// Cache the tunnel tracepath and restore the non tunnel tracepath as current.
|
|
FTracePath InternalExitTrace = TracePath;
|
|
TracePath = TunnelTraceStack.Pop();
|
|
// Add Tunnel Trace History
|
|
AddTunnelTraceHistory(TunnelBoundary, CurrSignal, InternalExitTrace, TracePath);
|
|
// Add timings to tunnel nodes.
|
|
if (Tunnel.IsValid())
|
|
{
|
|
// Update the entry and both exit sites ( one inside the tunnel and one exit site )
|
|
const double TunnelTiming = CurrSignal.GetTime() - CurrentNodeData.AverageEvents.Last().GetTime();
|
|
Tunnel->AddTunnelTiming(InstanceName, TracePath, InternalExitTrace, ScriptCodeOffset, TunnelTiming);
|
|
}
|
|
TracePath.AddExitPin(ScriptCodeOffset);
|
|
CurrentNodeData.AverageEvents.Reset();
|
|
}
|
|
}
|
|
}
|
|
else if (CurrSignal.GetType() == EScriptInstrumentation::TunnelEndOfThread)
|
|
{
|
|
TSharedPtr<FScriptExecutionTunnelEntry> TunnelEntry = TracePath.GetTunnel();
|
|
if (TunnelEntry.IsValid())
|
|
{
|
|
TSharedPtr<FScriptExecutionTunnelEntry> Tunnel = TracePath.GetTunnel();
|
|
FTracePath InternalExitTrace = TracePath;
|
|
TracePath = TunnelTraceStack.Pop();
|
|
// Update the entry and both exit sites ( one inside the tunnel and one exit site )
|
|
const double TunnelTiming = CurrSignal.GetTime() - CurrentNodeData.AverageEvents.Last().GetTime();
|
|
Tunnel->AddTunnelTiming(InstanceName, TracePath, InternalExitTrace, ScriptCodeOffset, TunnelTiming);
|
|
TracePath.AddExitPin(ScriptCodeOffset);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FScriptEventPlayback::ProcessExecutionSequence(FNodeSignalHelper& CurrentNodeData, const FScriptInstrumentedEvent& CurrSignal)
|
|
{
|
|
switch(CurrSignal.GetType())
|
|
{
|
|
// For a sequence node a push state represents the start of execution.
|
|
case EScriptInstrumentation::PushState:
|
|
{
|
|
// Convert the push state into a inclusive entry signal
|
|
FScriptInstrumentedEvent OverrideEvent(CurrSignal);
|
|
OverrideEvent.OverrideType(EScriptInstrumentation::NodeEntry);
|
|
CurrentNodeData.InclusiveEvents.Add(OverrideEvent);
|
|
CurrentNodeData.InclusiveTracePaths.Add(TracePath);
|
|
break;
|
|
}
|
|
// For a sequence node a restore state represents the end of execution of a sequence pin's path (excluding the last pin)
|
|
case EScriptInstrumentation::RestoreState:
|
|
{
|
|
// Convert the restore state into a node exit signal
|
|
FScriptInstrumentedEvent OverrideEvent(CurrSignal);
|
|
OverrideEvent.OverrideType(EScriptInstrumentation::NodeEntry);
|
|
CurrentNodeData.AverageEvents.Add(OverrideEvent);
|
|
CurrentNodeData.AverageTracePaths.Add(TracePath);
|
|
// Add Trace History
|
|
AddToTraceHistory(CurrentNodeData.ImpureNode, OverrideEvent);
|
|
break;
|
|
}
|
|
// For a sequence node a pop state represents the end of execution of all the sequence pins.
|
|
case EScriptInstrumentation::PopState:
|
|
{
|
|
// Convert the pop state into a inclusive exit signal
|
|
FScriptInstrumentedEvent OverrideEvent(CurrSignal);
|
|
OverrideEvent.OverrideType(EScriptInstrumentation::NodeExit);
|
|
CurrentNodeData.InclusiveEvents.Add(OverrideEvent);
|
|
CurrentNodeData.InclusiveTracePaths.Add(TracePath);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FScriptEventPlayback::AddToTraceHistory(const TSharedPtr<FScriptExecutionNode> ProfilerNode, const FScriptInstrumentedEvent& TraceSignal)
|
|
{
|
|
bool bAddHistoryEvent = true;
|
|
if (ProfilerNode->IsBranch() && TraceSignal.IsNodeExit())
|
|
{
|
|
// Add exec pins into the trace history for correct heat displays.
|
|
TSharedPtr<FScriptExecutionNode> PinNode = ProfilerNode->GetLinkedNodeByScriptOffset(TraceSignal.GetScriptCodeOffset());
|
|
if (PinNode.IsValid() && PinNode->HasFlags(EScriptExecutionNodeFlags::ExecPin))
|
|
{
|
|
FBlueprintExecutionTrace& NewTraceEvent = BlueprintContext->AddNewTraceHistoryEvent();
|
|
NewTraceEvent.TraceType = TraceSignal.GetType();
|
|
NewTraceEvent.TracePath = TracePath;
|
|
NewTraceEvent.InstanceName = InstanceName;
|
|
NewTraceEvent.FunctionName = CurrentFunctionName;
|
|
NewTraceEvent.GraphName = PinNode->GetGraphName();
|
|
NewTraceEvent.Offset = TraceSignal.GetScriptCodeOffset();
|
|
NewTraceEvent.ObservationTime = TraceSignal.GetTime();
|
|
NewTraceEvent.ProfilerNode = PinNode;
|
|
NewTraceEvent.PinReference = PinNode->GetObservedPin();
|
|
bAddHistoryEvent = false;
|
|
}
|
|
}
|
|
if (bAddHistoryEvent)
|
|
{
|
|
// Add Trace History
|
|
FBlueprintExecutionTrace& NewTraceEvent = BlueprintContext->AddNewTraceHistoryEvent();
|
|
NewTraceEvent.TraceType = TraceSignal.GetType();
|
|
NewTraceEvent.TracePath = TracePath;
|
|
NewTraceEvent.InstanceName = InstanceName;
|
|
NewTraceEvent.FunctionName = CurrentFunctionName;
|
|
NewTraceEvent.GraphName = ProfilerNode->GetGraphName();
|
|
NewTraceEvent.Offset = TraceSignal.GetScriptCodeOffset();
|
|
NewTraceEvent.ObservationTime = TraceSignal.GetTime();
|
|
NewTraceEvent.ProfilerNode = ProfilerNode;
|
|
NewTraceEvent.PinReference = ProfilerNode->GetObservedPin();
|
|
}
|
|
}
|
|
|
|
void FScriptEventPlayback::AddTunnelTraceHistory(const TSharedPtr<FScriptExecutionNode> TunnelBoundary, const FScriptInstrumentedEvent& TraceSignal, const FTracePath& InternalPath, const FTracePath& ExternalPath)
|
|
{
|
|
if (TunnelBoundary->HasFlags(EScriptExecutionNodeFlags::TunnelEntry))
|
|
{
|
|
// Add history for external tunnel entry pin
|
|
FBlueprintExecutionTrace& ExternalTraceEvent = BlueprintContext->AddNewTraceHistoryEvent();
|
|
ExternalTraceEvent.TraceType = TraceSignal.GetType();
|
|
ExternalTraceEvent.TracePath = ExternalPath;
|
|
ExternalTraceEvent.InstanceName = InstanceName;
|
|
ExternalTraceEvent.FunctionName = CurrentFunctionName;
|
|
ExternalTraceEvent.GraphName = TunnelBoundary->GetGraphName();
|
|
ExternalTraceEvent.Offset = TraceSignal.GetScriptCodeOffset();
|
|
ExternalTraceEvent.ObservationTime = TraceSignal.GetTime();
|
|
ExternalTraceEvent.ProfilerNode = TunnelBoundary;
|
|
ExternalTraceEvent.PinReference = TunnelBoundary->GetObservedPin();
|
|
// Add history for internal tunnel entry pin
|
|
if (TunnelBoundary->GetNumChildren() > 0)
|
|
{
|
|
// We might want to try harder to find the right child node here?!.
|
|
TSharedPtr<FScriptExecutionNode> InternalTunnelNode = TunnelBoundary->GetChildByIndex(0);
|
|
if (InternalTunnelNode.IsValid() && InternalTunnelNode->HasFlags(EScriptExecutionNodeFlags::TunnelEntryPin))
|
|
{
|
|
FBlueprintExecutionTrace& InternalTraceEvent = BlueprintContext->AddNewTraceHistoryEvent();
|
|
InternalTraceEvent.TraceType = TraceSignal.GetType();
|
|
InternalTraceEvent.TracePath = InternalPath;
|
|
InternalTraceEvent.InstanceName = InstanceName;
|
|
InternalTraceEvent.FunctionName = CurrentFunctionName;
|
|
InternalTraceEvent.GraphName = InternalTunnelNode->GetGraphName();
|
|
InternalTraceEvent.Offset = TraceSignal.GetScriptCodeOffset();
|
|
InternalTraceEvent.ObservationTime = TraceSignal.GetTime();
|
|
InternalTraceEvent.ProfilerNode = InternalTunnelNode;
|
|
InternalTraceEvent.PinReference = InternalTunnelNode->GetObservedPin();
|
|
}
|
|
}
|
|
}
|
|
else if (TunnelBoundary->HasFlags(EScriptExecutionNodeFlags::TunnelExit))
|
|
{
|
|
// Add history for internal tunnel exit pin
|
|
FBlueprintExecutionTrace& InternalTraceEvent = BlueprintContext->AddNewTraceHistoryEvent();
|
|
InternalTraceEvent.TraceType = TraceSignal.GetType();
|
|
InternalTraceEvent.TracePath = InternalPath;
|
|
InternalTraceEvent.InstanceName = InstanceName;
|
|
InternalTraceEvent.FunctionName = CurrentFunctionName;
|
|
InternalTraceEvent.GraphName = TunnelBoundary->GetGraphName();
|
|
InternalTraceEvent.Offset = TraceSignal.GetScriptCodeOffset();
|
|
InternalTraceEvent.ObservationTime = TraceSignal.GetTime();
|
|
InternalTraceEvent.ProfilerNode = TunnelBoundary;
|
|
InternalTraceEvent.PinReference = TunnelBoundary->GetObservedPin();
|
|
// Add history for external tunnel exit pin
|
|
TSharedPtr<FScriptExecutionTunnelEntry> TunnelEntryNode = InternalPath.GetTunnel();
|
|
TSharedPtr<FScriptExecutionTunnelExit> TunnelExit = StaticCastSharedPtr<FScriptExecutionTunnelExit>(TunnelBoundary);
|
|
if (TunnelExit.IsValid() && TunnelEntryNode.IsValid())
|
|
{
|
|
FBlueprintExecutionTrace& ExternalTraceEvent = BlueprintContext->AddNewTraceHistoryEvent();
|
|
ExternalTraceEvent.TraceType = TraceSignal.GetType();
|
|
ExternalTraceEvent.TracePath = ExternalPath;
|
|
ExternalTraceEvent.InstanceName = InstanceName;
|
|
ExternalTraceEvent.FunctionName = CurrentFunctionName;
|
|
ExternalTraceEvent.GraphName = TunnelEntryNode->GetGraphName();
|
|
ExternalTraceEvent.Offset = TraceSignal.GetScriptCodeOffset();
|
|
ExternalTraceEvent.ObservationTime = TraceSignal.GetTime();
|
|
ExternalTraceEvent.ProfilerNode = TunnelExit;
|
|
ExternalTraceEvent.PinReference = TunnelExit->GetExternalPin();
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|