Skip to content

Commit 6ffccbd

Browse files
committed
BREAKING C API CHANGE: Added Pre and Post Complete completion actions to C interface + improved completion action examples
1 parent 4528461 commit 6ffccbd

File tree

4 files changed

+239
-49
lines changed

4 files changed

+239
-49
lines changed

example/CompletionAction.cpp

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ static std::atomic<int32_t> gs_CountAsDeleted = {0};
3535
static std::atomic<int32_t> gs_CountBsRun = {0};
3636
static std::atomic<int32_t> gs_CountBsDeleted = {0};
3737

38-
3938
struct CompletionActionDelete : ICompletable
4039
{
4140
Dependency m_Dependency;
@@ -44,10 +43,10 @@ struct CompletionActionDelete : ICompletable
4443
// the dependency task is complete.
4544
void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ )
4645
{
47-
// always call base class OnDependenciesComplete first
46+
// Call base class OnDependenciesComplete BEFORE deleting depedent task or self
4847
ICompletable::OnDependenciesComplete( pTaskScheduler_, threadNum_ );
4948

50-
printf("OnDependenciesComplete called on thread %u\n", threadNum_ );
49+
printf("CompletionActionDelete::OnDependenciesComplete called on thread %u\n", threadNum_ );
5150

5251
// In this example we delete the dependency, which is safe to do as the task
5352
// manager will not dereference it at this point.
@@ -72,23 +71,56 @@ struct SelfDeletingTaskB : ITaskSet
7271

7372
void ExecuteRange( TaskSetPartition range_, uint32_t threadnum_ ) override
7473
{
75-
(void)range_;
76-
++gs_CountBsRun;
77-
printf("SelfDeletingTaskB on thread %u\n", threadnum_);
74+
if( 0 == range_.start )
75+
{
76+
// whilst would normally loop over range_ doing work here we want to only output info once per task
77+
++gs_CountBsRun;
78+
printf("SelfDeletingTaskB on thread %u with set size %u\n", threadnum_, m_SetSize);
79+
}
7880
}
7981

8082
CompletionActionDelete m_TaskDeleter;
8183
Dependency m_Dependency;
8284
};
8385

86+
struct CompletionActionModifyDependentTaskAndDelete : ICompletable
87+
{
88+
Dependency m_Dependency;
89+
90+
ITaskSet* m_pTaskToModify = nullptr;
91+
92+
// We override OnDependenciesComplete to provide an 'action' which occurs after
93+
// the dependency task is complete.
94+
void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ )
95+
{
96+
// Modify following task before calling OnDependenciesComplete
97+
m_pTaskToModify->m_SetSize = 10;
98+
99+
// Call base class OnDependenciesComplete AFTER modifying any depedent task
100+
ICompletable::OnDependenciesComplete( pTaskScheduler_, threadNum_ );
101+
102+
printf("CompletionActionModifyDependentTaskAndDelete::OnDependenciesComplete called on thread %u\n", threadNum_ );
103+
104+
// In this example we delete the dependency, which is safe to do as the task
105+
// manager will not dereference it at this point.
106+
// However the dependency task should have no other dependents,
107+
// This class can have dependencies.
108+
delete m_Dependency.GetDependencyTask(); // also deletes this as member
109+
}
110+
};
111+
84112
struct SelfDeletingTaskA : ITaskSet
85113
{
86114
SelfDeletingTaskA()
87115
{
88-
m_TaskDeleter.SetDependency( m_TaskDeleter.m_Dependency, this );
116+
m_TaskModifyAndDelete.SetDependency( m_TaskModifyAndDelete.m_Dependency, this );
89117
SelfDeletingTaskB* pNextTask = new SelfDeletingTaskB();
118+
90119
// we set the dependency of pNextTask on the task deleter, not on this
91-
pNextTask->SetDependency( pNextTask->m_Dependency, &m_TaskDeleter );
120+
pNextTask->SetDependency( pNextTask->m_Dependency, &m_TaskModifyAndDelete );
121+
122+
// Set the completion actions task to modify to be the following task
123+
m_TaskModifyAndDelete.m_pTaskToModify = pNextTask;
92124
}
93125

94126
~SelfDeletingTaskA()
@@ -101,16 +133,27 @@ struct SelfDeletingTaskA : ITaskSet
101133
{
102134
(void)range_;
103135
++gs_CountAsRun;
104-
printf("SelfDeletingTaskA on thread %u\n", threadnum_);
136+
printf("SelfDeletingTaskA on thread %u with set size %u\n", threadnum_, m_SetSize);
105137
}
106138

107-
CompletionActionDelete m_TaskDeleter;
139+
CompletionActionModifyDependentTaskAndDelete m_TaskModifyAndDelete;
108140
};
109141

110-
static const int RUNS = 10;
142+
static const int RUNS = 100000;
111143

112144
int main(int argc, const char * argv[])
113145
{
146+
// This examples shows CompletionActions used to modify a following tasks parameters and delete tasks
147+
// Task Graph for this example (with names shortened to fit on screen):
148+
//
149+
// pTaskSetA
150+
// ->pCompletionActionA-Modify-ICompletable::OnDependenciesComplete-Delete
151+
// ->pTaskSetB
152+
// ->pCompletionActionB-ICompletable::OnDependenciesComplete-Delete
153+
//
154+
// Note that pTaskSetB must depend on pCompletionActionA NOT pTaskSetA or it could run at the same time as pCompletionActionA
155+
// so cannot be modified.
156+
114157
g_TS.Initialize();
115158

116159
for( int run = 0; run< RUNS; ++run )

example/CompletionAction_c.c

Lines changed: 152 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -23,56 +23,180 @@
2323

2424
enkiTaskScheduler* pETS;
2525

26-
struct CompletionArgs
26+
struct CompletionArgs_ModifyTask
27+
{
28+
enkiTaskSet* pTaskB;
29+
uint32_t run;
30+
};
31+
32+
struct CompletionArgs_DeleteTask
2733
{
2834
enkiTaskSet* pTask;
35+
enkiDependency* pDependency; // in this example only 1 or 0 dependencies, but generally could be an array
2936
enkiCompletionAction* pCompletionAction;
37+
uint32_t run; // only required for example output, not needed for a general purpose delete task
3038
};
3139

32-
void CompletionFunction( void* pArgs_, uint32_t threadNum_ )
40+
// In this example all our TaskSet functions share the same args struct, but we could use different one
41+
struct TaskSetArgs
42+
{
43+
enkiTaskSet* pTask;
44+
const char* name;
45+
uint32_t run;
46+
};
47+
48+
void CompletionFunctionPreComplete_ModifyDependentTask( void* pArgs_, uint32_t threadNum_ )
49+
{
50+
struct CompletionArgs_ModifyTask* pCompletionArgs_ModifyTask = pArgs_;
51+
struct enkiParamsTaskSet paramsTaskNext = enkiGetParamsTaskSet( pCompletionArgs_ModifyTask->pTaskB );
52+
53+
printf("CompletionFunctionPreComplete_ModifyDependentTask for run %u running on thread %u\n",
54+
pCompletionArgs_ModifyTask->run, threadNum_ );
55+
56+
// in this function we can modify the parameters of any task which depends on this CompletionFunction
57+
// pre complete functions should not be used to delete the current CompletionAction, for that use PostComplete functions
58+
paramsTaskNext.setSize = 10; // modify the set size of the next task - for example this could be based on output from previous task
59+
enkiSetParamsTaskSet( pCompletionArgs_ModifyTask->pTaskB, paramsTaskNext );
60+
61+
free( pCompletionArgs_ModifyTask );
62+
}
63+
64+
65+
void CompletionFunctionPostComplete_DeleteTask( void* pArgs_, uint32_t threadNum_ )
3366
{
34-
struct CompletionArgs* pCompletionArgs = pArgs_;
35-
struct enkiParamsTaskSet params = enkiGetParamsTaskSet( pCompletionArgs->pTask );
36-
uint32_t* pTaskNum = params.pArgs;
37-
printf("CompletionFunction for task %u running on thread %u\n", *pTaskNum, threadNum_ );
38-
enkiDeleteCompletionAction( pETS, pCompletionArgs->pCompletionAction );
39-
enkiDeleteTaskSet( pETS, pCompletionArgs->pTask );
40-
free( pTaskNum );
41-
free( pCompletionArgs );
67+
struct CompletionArgs_DeleteTask* pCompletionArgs_DeleteTask = pArgs_;
68+
69+
printf("CompletionFunctionPostComplete_DeleteTask for run %u running on thread %u\n",
70+
pCompletionArgs_DeleteTask->run, threadNum_ );
71+
72+
// can free memory in post complete
73+
74+
// note must delete a dependency before you delete the dependency task and the task to run on completion
75+
if( pCompletionArgs_DeleteTask->pDependency )
76+
{
77+
enkiDeleteDependency( pETS, pCompletionArgs_DeleteTask->pDependency );
78+
}
79+
80+
free( enkiGetParamsTaskSet( pCompletionArgs_DeleteTask->pTask ).pArgs );
81+
enkiDeleteTaskSet( pETS, pCompletionArgs_DeleteTask->pTask );
82+
83+
enkiDeleteCompletionAction( pETS, pCompletionArgs_DeleteTask->pCompletionAction );
84+
85+
// safe to free our own args in this example as no other function dereferences them
86+
free( pCompletionArgs_DeleteTask );
4287
}
4388

4489
void TaskSetFunc( uint32_t start_, uint32_t end_, uint32_t threadnum_, void* pArgs_ )
4590
{
4691
(void)start_; (void)end_;
47-
uint32_t* pTaskNum = pArgs_;
48-
printf("Task %u running on thread %u\n", *pTaskNum, threadnum_);
92+
struct TaskSetArgs* pTaskSetArgs = pArgs_;
93+
struct enkiParamsTaskSet paramsTaskNext = enkiGetParamsTaskSet( pTaskSetArgs->pTask );
94+
if( 0 == start_ )
95+
{
96+
// for clarity in this example we only output one printf per taskset func called, but would normally loop from start_ to end_ doing work
97+
printf("Task %s for run %u running on thread %u has set size %u\n", pTaskSetArgs->name, pTaskSetArgs->run, threadnum_, paramsTaskNext.setSize);
98+
}
99+
100+
// A TastSetFunction is not a safe place to free it's own pArgs_ as when the setSize > 1 there may be multiple
101+
// calls to this function with the same pArgs_
49102
}
50103

104+
51105
int main(int argc, const char * argv[])
52106
{
107+
// This examples shows CompletionActions used to modify a following tasks parameters and free allocations
108+
// Task Graph for this example (with names shortened to fit on screen):
109+
//
110+
// pTaskSetA
111+
// ->pCompletionActionA-PreFunc-PostFunc
112+
// ->pTaskSetB
113+
// ->pCompletionActionB-(no PreFunc)-PostFunc
114+
//
115+
// Note that pTaskSetB must depend on pCompletionActionA NOT pTaskSetA or it could run at the same time as pCompletionActionA
116+
// so cannot be modified.
117+
118+
struct enkiTaskSet* pTaskSetA;
119+
struct enkiCompletionAction* pCompletionActionA;
120+
struct enkiTaskSet* pTaskSetB;
121+
struct enkiCompletionAction* pCompletionActionB;
122+
struct TaskSetArgs* pTaskSetArgsA;
123+
struct CompletionArgs_ModifyTask* pCompletionArgsA;
124+
struct enkiParamsCompletionAction paramsCompletionActionA;
125+
struct TaskSetArgs* pTaskSetArgsB;
126+
struct enkiDependency* pDependencyOfTaskSetBOnCompletionActionA;
127+
struct CompletionArgs_DeleteTask* pCompletionArgs_DeleteTaskA;
128+
struct CompletionArgs_DeleteTask* pCompletionArgs_DeleteTaskB;
129+
struct enkiParamsCompletionAction paramsCompletionActionB;
53130
int run;
54-
struct enkiParamsCompletionAction paramsCompletionAction;
55-
uint32_t* pTaskNum;
56-
struct CompletionArgs* pCompletionArgs;
57131

58132
pETS = enkiNewTaskScheduler();
59133
enkiInitTaskScheduler( pETS );
60134

61-
// Here we demonstrate using the completion action to delete the tasks on the fly
62135
for( run=0; run<10; ++run )
63136
{
64-
pCompletionArgs = malloc(sizeof(struct CompletionArgs)); // we will free in CompletionFunction
65-
pCompletionArgs->pTask = enkiCreateTaskSet( pETS, TaskSetFunc );
66-
pTaskNum = malloc(sizeof(uint32_t)); // we will free in CompletionFunction
67-
*pTaskNum = run;
68-
enkiSetArgsTaskSet( pCompletionArgs->pTask, pTaskNum );
69-
pCompletionArgs->pCompletionAction = enkiCreateCompletionAction( pETS, CompletionFunction );
70-
paramsCompletionAction = enkiGetParamsCompletionAction( pCompletionArgs->pCompletionAction );
71-
paramsCompletionAction.pArgs = pCompletionArgs;
72-
paramsCompletionAction.pDependency = enkiGetCompletableFromTaskSet( pCompletionArgs->pTask );
73-
enkiSetParamsCompletionAction( pCompletionArgs->pCompletionAction, paramsCompletionAction );
74-
75-
enkiAddTaskSet( pETS, pCompletionArgs->pTask );
137+
// Create all this runs tasks and completion actions
138+
pTaskSetA = enkiCreateTaskSet( pETS, TaskSetFunc );
139+
pCompletionActionA = enkiCreateCompletionAction( pETS,
140+
CompletionFunctionPreComplete_ModifyDependentTask,
141+
CompletionFunctionPostComplete_DeleteTask );
142+
pTaskSetB = enkiCreateTaskSet( pETS, TaskSetFunc );
143+
pCompletionActionB = enkiCreateCompletionAction( pETS,
144+
NULL,
145+
CompletionFunctionPostComplete_DeleteTask );
146+
147+
// Set args for TaskSetA
148+
pTaskSetArgsA = malloc(sizeof(struct TaskSetArgs));
149+
pTaskSetArgsA->run = run;
150+
pTaskSetArgsA->pTask = pTaskSetA;
151+
pTaskSetArgsA->name = "A";
152+
enkiSetArgsTaskSet( pTaskSetA, pTaskSetArgsA );
153+
154+
// Set args for CompletionActionA, and make dependent on TaskSetA through pDependency
155+
pCompletionArgsA = malloc(sizeof(struct CompletionArgs_ModifyTask));
156+
pCompletionArgsA->pTaskB = pTaskSetB;
157+
pCompletionArgsA->run = run;
158+
pCompletionArgs_DeleteTaskA = malloc(sizeof(struct CompletionArgs_DeleteTask));
159+
pCompletionArgs_DeleteTaskA->pTask = pTaskSetA;
160+
pCompletionArgs_DeleteTaskA->pCompletionAction = pCompletionActionA;
161+
pCompletionArgs_DeleteTaskA->pDependency = NULL;
162+
pCompletionArgs_DeleteTaskA->run = run;
163+
164+
paramsCompletionActionA = enkiGetParamsCompletionAction( pCompletionActionA );
165+
paramsCompletionActionA.pArgsPreComplete = pCompletionArgsA;
166+
paramsCompletionActionA.pArgsPostComplete = pCompletionArgs_DeleteTaskA;
167+
paramsCompletionActionA.pDependency = enkiGetCompletableFromTaskSet( pTaskSetA );
168+
enkiSetParamsCompletionAction( pCompletionActionA, paramsCompletionActionA );
169+
170+
171+
// Set args for TaskSetB
172+
pTaskSetArgsB = malloc(sizeof(struct TaskSetArgs));
173+
pTaskSetArgsB->run = run;
174+
pTaskSetArgsB->pTask = pTaskSetB;
175+
pTaskSetArgsB->name = "B";
176+
enkiSetArgsTaskSet( pTaskSetB, pTaskSetArgsB );
177+
178+
// TaskSetB depends on pCompletionActionA
179+
pDependencyOfTaskSetBOnCompletionActionA = enkiCreateDependency( pETS );
180+
enkiSetDependency( pDependencyOfTaskSetBOnCompletionActionA,
181+
enkiGetCompletableFromCompletionAction( pCompletionActionA ),
182+
enkiGetCompletableFromTaskSet( pTaskSetB ) );
183+
184+
// Set args for CompletionActionB, and make dependent on TaskSetB through pDependency
185+
pCompletionArgs_DeleteTaskB = malloc(sizeof(struct CompletionArgs_DeleteTask));
186+
pCompletionArgs_DeleteTaskB->pTask = pTaskSetB;
187+
pCompletionArgs_DeleteTaskB->pDependency = pDependencyOfTaskSetBOnCompletionActionA;
188+
pCompletionArgs_DeleteTaskB->pCompletionAction = pCompletionActionB;
189+
pCompletionArgs_DeleteTaskB->run = run;
190+
191+
paramsCompletionActionB = enkiGetParamsCompletionAction( pCompletionActionB );
192+
paramsCompletionActionB.pArgsPreComplete = NULL; // pCompletionActionB does not have a PreComplete function
193+
paramsCompletionActionB.pArgsPostComplete = pCompletionArgs_DeleteTaskB;
194+
paramsCompletionActionB.pDependency = enkiGetCompletableFromTaskSet( pTaskSetB );
195+
enkiSetParamsCompletionAction( pCompletionActionB, paramsCompletionActionB );
196+
197+
198+
// To launch all, we only add the first TaskSet
199+
enkiAddTaskSet( pETS, pTaskSetA );
76200
}
77201
enkiWaitForAll( pETS );
78202

src/TaskScheduler_c.cpp

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,30 @@ struct enkiPinnedTask : IPinnedTask
8181

8282
struct enkiCompletionAction : ICompletable
8383
{
84-
void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ )
84+
void OnDependenciesComplete( TaskScheduler* pTaskScheduler_, uint32_t threadNum_ ) override
8585
{
86+
if( completionFunctionPreComplete )
87+
{
88+
completionFunctionPreComplete( pArgsPreComplete, threadNum_ );
89+
}
90+
91+
// make temporaries for post completion as this task could get deleted after OnDependenciesComplete
92+
enkiCompletionFunction tempCompletionFunctionPostComplete = completionFunctionPostComplete;
93+
void* ptempArgsPostComplete = pArgsPostComplete;
94+
8695
ICompletable::OnDependenciesComplete( pTaskScheduler_, threadNum_ );
8796

88-
completionFunction( pArgs, threadNum_ );
97+
if( tempCompletionFunctionPostComplete )
98+
{
99+
tempCompletionFunctionPostComplete( ptempArgsPostComplete, threadNum_ );
100+
}
89101
}
90102

91-
enkiCompletionFunction completionFunction;
103+
enkiCompletionFunction completionFunctionPreComplete;
104+
enkiCompletionFunction completionFunctionPostComplete;
92105
Dependency dependency;
93-
void* pArgs = NULL;
106+
void* pArgsPreComplete = NULL;
107+
void* pArgsPostComplete = NULL;
94108
};
95109

96110
struct enkiDependency : Dependency {}; // empty struct which we will use for dependencies
@@ -476,13 +490,14 @@ void enkiSetDependency( enkiDependency* pDependency_, enkiCompletable* pDependen
476490
pTaskToRunOnCompletion_->SetDependency( *pDependency_, pDependencyTask_ );
477491
}
478492

479-
enkiCompletionAction* enkiCreateCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionFunction completionFunc_ )
493+
enkiCompletionAction* enkiCreateCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionFunction completionFunctionPreComplete_, enkiCompletionFunction completionFunctionPostComplete_ )
480494
{
481495
const CustomAllocator& customAllocator = pETS_->GetConfig().customAllocator;
482496
enkiCompletionAction* pCA = (enkiCompletionAction*)customAllocator.alloc(
483497
alignof(enkiCompletionAction), sizeof(enkiCompletionAction), customAllocator.userData, ENKI_FILE_AND_LINE );
484498
new(pCA) enkiCompletionAction();
485-
pCA->completionFunction = completionFunc_;
499+
pCA->completionFunctionPreComplete = completionFunctionPreComplete_;
500+
pCA->completionFunctionPostComplete = completionFunctionPostComplete_;
486501
return pCA;
487502
}
488503

@@ -497,14 +512,16 @@ void enkiDeleteCompletionAction( enkiTaskScheduler* pETS_, enkiCompletionAction*
497512
enkiParamsCompletionAction enkiGetParamsCompletionAction( enkiCompletionAction* pCompletionAction_ )
498513
{
499514
enkiParamsCompletionAction params;
500-
params.pArgs = pCompletionAction_->pArgs;
515+
params.pArgsPreComplete = pCompletionAction_->pArgsPreComplete;
516+
params.pArgsPostComplete = pCompletionAction_->pArgsPostComplete;
501517
params.pDependency = reinterpret_cast<const enkiCompletable*>(
502518
pCompletionAction_->dependency.GetDependencyTask() );
503519
return params;
504520
}
505521

506522
void enkiSetParamsCompletionAction( enkiCompletionAction* pCompletionAction_, enkiParamsCompletionAction params_ )
507523
{
508-
pCompletionAction_->pArgs = params_.pArgs;
524+
pCompletionAction_->pArgsPreComplete = params_.pArgsPreComplete;
525+
pCompletionAction_->pArgsPostComplete = params_.pArgsPostComplete;
509526
pCompletionAction_->SetDependency( pCompletionAction_->dependency, params_.pDependency );
510527
}

0 commit comments

Comments
 (0)