// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Async/TaskGraphInterfaces.h" #include "Misc/GeneratedTypeName.h" #include "MovieSceneEntityIDs.h" #include "EntitySystem/MovieSceneEntityManager.h" #include "EntitySystem/EntityAllocationIterator.h" #include "EntitySystem/MovieSceneEntityRange.h" #include "EntitySystem/MovieSceneComponentAccessors.h" #include "EntitySystem/MovieSceneEntityManager.h" #include "EntitySystem/MovieSceneSystemTaskDependencies.h" #include "EntitySystem/MovieSceneComponentPtr.h" #include "EntitySystem/IMovieSceneTaskScheduler.h" #include "Templates/AndOrNot.h" #include namespace UE { namespace MovieScene { DECLARE_CYCLE_STAT(TEXT("Aquire Component Access Locks"), MovieSceneEval_AquireComponentAccessLocks, STATGROUP_MovieSceneECS); DECLARE_CYCLE_STAT(TEXT("Release Component Access Locks"), MovieSceneEval_ReleaseComponentAccessLocks, STATGROUP_MovieSceneECS); template struct TReadAccess; template struct TOptionalReadAccess; template struct TWriteAccess; template struct TOptionalWriteAccess; template struct TReadOneOfAccessor; template struct TReadOneOrMoreOfAccessor; template struct TPrelockedDataOffsets; template struct TFilteredEntityTask; template struct TEntityTaskComponents; template struct TEntityTaskComponentsImpl; template struct TEntityTask; template struct TEntityTaskBase; template struct TScheduledEntityTask; template struct TEntityAllocationTask; template struct TEntityAllocationTaskBase; template struct TUnstructuredTask; template struct TEntityTaskCaller; struct FCommonEntityTaskParams { FCommonEntityTaskParams() : TaskParams(nullptr) {} /** Task parameters */ FTaskParams TaskParams; /** (deprecated) The thread that this task wants to run on */ ENamedThreads::Type DesiredThread = ENamedThreads::AnyHiPriThreadHiPriTask; /** Useful for debugging to break the debugger when this task is run */ bool bBreakOnRun = false; }; /** Default traits specialized for each user TaskImplInstance */ template struct TDefaultEntityTaskTraits { enum { /** * When true, the various component accessors are passed to the task callback as separate parameters. When false, they are passed through as a combined template * * For example: * * struct FForEach_Expanded * { * void ForEachEntity(float, uint16, UObject*); * void ForEachAllocation(const FEntityAllocation*, TRead, TRead, TRead); * }; * struct FForEach_NoExpansion * { * void ForEachEntity(const TEntityPtr&); * void ForEachAllocation(const FEntityAllocation*, const TEntityTaskComponents, TRead, TRead>&); * }; * template<> struct TEntityTaskTraits : TDefaultEntityTaskTraits { enum { AutoExpandAccessors = false }; }; * * FEntityTaskBuilder().Read().Read().Read().Dispatch_PerEntity(...); * FEntityTaskBuilder().Read().Read().Read().Dispatch_PerEntity(...); * FEntityTaskBuilder().Read().Read().Read().Dispatch_PerAllocation(...); * FEntityTaskBuilder().Read().Read().Read().Dispatch_PerAllocation(...); */ AutoExpandAccessors = true, }; }; /** Optionally specialized traits for user TaskImplInstances */ template struct TEntityTaskTraits : TDefaultEntityTaskTraits { }; /** Utility that promotes callbacks that return void to always return 'true' when iterating entities*/ struct FEntityIterationResult { template friend FORCEINLINE FEntityIterationResult operator,(T, FEntityIterationResult) { return FEntityIterationResult { true }; } friend FORCEINLINE FEntityIterationResult operator,(bool In, FEntityIterationResult) { return FEntityIterationResult{ In }; } FORCEINLINE explicit operator bool() const { return Value; } bool Value = true; }; template static FTaskID SchedulePreTask(IEntitySystemScheduler*, const FTaskParams&, const TSharedPtr&, void*) { return FTaskID::None(); } template static FTaskID SchedulePostTask(IEntitySystemScheduler*, const FTaskParams&, const TSharedPtr&, void*) { return FTaskID::None(); } template static FTaskID SchedulePreTask(IEntitySystemScheduler* InScheduler, const FTaskParams& Params, const TSharedPtr& Context, TaskImplType* Unused, decltype(&TaskImplType::PreTask)* = 0) { struct FCallPreTask { static void Run(const ITaskContext* Context, FEntityAllocationWriteContext WriteContext) { static_cast(Context)->PreTask(); } }; TaskFunctionPtr Function(TInPlaceType(), &FCallPreTask::Run); return InScheduler->AddTask(Params, Context, Function); } template static FTaskID SchedulePostTask(IEntitySystemScheduler* InScheduler, const FTaskParams& Params, const TSharedPtr& Context, TaskImplType* Unused, decltype(&TaskImplType::PostTask)* = 0) { struct FCallPostTask { static void Run(const ITaskContext* Context, FEntityAllocationWriteContext WriteContext) { static_cast(Context)->PostTask(); } }; TaskFunctionPtr Function(TInPlaceType(), &FCallPostTask::Run); return InScheduler->AddTask(Params, Context, Function); } /** * Defines the accessors for each desired component of an entity task */ template struct TEntityTaskComponents : TEntityTaskComponentsImpl, T...> { using Super = TEntityTaskComponentsImpl, T...>; /** * Constrain this task to only run for entities that have all the specified components or tags */ TFilteredEntityTask< T... > FilterAll(const FComponentMask& InComponentMask) { TFilteredEntityTask< T... > Filtered(*this); Filtered.FilterAll(InComponentMask); return Filtered; } /** * Constrain this task to only run for entities that have all the specified components or tags */ TFilteredEntityTask< T... > FilterAll(std::initializer_list InComponentTypes) { TFilteredEntityTask< T... > Filtered(*this); Filtered.FilterAll(InComponentTypes); return Filtered; } /** * Constrain this task to only run for entities that have none the specified components or tags */ TFilteredEntityTask< T... > FilterNone(const FComponentMask& InComponentMask) { TFilteredEntityTask< T... > Filtered(*this); Filtered.FilterNone(InComponentMask); return Filtered; } /** * Constrain this task to only run for entities that have none the specified components or tags */ TFilteredEntityTask< T... > FilterNone(std::initializer_list InComponentTypes) { TFilteredEntityTask< T... > Filtered(*this); Filtered.FilterNone(InComponentTypes); return Filtered; } /** * Constrain this task to only run for entities that have at least one of the specified components or tags */ TFilteredEntityTask< T... > FilterAny(const FComponentMask& InComponentMask) { TFilteredEntityTask< T... > Filtered(*this); Filtered.FilterAny(InComponentMask); return Filtered; } /** * Constrain this task to only run for entities that have at least one of the specified components or tags */ TFilteredEntityTask< T... > FilterAny(std::initializer_list InComponentTypes) { TFilteredEntityTask< T... > Filtered(*this); Filtered.FilterAny(InComponentTypes); return Filtered; } /** * Constrain this task to only run for entities that do not have the specific combination of components or tags */ TFilteredEntityTask< T... > FilterOut(const FComponentMask& InComponentMask) { TFilteredEntityTask< T... > Filtered(*this); Filtered.FilterOut(InComponentMask); return Filtered; } /** * Constrain this task to only run for entities that do not have the specific combination of components or tags */ TFilteredEntityTask< T... > FilterOut(std::initializer_list InComponentTypes) { TFilteredEntityTask< T... > Filtered(*this); Filtered.FilterOut(InComponentTypes); return Filtered; } /** * Combine this task's filter with the specified filter */ TFilteredEntityTask< T... > CombineFilter(const FEntityComponentFilter& InFilter) { return TFilteredEntityTask< T... >(*this, InFilter); } /** * Assign a desired thread for this task to run on */ TEntityTaskComponents< T... >& SetDesiredThread(ENamedThreads::Type InDesiredThread) { this->CommonParams.DesiredThread = InDesiredThread; this->CommonParams.TaskParams.bForceGameThread = (InDesiredThread == ENamedThreads::GameThread || InDesiredThread == ENamedThreads::GameThread_Local); return *this; } /** * Assign a stat ID for this task */ TEntityTaskComponents< T... >& SetStat(TStatId InStatId) { this->CommonParams.TaskParams.StatId = InStatId; return *this; } /** * Assign the scheduled task parameters for this task */ TEntityTaskComponents< T... >& SetParams(const FTaskParams& InOtherParams) { this->CommonParams.TaskParams = InOtherParams; return *this; } /** * Dispatch a custom task that runs non-structured logic. * Tasks must implement a Run function that doesn't take any argument. * * @param EntityManager The entity manager to run the task on * @param Prerequisites Prerequisite tasks that must run before this one, or nullptr if there are no prerequisites * @param Subsequents (Optional) Subsequent task tracking that this task should be added to for each writable component type * @param InArgs Optional arguments that are forwarded to the constructor of TaskImpl * @return A pointer to the graph event for the task, or nullptr if this task is not valid (ie contains invalid component types that would be necessary for the task to run), or threading is disabled */ template FGraphEventRef Dispatch(FEntityManager* EntityManager, const FSystemTaskPrerequisites& Prerequisites, FSystemSubsequentTasks* Subsequents, TaskConstructionArgs&&... InArgs) const { static_assert(sizeof...(T) == 0, "Dispatch() is only for non-structured logic, which means that any call to Read or Write (or their variants) won't do anything -- please remove them"); checkfSlow(IsInGameThread(), TEXT("Tasks can only be dispatched from the game thread.")); const bool bRunInline = !ensure(EntityManager->IsLockedDown()) || EntityManager->GetThreadingModel() == EEntityThreadingModel::NoThreading; if (bRunInline) { TaskImpl Task{ Forward(InArgs)... }; Task.Run(); return nullptr; } else { FGraphEventArray GatheredPrereqs; this->PopulatePrerequisites(Prerequisites, &GatheredPrereqs); ENamedThreads::Type ThisThread = EntityManager->GetDispatchThread(); checkSlow(ThisThread != ENamedThreads::AnyThread); FGraphEventRef NewTask = TGraphTask< TUnstructuredTask >::CreateTask(GatheredPrereqs.Num() != 0 ? &GatheredPrereqs : nullptr, ThisThread) .ConstructAndDispatchWhenReady( this->CommonParams, Forward(InArgs)... ); if (Subsequents) { this->PopulateSubsequents(NewTask, *Subsequents); } return NewTask; } } /** * Dispatch a task for every allocation that matches the filters and component types. Must be explicitly instantiated with the task type to dispatch. Construction arguments are deduced. * Tasks must implement a ForEachAllocation function that matches this task's component accessor types. * * For example: * struct FForEachAllocation * { * void ForEachAllocation(FEntityAllocation* InAllocation, const TFilteredEntityTask< FEntityIDAccess, TRead >& InputTask); * }; * * TComponentTypeID FloatChannelComponent = ...; * * FGraphEventRef Task = FEntityTaskBuilder() * .ReadEntityIDs() * .Read(FloatChannelComponent) * .SetStat(GET_STATID(MyStatName)) * .SetDesiredThread(ENamedThreads::AnyThread) * .Dispatch_PerAllocation(EntityManager, Prerequisites); * * @param EntityManager The entity manager to run the task on. All component types *must* relate to this entity manager. * @param Prerequisites Prerequisite tasks that must run before this one * @param Subsequents (Optional) Subsequent task tracking that this task should be added to for each writable component type * @param InArgs Optional arguments that are forwarded to the constructor of TaskImpl * @return A pointer to the graph event for the task, or nullptr if this task is not valid (ie contains invalid component types that would be necessary for the task to run), or threading is disabled */ template FGraphEventRef Dispatch_PerAllocation(FEntityManager* EntityManager, const FSystemTaskPrerequisites& Prerequisites, FSystemSubsequentTasks* Subsequents, TaskConstructionArgs&&... InArgs) const { checkfSlow(IsInGameThread(), TEXT("Tasks can only be dispatched from the game thread.")); if (!this->IsValid()) { return nullptr; } // Quick check to prevent dispatching a task that might not have any work to do. We only check the accessors // here, so the task could still early-return with no work done if some custom filters end up matching // nothing. Callers should in this case do their best to prevent useless tasks being dispatched. if (!this->HasAnyWork(EntityManager)) { return nullptr; } // If this ensure triggers, we are not in the evaluation phase - the callee should be using RunInline_ or Iterate_ variants const bool bRunInline = !ensure(EntityManager->IsLockedDown()) || EntityManager->GetThreadingModel() == EEntityThreadingModel::NoThreading; if (bRunInline) { TaskImpl Task{ Forward(InArgs)... }; TEntityAllocationTaskBase(EntityManager, *this).Run(Task); return nullptr; } else { FGraphEventArray GatheredPrereqs; this->PopulatePrerequisites(Prerequisites, &GatheredPrereqs); ENamedThreads::Type ThisThread = EntityManager->GetDispatchThread(); checkSlow(ThisThread != ENamedThreads::AnyThread); FGraphEventRef NewTask = TGraphTask< TEntityAllocationTask >::CreateTask(GatheredPrereqs.Num() != 0 ? &GatheredPrereqs : nullptr, ThisThread) .ConstructAndDispatchWhenReady( EntityManager, *this, Forward(InArgs)... ); if (Subsequents) { this->PopulateSubsequents(NewTask, *Subsequents); } return NewTask; } } template void RunInline_PerAllocation(FEntityManager* EntityManager, TaskImpl& Task) const { if (this->IsValid()) { TEntityAllocationTaskBase(EntityManager, *this).Run(Task); } } /** * Dispatch a task for every entity that matches the filters and component types. Must be explicitly instantiated with the task type to dispatch. Construction arguments are deduced. * Tasks must implement a ForEachEntity function that matches this task's component accessor types. * * For example: * struct FForEachEntity * { * void ForEachEntity(FMovieSceneEntityID InEntityID, const FMovieSceneFloatChannel& Channel); * }; * * TComponentTypeID FloatChannelComponent = ...; * * FGraphEventRef Task = FEntityTaskBuilder() * .ReadEntityIDs() * .Read(FloatChannelComponent) * .SetStat(GET_STATID(MyStatName)) * .SetDesiredThread(ENamedThreads::AnyThread) * .Dispatch_PerEntity(EntityManager, nullptr); * * @param EntityManager The entity manager to run the task on. All component types *must* relate to this entity manager. * @param Prerequisites Prerequisite tasks that must run before this one, or nullptr if there are no prerequisites * @param Subsequents (Optional) Subsequent task tracking that this task should be added to for each writable component type * @param InArgs Optional arguments that are forwarded to the constructor of TaskImpl * @return A pointer to the graph event for the task, or nullptr if this task is not valid (ie contains invalid component types that would be necessary for the task to run), or threading is disabled */ template FGraphEventRef Dispatch_PerEntity(FEntityManager* EntityManager, const FSystemTaskPrerequisites& Prerequisites, FSystemSubsequentTasks* Subsequents, TaskConstructionArgs&&... InArgs) const { checkfSlow(IsInGameThread(), TEXT("Tasks can only be dispatched from the game thread.")); if (!this->IsValid()) { return nullptr; } // See comment in Dispatch_PerAllocation() if (!this->HasAnyWork(EntityManager)) { return nullptr; } // If this ensure triggers, we are not in the evaluation phase - the callee should be using RunInline_ or Iterate_ variants const bool bRunInline = !ensure(EntityManager->IsLockedDown()) || EntityManager->GetThreadingModel() == EEntityThreadingModel::NoThreading; if (bRunInline) { TaskImpl Task{ Forward(InArgs)... }; TEntityTaskBase(EntityManager, *this).Run(Task); return nullptr; } else { FGraphEventArray GatheredPrereqs; this->PopulatePrerequisites(Prerequisites, &GatheredPrereqs); ENamedThreads::Type ThisThread = EntityManager->GetDispatchThread(); checkSlow(ThisThread != ENamedThreads::AnyThread); FGraphEventRef NewTask = TGraphTask< TEntityTask >::CreateTask(GatheredPrereqs.Num() != 0 ? &GatheredPrereqs : nullptr, ThisThread) .ConstructAndDispatchWhenReady( EntityManager, *this, Forward(InArgs)... ); if (Subsequents) { this->PopulateSubsequents(NewTask, *Subsequents); Subsequents->AddRootTask(NewTask); } return NewTask; } } template FTaskID Fork_PerEntity(FEntityManager* EntityManager, IEntitySystemScheduler* InScheduler, TaskConstructionArgs&&... InArgs) const { FTaskParams FinalParams = this->CommonParams.TaskParams; FinalParams.bSerialTasks = false; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) FinalParams.DebugName = GetGeneratedTypeName(); #endif return ScheduleImpl( EntityManager, InScheduler, FinalParams, TaskFunctionPtr(TInPlaceType(), TScheduledEntityTask::ScheduledRun_PerEntity), Forward(InArgs)...); } template FTaskID Fork_PerAllocation(FEntityManager* EntityManager, IEntitySystemScheduler* InScheduler, TaskConstructionArgs&&... InArgs) const { FTaskParams FinalParams = this->CommonParams.TaskParams; FinalParams.bSerialTasks = false; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) FinalParams.DebugName = GetGeneratedTypeName(); #endif return ScheduleImpl( EntityManager, InScheduler, FinalParams, TaskFunctionPtr(TInPlaceType(), TScheduledEntityTask::ScheduledRun_PerAllocation), Forward(InArgs)...); } template FTaskID Schedule_PerEntity(FEntityManager* EntityManager, IEntitySystemScheduler* InScheduler, TaskConstructionArgs&&... InArgs) const { FTaskParams FinalParams = this->CommonParams.TaskParams; FinalParams.bSerialTasks = true; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) FinalParams.DebugName = GetGeneratedTypeName(); #endif return ScheduleImpl( EntityManager, InScheduler, FinalParams, TaskFunctionPtr(TInPlaceType(), TScheduledEntityTask::ScheduledRun_PerEntity), Forward(InArgs)...); } template FTaskID Schedule_PerAllocation(FEntityManager* EntityManager, IEntitySystemScheduler* InScheduler, TaskConstructionArgs&&... InArgs) const { FTaskParams FinalParams = this->CommonParams.TaskParams; FinalParams.bSerialTasks = false; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) FinalParams.DebugName = GetGeneratedTypeName(); #endif return ScheduleImpl( EntityManager, InScheduler, FinalParams, TaskFunctionPtr(TInPlaceType(), TScheduledEntityTask::ScheduledRun_PerAllocation), Forward(InArgs)...); } template void RunInline_PerEntity(FEntityManager* EntityManager, TaskImpl& Task) const { if (this->IsValid()) { TEntityTaskBase(EntityManager, *this).Run(Task); } } public: TEntityTaskComponents() { static_assert(sizeof...(T) == 0, "Default construction is only supported for TEntityTaskComponents<>"); } template explicit TEntityTaskComponents(const FCommonEntityTaskParams& InCommonParams, ConstructionTypes&&... InTypes) : Super(InCommonParams, Forward(InTypes)...) {} private: template FTaskID ScheduleImpl(FEntityManager* EntityManager, IEntitySystemScheduler* InScheduler, const FTaskParams& TaskParams, TaskFunctionPtr InFunction, TaskConstructionArgs&&... InArgs) const { if (!this->IsValid()) { return FTaskID::None(); } if (!this->HasAnyWork(EntityManager)) { return FTaskID::None(); } using TaskType = TScheduledEntityTask; TSharedPtr SharedTask = MakeShared(*this, Forward(InArgs)...); FComponentMask ReadDependencies, WriteDependencies; this->PopulateReadWriteDependencies(ReadDependencies, WriteDependencies); FEntityComponentFilter Filter; this->PopulateFilter(&Filter); auto PrelockComponentData = [this](FEntityAllocationIteratorItem Allocation, TArray& OutComponentHeaders) { this->PreLockComponentHeaders(Allocation, OutComponentHeaders); }; FTaskID TaskID = InScheduler->CreateForkedAllocationTask(TaskParams, SharedTask, InFunction, PrelockComponentData, Filter, ReadDependencies, WriteDependencies); if (!TaskID && TaskParams.bForcePrePostTask) { TaskID = InScheduler->AddNullTask(); } if (TaskID) { FTaskID PreTask = SchedulePreTask(InScheduler, TaskParams, SharedTask, (TaskImpl*)0); InScheduler->AddChildFront(TaskID, PreTask); FTaskID PostTask = SchedulePostTask(InScheduler, TaskParams, SharedTask, (TaskImpl*)0); InScheduler->AddChildBack(TaskID, PostTask); } return TaskID; } }; template struct TEntityTaskComponentsImpl, T...> { /** * Read the entity ID along with this task. Passed to the task as an FMovieSceneEntityID */ TEntityTaskComponents< T..., FEntityIDAccess > ReadEntityIDs() const { return TEntityTaskComponents< T..., FEntityIDAccess >( CommonParams, Accessors.template Get()..., FEntityIDAccess{} ); } /** * Read the value of a component. Passed to the task as either a const T& or T depending on the type specified in the task function. * @note Supplying an invalid ComponentType will be handled gracefully, but will result in no task being dispatched. * * @param ComponentType A valid component type to read. */ template TEntityTaskComponents> Read(TComponentTypeID ComponentType) const { return TEntityTaskComponents >(CommonParams, Accessors.template Get()..., TReadAccess(ComponentType)); } /** * Read the value of only one of the specified components. Only entities with exactly one of these components will be read. Per-entity iteration not supported with this accessor. * @note Supplying an invalid ComponentType will be handled gracefully, but will result in no task being dispatched. * * @param ComponentTypes The component types to visit */ template TEntityTaskComponents> ReadOneOf(TComponentTypeID... ComponentTypes) const { return TEntityTaskComponents >( CommonParams, Accessors.template Get()..., TReadOneOfAccessor(ComponentTypes...) ); } /** * Read the value of one or more of the specified components. Entities with at least one of these components will be read. Per-entity iteration not supported with this accessor. * * @param ComponentTypes The component types to visit */ template TEntityTaskComponents> ReadOneOrMoreOf(TComponentTypeID... ComponentTypes) const { return TEntityTaskComponents >(CommonParams, Accessors.template Get()..., TReadOneOrMoreOfAccessor(ComponentTypes...)); } /** * Read all of the specified components and pass them through to the task as individual parameters * * @param ComponentTypes The component types to visit */ template TEntityTaskComponents...> ReadAllOf(TComponentTypeID... ComponentTypes) const { return TEntityTaskComponents... >( CommonParams, Accessors.template Get()..., TReadAccess(ComponentTypes)... ); } /** * Read any of the specified components and pass them through to the task as individual optional parameters * * @param ComponentTypes The component types to visit */ template TEntityTaskComponents...> ReadAnyOf(TComponentTypeID... ComponentTypes) const { return TEntityTaskComponents... >( CommonParams, Accessors.template Get()..., TOptionalReadAccess(ComponentTypes)... ); } /** * Read the type-erased value of a component. Passed to the task as a const void* * @note Supplying an invalid ComponentType will be handled gracefully, but will result in no task being dispatched. * * @param ComponentType A valid component type to read. */ TEntityTaskComponents ReadErased(FComponentTypeID ComponentType) const { return TEntityTaskComponents(CommonParams, Accessors.template Get()..., FErasedReadAccess(ComponentType)); } /** * Optionally read the type-erased value of a component. Passed to the task as a const void* * @note Supplying an invalid ComponentType will be handled gracefully, but will result in no task being dispatched. * * @param ComponentType A valid component type to read. */ TEntityTaskComponents ReadErasedOptional(FComponentTypeID ComponentType, FComponentTypeIDFilter InConditionType = FComponentTypeIDFilter()) const { return TEntityTaskComponents(CommonParams, Accessors.template Get()..., FErasedOptionalReadAccess(ComponentType, InConditionType)); } /** * Optionally read the value of a component. ComponentType may be invalid, and the component may or may not exist for some/all of the entities in the resulting task * @note Always passed to the task as a const T* pointer which must be checked for null */ template TEntityTaskComponents> ReadOptional(TComponentTypeID ComponentType, FComponentTypeIDFilter InConditionType = FComponentTypeIDFilter()) const { return TEntityTaskComponents >(CommonParams, Accessors.template Get()..., TOptionalReadAccess(ComponentType, InConditionType)); } /** * Write the value of a component in a thread safe manner. Passed to the task as a T& so the value can be modified or overwritten. * @note Supplying an invalid ComponentType will be handled gracefully, but will result in no task being dispatched. * * @param ComponentType A valid component type to read. */ template TEntityTaskComponents> Write(TComponentTypeID ComponentType) const { return TEntityTaskComponents >(CommonParams, Accessors.template Get()..., TWriteAccess(ComponentType)); } /** * Write all of the specified components and pass them through to the task as individual parameters * * @param ComponentTypes The component types to visit */ template TEntityTaskComponents...> WriteAllOf(TComponentTypeID... ComponentTypes) const { return TEntityTaskComponents... >( CommonParams, Accessors.template Get()..., TWriteAccess(ComponentTypes)... ); } /** * Write the type-erased value of a component. Passed to the task as a void* * @note Supplying an invalid ComponentType will be handled gracefully, but will result in no task being dispatched. * * @param ComponentType A valid component type to read. */ TEntityTaskComponents WriteErased(FComponentTypeID ComponentType) const { return TEntityTaskComponents(CommonParams, Accessors.template Get()..., FErasedWriteAccess(ComponentType)); } /** * Optionally write the value of a component in a thread safe manner if it exists. Passed to the task as a T* which must be checked for nullptr. * @note Always passed to the task as a T* pointer which must be checked for null */ template TEntityTaskComponents> WriteOptional(TComponentTypeID ComponentType, FComponentTypeIDFilter InConditionType = FComponentTypeIDFilter()) const { return TEntityTaskComponents >(CommonParams, Accessors.template Get()..., TOptionalWriteAccess(ComponentType, InConditionType)); } TEntityTaskComponents PassthroughFilter(const FEntityComponentFilter& InFilter) const { return TEntityTaskComponents(CommonParams, Accessors.template Get()..., FFilterMatchPassthrough{ InFilter }); } bool HasBeenWrittenToSince(uint32 InSystemVersion) { bool bAnyWrittenTo = true; int Temp[] = { ( bAnyWrittenTo |= HasBeenWrittenToSince(&Accessors.template Get(), InSystemVersion), 0)... }; (void)Temp; return bAnyWrittenTo; } /** * Check whether this task data is well-formed in the sense that it can perform meaningful work. */ bool IsValid() const { bool bAllValid = true; int Temp[] = { ( bAllValid &= IsAccessorValid(&Accessors.template Get()), 0)..., 0 }; (void)Temp; return bAllValid; } /** * Check whether all required accessors correspond to component types that are present in the given entity manager. */ bool HasAnyWork(const FEntityManager* EntityManager) const { bool bAllHaveWork = true; int Temp[] = { ( bAllHaveWork &= HasAccessorWork(EntityManager, &Accessors.template Get()), 0)..., 0 }; (void)Temp; return bAllHaveWork; } /** Utility function called when the task is dispatched to populate the filter based on our component typs */ void PopulateFilter(FEntityComponentFilter* OutFilter) const { int Temp[] = { (AddAccessorToFilter(&Accessors.template Get(), OutFilter), 0)..., 0 }; (void)Temp; } /** Utility function called when the task is dispatched to populate the filter based on our component typs */ void PopulatePrerequisites(const FSystemTaskPrerequisites& InPrerequisites, FGraphEventArray* OutGatheredPrereqs) const { // Gather any root tasks InPrerequisites.FilterByComponent(*OutGatheredPrereqs, FComponentTypeID::Invalid()); int Temp[] = { (UE::MovieScene::PopulatePrerequisites(&Accessors.template Get(), InPrerequisites, OutGatheredPrereqs), 0)..., 0 }; (void)Temp; } /** Utility function called when the task is dispatched to populate the filter based on our component typs */ void PopulateSubsequents(const FGraphEventRef& InEvent, FSystemSubsequentTasks& OutSubsequents) const { OutSubsequents.AddRootTask(InEvent); int Temp[] = { (UE::MovieScene::PopulateSubsequents(&Accessors.template Get(), InEvent, OutSubsequents), 0)..., 0 }; (void)Temp; } void PreLockComponentHeaders(FEntityAllocationIteratorItem Item, TArray& OutComponentHeaders) const { constexpr TPrelockedDataOffsets PrelockedDataOffsets; int32 StartIndex = OutComponentHeaders.Num(); OutComponentHeaders.AddDefaulted(PrelockedDataOffsets.Num); ( Accessors.template Get().PreLockComponentData(Item, &OutComponentHeaders[StartIndex + PrelockedDataOffsets.StartOffset[Indices]]), ... ); } void PopulateReadWriteDependencies(FComponentMask& OutReadDependencies, FComponentMask& OutWriteDependencies) const { int Temp[] = { (UE::MovieScene::PopulateReadWriteDependencies(&Accessors.template Get(), OutReadDependencies, OutWriteDependencies), 0)..., 0 }; (void)Temp; } /** * Perform a thread-safe iteration of all matching allocations within the specified entity manager using this task, inline on the current thread * * @param EntityManager The entity manager to iterate allocations for. All component type IDs in this class must relate to this entity manager * @param InCallback A callable type that matches the signature defined by ForEachAllocation ie void(FEntityAllocation*, const TFilteredEntityTask&) */ template void Iterate_PerAllocation(FEntityManager* EntityManager, Callback&& InCallback) const { FEntityComponentFilter Filter; PopulateFilter(&Filter); Iterate_PerAllocationImpl(EntityManager, Filter, InCallback); } /** * Perform a thread-safe iteration of all matching entities specified entity manager using this task, inline on the current thread * * @param EntityManager The entity manager to iterate allocations for. All component type IDs in this class must relate to this entity manager * @param InCallback A callable type that matches the signature defined by ForEachEntity ie void(typename T::AccessType...) */ template void Iterate_PerEntity(FEntityManager* EntityManager, Callback&& InCallback) const { FEntityComponentFilter Filter; PopulateFilter(&Filter); Iterate_PerEntityImpl(EntityManager, Filter, InCallback); } /** * Implementation function for Iterate_PerEntity * * @param EntityManager The entity manager to iterate allocations for. All component type IDs in this class must relate to this entity manager * @param Filter Filter that at least must match the types specified by this task * @param InCallback A callable type that matches the signature defined by ForEachEntity ie void(typename T::AccessType...) */ template void Iterate_PerEntityImpl(FEntityManager* EntityManager, const FEntityComponentFilter& Filter, Callback&& InCallback) const { using TupleType = TTuple< decltype(Accessors.template Get().LockComponentData(DeclVal(), DeclVal()))... >; if (IsValid()) { FEntityAllocationWriteContext WriteContext(*EntityManager); EComponentHeaderLockMode LockMode = EntityManager->GetThreadingModel() == EEntityThreadingModel::NoThreading ? EComponentHeaderLockMode::LockFree : EComponentHeaderLockMode::Mutex; for (FEntityAllocationIteratorItem Item : EntityManager->Iterate(&Filter)) { FEntityIterationResult Result; FEntityAllocation* Allocation = Item.GetAllocation(); FEntityAllocationMutexGuard LockGuard(Allocation, LockMode); // Lock the components we want to access TupleType ComponentData( Accessors.template Get().LockComponentData(Item, WriteContext)... ); const int32 Num = Allocation->Num(); for (int32 ComponentOffset = 0; ComponentOffset < Num && Result.Value; ++ComponentOffset ) { Result = (InCallback( GetComponentAtIndex(&ComponentData.template Get(), ComponentOffset)... ), Result); } } } } /** * Implementation function for Iterate_PerAllocation * * @param EntityManager The entity manager to iterate allocations for. All component type IDs in this class must relate to this entity manager * @param Filter Filter that at least must match the types specified by this task * @param InCallback A callable type that matches the signature defined by ForEachAllocation ie void(FEntityAllocation*, const TFilteredEntityTask&) */ template void Iterate_PerAllocationImpl(FEntityManager* EntityManager, const FEntityComponentFilter& Filter, Callback&& InCallback) const { if (IsValid()) { FEntityAllocationWriteContext WriteContext(*EntityManager); EComponentHeaderLockMode LockMode = EntityManager->GetThreadingModel() == EEntityThreadingModel::NoThreading ? EComponentHeaderLockMode::LockFree : EComponentHeaderLockMode::Mutex; for (FEntityAllocationIteratorItem Item : EntityManager->Iterate(&Filter)) { FEntityAllocationMutexGuard LockGuard(Item.GetAllocation(), LockMode); // Lock on the components we want to access auto ComponentData = MakeTuple( Accessors.template Get().LockComponentData(Item, WriteContext)... ); FEntityIterationResult Result = (InCallback(Item, ComponentData.template Get()...), FEntityIterationResult{}); if (!Result) { break; } } } } public: /** * Get the accessor for a specific index within this task */ template FORCEINLINE auto GetAccessor() const { static_assert(Index < sizeof...(T), "Invalid component index specified"); return Accessors.template Get(); } FString ToString(FEntityManager* EntityManager) const { #if UE_MOVIESCENE_ENTITY_DEBUG FString Result; int Unused[] = { (AccessorToString(&Accessors.template Get(), EntityManager, Result), 0)... }; (void)Unused; return Result; #else return TEXT(" - enable UE_MOVIESCENE_ENTITY_DEBUG"); #endif } protected: TEntityTaskComponentsImpl() {} template explicit TEntityTaskComponentsImpl(const FCommonEntityTaskParams& InCommonParams, ConstructionTypes&&... InTypes) : Accessors{ Forward(InTypes)... } , CommonParams(InCommonParams) {} protected: template friend struct TEntityTaskComponentsImpl; TTuple Accessors; public: FCommonEntityTaskParams CommonParams; }; struct FNoopTask { template FORCEINLINE static void ForEachAllocation(T&&...) {} template FORCEINLINE static void ForEachEntity(T&&...) {} }; /** * Main entry point utility for create tasks that run over component data */ struct FEntityTaskBuilder : TEntityTaskComponents<> { FEntityTaskBuilder() : TEntityTaskComponents<>() {} }; template struct TFilteredEntityTask { TFilteredEntityTask(const TEntityTaskComponents& InComponents) : Components(InComponents) { Components.PopulateFilter(&Filter); } TFilteredEntityTask(const TEntityTaskComponents& InComponents, const FEntityComponentFilter& InFilter) : Components(InComponents) , Filter(InFilter) { Components.PopulateFilter(&Filter); } /** * Constrain this task to only run for entities that have all the specified components or tags */ TFilteredEntityTask< T... >& FilterAll(const FComponentMask& InComponentMask) { Filter.All(InComponentMask); return *this; } /** * Constrain this task to only run for entities that have all the specified components or tags */ TFilteredEntityTask< T... >& FilterAll(std::initializer_list InComponentTypes) { Filter.All(InComponentTypes); return *this; } /** * Constrain this task to only run for entities that have none the specified components or tags */ TFilteredEntityTask< T... >& FilterNone(const FComponentMask& InComponentMask) { Filter.None(InComponentMask); return *this; } /** * Constrain this task to only run for entities that have none the specified components or tags */ TFilteredEntityTask< T... >& FilterNone(std::initializer_list InComponentTypes) { Filter.None(InComponentTypes); return *this; } /** * Constrain this task to only run for entities that have at least one of the specified components or tags */ TFilteredEntityTask< T... >& FilterAny(const FComponentMask& InComponentMask) { Filter.Any(InComponentMask); return *this; } /** * Constrain this task to only run for entities that have at least one of the specified components or tags */ TFilteredEntityTask< T... >& FilterAny(std::initializer_list InComponentTypes) { Filter.Any(InComponentTypes); return *this; } /** * Constrain this task to only run for entities that do not have the specific combination of components or tags */ TFilteredEntityTask< T... >& FilterOut(const FComponentMask& InComponentMask) { Filter.Deny(InComponentMask); return *this; } /** * Constrain this task to only run for entities that do not have the specific combination of components or tags */ TFilteredEntityTask< T... >& FilterOut(std::initializer_list InComponentTypes) { Filter.Deny(InComponentTypes); return *this; } /** * Combine this task's filter with the specified filter */ TFilteredEntityTask< T... >& CombineFilter(const FEntityComponentFilter& InFilter) { Filter.Combine(InFilter); return *this; } /** * Assign a desired thread for this task to run on */ TFilteredEntityTask< T... >& SetDesiredThread(ENamedThreads::Type InDesiredThread) { Components.CommonParams.DesiredThread = InDesiredThread; Components.CommonParams.TaskParams.bForceGameThread = (InDesiredThread == ENamedThreads::GameThread || InDesiredThread == ENamedThreads::GameThread_Local); return *this; } /** * Assign a stat ID for this task */ TFilteredEntityTask< T... >& SetStat(TStatId InStatId) { Components.CommonParams.TaskParams.StatId = InStatId; return *this; } /** * Assign the scheduled task parameters for this task */ TFilteredEntityTask< T... >& SetParams(const FTaskParams& InOtherParams) { Components.CommonParams.TaskParams = InOtherParams; return *this; } /** * Get the desired thread for this task to run on */ ENamedThreads::Type GetDesiredThread() const { return Components.CommonParams.DesiredThread; } /** * Return this task's stat id */ TStatId GetStatId() const { return Components.CommonParams.TaskParams.StatId; } /** * Check whether we should break the debugger when this task is run */ bool ShouldBreakOnRun() const { return Components.CommonParams.bBreakOnRun; } /** * Access the pre-populated filter that should be used for iterating relevant entities for this task */ const FEntityComponentFilter& GetFilter() const { return Filter; } /** * Access the underlying component access definitions */ const TEntityTaskComponents& GetComponents() const { return Components; } TFilteredEntityTask& AddDynamicReadDependency(std::initializer_list InReadDependencies) { DynamicReadMask.SetAll(InReadDependencies); return *this; } template TFilteredEntityTask& AddDynamicReadDependency(TArrayView InReadDependencies) { for (FComponentTypeID Component : InReadDependencies) { DynamicReadMask.Set(Component); } return *this; } TFilteredEntityTask& AddDynamicReadDependency(const FComponentMask& InDynamicReadDependency) { DynamicReadMask.CombineWithBitwiseOR(InDynamicReadDependency, EBitwiseOperatorFlags::MaxSize); return *this; } TFilteredEntityTask& AddDynamicWriteDependency(std::initializer_list InWriteDependencies) { DynamicWriteMask.SetAll(InWriteDependencies); return *this; } template TFilteredEntityTask& AddDynamicWriteDependency(TArrayView InWriteDependencies) { for (FComponentTypeID Component : InWriteDependencies) { DynamicWriteMask.Set(Component); } return *this; } TFilteredEntityTask& AddDynamicWriteDependency(const FComponentMask& InDynamicWriteDependency) { DynamicWriteMask.CombineWithBitwiseOR(InDynamicWriteDependency, EBitwiseOperatorFlags::MaxSize); return *this; } /** * Dispatch a task for every entity that matches the filters and component types. Must be explicitly instantiated with the task type to dispatch. Construction arguments are deduced. * Tasks must implement a ForEachEntity function that matches this task's component accessor types. * * For example: * struct FForEachEntity * { * void ForEachEntity(FMovieSceneEntityID InEntityID, const FMovieSceneFloatChannel& Channel); * }; * * TComponentTypeID FloatChannelComponent = ...; * * FGraphEventRef Task = FEntityTaskBuilder() * .ReadEntityIDs() * .Read(FloatChannelComponent) * .SetStat(GET_STATID(MyStatName)) * .SetDesiredThread(ENamedThreads::AnyThread) * .Dispatch_PerEntity(EntityManager, Prerequisites); * * @param EntityManager The entity manager to run the task on. All component types *must* relate to this entity manager. * @param Prerequisites Prerequisite tasks that must run before this one, or nullptr if there are no prerequisites * @param Subsequents (Optional) Subsequent task tracking that this task should be added to for each writable component type * @param InArgs Optional arguments that are forwarded to the constructor of TaskImpl * @return A pointer to the graph event for the task, or nullptr if this task is not valid (ie contains invalid component types that would be necessary for the task to run), or threading is disabled */ template FGraphEventRef Dispatch_PerAllocation(FEntityManager* EntityManager, const FSystemTaskPrerequisites& Prerequisites, FSystemSubsequentTasks* Subsequents, TaskConstructionArgs&&... InArgs) const { checkfSlow(IsInGameThread(), TEXT("Tasks can only be dispatched from the game thread.")); if (!Components.IsValid()) { return nullptr; } // See comment in TEntityTaskComponents' Dispatch_PerAllocation() if (!Components.HasAnyWork(EntityManager)) { return nullptr; } // If this ensure triggers, we are not in the evaluation phase - the callee should be using RunInline_ or Iterate_ variants const bool bRunInline = !ensure(EntityManager->IsLockedDown()) || EntityManager->GetThreadingModel() == EEntityThreadingModel::NoThreading; if (bRunInline) { TaskImpl Task{ Forward(InArgs)... }; TEntityAllocationTaskBase(EntityManager, *this).Run(Task); return nullptr; } else { FGraphEventArray GatheredPrereqs; Components.PopulatePrerequisites(Prerequisites, &GatheredPrereqs); ENamedThreads::Type ThisThread = EntityManager->GetDispatchThread(); checkSlow(ThisThread != ENamedThreads::AnyThread); FGraphEventRef NewTask = TGraphTask< TEntityAllocationTask >::CreateTask(GatheredPrereqs.Num() != 0 ? &GatheredPrereqs : nullptr, ThisThread) .ConstructAndDispatchWhenReady( EntityManager, *this, Forward(InArgs)... ); if (Subsequents) { Components.PopulateSubsequents(NewTask, *Subsequents); } return NewTask; } } template void RunInline_PerAllocation(FEntityManager* EntityManager, TaskImpl& Task) const { if (Components.IsValid()) { TEntityAllocationTaskBase(EntityManager, *this).Run(Task); } } /** * Dispatch a task for every entity that matches the filters and component types. Must be explicitly instantiated with the task type to dispatch. Construction arguments are deduced. * Tasks must implement a ForEachEntity function that matches this task's component accessor types. * * For example: * struct FForEachEntity * { * void ForEachEntity(FMovieSceneEntityID InEntityID, const FMovieSceneFloatChannel& Channel); * }; * * TComponentTypeID FloatChannelComponent = ...; * * FGraphEventRef Task = FEntityTaskBuilder() * .ReadEntityIDs() * .Read(FloatChannelComponent) * .SetStat(GET_STATID(MyStatName)) * .SetDesiredThread(ENamedThreads::AnyThread) * .Dispatch_PerEntity(EntityManager, Prerequisites); * * @param EntityManager The entity manager to run the task on. All component types *must* relate to this entity manager. * @param Prerequisites Prerequisite tasks that must run before this one, or nullptr if there are no prerequisites * @param Subsequents (Optional) Subsequent task tracking that this task should be added to for each writable component type * @param InArgs Optional arguments that are forwarded to the constructor of TaskImpl * @return A pointer to the graph event for the task, or nullptr if this task is not valid (ie contains invalid component types that would be necessary for the task to run), or threading is disabled */ template FGraphEventRef Dispatch_PerEntity(FEntityManager* EntityManager, const FSystemTaskPrerequisites& Prerequisites, FSystemSubsequentTasks* Subsequents, TaskConstructionArgs&&... InArgs) const { checkfSlow(IsInGameThread(), TEXT("Tasks can only be dispatched from the game thread.")); if (!Components.IsValid()) { return nullptr; } // See comment in TEntityTaskComponents' Dispatch_PerAllocation() if (!Components.HasAnyWork(EntityManager)) { return nullptr; } // If this ensure triggers, we are not in the evaluation phase - the callee should be using RunInline_ or Iterate_ variants const bool bRunInline = !ensure(EntityManager->IsLockedDown()) || EntityManager->GetThreadingModel() == EEntityThreadingModel::NoThreading; if (bRunInline) { TaskImpl Task{ Forward(InArgs)... }; TEntityTaskBase(EntityManager, *this).Run(Task); return nullptr; } else { FGraphEventArray GatheredPrereqs; Components.PopulatePrerequisites(Prerequisites, &GatheredPrereqs); ENamedThreads::Type ThisThread = EntityManager->GetDispatchThread(); checkSlow(ThisThread != ENamedThreads::AnyThread); FGraphEventRef NewTask = TGraphTask< TEntityTask >::CreateTask(GatheredPrereqs.Num() != 0 ? &GatheredPrereqs : nullptr, ThisThread) .ConstructAndDispatchWhenReady( EntityManager, *this, Forward(InArgs)... ); if (Subsequents) { Components.PopulateSubsequents(NewTask, *Subsequents); } return NewTask; } } template FTaskID Fork_PerEntity(FEntityManager* EntityManager, IEntitySystemScheduler* InScheduler, TaskConstructionArgs&&... InArgs) const { FTaskParams FinalParams = this->Components.CommonParams.TaskParams; FinalParams.bSerialTasks = false; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) FinalParams.DebugName = GetGeneratedTypeName(); #endif return ScheduleImpl( EntityManager, InScheduler, FinalParams, TaskFunctionPtr(TInPlaceType(), TScheduledEntityTask::ScheduledRun_PerEntity), Forward(InArgs)...); } template FTaskID Fork_PerAllocation(FEntityManager* EntityManager, IEntitySystemScheduler* InScheduler, TaskConstructionArgs&&... InArgs) const { FTaskParams FinalParams = this->Components.CommonParams.TaskParams; FinalParams.bSerialTasks = false; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) FinalParams.DebugName = GetGeneratedTypeName(); #endif return ScheduleImpl( EntityManager, InScheduler, FinalParams, TaskFunctionPtr(TInPlaceType(), TScheduledEntityTask::ScheduledRun_PerAllocation), Forward(InArgs)...); } template FTaskID Schedule_PerEntity(FEntityManager* EntityManager, IEntitySystemScheduler* InScheduler, TaskConstructionArgs&&... InArgs) const { FTaskParams FinalParams = this->Components.CommonParams.TaskParams; FinalParams.bSerialTasks = true; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) FinalParams.DebugName = GetGeneratedTypeName(); #endif return ScheduleImpl( EntityManager, InScheduler, FinalParams, TaskFunctionPtr(TInPlaceType(), TScheduledEntityTask::ScheduledRun_PerEntity), Forward(InArgs)...); } template FTaskID Schedule_PerAllocation(FEntityManager* EntityManager, IEntitySystemScheduler* InScheduler, TaskConstructionArgs&&... InArgs) const { FTaskParams FinalParams = this->Components.CommonParams.TaskParams; FinalParams.bSerialTasks = true; #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) FinalParams.DebugName = GetGeneratedTypeName(); #endif return ScheduleImpl( EntityManager, InScheduler, FinalParams, TaskFunctionPtr(TInPlaceType(), TScheduledEntityTask::ScheduledRun_PerAllocation), Forward(InArgs)...); } template void RunInline_PerEntity(FEntityManager* EntityManager, TaskImpl Task) const { if (Components.IsValid()) { TEntityTaskBase(EntityManager, *this).Run(Task); } } /** * Perform a thread-safe iteration of all matching entities specified entity manager using this task, inline on the current thread * * @param EntityManager The entity manager to iterate allocations for. All component type IDs in this class must relate to this entity manager * @param InCallback A callable type that matches the signature defined by ForEachEntity ie void(typename T::AccessType...) */ template void Iterate_PerEntity(FEntityManager* EntityManager, Callback&& InCallback) const { Components.Iterate_PerEntityImpl(EntityManager, Filter, Forward(InCallback)); } /** * Perform a thread-safe iteration of all matching allocations within the specified entity manager using this task, inline on the current thread * * @param EntityManager The entity manager to iterate allocations for. All component type IDs in this class must relate to this entity manager * @param InCallback A callable type that matches the signature defined by ForEachAllocation ie void(FEntityAllocation*, const TFilteredEntityTask&) */ template void Iterate_PerAllocation(FEntityManager* EntityManager, Callback&& InCallback) const { Components.Iterate_PerAllocationImpl(EntityManager, Filter, Forward(InCallback)); } private: template FTaskID ScheduleImpl(FEntityManager* EntityManager, IEntitySystemScheduler* InScheduler, const FTaskParams& TaskParams, TaskFunctionPtr InFunction, TaskConstructionArgs&&... InArgs) const { if (!Components.IsValid()) { return FTaskID::None(); } if (!Components.HasAnyWork(EntityManager)) { return FTaskID::None(); } using TaskType = TScheduledEntityTask; TSharedPtr SharedTask = MakeShared(Components, Forward(InArgs)...); FComponentMask ReadDependencies = DynamicReadMask, WriteDependencies = DynamicWriteMask; Components.PopulateReadWriteDependencies(ReadDependencies, WriteDependencies); auto PrelockComponentData = [this](FEntityAllocationIteratorItem Allocation, TArray& OutComponentHeaders) { this->Components.PreLockComponentHeaders(Allocation, OutComponentHeaders); }; FTaskID TaskID = InScheduler->CreateForkedAllocationTask(TaskParams, SharedTask, InFunction, PrelockComponentData, Filter, ReadDependencies, WriteDependencies); if (!TaskID && TaskParams.bForcePrePostTask) { TaskID = InScheduler->AddNullTask(); } if (TaskID) { FTaskID PreTask = SchedulePreTask(InScheduler, TaskParams, SharedTask, (TaskImpl*)0); InScheduler->AddChildFront(TaskID, PreTask); FTaskID PostTask = SchedulePostTask(InScheduler, TaskParams, SharedTask, (TaskImpl*)0); InScheduler->AddChildBack(TaskID, PostTask); } return TaskID; } private: TEntityTaskComponents Components; FEntityComponentFilter Filter; FComponentMask DynamicReadMask; FComponentMask DynamicWriteMask; }; template struct TEntityTaskBase { explicit TEntityTaskBase(FEntityManager* InEntityManager, const TEntityTaskComponents& InComponents) : FilteredTask(InComponents) , EntityManager(InEntityManager) , WriteContext(*InEntityManager) {} explicit TEntityTaskBase(FEntityManager* InEntityManager, const TFilteredEntityTask& InFilteredTask) : FilteredTask(InFilteredTask) , EntityManager(InEntityManager) , WriteContext(*InEntityManager) {} TStatId GetStatId() const { return FilteredTask.GetStatId(); } ENamedThreads::Type GetDesiredThread() const { return FilteredTask.GetDesiredThread(); } void Run(TaskImpl& TaskImplInstance) { UE_LOG(LogMovieSceneECS, VeryVerbose, TEXT("Running entity task the following components: %s"), *FilteredTask.GetComponents().ToString(EntityManager)); PreTask(&TaskImplInstance); EComponentHeaderLockMode LockMode = EntityManager->GetThreadingModel() == EEntityThreadingModel::NoThreading ? EComponentHeaderLockMode::LockFree : EComponentHeaderLockMode::Mutex; for (FEntityAllocationIteratorItem Item : EntityManager->Iterate(&FilteredTask.GetFilter())) { FEntityAllocationMutexGuard LockGuard(Item, LockMode); Caller::ForEachEntityImpl(TaskImplInstance, Item, WriteContext, FilteredTask.GetComponents()); } PostTask(&TaskImplInstance); } protected: static void PreTask(void*, ...){} template static void PreTask(T* InTask, decltype(&T::PreTask)* = 0) { InTask->PreTask(); } static void PostTask(void*, ...){} template static void PostTask(T* InTask, decltype(&T::PostTask)* = 0) { InTask->PostTask(); } using Caller = TEntityTaskCaller::AutoExpandAccessors >; TFilteredEntityTask FilteredTask; FEntityManager* EntityManager; FEntityAllocationWriteContext WriteContext; }; template struct TEntityTask : TEntityTaskBase { template explicit TEntityTask(FEntityManager* InEntityManager, const TEntityTaskComponents& InComponents, ArgTypes&&... InArgs) : TEntityTaskBase(InEntityManager, InComponents) , TaskImplInstance{ Forward(InArgs)... } {} template explicit TEntityTask(FEntityManager* InEntityManager, const TFilteredEntityTask& InFilteredTask, ArgTypes&&... InArgs) : TEntityTaskBase(InEntityManager, InFilteredTask) , TaskImplInstance{ Forward(InArgs)... } {} static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; } void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& CompletionGraphEvent) { if (this->FilteredTask.ShouldBreakOnRun()) { UE_DEBUG_BREAK(); } if ((this->FilteredTask.GetDesiredThread() & ENamedThreads::AnyThread) == 0) { checkf(CurrentThread == this->FilteredTask.GetDesiredThread(), TEXT("MovieScene evaluation task is not being run on its desired thread")); } this->Run(TaskImplInstance); } private: TaskImpl TaskImplInstance; }; template struct TScheduledEntityTask : ITaskContext { template explicit TScheduledEntityTask(const TEntityTaskComponents& InComponents, ArgTypes&&... InArgs) : TaskImplInstance{ Forward(InArgs)... } , Components(InComponents) {} void PreTask() const { // This const_cast is ok because we know nothing else can touch this task in PreTask PreTaskImpl(const_cast(&TaskImplInstance)); } void PostTask() const { // This const_cast is ok because we know nothing else can touch this task in PostTask PostTaskImpl(const_cast(&TaskImplInstance)); } static void ScheduledRun_PerEntity(FEntityAllocationIteratorItem Item, TArrayView PreLockedData, const ITaskContext* Context, FEntityAllocationWriteContext WriteContext) { const TScheduledEntityTask* This = static_cast*>(Context); Caller::ForEachEntityImpl(This->TaskImplInstance, Item, PreLockedData, WriteContext, This->Components); } static void ScheduledRun_PerAllocation(FEntityAllocationIteratorItem Item, TArrayView PreLockedData, const ITaskContext* Context, FEntityAllocationWriteContext WriteContext) { const TScheduledEntityTask* This = static_cast*>(Context); Caller::ForEachAllocationImpl(This->TaskImplInstance, Item, PreLockedData, WriteContext, This->Components); } private: static void PreTaskImpl(void*, ...){} template static void PreTaskImpl(T* InTask, decltype(&T::PreTask)* = 0) { InTask->PreTask(); } static void PostTaskImpl(void*, ...){} template static void PostTaskImpl(T* InTask, decltype(&T::PostTask)* = 0) { InTask->PostTask(); } using Caller = TEntityTaskCaller::AutoExpandAccessors >; TaskImpl TaskImplInstance; TEntityTaskComponents Components; }; template struct TEntityAllocationTaskBase { explicit TEntityAllocationTaskBase(FEntityManager* InEntityManager, const TEntityTaskComponents& InComponents) : ComponentFilter(InComponents) , EntityManager(InEntityManager) , WriteContext(*InEntityManager) {} explicit TEntityAllocationTaskBase(FEntityManager* InEntityManager, const TFilteredEntityTask& InComponentFilter) : ComponentFilter(InComponentFilter) , EntityManager(InEntityManager) , WriteContext(*InEntityManager) {} TStatId GetStatId() const { return ComponentFilter.GetStatId(); } ENamedThreads::Type GetDesiredThread() const { return ComponentFilter.GetDesiredThread(); } void Run(TaskImpl& TaskImplInstance) { UE_LOG(LogMovieSceneECS, VeryVerbose, TEXT("Running entity task the following components: %s"), *ComponentFilter.GetComponents().ToString(EntityManager)); PreTask(&TaskImplInstance); EComponentHeaderLockMode LockMode = EntityManager->GetThreadingModel() == EEntityThreadingModel::NoThreading ? EComponentHeaderLockMode::LockFree : EComponentHeaderLockMode::Mutex; for (FEntityAllocationIteratorItem Item : EntityManager->Iterate(&ComponentFilter.GetFilter())) { FEntityAllocationMutexGuard LockGuard(Item.GetAllocation(), LockMode); Caller::ForEachAllocationImpl(TaskImplInstance, Item, WriteContext, ComponentFilter.GetComponents()); } PostTask(&TaskImplInstance); } private: static void PreTask(void*, ...){} template static void PreTask(T* InTask, decltype(&T::PreTask)* = 0) { InTask->PreTask(); } static void PostTask(void*, ...){} template static void PostTask(T* InTask, decltype(&T::PostTask)* = 0) { InTask->PostTask(); } protected: using Caller = TEntityTaskCaller::AutoExpandAccessors >; TFilteredEntityTask ComponentFilter; FEntityManager* EntityManager; FEntityAllocationWriteContext WriteContext; }; template struct TEntityAllocationTask : TEntityAllocationTaskBase { template explicit TEntityAllocationTask(FEntityManager* InEntityManager, const TEntityTaskComponents& InComponents, ArgTypes&&... InArgs) : TEntityAllocationTaskBase(InEntityManager, InComponents) , TaskImplInstance{ Forward(InArgs)... } {} template explicit TEntityAllocationTask(FEntityManager* InEntityManager, const TFilteredEntityTask& InComponentFilter, ArgTypes&&... InArgs) : TEntityAllocationTaskBase(InEntityManager, InComponentFilter) , TaskImplInstance{ Forward(InArgs)... } {} static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; } void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& CompletionGraphEvent) { if (this->ComponentFilter.ShouldBreakOnRun()) { UE_DEBUG_BREAK(); } if ((this->ComponentFilter.GetDesiredThread() & ENamedThreads::AnyThread) == 0) { checkf(CurrentThread == this->ComponentFilter.GetDesiredThread(), TEXT("MovieScene evaluation task is not being run on its desired thread")); } this->Run(TaskImplInstance); } private: TaskImpl TaskImplInstance; }; template struct TUnstructuredTask { template explicit TUnstructuredTask(const FCommonEntityTaskParams& InCommonParams, ArgTypes&&... InArgs) : TaskImplInstance{ Forward(InArgs)... } , CommonParams(InCommonParams) {} TStatId GetStatId() const { return CommonParams.TaskParams.StatId; } ENamedThreads::Type GetDesiredThread() const { return CommonParams.DesiredThread; } static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; } void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& CompletionGraphEvent) { if (CommonParams.bBreakOnRun) { UE_DEBUG_BREAK(); } if ((CommonParams.DesiredThread & ENamedThreads::AnyThread) == 0) { checkf(CurrentThread == CommonParams.DesiredThread, TEXT("MovieScene evaluation task is not being run on its desired thread")); } UE_LOG(LogMovieSceneECS, VeryVerbose, TEXT("Running unstructured task")); TaskImplInstance.Run(); } private: TaskImpl TaskImplInstance; FCommonEntityTaskParams CommonParams; }; template struct TEntityTaskCaller_AutoExpansion; template struct TPrelockedDataOffsets { static constexpr int32 Num = (... + AccessorTypes::PreLockedDataNum); int32 StartOffset[sizeof...(AccessorTypes)]={}; constexpr TPrelockedDataOffsets() { int Index = 0, DataIndex = 0; (..., Assign(Index, DataIndex, AccessorTypes::PreLockedDataNum)); } constexpr void Assign(int& Index, int& DataIndex, int DataSize) { StartOffset[Index] = DataIndex; DataIndex += DataSize; ++Index; } }; template struct TEntityTaskCaller_AutoExpansion> { template static void ForEachEntityImpl(TaskImpl& TaskImplInstance, FEntityAllocationIteratorItem Item, TArrayView PreLockedData, FEntityAllocationWriteContext WriteContext, const TEntityTaskComponents& Components) { FEntityIterationResult Result; constexpr TPrelockedDataOffsets PrelockedDataOffsets; auto ResolvedComponentData = MakeTuple( Components.template GetAccessor().ResolvePreLockedComponentData(Item, &PreLockedData[PrelockedDataOffsets.StartOffset[Indices]], WriteContext)... ); const int32 Num = Item.GetAllocation()->Num(); for (int32 ComponentOffset = 0; ComponentOffset < Num && Result.Value; ++ComponentOffset ) { Result = (TaskImplInstance.ForEachEntity(GetComponentAtIndex(&ResolvedComponentData.template Get(), ComponentOffset)... ), Result); } } template static void ForEachEntityImpl(TaskImpl& TaskImplInstance, FEntityAllocationIteratorItem Item, FEntityAllocationWriteContext WriteContext, const TEntityTaskComponents& Components) { FEntityIterationResult Result; // Lock the components we want to access auto LockedComponentData = MakeTuple( Components.template GetAccessor().LockComponentData(Item, WriteContext)... ); const int32 Num = Item.GetAllocation()->Num(); for (int32 ComponentOffset = 0; ComponentOffset < Num && Result.Value; ++ComponentOffset ) { Result = (TaskImplInstance.ForEachEntity(GetComponentAtIndex(&LockedComponentData.template Get(), ComponentOffset)... ), Result); } } template static void ForEachAllocationImpl(TaskImpl& TaskImplInstance, FEntityAllocationIteratorItem Item, TArrayView PreLockedData, FEntityAllocationWriteContext WriteContext, const TEntityTaskComponents& Components) { constexpr TPrelockedDataOffsets PrelockedDataOffsets; const FEntityAllocation* Allocation = Item.GetAllocation(); TaskImplInstance.ForEachAllocation(Item, Components.template GetAccessor().ResolvePreLockedComponentData(Item, &PreLockedData[PrelockedDataOffsets.StartOffset[Indices]], WriteContext)...); } template static void ForEachAllocationImpl(TaskImpl& TaskImplInstance, FEntityAllocationIteratorItem Item, FEntityAllocationWriteContext WriteContext, const TEntityTaskComponents& Components) { auto LockedComponentData = MakeTuple( Components.template GetAccessor().LockComponentData(Item, WriteContext)... ); TaskImplInstance.ForEachAllocation(Item, LockedComponentData.template Get()...); } }; template struct TEntityTaskCaller : TEntityTaskCaller_AutoExpansion> { }; template struct TEntityTaskCaller { template FORCEINLINE static void ForEachEntityImpl(TaskImpl& TaskImplInstance, ...) { static_assert(!std::is_same_v, "non-expanded entity iteration is not supported"); } template FORCEINLINE static void ForEachAllocationImpl(TaskImpl& TaskImplInstance, FEntityAllocationIteratorItem Item, TArrayView PreLockedData, FEntityAllocationWriteContext WriteContext, const TEntityTaskComponents& Components) { TaskImplInstance.ForEachAllocation(Item, Components, PreLockedData); } template FORCEINLINE static void ForEachAllocationImpl(TaskImpl& TaskImplInstance, FEntityAllocationIteratorItem Item, const TEntityTaskComponents& Components) { TaskImplInstance.ForEachAllocation(Item, Components); } }; } // namespace MovieScene } // namespace UE