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
160 virtual Barrier * CreateBarrier() = 0;
161
163 virtual void DestroyBarrier(Barrier *inBarrier) = 0;
164
166 virtual void WaitForJobs(Barrier *inBarrier) = 0;
167
168protected:
170 class Job
171 {
172 public:
174
176 Job([[maybe_unused]] const char *inJobName, [[maybe_unused]] ColorArg inColor, JobSystem *inJobSystem, const JobFunction &inJobFunction, uint32 inNumDependencies) :
177 #if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
178 mJobName(inJobName),
179 mColor(inColor),
180 #endif // defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
181 mJobSystem(inJobSystem),
182 mJobFunction(inJobFunction),
183 mNumDependencies(inNumDependencies)
184 {
185 }
186
188 inline JobSystem * GetJobSystem() { return mJobSystem; }
189
191 inline void AddRef()
192 {
193 // Adding a reference can use relaxed memory ordering
194 mReferenceCount.fetch_add(1, memory_order_relaxed);
195 }
196 inline void Release()
197 {
198 #ifndef JPH_TSAN_ENABLED
199 // Releasing a reference must use release semantics...
200 if (mReferenceCount.fetch_sub(1, memory_order_release) == 1)
201 {
202 // ... so that we can use acquire to ensure that we see any updates from other threads that released a ref before freeing the job
203 atomic_thread_fence(memory_order_acquire);
204 mJobSystem->FreeJob(this);
205 }
206 #else
207 // But under TSAN, we cannot use atomic_thread_fence, so we use an acq_rel operation unconditionally instead
208 if (mReferenceCount.fetch_sub(1, memory_order_acq_rel) == 1)
209 mJobSystem->FreeJob(this);
210 #endif
211 }
212
214 inline void AddDependency(int inCount);
215
218 inline bool RemoveDependency(int inCount);
219
222 inline void RemoveDependencyAndQueue(int inCount);
223
225 inline bool SetBarrier(Barrier *inBarrier)
226 {
227 intptr_t barrier = 0;
228 if (mBarrier.compare_exchange_strong(barrier, reinterpret_cast<intptr_t>(inBarrier), memory_order_relaxed))
229 return true;
230 JPH_ASSERT(barrier == cBarrierDoneState, "A job can only belong to 1 barrier");
231 return false;
232 }
233
236 {
237 // Transition job to executing state
238 uint32 state = 0; // We can only start running with a dependency counter of 0
239 if (!mNumDependencies.compare_exchange_strong(state, cExecutingState, memory_order_acquire))
240 return state; // state is updated by compare_exchange_strong to the current value
241
242 // Run the job function
243 {
244 JPH_PROFILE(mJobName, mColor.GetUInt32());
245 mJobFunction();
246 }
247
248 // 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
249 intptr_t barrier = mBarrier.load(memory_order_relaxed);
250 for (;;)
251 {
252 if (mBarrier.compare_exchange_weak(barrier, cBarrierDoneState, memory_order_relaxed))
253 break;
254 }
255 JPH_ASSERT(barrier != cBarrierDoneState);
256
257 // Mark job as done
258 state = cExecutingState;
259 mNumDependencies.compare_exchange_strong(state, cDoneState, memory_order_relaxed);
260 JPH_ASSERT(state == cExecutingState);
261
262 // 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
263 if (barrier != 0)
264 reinterpret_cast<Barrier *>(barrier)->OnJobFinished(this);
265
266 return cDoneState;
267 }
268
270 inline bool CanBeExecuted() const { return mNumDependencies.load(memory_order_relaxed) == 0; }
271
273 inline bool IsDone() const { return mNumDependencies.load(memory_order_relaxed) == cDoneState; }
274
275 #if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
277 const char * GetName() const { return mJobName; }
278 #endif // defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
279
280 static constexpr uint32 cExecutingState = 0xe0e0e0e0;
281 static constexpr uint32 cDoneState = 0xd0d0d0d0;
282
283 static constexpr intptr_t cBarrierDoneState = ~intptr_t(0);
284
285private:
286 #if defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
287 const char * mJobName;
288 Color mColor;
289 #endif // defined(JPH_EXTERNAL_PROFILE) || defined(JPH_PROFILE_ENABLED)
290 JobSystem * mJobSystem;
291 atomic<intptr_t> mBarrier = 0;
292 JobFunction mJobFunction;
293 atomic<uint32> mReferenceCount = 0;
294 atomic<uint32> mNumDependencies;
295 };
296
298 virtual void QueueJob(Job *inJob) = 0;
299
301 virtual void QueueJobs(Job **inJobs, uint inNumJobs) = 0;
302
304 virtual void FreeJob(Job *inJob) = 0;
305};
306
308
310
311#include "JobSystem.inl"
#define JPH_EXPORT
Definition: Core.h:236
unsigned int uint
Definition: Core.h:452
#define JPH_NAMESPACE_END
Definition: Core.h:378
std::uint32_t uint32
Definition: Core.h:455
#define JPH_NAMESPACE_BEGIN
Definition: Core.h:372
#define JPH_ASSERT(...)
Definition: IssueReporting.h:33
#define JPH_OVERRIDE_NEW_DELETE
Macro to override the new and delete functions.
Definition: Memory.h:31
#define JPH_PROFILE(...)
Definition: Profiler.h:268
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:171
const char * GetName() const
Get the name of the job.
Definition: JobSystem.h:277
void Release()
Definition: JobSystem.h:196
JPH_OVERRIDE_NEW_DELETE Job(const char *inJobName, ColorArg inColor, JobSystem *inJobSystem, const JobFunction &inJobFunction, uint32 inNumDependencies)
Constructor.
Definition: JobSystem.h:176
uint32 Execute()
Run the job function, returns the number of dependencies that this job still has or cExecutingState o...
Definition: JobSystem.h:235
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:225
JobSystem * GetJobSystem()
Get the jobs system to which this job belongs.
Definition: JobSystem.h:188
bool CanBeExecuted() const
Test if the job can be executed.
Definition: JobSystem.h:270
bool IsDone() const
Test if the job finished executing.
Definition: JobSystem.h:273
void AddRef()
Add or release a reference to this object.
Definition: JobSystem.h:191
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.
Class that makes another class non-copyable. Usage: Inherit from NonCopyable.
Definition: NonCopyable.h:11
Definition: Reference.h:107
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