Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

140 lines
7.5 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "PoseSearch/PoseSearchContext.h"
#include "PoseSearch/PoseSearchInteractionLibrary.h"
#include "PoseSearch/PoseSearchInteractionValidator.h"
#include "Subsystems/WorldSubsystem.h"
#include "PoseSearchInteractionSubsystem.generated.h"
#define UE_API POSESEARCH_API
struct FPoseSearchContinuingProperties;
class UAnimInstance;
class UPoseSearchDatabase;
namespace UE::PoseSearch
{
struct FAnimContextInfos;
struct FTagToDatabases;
} // namespace UE::PoseSearch
// World subsystem accepting the publication of characters (via their AnimInstance(s)) FPoseSearchInteractionAvailability, representing the characters willingness to partecipate in an
// interaction with other characters from the next frame forward via Query_AnyThread method.
//
// The same method will return the FPoseSearchBlueprintResult from the PREVIOUS Tick processing (categorization of FPoseSearchInteractionAvailability(s) in multiple FInteractionIsland(s)),
// to the requesting character, containing the animation to play at what time, and the assigned role to partecipate in the selected interaction within the assigned
// FInteractionIsland
// Execution model and threading details:
/////////////////////////////////////////
//
// - by calling UPoseSearchInteractionLibrary::MotionMatchInteraction_Pure(TArray<FPoseSearchInteractionAvailability> Availabilities, UObject* AnimInstance), characters publish their availabilities
// to partecipate in interactions to the UPoseSearchInteractionSubsystem
// - UPoseSearchInteractionSubsystem::Tick processes those FPoseSearchInteractionAvailability(s) and creates/updates UE::PoseSearch::FInteractionIsland. For each FInteractionIsland it injects
// a tick prerequisite via FInteractionIsland::InjectToActor (that calls AddPrerequisite) to all the Actors in the same island.
// NoTe: the next frame the execution will be:
// for each island[k]
// {
// for each Actor[k][i]
// {
// Tick all the TickActorComponents prerequisites, such as CharacterMovementComponent[k][i] (or Mover) in parallel
// }
//
// Tick Island[k].PreTickFunction (that eventually generates the trajectories with all the updated CMCs or Mover)
//
// Tick Actor[k][0].SkeletalMeshComponent (or AnimNextComponent, that performs the MotionMatchInteraction queries for all the involved actors via DoSearch_AnyThread)
//
// Tick Island[k].PostTickFunction (currently just a threading fence for the execution of all the other SkeletalMeshComponent(s) or AnimNextComponent(s))
//
// for each Actor[k][i]
// {
// if (i != 0)
// Tick SkeletalMeshComponent[k][i] (or AnimNextComponent(s) that DoSearch_AnyThread get the cached result calculated by Tick Actor[k][0].SkeletalMeshComponent) in parallel
// }
// }
// - next frame UPoseSearchInteractionLibrary::MotionMatchInteraction_Pure(TArray<FPoseSearchInteractionAvailability> Availabilities, UObject* AnimInstance), with the context of all the published
// availabilities and created islands, will find the associated FInteractionIsland to the AnimInstance and call FInteractionIsland::DoSearch_AnyThread
// (via UPoseSearchInteractionSubsystem::Query_AnyThread) that will perform ALL (YES, ALL, so the bigger the island the slower the execution) the motion matching searches
// for all the possible Actors / databases / Roles combinations, and populate FInteractionIsland::SearchResults with ALL the results for the island.
// Ultimately the MotionMatchInteraction_Pure will return the SearchResults associated to the requesting AnimInstance with information about what animation to play
// at what time with wich Role.
UCLASS(MinimalAPI, Experimental, Category="Animation|Pose Search")
class UPoseSearchInteractionSubsystem : public UTickableWorldSubsystem
{
GENERATED_BODY()
public:
static UE_API UPoseSearchInteractionSubsystem* GetSubsystem_AnyThread(const UObject* AnimInstance);
UE_API virtual void Initialize(FSubsystemCollectionBase& Collection) override;
UE_API virtual void Deinitialize() override;
// it processes FPoseSearchInteractionAvailability(s) and creates/updates FInteractionIsland
UE_API virtual void Tick(float DeltaSeconds) override;
UE_API virtual TStatId GetStatId() const override;
// publishing FPoseSearchInteractionAvailability(s) for the requesting character (via AnimContext as UAnimInstance or UAnimNextComponent)
// and getting the FPoseSearchBlueprintResult from the PREVIOUS Tick update
// containing the animation to play at what time, and the assigned role to partecipate in the selected interaction.
// Either a PoseHistoryName or a PoseHistory are required to perform the associated motion matching searches
// if bValidateResultAgainstAvailabilities is true, the result will be validated against the currently published availabilities,
// that could differ from the previous frame one for this AnimContext, and discarded if not valid within the context of the new availabilities
UE_API void Query_AnyThread(const TArrayView<const FPoseSearchInteractionAvailability> Availabilities, const UObject* AnimContext,
FPoseSearchBlueprintResult& Result, FName PoseHistoryName, const UE::PoseSearch::IPoseHistory* PoseHistory, bool bValidateResultAgainstAvailabilities);
UE_API void GetResult_AnyThread(const UObject* AnimContext, FPoseSearchBlueprintResult& Result, bool bCompareOwningActors = false);
TConstArrayView<UE::PoseSearch::FInteractionIsland*> GetInteractionIslands() const { return Islands; }
private:
#if ENABLE_ANIM_DEBUG
friend struct UE::PoseSearch::FInteractionValidator;
#endif // ENABLE_ANIM_DEBUG
UE::PoseSearch::FInteractionIsland& CreateIsland();
UE::PoseSearch::FInteractionIsland& GetAvailableIsland();
void DestroyIsland(int32 Index);
void DestroyAllIslands();
void RegenerateAllIslands(float DeltaSeconds);
bool ConsolidateAnimContextsAvailabilities();
#if DO_CHECK
bool ValidateAllIslands() const;
#endif // DO_CHECK
void PopulateContinuingProperties(float DeltaSeconds, TArrayView<UE::PoseSearch::FInteractionSearchContext> SearchContexts) const;
UE::PoseSearch::FInteractionIsland* FindIsland(const UObject* AnimContext, bool bCompareOwningActors = false);
#if ENABLE_ANIM_DEBUG
void DebugDrawIslands() const;
void DebugLogTickDependencies() const;
#endif // ENABLE_ANIM_DEBUG
void AddAvailabilities(const TArrayView<const FPoseSearchInteractionAvailability> Availabilities, const UObject* AnimContext, FName PoseHistoryName, const UE::PoseSearch::IPoseHistory* PoseHistory);
void GenerateAnimContextInfosAndTagToDatabases(UE::PoseSearch::FAnimContextInfos& AnimContextInfos, UE::PoseSearch::FTagToDatabases& TagToDatabases) const;
void GenerateSearchContexts(float DeltaSeconds, UE::PoseSearch::FInteractionSearchContexts& SearchContexts) const;
void UpdateValidInteractionSearches();
static void OnInteractionStart(UE::PoseSearch::FValidInteractionSearch& ValidInteractionSearch);
static void OnInteractionContinuing(UE::PoseSearch::FValidInteractionSearch& ValidInteractionSearch);
static void OnInteractionEnd(UE::PoseSearch::FValidInteractionSearch& ValidInteractionSearch);
// it could contain duplicated availabilities
UPROPERTY(Transient)
TArray<FPoseSearchInteractionAnimContextAvailabilities> AnimContextsAvailabilitiesBuffer;
FThreadSafeCounter AnimContextsAvailabilitiesIndex = 0;
int32 AnimContextsAvailabilitiesNum = 0;
// array of groups of characters that needs to be anaylzed together for possible interactions
TArray<UE::PoseSearch::FInteractionIsland*> Islands;
TArray<UE::PoseSearch::FValidInteractionSearch> ValidInteractionSearches;
};
#undef UE_API