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

460 lines
17 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MassAgentSubsystem.h"
#include "MassCommandBuffer.h"
#include "Engine/World.h"
#include "MassEntityManager.h"
#include "MassCommonTypes.h"
#include "MassSimulationSubsystem.h"
#include "MassSpawnerSubsystem.h"
#include "MassActorTypes.h"
#include "MassAgentComponent.h"
#include "MassEntityTemplateRegistry.h"
#include "VisualLogger/VisualLogger.h"
#include "MassEntityView.h"
#include "MassReplicationSubsystem.h"
#include "Engine/NetDriver.h"
#include "MassEntityUtils.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(MassAgentSubsystem)
namespace FMassAgentSubsystemHelper
{
inline void InitializeAgentComponentFragments(const UMassAgentComponent& AgentComp, FMassEntityView& EntityView, const EMassTranslationDirection Direction
, TConstArrayView<FMassEntityTemplateData::FObjectFragmentInitializerFunction> ObjectFragmentInitializers)
{
AActor* Owner = AgentComp.GetOwner();
check(Owner);
for (const FMassEntityTemplateData::FObjectFragmentInitializerFunction& Initializer : ObjectFragmentInitializers)
{
Initializer(*Owner, EntityView, Direction);
}
}
}
//----------------------------------------------------------------------//
// UMassAgentSubsystem
//----------------------------------------------------------------------//
void UMassAgentSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
// making sure UMassSimulationSubsystem gets created before the MassAgentSubsystem
Collection.InitializeDependency<UMassSimulationSubsystem>();
Collection.InitializeDependency<UMassSpawnerSubsystem>();
#if UE_REPLICATION_COMPILE_CLIENT_CODE
Collection.InitializeDependency<UMassReplicationSubsystem>();
#endif // UE_REPLICATION_COMPILE_CLIENT_CODE
UWorld* World = GetWorld();
check(World);
EntityManager = UE::Mass::Utils::GetEntityManagerChecked(*World).AsShared();
SpawnerSystem = UWorld::GetSubsystem<UMassSpawnerSubsystem>(World);
SimulationSystem = UWorld::GetSubsystem<UMassSimulationSubsystem>(World);
SimulationSystem->GetOnProcessingPhaseStarted(EMassProcessingPhase::PrePhysics).AddUObject(this, &UMassAgentSubsystem::OnProcessingPhaseStarted, EMassProcessingPhase::PrePhysics);
#if UE_REPLICATION_COMPILE_CLIENT_CODE
ReplicationSubsystem = UWorld::GetSubsystem<UMassReplicationSubsystem>(World);
check(ReplicationSubsystem);
ReplicationSubsystem->GetOnMassAgentAdded().AddUObject(this, &UMassAgentSubsystem::OnMassAgentAddedToReplication);
ReplicationSubsystem->GetOnRemovingMassAgent().AddUObject(this, &UMassAgentSubsystem::OnMassAgentRemovedFromReplication);
#endif // UE_REPLICATION_COMPILE_CLIENT_CODE
}
void UMassAgentSubsystem::Deinitialize()
{
EntityManager.Reset();
Super::Deinitialize();
}
FMassEntityTemplateID UMassAgentSubsystem::RegisterAgentComponent(UMassAgentComponent& AgentComp)
{
check(EntityManager);
check(SpawnerSystem);
if (AgentComp.IsPuppet())
{
MakePuppet(AgentComp);
return AgentComp.GetTemplateID();
}
// @todo note that this will happen when a regular actor with AgentComp gets unregister and then registered again.
if (AgentComp.GetTemplateID().IsValid())
{
UE_VLOG_UELOG(this, LogMassActor, Warning, TEXT("UMassAgentSubsystem::RegisterAgentComponent called while the given agent component has already been registered (Owner: %s, entity handle %s, template ID %s)")
, *GetNameSafe(AgentComp.GetOwner()), *AgentComp.GetEntityHandle().DebugGetDescription(), *AgentComp.GetTemplateID().ToString());
return FMassEntityTemplateID();
}
AActor* AgentActor = AgentComp.GetOwner();
check(AgentActor);
UWorld* World = AgentActor->GetWorld();
check(World);
const FMassEntityConfig& EntityConfig = AgentComp.GetEntityConfig();
const FMassEntityTemplate& EntityTemplate = EntityConfig.GetOrCreateEntityTemplate(*World);
#if UE_REPLICATION_COMPILE_CLIENT_CODE
if (AgentComp.IsNetSimulating())
{
AgentComp.PuppetReplicationPending();
}
else
#endif // UE_REPLICATION_COMPILE_CLIENT_CODE
{
FMassAgentInitializationQueue& AgentQueue = PendingAgentEntities.FindOrAdd(EntityTemplate.GetTemplateID());
// Agent already in the queue! Earlier conditions should have failed or data is inconsistent.
check(AgentQueue.AgentComponents.Find(&AgentComp) == INDEX_NONE);
AgentQueue.AgentComponents.Add(&AgentComp);
UE_VLOG(this, LogMassActor, Verbose, TEXT("%s registered and PENDING entity creation."), *AgentActor->GetName());
AgentComp.EntityCreationPending();
}
return EntityTemplate.GetTemplateID();
}
void UMassAgentSubsystem::UpdateAgentComponent(const UMassAgentComponent& AgentComp)
{
check(EntityManager);
check(SpawnerSystem)
if (!ensureMsgf(AgentComp.GetEntityHandle().IsValid(), TEXT("Caling %s is valid only for already registered MassAgentComponents"), ANSI_TO_TCHAR(__FUNCTION__)))
{
UE_VLOG(this, LogMassActor, Warning, TEXT("%s: called while the given agent component has not been registered yet (Owner: %s)")
, ANSI_TO_TCHAR(__FUNCTION__), *GetNameSafe(AgentComp.GetOwner()));
return;
}
AActor* AgentActor = AgentComp.GetOwner();
check(AgentActor);
UWorld* World = AgentActor->GetWorld();
check(World);
const FMassEntityConfig& EntityConfig = AgentComp.GetEntityConfig();
const FMassEntityTemplate& EntityTemplate = EntityConfig.GetOrCreateEntityTemplate(*World);
const FMassEntityHandle Entity = AgentComp.GetEntityHandle();
const FMassArchetypeHandle CurrentArchetypeHandle = EntityManager->GetArchetypeForEntity(Entity);
if (CurrentArchetypeHandle == EntityTemplate.GetArchetype())
{
UE_VLOG(this, LogMassActor, Log, TEXT("%s called for %s but no archetype changes have been found")
, ANSI_TO_TCHAR(__FUNCTION__), *AgentActor->GetName());
}
else
{
// the tricky case: we want to move the entity over to the new archetype and initialize its fragments,
// but only the ones that the previous archetype didn't have - "delta initialize"
ensureMsgf(false, TEXT("Not implemented yet"));
// @todo add override flag so that we always initialize a new when moving between archetypes
}
check(AgentComp.GetEntityHandle().IsValid());
}
void UMassAgentSubsystem::UnregisterAgentComponent(UMassAgentComponent& AgentComp)
{
if (!EntityManager || SpawnerSystem == nullptr || SimulationSystem == nullptr)
{
return;
}
if (AgentComp.IsPuppet())
{
if (AgentComp.IsPuppetPendingInitialization())
{
FMassAgentInitializationQueue* PuppetQueue = PendingPuppets.Find(AgentComp.GetTemplateID());
if (ensureMsgf(PuppetQueue, TEXT("Trying to remove a puppet's agent component from initialization queue but there's no such queue")))
{
ensureMsgf(PuppetQueue->AgentComponents.Remove(&AgentComp), TEXT("Trying to remove a puppet's agent component from initialization queue while it's not in the queue"));
}
AgentComp.PuppetInitializationAborted();
}
else if (ensureMsgf(AgentComp.GetEntityHandle().IsValid(), TEXT("Trying to unregister a puppet's agent component while it's neither pending initialization nor having a valid entity handle")) )
{
if (AgentComp.GetPuppetSpecificAddition().IsEmpty() == false)
{
const FMassEntityHandle Entity = AgentComp.GetEntityHandle();
// remove fragments that have been added for the puppet agent
if (EntityManager->IsProcessing())
{
// need to request via command buffer since we can't move entities while processing is happening
FMassArchetypeCompositionDescriptor Composition = AgentComp.GetPuppetSpecificAddition();
EntityManager->Defer().PushCommand<FMassDeferredRemoveCommand>([Entity, Composition](FMassEntityManager& InOutEntityManager)
{
if (InOutEntityManager.IsEntityValid(Entity) == false)
{
return;
}
InOutEntityManager.RemoveCompositionFromEntity(Entity, Composition);
});
}
else
{
EntityManager->RemoveCompositionFromEntity(Entity, AgentComp.GetPuppetSpecificAddition());
}
}
AgentComp.PuppetUnregistrationDone();
}
}
else
{
if (AgentComp.GetEntityHandle().IsValid())
{
// the entity has already been created. Destroy!
const FMassEntityTemplate* EntityTemplate = nullptr;
AActor* AgentActor = AgentComp.GetOwner();
UWorld* World = AgentActor ? AgentActor->GetWorld() : nullptr;
if (ensure(World))
{
const FMassEntityConfig& EntityConfig = AgentComp.GetEntityConfig();
EntityTemplate = &EntityConfig.GetEntityTemplateChecked(*World);
}
FMassEntityHandle Entity = AgentComp.GetEntityHandle();
// Clearing the entity before it become invalid as the clear contains notifications
AgentComp.ClearEntityHandle();
// Destroy the entity
if (EntityManager->IsProcessing())
{
// need to request via command buffer since we can't move entities while processing is happening
EntityManager->Defer().DestroyEntity(Entity);
}
else if (ensure(EntityTemplate))
{
SpawnerSystem->DestroyEntities(TArrayView<FMassEntityHandle>(&Entity, 1));
}
}
else if (AgentComp.IsEntityPendingCreation())
{
// hasn't been registered yet. Just remove it from the queue
FMassAgentInitializationQueue* AgentQueue = PendingAgentEntities.Find(AgentComp.GetTemplateID());
if (ensureMsgf(AgentQueue, TEXT("Trying to remove an agent component from initialization queue but there's no such queue")))
{
ensureMsgf(AgentQueue->AgentComponents.Remove(&AgentComp), TEXT("Trying to remove an agent component from initialization queue it's missing from the queue"));
}
AgentComp.EntityCreationAborted();
}
}
}
void UMassAgentSubsystem::ShutdownAgentComponent(UMassAgentComponent& AgentComp)
{
UnregisterAgentComponent(AgentComp);
#if UE_REPLICATION_COMPILE_CLIENT_CODE
ReplicatedAgentComponents.Remove(AgentComp.GetNetID());
#endif // UE_REPLICATION_COMPILE_CLIENT_CODE
}
void UMassAgentSubsystem::MakePuppet(UMassAgentComponent& AgentComp)
{
if (!ensureMsgf(AgentComp.GetTemplateID().IsValid(), TEXT("%s tried to used %s as puppet but it doesn't have a valid entity template ID"), ANSI_TO_TCHAR(__FUNCTION__), *GetNameSafe(AgentComp.GetOwner())))
{
return;
}
if (AgentComp.IsEntityPendingCreation())
{
FMassAgentInitializationQueue* AgentQueue = PendingAgentEntities.Find(AgentComp.GetTemplateID());
if (ensureMsgf(AgentQueue, TEXT("Trying to remove an agent component from initialization queue but there's no such queue")))
{
ensureMsgf(AgentQueue->AgentComponents.Remove(&AgentComp), TEXT("Trying to remove an agent component from initialization queue it's missing from the queue"));
}
}
FMassAgentInitializationQueue& PuppetQueue = PendingPuppets.FindOrAdd(AgentComp.GetTemplateID());
// Agent already in the queue! Earlier conditions should have failed or data is inconsistent.
check(PuppetQueue.AgentComponents.Find(&AgentComp) == INDEX_NONE);
PuppetQueue.AgentComponents.Add(&AgentComp);
AgentComp.PuppetInitializationPending();
}
void UMassAgentSubsystem::OnProcessingPhaseStarted(const float DeltaSeconds, const EMassProcessingPhase Phase)
{
switch (Phase)
{
case EMassProcessingPhase::PrePhysics:
// initialize what needs initialization
if (PendingAgentEntities.Num() > 0 || PendingPuppets.Num() > 0)
{
HandlePendingInitialization();
}
break;
default:
// unhandled phases, by design, not every phase needs to be handled by the Actor subsystem
break;
}
}
void UMassAgentSubsystem::HandlePendingInitialization()
{
check(EntityManager);
check(SpawnerSystem);
check(SimulationSystem);
check(SimulationSystem->IsDuringMassProcessing() == false);
{
// this block is scoped so that CreationContext gets released by the end
TSharedPtr<FMassEntityManager::FEntityCreationContext> CreationContext = EntityManager->GetOrMakeCreationContext();
for (TTuple<FMassEntityTemplateID, FMassAgentInitializationQueue>& Data : PendingAgentEntities)
{
const FMassEntityTemplateID EntityTemplateID = Data.Get<0>();
const TSharedRef<FMassEntityTemplate>* EntityTemplatePtr = SpawnerSystem->GetTemplateRegistryInstance().FindTemplateFromTemplateID(EntityTemplateID);
check(EntityTemplatePtr);
const FMassEntityTemplate& EntityTemplate = EntityTemplatePtr->Get();
TArray<TObjectPtr<UMassAgentComponent>>& AgentComponents = Data.Get<1>().AgentComponents;
const int32 NewEntityCount = AgentComponents.Num();
if (NewEntityCount <= 0)
{
// this case is perfectly fine, all agents registered and unregistered before we processed the queue
continue;
}
TArray<FMassEntityHandle> Entities;
CreationContext = SpawnerSystem->SpawnEntities(EntityTemplate, NewEntityCount, Entities);
check(Entities.Num() == NewEntityCount);
if (EntityTemplate.GetObjectFragmentInitializers().Num())
{
const TConstArrayView<FMassEntityTemplateData::FObjectFragmentInitializerFunction> ObjectFragmentInitializers = EntityTemplate.GetObjectFragmentInitializers();
for (int AgentIndex = 0; AgentIndex < Entities.Num(); ++AgentIndex)
{
FMassEntityView EntityView(EntityTemplate.GetArchetype(), Entities[AgentIndex]);
FMassAgentSubsystemHelper::InitializeAgentComponentFragments(*AgentComponents[AgentIndex], EntityView, EMassTranslationDirection::ActorToMass, ObjectFragmentInitializers);
}
}
for (int AgentIndex = 0; AgentIndex < Entities.Num(); ++AgentIndex)
{
AgentComponents[AgentIndex]->SetEntityHandle(Entities[AgentIndex]);
}
}
}
PendingAgentEntities.Reset();
for (TTuple<FMassEntityTemplateID, FMassAgentInitializationQueue>& Data : PendingPuppets)
{
const FMassEntityTemplateID EntityTemplateID = Data.Get<0>();
const TSharedRef<FMassEntityTemplate>* EntityTemplatePtr = SpawnerSystem->GetTemplateRegistryInstance().FindTemplateFromTemplateID(EntityTemplateID);
if (!ensure(EntityTemplatePtr))
{
// note that this condition is temporary, we'll be switched to a `check` once we set up characters
continue;
}
const FMassEntityTemplate& EntityTemplate = EntityTemplatePtr->Get();
const FMassArchetypeCompositionDescriptor TemplateDescriptor = EntityTemplate.GetCompositionDescriptor();
TArray<TObjectPtr<UMassAgentComponent>>& AgentComponents = Data.Get<1>().AgentComponents;
for (UMassAgentComponent* AgentComp : AgentComponents)
{
const FMassEntityHandle PuppetEntity = AgentComp->GetEntityHandle();
if (!ensureMsgf(PuppetEntity.IsSet(), TEXT("Trying to initialize puppet's fragments while the pupped doesn't have a corresponding Entity identifier set. This should not happen.")))
{
continue;
}
FMassArchetypeCompositionDescriptor& PuppetDescriptor = AgentComp->GetMutablePuppetSpecificAddition();
PuppetDescriptor = TemplateDescriptor;
EntityManager->AddCompositionToEntity_GetDelta(PuppetEntity, PuppetDescriptor);
if (EntityTemplate.GetObjectFragmentInitializers().Num())
{
const FMassArchetypeHandle ArchetypeHandle = EntityManager->GetArchetypeForEntity(PuppetEntity);
FMassEntityView EntityView(ArchetypeHandle, PuppetEntity);
FMassAgentSubsystemHelper::InitializeAgentComponentFragments(*AgentComp, EntityView, EMassTranslationDirection::MassToActor, EntityTemplate.GetObjectFragmentInitializers());
}
AgentComp->PuppetInitializationDone();
}
}
PendingPuppets.Reset();
}
void UMassAgentSubsystem::NotifyMassAgentComponentReplicated(UMassAgentComponent& AgentComp)
{
#if UE_REPLICATION_COMPILE_CLIENT_CODE
UWorld* World = GetWorld();
if (World && ensureMsgf(World->IsNetMode(NM_Client), TEXT("%s: Expecting to only be called in network game on the client"), ANSI_TO_TCHAR(__FUNCTION__)))
{
check(ReplicationSubsystem);
check(AgentComp.GetNetID().IsValid());
check(!ReplicatedAgentComponents.Find(AgentComp.GetNetID()));
ReplicatedAgentComponents.Add(AgentComp.GetNetID(), &AgentComp);
const FMassEntityHandle Entity = ReplicationSubsystem->FindEntity(AgentComp.GetNetID());
// If not found, the NotifyMassAgentAddedToReplication will link it later once replicated.
if (Entity.IsSet())
{
AgentComp.SetReplicatedPuppetHandle(FMassEntityHandle(Entity));
MakePuppet(AgentComp);
}
else
{
AgentComp.MakePuppetAReplicatedOrphan();
}
}
#endif // UE_REPLICATION_COMPILE_CLIENT_CODE
}
void UMassAgentSubsystem::NotifyMassAgentComponentEntityAssociated(const UMassAgentComponent& AgentComp) const
{
OnMassAgentComponentEntityAssociated.Broadcast(AgentComp);
}
void UMassAgentSubsystem::NotifyMassAgentComponentEntityDetaching(const UMassAgentComponent& AgentComp) const
{
OnMassAgentComponentEntityDetaching.Broadcast(AgentComp);
}
void UMassAgentSubsystem::OnMassAgentAddedToReplication(FMassNetworkID NetID, FMassEntityHandle Entity)
{
#if UE_REPLICATION_COMPILE_CLIENT_CODE
UWorld* World = GetWorld();
if (World && World->IsNetMode(NM_Client))
{
if (TObjectPtr<UMassAgentComponent>* AgentComp = ReplicatedAgentComponents.Find(NetID))
{
(*AgentComp)->SetReplicatedPuppetHandle(FMassEntityHandle(Entity));
MakePuppet(**AgentComp);
}
}
#endif // UE_REPLICATION_COMPILE_CLIENT_CODE
}
void UMassAgentSubsystem::OnMassAgentRemovedFromReplication(FMassNetworkID NetID, FMassEntityHandle Entity)
{
#if UE_REPLICATION_COMPILE_CLIENT_CODE
UWorld* World = GetWorld();
if (World && World->IsNetMode(NM_Client))
{
if (TObjectPtr<UMassAgentComponent>* AgentComp = ReplicatedAgentComponents.Find(NetID))
{
UnregisterAgentComponent(**AgentComp);
(*AgentComp)->ClearReplicatedPuppetHandle();
}
}
#endif // UE_REPLICATION_COMPILE_CLIENT_CODE
}