Files
UnrealEngine/Engine/Source/Runtime/GameplayTasks/Private/GameplayTasksComponent.cpp
2025-05-18 13:04:45 +08:00

961 lines
27 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "GameplayTasksComponent.h"
#include "UObject/Package.h"
#include "Net/UnrealNetwork.h"
#include "Engine/ActorChannel.h"
#include "GameFramework/Actor.h"
#include "VisualLogger/VisualLoggerTypes.h"
#include "VisualLogger/VisualLogger.h"
#include "GameplayTasksPrivate.h"
#include "Logging/MessageLog.h"
#include "Net/Core/PushModel/PushModel.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(GameplayTasksComponent)
#define LOCTEXT_NAMESPACE "GameplayTasksComponent"
namespace
{
FORCEINLINE const TCHAR* GetGameplayTaskEventName(EGameplayTaskEvent Event)
{
/*static const UEnum* GameplayTaskEventEnum = StaticEnum<EGameplayTaskEvent>();
return GameplayTaskEventEnum->GetDisplayNameTextByValue(static_cast<int64>(Event)).ToString();*/
return Event == EGameplayTaskEvent::Add ? TEXT("Add") : TEXT("Remove");
}
}
UGameplayTasksComponent::FEventLock::FEventLock(UGameplayTasksComponent* InOwner) : Owner(InOwner)
{
if (Owner)
{
Owner->EventLockCounter++;
}
}
UGameplayTasksComponent::FEventLock::~FEventLock()
{
if (Owner)
{
Owner->EventLockCounter--;
if (Owner->TaskEvents.Num() && Owner->CanProcessEvents())
{
Owner->ProcessTaskEvents();
}
}
}
UGameplayTasksComponent::UGameplayTasksComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PrimaryComponentTick.TickGroup = TG_DuringPhysics;
PrimaryComponentTick.bStartWithTickEnabled = false;
PrimaryComponentTick.bCanEverTick = true;
SetIsReplicatedByDefault(true);
bInEventProcessingInProgress = false;
TopActivePriority = 0;
}
void UGameplayTasksComponent::ReadyForReplication()
{
Super::ReadyForReplication();
REDIRECT_TO_VLOG(GetOwner());
if (IsUsingRegisteredSubObjectList())
{
for (UGameplayTask* SimulatedTask : SimulatedTasks)
{
if (SimulatedTask)
{
AddReplicatedSubObject(SimulatedTask, COND_SkipOwner);
}
}
}
}
void UGameplayTasksComponent::OnGameplayTaskActivated(UGameplayTask& Task)
{
// process events after finishing all operations
FEventLock ScopeEventLock(this);
KnownTasks.Add(&Task);
if (Task.IsTickingTask())
{
check(TickingTasks.Contains(&Task) == false);
TickingTasks.Add(&Task);
// If this is our first ticking task, set this component as active so it begins ticking
if (TickingTasks.Num() == 1)
{
UpdateShouldTick();
}
}
if (Task.IsSimulatedTask())
{
const bool bWasAdded = AddSimulatedTask(&Task);
check(bWasAdded == true);
}
IGameplayTaskOwnerInterface* TaskOwner = Task.GetTaskOwner();
if (!Task.IsOwnedByTasksComponent() && TaskOwner)
{
TaskOwner->OnGameplayTaskActivated(Task);
}
}
void UGameplayTasksComponent::OnGameplayTaskDeactivated(UGameplayTask& Task)
{
// process events after finishing all operations
FEventLock ScopeEventLock(this);
const bool bIsFinished = (Task.GetState() == EGameplayTaskState::Finished);
if (Task.GetChildTask() && bIsFinished)
{
if (Task.HasOwnerFinished())
{
Task.GetChildTask()->TaskOwnerEnded();
}
else
{
Task.GetChildTask()->EndTask();
}
}
if (Task.IsTickingTask())
{
// If we are removing our last ticking task, set this component as inactive so it stops ticking
TickingTasks.RemoveSingleSwap(&Task);
}
if (bIsFinished)
{
// using RemoveSwap rather than RemoveSingleSwap since a Task can be added
// to KnownTasks both when activating as well as unpausing
// while removal happens only once. It's cheaper to handle it here.
KnownTasks.RemoveSwap(&Task);
}
if (Task.IsSimulatedTask())
{
RemoveSimulatedTask(&Task);
}
// Resource-using task
if (Task.RequiresPriorityOrResourceManagement() && bIsFinished)
{
OnTaskEnded(Task);
}
IGameplayTaskOwnerInterface* TaskOwner = Task.GetTaskOwner();
if (!Task.IsOwnedByTasksComponent() && !Task.HasOwnerFinished() && TaskOwner)
{
TaskOwner->OnGameplayTaskDeactivated(Task);
}
UpdateShouldTick();
}
void UGameplayTasksComponent::OnTaskEnded(UGameplayTask& Task)
{
ensure(Task.RequiresPriorityOrResourceManagement() == true);
RemoveResourceConsumingTask(Task);
}
void UGameplayTasksComponent::GetLifetimeReplicatedProps(TArray< FLifetimeProperty >& OutLifetimeProps) const
{
// Intentionally not calling super: We do not want to replicate bActive which controls ticking. We sometimes need to tick on client predictively.
DISABLE_ALL_CLASS_REPLICATED_PROPERTIES_FAST(Super, EFieldIteratorFlags::IncludeSuper);
const FDoRepLifetimeParams Params{ COND_SkipOwner, REPNOTIFY_Always, true };
DOREPLIFETIME_WITH_PARAMS_FAST(UGameplayTasksComponent, SimulatedTasks, Params);
}
bool UGameplayTasksComponent::ReplicateSubobjects(UActorChannel* Channel, class FOutBunch *Bunch, FReplicationFlags *RepFlags)
{
#if SUBOBJECT_TRANSITION_VALIDATION
// When true it means we are calling this function to find any leftover replicated subobjects in classes that transitioned to the new registry list.
// This shared class needs to keep supporting the old ways until we fully deprecate the API, so by only returning false we prevent the ensures to trigger
if (UActorChannel::CanIgnoreDeprecatedReplicateSubObjects())
{
return false;
}
#endif
bool WroteSomething = Super::ReplicateSubobjects(Channel, Bunch, RepFlags);
if (!RepFlags->bNetOwner)
{
for (UGameplayTask* SimulatedTask : GetSimulatedTasks())
{
if (IsValid(SimulatedTask))
{
WroteSomething |= Channel->ReplicateSubobject(SimulatedTask, *Bunch, *RepFlags);
}
}
}
return WroteSomething;
}
void UGameplayTasksComponent::OnRep_SimulatedTasks(const TArray<UGameplayTask*>& PreviousSimulatedTasks)
{
if (IsUsingRegisteredSubObjectList())
{
// Find if any tasks got removed
for (UGameplayTask* OldSimulatedTask : PreviousSimulatedTasks)
{
if (OldSimulatedTask)
{
const bool bIsRemoved = SimulatedTasks.Find(OldSimulatedTask) == INDEX_NONE;
if (bIsRemoved)
{
RemoveReplicatedSubObject(OldSimulatedTask);
}
}
}
}
for (UGameplayTask* SimulatedTask : GetSimulatedTasks())
{
if (SimulatedTask)
{
// If the task needs to be ticked and isn't yet.
if (SimulatedTask->IsTickingTask() && TickingTasks.Contains(SimulatedTask) == false)
{
SimulatedTask->InitSimulatedTask(*this);
TickingTasks.Add(SimulatedTask);
// If this is our first ticking task, set this component as active so it begins ticking
if (TickingTasks.Num() == 1)
{
UpdateShouldTick();
}
}
// See if it's a new task that needs to be registered
if (IsUsingRegisteredSubObjectList())
{
const bool bIsNew = PreviousSimulatedTasks.Find(SimulatedTask) == INDEX_NONE;
if (bIsNew)
{
AddReplicatedSubObject(SimulatedTask, COND_SkipOwner);
}
}
}
}
}
void UGameplayTasksComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction)
{
SCOPE_CYCLE_COUNTER(STAT_TickGameplayTasks);
Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
// Because we have no control over what a task may do when it ticks, we must be careful.
// Ticking a task may kill the task right here. It could also potentially kill another task
// which was waiting on the original task to do something. Since when a tasks is killed, it removes
// itself from the TickingTask list, we will make a copy of the tasks we want to service before ticking any
int32 NumTickingTasks = TickingTasks.Num();
int32 NumActuallyTicked = 0;
switch (NumTickingTasks)
{
case 0:
break;
case 1:
{
UGameplayTask* TickingTask = TickingTasks[0];
if (IsValid(TickingTask))
{
TickingTask->TickTask(DeltaTime);
NumActuallyTicked++;
}
}
break;
default:
{
static TArray<UGameplayTask*> LocalTickingTasks;
LocalTickingTasks.Reset();
LocalTickingTasks.Append(TickingTasks);
for (UGameplayTask* TickingTask : LocalTickingTasks)
{
if (IsValid(TickingTask))
{
TickingTask->TickTask(DeltaTime);
NumActuallyTicked++;
}
}
}
break;
};
// Stop ticking if no more active tasks
if (NumActuallyTicked == 0)
{
TickingTasks.SetNum(0, EAllowShrinking::No);
UpdateShouldTick();
}
}
bool UGameplayTasksComponent::GetShouldTick() const
{
return TickingTasks.Num() > 0;
}
void UGameplayTasksComponent::RequestTicking()
{
if (IsActive() == false)
{
SetActive(true);
}
}
void UGameplayTasksComponent::UpdateShouldTick()
{
const bool bShouldTick = GetShouldTick();
if (IsActive() != bShouldTick)
{
SetActive(bShouldTick);
}
}
//----------------------------------------------------------------------//
// Priority and resources handling
//----------------------------------------------------------------------//
void UGameplayTasksComponent::AddTaskReadyForActivation(UGameplayTask& NewTask)
{
UE_VLOG(this, LogGameplayTasks, Log, TEXT("AddTaskReadyForActivation %s"), *NewTask.GetName());
ensure(NewTask.RequiresPriorityOrResourceManagement() == true);
TaskEvents.Add(FGameplayTaskEventData(EGameplayTaskEvent::Add, NewTask));
// trigger the actual processing only if it was the first event added to the list
if (TaskEvents.Num() == 1 && CanProcessEvents())
{
ProcessTaskEvents();
}
}
void UGameplayTasksComponent::RemoveResourceConsumingTask(UGameplayTask& Task)
{
UE_VLOG(this, LogGameplayTasks, Log, TEXT("RemoveResourceConsumingTask %s"), *Task.GetName());
TaskEvents.Add(FGameplayTaskEventData(EGameplayTaskEvent::Remove, Task));
// trigger the actual processing only if it was the first event added to the list
if (TaskEvents.Num() == 1 && CanProcessEvents())
{
ProcessTaskEvents();
}
}
void UGameplayTasksComponent::EndAllResourceConsumingTasksOwnedBy(const IGameplayTaskOwnerInterface& TaskOwner)
{
FEventLock ScopeEventLock(this);
for (int32 Idx = 0; Idx < TaskPriorityQueue.Num(); Idx++)
{
if (TaskPriorityQueue[Idx] && TaskPriorityQueue[Idx]->GetTaskOwner() == &TaskOwner)
{
// finish task, remove event will be processed after all locks are cleared
TaskPriorityQueue[Idx]->TaskOwnerEnded();
}
}
}
bool UGameplayTasksComponent::FindAllResourceConsumingTasksOwnedBy(const IGameplayTaskOwnerInterface& TaskOwner, TArray<UGameplayTask*>& FoundTasks) const
{
int32 NumFound = 0;
for (int32 TaskIndex = 0; TaskIndex < TaskPriorityQueue.Num(); TaskIndex++)
{
if (TaskPriorityQueue[TaskIndex] && TaskPriorityQueue[TaskIndex]->GetTaskOwner() == &TaskOwner)
{
FoundTasks.Add(TaskPriorityQueue[TaskIndex]);
NumFound++;
}
}
return (NumFound > 0);
}
UGameplayTask* UGameplayTasksComponent::FindResourceConsumingTaskByName(const FName TaskInstanceName) const
{
for (int32 TaskIndex = 0; TaskIndex < TaskPriorityQueue.Num(); TaskIndex++)
{
if (TaskPriorityQueue[TaskIndex] && TaskPriorityQueue[TaskIndex]->GetInstanceName() == TaskInstanceName)
{
return TaskPriorityQueue[TaskIndex];
}
}
return nullptr;
}
bool UGameplayTasksComponent::HasActiveTasks(UClass* TaskClass) const
{
for (int32 Idx = 0; Idx < KnownTasks.Num(); Idx++)
{
if (KnownTasks[Idx] && KnownTasks[Idx]->IsA(TaskClass))
{
return true;
}
}
return false;
}
void UGameplayTasksComponent::ProcessTaskEvents()
{
static const int32 MaxIterations = 16;
bInEventProcessingInProgress = true;
int32 IterCounter = 0;
while (TaskEvents.Num() > 0)
{
IterCounter++;
if (IterCounter > MaxIterations)
{
UE_VLOG(this, LogGameplayTasks, Error, TEXT("UGameplayTasksComponent::ProcessTaskEvents has exceeded allowes number of iterations. Check your GameplayTasks for logic loops!"));
TaskEvents.Reset();
break;
}
for (int32 EventIndex = 0; EventIndex < TaskEvents.Num(); ++EventIndex)
{
UE_VLOG(this, LogGameplayTasks, Verbose, TEXT("UGameplayTasksComponent::ProcessTaskEvents: %s event %s")
, *TaskEvents[EventIndex].RelatedTask.GetName(), GetGameplayTaskEventName(TaskEvents[EventIndex].Event));
if (!IsValid(&TaskEvents[EventIndex].RelatedTask))
{
UE_VLOG(this, LogGameplayTasks, Verbose, TEXT("%s is invalid"), *TaskEvents[EventIndex].RelatedTask.GetName());
// we should ignore it, but just in case run the removal code.
RemoveTaskFromPriorityQueue(TaskEvents[EventIndex].RelatedTask);
continue;
}
switch (TaskEvents[EventIndex].Event)
{
case EGameplayTaskEvent::Add:
if (TaskEvents[EventIndex].RelatedTask.TaskState != EGameplayTaskState::Finished)
{
AddTaskToPriorityQueue(TaskEvents[EventIndex].RelatedTask);
}
else
{
UE_VLOG(this, LogGameplayTasks, Error, TEXT("UGameplayTasksComponent::ProcessTaskEvents trying to add a finished task to priority queue!"));
}
break;
case EGameplayTaskEvent::Remove:
RemoveTaskFromPriorityQueue(TaskEvents[EventIndex].RelatedTask);
break;
default:
checkNoEntry();
break;
}
}
TaskEvents.Reset();
UpdateTaskActivations();
// task activation changes may create new events, loop over to check it
}
bInEventProcessingInProgress = false;
}
void UGameplayTasksComponent::AddTaskToPriorityQueue(UGameplayTask& NewTask)
{
if ((NewTask.GetResourceOverlapPolicy() == ETaskResourceOverlapPolicy::RequestCancelAndStartOnTop)
|| (NewTask.GetResourceOverlapPolicy() == ETaskResourceOverlapPolicy::RequestCancelAndStartAtEnd))
{
const FGameplayResourceSet NewClaimedResources = NewTask.GetClaimedResources();
TArray<UGameplayTask*, TInlineAllocator<2>> CancelList;
for (UGameplayTask* Task : TaskPriorityQueue)
{
if (Task != nullptr
&& Task->GetPriority() <= NewTask.GetPriority()
&& Task->GetClaimedResources().HasAnyID(NewClaimedResources))
{
// Postpone cancelling, as cancel can call EndTask() and may alter the TaskPriorityQueue.
CancelList.Add(Task);
}
}
for (UGameplayTask* Task : CancelList)
{
Task->ExternalCancel();
}
}
const bool bStartOnTopOfSamePriority = (NewTask.GetResourceOverlapPolicy() == ETaskResourceOverlapPolicy::StartOnTop)
|| (NewTask.GetResourceOverlapPolicy() == ETaskResourceOverlapPolicy::RequestCancelAndStartOnTop);
int32 InsertionPoint = INDEX_NONE;
for (int32 Idx = 0; Idx < TaskPriorityQueue.Num(); ++Idx)
{
if (TaskPriorityQueue[Idx] == nullptr)
{
continue;
}
if ((bStartOnTopOfSamePriority && TaskPriorityQueue[Idx]->GetPriority() <= NewTask.GetPriority())
|| (!bStartOnTopOfSamePriority && TaskPriorityQueue[Idx]->GetPriority() < NewTask.GetPriority()))
{
TaskPriorityQueue.Insert(&NewTask, Idx);
InsertionPoint = Idx;
break;
}
}
if (InsertionPoint == INDEX_NONE)
{
TaskPriorityQueue.Add(&NewTask);
}
}
void UGameplayTasksComponent::RemoveTaskFromPriorityQueue(UGameplayTask& Task)
{
const int32 RemovedTaskIndex = TaskPriorityQueue.Find(&Task);
if (RemovedTaskIndex != INDEX_NONE)
{
TaskPriorityQueue.RemoveAt(RemovedTaskIndex, EAllowShrinking::No);
}
else
{
// take a note and ignore
UE_VLOG(this, LogGameplayTasks, Verbose, TEXT("RemoveTaskFromPriorityQueue for %s called, but it's not in the queue. Might have been already removed"), *Task.GetName());
}
}
void UGameplayTasksComponent::UpdateTaskActivations()
{
FGameplayResourceSet ResourcesClaimed;
bool bHasNulls = false;
if (TaskPriorityQueue.Num() > 0)
{
TArray<UGameplayTask*> ActivationList;
ActivationList.Reserve(TaskPriorityQueue.Num());
FGameplayResourceSet ResourcesBlocked;
for (int32 TaskIndex = 0; TaskIndex < TaskPriorityQueue.Num(); ++TaskIndex)
{
if (TaskPriorityQueue[TaskIndex])
{
const FGameplayResourceSet RequiredResources = TaskPriorityQueue[TaskIndex]->GetRequiredResources();
const FGameplayResourceSet ClaimedResources = TaskPriorityQueue[TaskIndex]->GetClaimedResources();
if (RequiredResources.GetOverlap(ResourcesBlocked).IsEmpty())
{
// postpone activations, it's some tasks (like MoveTo) require pausing old ones first
ActivationList.Add(TaskPriorityQueue[TaskIndex]);
ResourcesClaimed.AddSet(ClaimedResources);
}
else
{
TaskPriorityQueue[TaskIndex]->PauseInTaskQueue();
}
ResourcesBlocked.AddSet(ClaimedResources);
}
else
{
bHasNulls = true;
UE_VLOG(this, LogGameplayTasks, Warning, TEXT("UpdateTaskActivations found null entry in task queue at index:%d!"), TaskIndex);
}
}
for (int32 Idx = 0; Idx < ActivationList.Num(); Idx++)
{
// check if task wasn't already finished as a result of activating previous elements of this list
if (IsValid(ActivationList[Idx])
&& ActivationList[Idx]->IsFinished() == false)
{
ActivationList[Idx]->ActivateInTaskQueue();
}
}
}
SetCurrentlyClaimedResources(ResourcesClaimed);
// remove all null entries after processing activation changes
if (bHasNulls)
{
TaskPriorityQueue.RemoveAll([](UGameplayTask* Task) { return Task == nullptr; });
}
}
void UGameplayTasksComponent::SetCurrentlyClaimedResources(FGameplayResourceSet NewClaimedSet)
{
if (CurrentlyClaimedResources != NewClaimedSet)
{
FGameplayResourceSet ReleasedResources = FGameplayResourceSet(CurrentlyClaimedResources).RemoveSet(NewClaimedSet);
FGameplayResourceSet ClaimedResources = FGameplayResourceSet(NewClaimedSet).RemoveSet(CurrentlyClaimedResources);
CurrentlyClaimedResources = NewClaimedSet;
OnClaimedResourcesChange.Broadcast(ClaimedResources, ReleasedResources);
}
}
//----------------------------------------------------------------------//
// debugging
//----------------------------------------------------------------------//
#if WITH_GAMEPLAYTASK_DEBUG
FString UGameplayTasksComponent::GetTickingTasksDescription() const
{
FString TasksDescription;
for (auto& Task : TickingTasks)
{
if (Task)
{
TasksDescription += FString::Printf(TEXT("\n%s %s"), *GetTaskStateName(Task->GetState()), *Task->GetDebugDescription());
}
else
{
TasksDescription += TEXT("\nNULL");
}
}
return TasksDescription;
}
FString UGameplayTasksComponent::GetKnownTasksDescription() const
{
FString TasksDescription;
for (auto& Task : KnownTasks)
{
if (Task)
{
TasksDescription += FString::Printf(TEXT("\n%s %s"), *GetTaskStateName(Task->GetState()), *Task->GetDebugDescription());
}
else
{
TasksDescription += TEXT("\nNULL");
}
}
return TasksDescription;
}
FString UGameplayTasksComponent::GetTasksPriorityQueueDescription() const
{
FString TasksDescription;
for (auto Task : TaskPriorityQueue)
{
if (Task != nullptr)
{
TasksDescription += FString::Printf(TEXT("\n%s %s"), *GetTaskStateName(Task->GetState()), *Task->GetDebugDescription());
}
else
{
TasksDescription += TEXT("\nNULL");
}
}
return TasksDescription;
}
FString UGameplayTasksComponent::GetTaskStateName(EGameplayTaskState Value)
{
static const UEnum* Enum = StaticEnum<EGameplayTaskState>();
check(Enum);
return Enum->GetNameStringByValue(int64(Value));
}
#endif // WITH_GAMEPLAYTASK_DEBUG
FConstGameplayTaskIterator UGameplayTasksComponent::GetTickingTaskIterator() const
{
return TickingTasks.CreateConstIterator();
}
FConstGameplayTaskIterator UGameplayTasksComponent::GetKnownTaskIterator() const
{
return KnownTasks.CreateConstIterator();
}
FConstGameplayTaskIterator UGameplayTasksComponent::GetPriorityQueueIterator() const
{
return TaskPriorityQueue.CreateConstIterator();
}
FConstGameplayTaskIterator UGameplayTasksComponent::GetSimulatedTaskIterator() const
{
return SimulatedTasks.CreateConstIterator();
}
#if ENABLE_VISUAL_LOG
void UGameplayTasksComponent::GrabDebugSnapshot(FVisualLogEntry* Snapshot) const
{
DescribeSelfToVisLog(Snapshot);
}
void UGameplayTasksComponent::DescribeSelfToVisLog(FVisualLogEntry* Snapshot) const
{
static const FString CategoryName = TEXT("GameplayTasks");
static const FString PriorityQueueName = TEXT("Priority Queue");
static const FString OtherTasksName = TEXT("Other tasks");
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (!IsThisNotNull(this, "UGameplayTasksComponent::DescribeSelfToVisLog") || !IsValidChecked(this))
PRAGMA_ENABLE_DEPRECATION_WARNINGS
{
return;
}
FString NotInQueueDesc;
for (auto& Task : KnownTasks)
{
if (Task)
{
if (!Task->RequiresPriorityOrResourceManagement())
{
NotInQueueDesc += FString::Printf(TEXT("\n%s %s %s %s"),
*GetTaskStateName(Task->GetState()), *Task->GetDebugDescription(),
Task->IsTickingTask() ? TEXT("[TICK]") : TEXT(""),
Task->IsSimulatedTask() ? TEXT("[REP]") : TEXT(""));
}
}
else
{
NotInQueueDesc += TEXT("\nNULL");
}
}
for (const TObjectPtr<UGameplayTask>& Task : SimulatedTasks)
{
if (Task && !KnownTasks.Contains(Task))
{
NotInQueueDesc += FString::Printf(TEXT("\n%s %s %s %s"),
*GetTaskStateName(Task->GetState()), *Task->GetDebugDescription(),
Task->IsTickingTask() ? TEXT("[TICK]") : TEXT(""),
Task->IsSimulatedTask() ? TEXT("[REP]") : TEXT(""));
}
}
FVisualLogStatusCategory StatusCategory(CategoryName);
StatusCategory.Add(OtherTasksName, NotInQueueDesc);
StatusCategory.Add(PriorityQueueName, GetTasksPriorityQueueDescription());
Snapshot->Status.Add(StatusCategory);
}
#endif // ENABLE_VISUAL_LOG
EGameplayTaskRunResult UGameplayTasksComponent::RunGameplayTask(IGameplayTaskOwnerInterface& TaskOwner, UGameplayTask& Task, uint8 Priority, FGameplayResourceSet AdditionalRequiredResources, FGameplayResourceSet AdditionalClaimedResources)
{
const FText NoneText = FText::FromString(TEXT("None"));
if (Task.GetState() == EGameplayTaskState::Paused || Task.GetState() == EGameplayTaskState::Active)
{
// return as success if already running for the same owner, failure otherwise
return Task.GetTaskOwner() == &TaskOwner
? (Task.GetState() == EGameplayTaskState::Paused ? EGameplayTaskRunResult::Success_Paused : EGameplayTaskRunResult::Success_Active)
: EGameplayTaskRunResult::Error;
}
// this is a valid situation if the task has been created via "Construct Object" mechanics
if (Task.GetState() == EGameplayTaskState::Uninitialized)
{
Task.InitTask(TaskOwner, Priority);
}
Task.AddRequiredResourceSet(AdditionalRequiredResources);
Task.AddClaimedResourceSet(AdditionalClaimedResources);
Task.ReadyForActivation();
switch (Task.GetState())
{
case EGameplayTaskState::AwaitingActivation:
case EGameplayTaskState::Paused:
return EGameplayTaskRunResult::Success_Paused;
case EGameplayTaskState::Active:
return EGameplayTaskRunResult::Success_Active;
case EGameplayTaskState::Finished:
return EGameplayTaskRunResult::Success_Active;
}
return EGameplayTaskRunResult::Error;
}
TArray<TObjectPtr<UGameplayTask>>& UGameplayTasksComponent::GetSimulatedTasks_Mutable()
{
MARK_PROPERTY_DIRTY_FROM_NAME(UGameplayTasksComponent, SimulatedTasks, this);
return SimulatedTasks;
}
bool UGameplayTasksComponent::AddSimulatedTask(UGameplayTask* NewTask)
{
if (NewTask == nullptr)
{
return false;
}
if (SimulatedTasks.Find(NewTask) == INDEX_NONE)
{
SimulatedTasks.Add(NewTask);
SetSimulatedTasksNetDirty();
if (IsUsingRegisteredSubObjectList() && IsReadyForReplication())
{
AddReplicatedSubObject(NewTask, COND_SkipOwner);
}
return true;
}
return false;
}
void UGameplayTasksComponent::RemoveSimulatedTask(UGameplayTask* NewTask)
{
if (SimulatedTasks.RemoveSingle(NewTask) > 0)
{
SetSimulatedTasksNetDirty();
if (IsUsingRegisteredSubObjectList())
{
RemoveReplicatedSubObject(NewTask);
}
}
}
void UGameplayTasksComponent::SetSimulatedTasks(const TArray<UGameplayTask*>& NewSimulatedTasks)
{
if (IsUsingRegisteredSubObjectList() && IsReadyForReplication())
{
// Unregister all current tasks
for (UGameplayTask* OldGameplayTask : SimulatedTasks)
{
if (OldGameplayTask)
{
RemoveReplicatedSubObject(OldGameplayTask);
}
}
SimulatedTasks.Reset(NewSimulatedTasks.Num());
// Register the new tasks
for (UGameplayTask* NewGameplayTask : NewSimulatedTasks)
{
if (NewGameplayTask)
{
AddReplicatedSubObject(NewGameplayTask, COND_SkipOwner);
SimulatedTasks.Add(NewGameplayTask);
}
}
}
else
{
SimulatedTasks = NewSimulatedTasks;
}
SetSimulatedTasksNetDirty();
}
void UGameplayTasksComponent::SetSimulatedTasksNetDirty()
{
MARK_PROPERTY_DIRTY_FROM_NAME(UGameplayTasksComponent, SimulatedTasks, this);
}
//----------------------------------------------------------------------//
// BP API
//----------------------------------------------------------------------//
EGameplayTaskRunResult UGameplayTasksComponent::K2_RunGameplayTask(TScriptInterface<IGameplayTaskOwnerInterface> TaskOwner, UGameplayTask* Task, uint8 Priority, TArray<TSubclassOf<UGameplayTaskResource> > AdditionalRequiredResources, TArray<TSubclassOf<UGameplayTaskResource> > AdditionalClaimedResources)
{
const FText NoneText = FText::FromString(TEXT("None"));
if (TaskOwner.GetInterface() == nullptr)
{
FMessageLog("PIE").Error(FText::Format(
LOCTEXT("RunGameplayTaskNullOwner", "Tried running a gameplay task {0} while owner is None!"),
Task ? FText::FromName(Task->GetFName()) : NoneText));
return EGameplayTaskRunResult::Error;
}
IGameplayTaskOwnerInterface& OwnerInstance = *TaskOwner;
if (Task == nullptr)
{
FMessageLog("PIE").Error(FText::Format(
LOCTEXT("RunNullGameplayTask", "Tried running a None task for {0}"),
FText::FromString(Cast<UObject>(&OwnerInstance)->GetName())
));
return EGameplayTaskRunResult::Error;
}
if (Task->GetState() == EGameplayTaskState::Paused || Task->GetState() == EGameplayTaskState::Active)
{
FMessageLog("PIE").Warning(FText::Format(
LOCTEXT("RunNullGameplayTask", "Tried running a None task for {0}"),
FText::FromString(Cast<UObject>(&OwnerInstance)->GetName())
));
// return as success if already running for the same owner, failure otherwise
return Task->GetTaskOwner() == &OwnerInstance
? (Task->GetState() == EGameplayTaskState::Paused ? EGameplayTaskRunResult::Success_Paused : EGameplayTaskRunResult::Success_Active)
: EGameplayTaskRunResult::Error;
}
// this is a valid situation if the task has been created via "Construct Object" mechanics
if (Task->GetState() == EGameplayTaskState::Uninitialized)
{
Task->InitTask(OwnerInstance, Priority);
}
Task->AddRequiredResourceSet(AdditionalRequiredResources);
Task->AddClaimedResourceSet(AdditionalClaimedResources);
Task->ReadyForActivation();
switch (Task->GetState())
{
case EGameplayTaskState::AwaitingActivation:
case EGameplayTaskState::Paused:
return EGameplayTaskRunResult::Success_Paused;
case EGameplayTaskState::Active:
return EGameplayTaskRunResult::Success_Active;
case EGameplayTaskState::Finished:
return EGameplayTaskRunResult::Success_Active;
}
return EGameplayTaskRunResult::Error;
}
//----------------------------------------------------------------------//
// FGameplayResourceSet
//----------------------------------------------------------------------//
FString FGameplayResourceSet::GetDebugDescription() const
{
static const int32 FlagsCount = sizeof(FFlagContainer) * 8;
FFlagContainer FlagsCopy = Flags;
int32 FlagIndex = 0;
#if WITH_GAMEPLAYTASK_DEBUG
static_assert(FlagsCount < TNumericLimits<uint8>::Max());
FString Description;
for (; FlagIndex < FlagsCount && FlagsCopy != 0; ++FlagIndex)
{
if (FlagsCopy & (1 << FlagIndex))
{
Description += UGameplayTaskResource::GetDebugDescription( IntCastChecked<uint8>(FlagIndex) );
Description += TEXT(' ');
}
FlagsCopy &= ~(1 << FlagIndex);
}
return Description;
#else
TCHAR Description[FlagsCount + 1];
for (; FlagIndex < FlagsCount && FlagsCopy != 0; ++FlagIndex)
{
Description[FlagIndex] = (FlagsCopy & (1 << FlagIndex)) ? TCHAR('1') : TCHAR('0');
FlagsCopy &= ~(1 << FlagIndex);
}
Description[FlagIndex] = TCHAR('\0');
return FString(Description);
#endif // WITH_GAMEPLAYTASK_DEBUG
}
#undef LOCTEXT_NAMESPACE