Jolt Physics
A multi core friendly Game Physics Engine
Loading...
Searching...
No Matches
JobSystem.h
Go to the documentation of this file.
1// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
2// SPDX-FileCopyrightText: 2021 Jorrit Rouwe
3// SPDX-License-Identifier: MIT
4
5#pragma once
6
8#include <Jolt/Core/Color.h>
12#include <Jolt/Core/Atomics.h>
13
15
70{
71protected:
72 class Job;
73
74public:
76
79 class JobHandle : private Ref<Job>
80 {
81 public:
83 inline JobHandle() = default;
84 inline JobHandle(const JobHandle &inHandle) = default;
85 inline JobHandle(JobHandle &&inHandle) noexcept : Ref<Job>(std::move(inHandle)) { }
86
88 inline explicit JobHandle(Job *inJob) : Ref<Job>(inJob) { }
89
91 inline JobHandle & operator = (const JobHandle &inHandle) = default;
92 inline JobHandle & operator = (JobHandle &&inHandle) noexcept = default;
93
95 inline bool IsValid() const { return GetPtr() != nullptr; }
96
98 inline bool IsDone() const { return GetPtr() != nullptr && GetPtr()->IsDone(); }
99
101 inline void AddDependency(int inCount = 1) const { GetPtr()->AddDependency(inCount); }
102
105 inline void RemoveDependency(int inCount = 1) const { GetPtr()->RemoveDependencyAndQueue(inCount); }
106
108 static inline void sRemoveDependencies(const JobHandle *inHandles, uint inNumHandles, int inCount = 1);
109
111 template <uint N>
112 static inline void sRemoveDependencies(StaticArray<JobHandle, N> &inHandles, int inCount = 1)
113 {
114 sRemoveDependencies(inHandles.data(), inHandles.size(), inCount);
115 }
116
118 using Ref<Job>::GetPtr;
119 };
120
122 class Barrier : public NonCopyable
123 {
124 public:
126
129 virtual void AddJob(const JobHandle &inJob) = 0;
130
133 virtual void AddJobs(const JobHandle *inHandles, uint inNumHandles) = 0;
134
135 protected:
137 friend class Job;
138
140 virtual ~Barrier() = default;
141
143 virtual void OnJobFinished(Job *inJob) = 0;
144 };
145
147 using JobFunction = function<void()>;
148
150 virtual ~JobSystem() = default;
151
153 virtual int GetMaxConcurrency() const = 0;
154
157 virtual JobHandle CreateJob(const char *inName, ColorArg inColor, const JobFunction &inJobFunction, uint32 inNumDependencies = 0) = 0;
158
161 virtual void CreateJobs(const char *inName, ColorArg inColor, const JobFunction &inJobFunction, JobHandle *outJobs, uint32 inNumJobs, uint32 inNumDependencies = 0) = 0;
162
164 virtual Barrier * CreateBarrier() = 0;
165
167 virtual void DestroyBarrier(Barrier *inBarrier) = 0;
168
170 virtual void WaitForJobs(Barrier *inBarrier) = 0;
171
172protected:
174 class Job
175 {
176 public:
178
180 Job([[maybe_unused]] const char *inJobName, [[maybe_unused]] ColorArg inColor, JobSystem *inJobSystem, const JobFunction &inJobFunction, uint32 inNumDependencies) :
181 mJobFunction(inJobFunction),
182 #if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
183 mJobName(inJobName),
184 mColor(inColor),
185 #endif // defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
186 mJobSystem(inJobSystem),
187 mNumDependencies(inNumDependencies)
188 {
189 }
190
192 inline JobSystem * GetJobSystem() { return mJobSystem; }
193
195 inline void AddRef(uint32 inCount = 1)
196 {
197 // Adding a reference can use relaxed memory ordering
198 mReferenceCount.fetch_add(inCount, memory_order_relaxed);
199 }
200 inline void Release()
201 {
202 #ifndef JPH_TSAN_ENABLED
203 // Releasing a reference must use release semantics...
204 if (mReferenceCount.fetch_sub(1, memory_order_release) == 1)
205 {
206 // ... so that we can use acquire to ensure that we see any updates from other threads that released a ref before freeing the job
207 atomic_thread_fence(memory_order_acquire);
208 mJobSystem->FreeJob(this);
209 }
210 #else
211 // But under TSAN, we cannot use atomic_thread_fence, so we use an acq_rel operation unconditionally instead
212 if (mReferenceCount.fetch_sub(1, memory_order_acq_rel) == 1)
213 mJobSystem->FreeJob(this);
214 #endif
215 }
216
218 inline void AddDependency(int inCount);
219
222 inline bool RemoveDependency(int inCount);
223
226 inline void RemoveDependencyAndQueue(int inCount);
227
229 inline bool SetBarrier(Barrier *inBarrier)
230 {
231 // Acquire on failure so that, if the job already finished, the caller sees all side effects of the job function
232 intptr_t barrier = 0;
233 if (mBarrier.compare_exchange_strong(barrier, reinterpret_cast<intptr_t>(inBarrier), memory_order_release, memory_order_acquire))
234 return true;
235 JPH_ASSERT(barrier == cBarrierDoneState, "A job can only belong to 1 barrier");
236 return false;
237 }
238
241 {
242 // Transition job to executing state
243 uint32 state = 0; // We can only start running with a dependency counter of 0
244 if (!mNumDependencies.compare_exchange_strong(state, cExecutingState, memory_order_acquire))
245 return state; // state is updated by compare_exchange_strong to the current value
246
247 // Run the job function
248 {
249 JPH_PROFILE(mJobName, mColor.GetUInt32());
250 mJobFunction();
251 }
252
253 // Clear the job function. It is a lambda with captured state that we want to destruct in a controlled fashion.
254 // If we release it after notifying the barrier then that state might access deleted data.
255 mJobFunction = {};
256
257 // Fetch the barrier pointer and exchange it for the done state, so we're sure that no barrier gets set after we want to call the callback
258 intptr_t barrier = mBarrier.load(memory_order_relaxed);
259 for (;;)
260 {
261 // Acquire to pair with the release in SetBarrier, release so that a failing SetBarrier sees the side effects of the job function
262 if (mBarrier.compare_exchange_weak(barrier, cBarrierDoneState, memory_order_acq_rel, memory_order_relaxed))
263 break;
264 }
265 JPH_ASSERT(barrier != cBarrierDoneState);
266
267 // Mark job as done
268 state = cExecutingState;
269 mNumDependencies.compare_exchange_strong(state, cDoneState, memory_order_relaxed);
270 JPH_ASSERT(state == cExecutingState);
271
272 // Notify the barrier after we've changed the job to the done state so that any thread reading the state after receiving the callback will see that the job has finished
273 if (barrier != 0)
274 reinterpret_cast<Barrier *>(barrier)->OnJobFinished(this);
275
276 return cDoneState;
277 }
278
280 inline bool CanBeExecuted() const { return mNumDependencies.load(memory_order_relaxed) == 0; }
281
283 inline bool IsDone() const { return mNumDependencies.load(memory_order_relaxed) == cDoneState; }
284
285 #if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
287 const char * GetName() const { return mJobName; }
288 #endif // defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
289
290 static constexpr uint32 cExecutingState = 0xe0e0e0e0;
291 static constexpr uint32 cDoneState = 0xd0d0d0d0;
292
293 static constexpr intptr_t cBarrierDoneState = ~intptr_t(0);
294
295private:
296 JobFunction mJobFunction;
297 #if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
298 const char * mJobName;
299 Color mColor;
300 #endif // defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
301 JobSystem * mJobSystem;
302 atomic<intptr_t> mBarrier = 0;
303 atomic<uint32> mReferenceCount = 0;
304 atomic<uint32> mNumDependencies;
305 };
306
308 virtual void QueueJob(Job *inJob) = 0;
309
311 virtual void QueueJobs(Job **inJobs, uint inNumJobs) = 0;
312
314 virtual void FreeJob(Job *inJob) = 0;
315};
316
318
320
321#include "JobSystem.inl"
#define JPH_EXPORT
Definition Core.h:303
unsigned int uint
Definition Core.h:564
#define JPH_NAMESPACE_END
Definition Core.h:487
std::uint32_t uint32
Definition Core.h:567
#define JPH_NAMESPACE_BEGIN
Definition Core.h:481
#define JPH_ASSERT(...)
Definition IssueReporting.h:28
#define JPH_OVERRIDE_NEW_DELETE
Macro to override the new and delete functions.
Definition Memory.h:51
#define JPH_PROFILE(...)
Definition Profiler.h:267
Class that holds an RGBA color with 8-bits per component.
Definition Color.h:16
A job barrier keeps track of a number of jobs and allows waiting until they are all completed.
Definition JobSystem.h:123
virtual ~Barrier()=default
Destructor, you should call JobSystem::DestroyBarrier instead of destructing this object directly.
virtual void OnJobFinished(Job *inJob)=0
Called by a Job to mark that it is finished.
virtual void AddJobs(const JobHandle *inHandles, uint inNumHandles)=0
virtual JPH_OVERRIDE_NEW_DELETE void AddJob(const JobHandle &inJob)=0
Definition JobSystem.h:80
JobHandle()=default
Constructor.
JobHandle(Job *inJob)
Constructor, only to be used by JobSystem.
Definition JobSystem.h:88
void RemoveDependency(int inCount=1) const
Definition JobSystem.h:105
JobHandle(JobHandle &&inHandle) noexcept
Definition JobSystem.h:85
bool IsDone() const
Check if this job has finished executing.
Definition JobSystem.h:98
static void sRemoveDependencies(StaticArray< JobHandle, N > &inHandles, int inCount=1)
Helper function to remove dependencies on a static array of job handles.
Definition JobSystem.h:112
bool IsValid() const
Check if this handle contains a job.
Definition JobSystem.h:95
JobHandle(const JobHandle &inHandle)=default
void AddDependency(int inCount=1) const
Add to the dependency counter.
Definition JobSystem.h:101
A class that contains information for a single unit of work.
Definition JobSystem.h:175
const char * GetName() const
Get the name of the job.
Definition JobSystem.h:287
void Release()
Definition JobSystem.h:200
JPH_OVERRIDE_NEW_DELETE Job(const char *inJobName, ColorArg inColor, JobSystem *inJobSystem, const JobFunction &inJobFunction, uint32 inNumDependencies)
Constructor.
Definition JobSystem.h:180
uint32 Execute()
Run the job function, returns the number of dependencies that this job still has or cExecutingState o...
Definition JobSystem.h:240
bool SetBarrier(Barrier *inBarrier)
Set the job barrier that this job belongs to and returns false if this was not possible because the j...
Definition JobSystem.h:229
JobSystem * GetJobSystem()
Get the jobs system to which this job belongs.
Definition JobSystem.h:192
bool CanBeExecuted() const
Test if the job can be executed.
Definition JobSystem.h:280
bool IsDone() const
Test if the job finished executing.
Definition JobSystem.h:283
void AddRef(uint32 inCount=1)
Add or release a reference to this object.
Definition JobSystem.h:195
Definition JobSystem.h:70
virtual JobHandle CreateJob(const char *inName, ColorArg inColor, const JobFunction &inJobFunction, uint32 inNumDependencies=0)=0
virtual Barrier * CreateBarrier()=0
Create a new barrier, used to wait on jobs.
virtual void WaitForJobs(Barrier *inBarrier)=0
Wait for a set of jobs to be finished, note that only 1 thread can be waiting on a barrier at a time.
virtual void DestroyBarrier(Barrier *inBarrier)=0
Destroy a barrier when it is no longer used. The barrier should be empty at this point.
virtual void QueueJobs(Job **inJobs, uint inNumJobs)=0
Adds a number of jobs at once to the job queue.
virtual int GetMaxConcurrency() const =0
Get maximum number of concurrently executing jobs.
virtual void FreeJob(Job *inJob)=0
Frees a job.
virtual ~JobSystem()=default
Destructor.
function< void()> JobFunction
Main function of the job.
Definition JobSystem.h:147
virtual void QueueJob(Job *inJob)=0
Adds a job to the job queue.
virtual void CreateJobs(const char *inName, ColorArg inColor, const JobFunction &inJobFunction, JobHandle *outJobs, uint32 inNumJobs, uint32 inNumDependencies=0)=0
Class that makes another class non-copyable. Usage: Inherit from NonCopyable.
Definition NonCopyable.h:11
Definition Reference.h:110
Simple variable length array backed by a fixed size buffer.
Definition StaticArray.h:14
const T * data() const
Definition StaticArray.h:138
size_type size() const
Returns amount of elements in the array.
Definition StaticArray.h:89