diff --git a/example/CompletionAction.cpp b/example/CompletionAction.cpp index adaee931..7bcbe09a 100644 --- a/example/CompletionAction.cpp +++ b/example/CompletionAction.cpp @@ -35,7 +35,6 @@ static std::atomic gs_CountAsDeleted = {0}; static std::atomic gs_CountBsRun = {0}; static std::atomic gs_CountBsDeleted = {0}; - struct CompletionActionDelete : ICompletable { Dependency m_Dependency; @@ -44,10 +43,10 @@ struct CompletionActionDelete : ICompletable // the dependency task is complete. void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ ) { - // always call base class OnDependenciesComplete first + // Call base class OnDependenciesComplete BEFORE deleting depedent task or self ICompletable::OnDependenciesComplete( pTaskScheduler_, threadNum_ ); - printf("OnDependenciesComplete called on thread %u\n", threadNum_ ); + printf("CompletionActionDelete::OnDependenciesComplete called on thread %u\n", threadNum_ ); // In this example we delete the dependency, which is safe to do as the task // manager will not dereference it at this point. @@ -72,23 +71,56 @@ struct SelfDeletingTaskB : ITaskSet void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override { - (void)range_; - ++gs_CountBsRun; - printf("SelfDeletingTaskB on thread %u\n", threadnum_); + if( 0 == range_.start ) + { + // whilst would normally loop over range_ doing work here we want to only output info once per task + ++gs_CountBsRun; + printf("SelfDeletingTaskB on thread %u with set size %u\n", threadnum_, m_SetSize); + } } CompletionActionDelete m_TaskDeleter; Dependency m_Dependency; }; +struct CompletionActionModifyDependentTaskAndDelete : ICompletable +{ + Dependency m_Dependency; + + ITaskSet* m_pTaskToModify = nullptr; + + // We override OnDependenciesComplete to provide an 'action' which occurs after + // the dependency task is complete. + void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ ) + { + // Modify following task before calling OnDependenciesComplete + m_pTaskToModify->m_SetSize = 10; + + // Call base class OnDependenciesComplete AFTER modifying any depedent task + ICompletable::OnDependenciesComplete( pTaskScheduler_, threadNum_ ); + + printf("CompletionActionModifyDependentTaskAndDelete::OnDependenciesComplete called on thread %u\n", threadNum_ ); + + // In this example we delete the dependency, which is safe to do as the task + // manager will not dereference it at this point. + // However the dependency task should have no other dependents, + // This class can have dependencies. + delete m_Dependency.GetDependencyTask(); // also deletes this as member + } +}; + struct SelfDeletingTaskA : ITaskSet { SelfDeletingTaskA() { - m_TaskDeleter.SetDependency( m_TaskDeleter.m_Dependency, this ); + m_TaskModifyAndDelete.SetDependency( m_TaskModifyAndDelete.m_Dependency, this ); SelfDeletingTaskB* pNextTask = new SelfDeletingTaskB(); + // we set the dependency of pNextTask on the task deleter, not on this - pNextTask->SetDependency( pNextTask->m_Dependency, &m_TaskDeleter ); + pNextTask->SetDependency( pNextTask->m_Dependency, &m_TaskModifyAndDelete ); + + // Set the completion actions task to modify to be the following task + m_TaskModifyAndDelete.m_pTaskToModify = pNextTask; } ~SelfDeletingTaskA() @@ -101,16 +133,27 @@ struct SelfDeletingTaskA : ITaskSet { (void)range_; ++gs_CountAsRun; - printf("SelfDeletingTaskA on thread %u\n", threadnum_); + printf("SelfDeletingTaskA on thread %u with set size %u\n", threadnum_, m_SetSize); } - CompletionActionDelete m_TaskDeleter; + CompletionActionModifyDependentTaskAndDelete m_TaskModifyAndDelete; }; -static const int RUNS = 10; +static const int RUNS = 100000; int main(int argc, const char * argv[]) { + // This examples shows CompletionActions used to modify a following tasks parameters and delete tasks + // Task Graph for this example (with names shortened to fit on screen): + // + // pTaskSetA + // ->pCompletionActionA-Modify-ICompletable::OnDependenciesComplete-Delete + // ->pTaskSetB + // ->pCompletionActionB-ICompletable::OnDependenciesComplete-Delete + // + // Note that pTaskSetB must depend on pCompletionActionA NOT pTaskSetA or it could run at the same time as pCompletionActionA + // so cannot be modified. + g_TS.Initialize(); for( int run = 0; run< RUNS; ++run ) diff --git a/example/CompletionAction_c.c b/example/CompletionAction_c.c index a51b5b16..342aafb0 100644 --- a/example/CompletionAction_c.c +++ b/example/CompletionAction_c.c @@ -23,56 +23,180 @@ enkiTaskScheduler* pETS; -struct CompletionArgs +struct CompletionArgs_ModifyTask +{ + enkiTaskSet* pTaskB; + uint32_t run; +}; + +struct CompletionArgs_DeleteTask { enkiTaskSet* pTask; + enkiDependency* pDependency; // in this example only 1 or 0 dependencies, but generally could be an array enkiCompletionAction* pCompletionAction; + uint32_t run; // only required for example output, not needed for a general purpose delete task }; -void CompletionFunction( void* pArgs_, uint32_t threadNum_ ) +// In this example all our TaskSet functions share the same args struct, but we could use different one +struct TaskSetArgs +{ + enkiTaskSet* pTask; + const char* name; + uint32_t run; +}; + +void CompletionFunctionPreComplete_ModifyDependentTask( void* pArgs_, uint32_t threadNum_ ) +{ + struct CompletionArgs_ModifyTask* pCompletionArgs_ModifyTask = pArgs_; + struct enkiParamsTaskSet paramsTaskNext = enkiGetParamsTaskSet( pCompletionArgs_ModifyTask->pTaskB ); + + printf("CompletionFunctionPreComplete_ModifyDependentTask for run %u running on thread %u\n", + pCompletionArgs_ModifyTask->run, threadNum_ ); + + // in this function we can modify the parameters of any task which depends on this CompletionFunction + // pre complete functions should not be used to delete the current CompletionAction, for that use PostComplete functions + paramsTaskNext.setSize = 10; // modify the set size of the next task - for example this could be based on output from previous task + enkiSetParamsTaskSet( pCompletionArgs_ModifyTask->pTaskB, paramsTaskNext ); + + free( pCompletionArgs_ModifyTask ); +} + + +void CompletionFunctionPostComplete_DeleteTask( void* pArgs_, uint32_t threadNum_ ) { - struct CompletionArgs* pCompletionArgs = pArgs_; - struct enkiParamsTaskSet params = enkiGetParamsTaskSet( pCompletionArgs->pTask ); - uint32_t* pTaskNum = params.pArgs; - printf("CompletionFunction for task %u running on thread %u\n", *pTaskNum, threadNum_ ); - enkiDeleteCompletionAction( pETS, pCompletionArgs->pCompletionAction ); - enkiDeleteTaskSet( pETS, pCompletionArgs->pTask ); - free( pTaskNum ); - free( pCompletionArgs ); + struct CompletionArgs_DeleteTask* pCompletionArgs_DeleteTask = pArgs_; + + printf("CompletionFunctionPostComplete_DeleteTask for run %u running on thread %u\n", + pCompletionArgs_DeleteTask->run, threadNum_ ); + + // can free memory in post complete + + // note must delete a dependency before you delete the dependency task and the task to run on completion + if( pCompletionArgs_DeleteTask->pDependency ) + { + enkiDeleteDependency( pETS, pCompletionArgs_DeleteTask->pDependency ); + } + + free( enkiGetParamsTaskSet( pCompletionArgs_DeleteTask->pTask ).pArgs ); + enkiDeleteTaskSet( pETS, pCompletionArgs_DeleteTask->pTask ); + + enkiDeleteCompletionAction( pETS, pCompletionArgs_DeleteTask->pCompletionAction ); + + // safe to free our own args in this example as no other function dereferences them + free( pCompletionArgs_DeleteTask ); } void TaskSetFunc( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ ) { (void)start_; (void)end_; - uint32_t* pTaskNum = pArgs_; - printf("Task %u running on thread %u\n", *pTaskNum, threadnum_); + struct TaskSetArgs* pTaskSetArgs = pArgs_; + struct enkiParamsTaskSet paramsTaskNext = enkiGetParamsTaskSet( pTaskSetArgs->pTask ); + if( 0 == start_ ) + { + // for clarity in this example we only output one printf per taskset func called, but would normally loop from start_ to end_ doing work + printf("Task %s for run %u running on thread %u has set size %u\n", pTaskSetArgs->name, pTaskSetArgs->run, threadnum_, paramsTaskNext.setSize); + } + + // A TastSetFunction is not a safe place to free it's own pArgs_ as when the setSize > 1 there may be multiple + // calls to this function with the same pArgs_ } + int main(int argc, const char * argv[]) { + // This examples shows CompletionActions used to modify a following tasks parameters and free allocations + // Task Graph for this example (with names shortened to fit on screen): + // + // pTaskSetA + // ->pCompletionActionA-PreFunc-PostFunc + // ->pTaskSetB + // ->pCompletionActionB-(no PreFunc)-PostFunc + // + // Note that pTaskSetB must depend on pCompletionActionA NOT pTaskSetA or it could run at the same time as pCompletionActionA + // so cannot be modified. + + struct enkiTaskSet* pTaskSetA; + struct enkiCompletionAction* pCompletionActionA; + struct enkiTaskSet* pTaskSetB; + struct enkiCompletionAction* pCompletionActionB; + struct TaskSetArgs* pTaskSetArgsA; + struct CompletionArgs_ModifyTask* pCompletionArgsA; + struct enkiParamsCompletionAction paramsCompletionActionA; + struct TaskSetArgs* pTaskSetArgsB; + struct enkiDependency* pDependencyOfTaskSetBOnCompletionActionA; + struct CompletionArgs_DeleteTask* pCompletionArgs_DeleteTaskA; + struct CompletionArgs_DeleteTask* pCompletionArgs_DeleteTaskB; + struct enkiParamsCompletionAction paramsCompletionActionB; int run; - struct enkiParamsCompletionAction paramsCompletionAction; - uint32_t* pTaskNum; - struct CompletionArgs* pCompletionArgs; pETS = enkiNewTaskScheduler(); enkiInitTaskScheduler( pETS ); - // Here we demonstrate using the completion action to delete the tasks on the fly for( run=0; run<10; ++run ) { - pCompletionArgs = malloc(sizeof(struct CompletionArgs)); // we will free in CompletionFunction - pCompletionArgs->pTask = enkiCreateTaskSet( pETS, TaskSetFunc ); - pTaskNum = malloc(sizeof(uint32_t)); // we will free in CompletionFunction - *pTaskNum = run; - enkiSetArgsTaskSet( pCompletionArgs->pTask, pTaskNum ); - pCompletionArgs->pCompletionAction = enkiCreateCompletionAction( pETS, CompletionFunction ); - paramsCompletionAction = enkiGetParamsCompletionAction( pCompletionArgs->pCompletionAction ); - paramsCompletionAction.pArgs = pCompletionArgs; - paramsCompletionAction.pDependency = enkiGetCompletableFromTaskSet( pCompletionArgs->pTask ); - enkiSetParamsCompletionAction( pCompletionArgs->pCompletionAction, paramsCompletionAction ); - - enkiAddTaskSet( pETS, pCompletionArgs->pTask ); + // Create all this runs tasks and completion actions + pTaskSetA = enkiCreateTaskSet( pETS, TaskSetFunc ); + pCompletionActionA = enkiCreateCompletionAction( pETS, + CompletionFunctionPreComplete_ModifyDependentTask, + CompletionFunctionPostComplete_DeleteTask ); + pTaskSetB = enkiCreateTaskSet( pETS, TaskSetFunc ); + pCompletionActionB = enkiCreateCompletionAction( pETS, + NULL, + CompletionFunctionPostComplete_DeleteTask ); + + // Set args for TaskSetA + pTaskSetArgsA = malloc(sizeof(struct TaskSetArgs)); + pTaskSetArgsA->run = run; + pTaskSetArgsA->pTask = pTaskSetA; + pTaskSetArgsA->name = "A"; + enkiSetArgsTaskSet( pTaskSetA, pTaskSetArgsA ); + + // Set args for CompletionActionA, and make dependent on TaskSetA through pDependency + pCompletionArgsA = malloc(sizeof(struct CompletionArgs_ModifyTask)); + pCompletionArgsA->pTaskB = pTaskSetB; + pCompletionArgsA->run = run; + pCompletionArgs_DeleteTaskA = malloc(sizeof(struct CompletionArgs_DeleteTask)); + pCompletionArgs_DeleteTaskA->pTask = pTaskSetA; + pCompletionArgs_DeleteTaskA->pCompletionAction = pCompletionActionA; + pCompletionArgs_DeleteTaskA->pDependency = NULL; + pCompletionArgs_DeleteTaskA->run = run; + + paramsCompletionActionA = enkiGetParamsCompletionAction( pCompletionActionA ); + paramsCompletionActionA.pArgsPreComplete = pCompletionArgsA; + paramsCompletionActionA.pArgsPostComplete = pCompletionArgs_DeleteTaskA; + paramsCompletionActionA.pDependency = enkiGetCompletableFromTaskSet( pTaskSetA ); + enkiSetParamsCompletionAction( pCompletionActionA, paramsCompletionActionA ); + + + // Set args for TaskSetB + pTaskSetArgsB = malloc(sizeof(struct TaskSetArgs)); + pTaskSetArgsB->run = run; + pTaskSetArgsB->pTask = pTaskSetB; + pTaskSetArgsB->name = "B"; + enkiSetArgsTaskSet( pTaskSetB, pTaskSetArgsB ); + + // TaskSetB depends on pCompletionActionA + pDependencyOfTaskSetBOnCompletionActionA = enkiCreateDependency( pETS ); + enkiSetDependency( pDependencyOfTaskSetBOnCompletionActionA, + enkiGetCompletableFromCompletionAction( pCompletionActionA ), + enkiGetCompletableFromTaskSet( pTaskSetB ) ); + + // Set args for CompletionActionB, and make dependent on TaskSetB through pDependency + pCompletionArgs_DeleteTaskB = malloc(sizeof(struct CompletionArgs_DeleteTask)); + pCompletionArgs_DeleteTaskB->pTask = pTaskSetB; + pCompletionArgs_DeleteTaskB->pDependency = pDependencyOfTaskSetBOnCompletionActionA; + pCompletionArgs_DeleteTaskB->pCompletionAction = pCompletionActionB; + pCompletionArgs_DeleteTaskB->run = run; + + paramsCompletionActionB = enkiGetParamsCompletionAction( pCompletionActionB ); + paramsCompletionActionB.pArgsPreComplete = NULL; // pCompletionActionB does not have a PreComplete function + paramsCompletionActionB.pArgsPostComplete = pCompletionArgs_DeleteTaskB; + paramsCompletionActionB.pDependency = enkiGetCompletableFromTaskSet( pTaskSetB ); + enkiSetParamsCompletionAction( pCompletionActionB, paramsCompletionActionB ); + + + // To launch all, we only add the first TaskSet + enkiAddTaskSet( pETS, pTaskSetA ); } enkiWaitForAll( pETS ); diff --git a/src/TaskScheduler_c.cpp b/src/TaskScheduler_c.cpp index 2a2cb42f..ab31f147 100644 --- a/src/TaskScheduler_c.cpp +++ b/src/TaskScheduler_c.cpp @@ -81,16 +81,30 @@ struct enkiPinnedTask : IPinnedTask struct enkiCompletionAction : ICompletable { - void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ ) + void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ ) override { + if( completionFunctionPreComplete ) + { + completionFunctionPreComplete( pArgsPreComplete, threadNum_ ); + } + + // make temporaries for post completion as this task could get deleted after OnDependenciesComplete + enkiCompletionFunction tempCompletionFunctionPostComplete = completionFunctionPostComplete; + void* ptempArgsPostComplete = pArgsPostComplete; + ICompletable::OnDependenciesComplete( pTaskScheduler_, threadNum_ ); - completionFunction( pArgs, threadNum_ ); + if( tempCompletionFunctionPostComplete ) + { + tempCompletionFunctionPostComplete( ptempArgsPostComplete, threadNum_ ); + } } - enkiCompletionFunction completionFunction; + enkiCompletionFunction completionFunctionPreComplete; + enkiCompletionFunction completionFunctionPostComplete; Dependency dependency; - void* pArgs = NULL; + void* pArgsPreComplete = NULL; + void* pArgsPostComplete = NULL; }; struct enkiDependency : Dependency {}; // empty struct which we will use for dependencies @@ -476,13 +490,14 @@ void enkiSetDependency( enkiDependency* pDependency_, enkiCompletable* pDependen pTaskToRunOnCompletion_->SetDependency( *pDependency_, pDependencyTask_ ); } -enkiCompletionAction* enkiCreateCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionFunction completionFunc_ ) +enkiCompletionAction* enkiCreateCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionFunction completionFunctionPreComplete_, enkiCompletionFunction completionFunctionPostComplete_ ) { const CustomAllocator& customAllocator = pETS_->GetConfig().customAllocator; enkiCompletionAction* pCA = (enkiCompletionAction*)customAllocator.alloc( alignof(enkiCompletionAction), sizeof(enkiCompletionAction), customAllocator.userData, ENKI_FILE_AND_LINE ); new(pCA) enkiCompletionAction(); - pCA->completionFunction = completionFunc_; + pCA->completionFunctionPreComplete = completionFunctionPreComplete_; + pCA->completionFunctionPostComplete = completionFunctionPostComplete_; return pCA; } @@ -497,7 +512,8 @@ void enkiDeleteCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionAction* enkiParamsCompletionAction enkiGetParamsCompletionAction( enkiCompletionAction* pCompletionAction_ ) { enkiParamsCompletionAction params; - params.pArgs = pCompletionAction_->pArgs; + params.pArgsPreComplete = pCompletionAction_->pArgsPreComplete; + params.pArgsPostComplete = pCompletionAction_->pArgsPostComplete; params.pDependency = reinterpret_cast( pCompletionAction_->dependency.GetDependencyTask() ); return params; @@ -505,6 +521,7 @@ enkiParamsCompletionAction enkiGetParamsCompletionAction( enkiCompletionAction* void enkiSetParamsCompletionAction( enkiCompletionAction* pCompletionAction_, enkiParamsCompletionAction params_ ) { - pCompletionAction_->pArgs = params_.pArgs; + pCompletionAction_->pArgsPreComplete = params_.pArgsPreComplete; + pCompletionAction_->pArgsPostComplete = params_.pArgsPostComplete; pCompletionAction_->SetDependency( pCompletionAction_->dependency, params_.pDependency ); } diff --git a/src/TaskScheduler_c.h b/src/TaskScheduler_c.h index ea10d64b..5c1ee3fa 100644 --- a/src/TaskScheduler_c.h +++ b/src/TaskScheduler_c.h @@ -99,7 +99,8 @@ struct enkiParamsPinnedTask struct enkiParamsCompletionAction { - void* pArgs; + void* pArgsPreComplete; + void* pArgsPostComplete; const enkiCompletable* pDependency; // task which when complete triggers completion function }; @@ -370,7 +371,12 @@ ENKITS_API void enkiSetDependency( /* -------------------------- Completion Actions --------------------------- */ // Create a CompletionAction. -ENKITS_API enkiCompletionAction* enkiCreateCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionFunction completionFunc_ ); +// completionFunctionPreComplete_ - function called BEFORE the complete action task is 'complete', which means this is prior to dependent tasks being run. +// this function can thus alter any task arguments of the dependencies. +// completionFunctionPostComplete_ - function called AFTER the complete action task is 'complete'. Dependent tasks may have already been started. +// This function can delete the completion action if needed as it will no longer be accessed by other functions. +// It is safe to set either of these to NULL if you do not require that function +ENKITS_API enkiCompletionAction* enkiCreateCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionFunction completionFunctionPreComplete_, enkiCompletionFunction completionFunctionPostComplete_ ); // Delete a CompletionAction. ENKITS_API void enkiDeleteCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionAction* pCompletionAction_ );