Pioneer
JobQueue.h
Go to the documentation of this file.
1 // Copyright © 2008-2021 Pioneer Developers. See AUTHORS.txt for details
2 // Licensed under the terms of the GPL v3. See licenses/GPL-3.txt
3 
4 #ifndef JOBQUEUE_H
5 #define JOBQUEUE_H
6 
7 #include "SDL_thread.h"
8 #include <cassert>
9 #include <deque>
10 #include <set>
11 #include <string>
12 #include <vector>
13 
14 static const uint32_t MAX_THREADS = 64;
15 
16 class JobClient;
17 class JobQueue;
18 
19 // represents a single unit of work that you want done
20 // subclass and implement:
21 //
22 // OnRun: called from worker thread, and does the actual stuff you want done.
23 // store all your data in the object itself.
24 //
25 // OnFinish: called from the main thread once the worker completes the job.
26 // this is where you deliver the results from the worker
27 //
28 // OnCancel: optional. called from the main thread to tell the job that its
29 // results are not wanted. it should arrange for OnRun to return
30 // as quickly as possible. OnFinish will not be called for the job
31 class Job {
32 public:
33  // This is the RAII handle for a queued Job. A job is cancelled when the
34  // Job::Handle is destroyed. There is at most one Job::Handle for each Job
35  // (non-queued Jobs have no handle). Job::Handle is not copyable only
36  // moveable.
37  class Handle {
38  public:
39  Handle() :
40  m_id(++s_nextId),
41  m_job(nullptr),
42  m_queue(nullptr),
43  m_client(nullptr) {}
44  Handle(Handle &&other);
45  Handle &operator=(Handle &&other);
46  ~Handle();
47 
48  Handle(const Handle &) = delete;
49  Handle &operator=(const Handle &) = delete;
50 
51  bool HasJob() const { return m_job != nullptr; }
52  Job *GetJob() const { return m_job; }
53 
54  bool operator<(const Handle &other) const { return m_id < other.m_id; }
55 
56  private:
57  friend class Job;
58  friend class AsyncJobQueue;
59  friend class SyncJobQueue;
60 
61  Handle(Job *job, JobQueue *queue, JobClient *client);
62  void Unlink();
63 
64  static Uint64 s_nextId;
65 
66  Uint64 m_id;
67  Job *m_job;
68  JobQueue *m_queue;
69  JobClient *m_client;
70  };
71 
72 public:
73  Job() :
74  cancelled(false),
75  m_handle(nullptr) {}
76  virtual ~Job();
77 
78  Job(const Job &) = delete;
79  Job &operator=(const Job &) = delete;
80 
81  virtual void OnRun() = 0;
82  virtual void OnFinish() = 0;
83  virtual void OnCancel() {}
84 
85 private:
86  friend class AsyncJobQueue;
87  friend class SyncJobQueue;
88  friend class JobRunner;
89 
90  void UnlinkHandle();
91  const Handle *GetHandle() const { return m_handle; }
92  void SetHandle(Handle *handle) { m_handle = handle; }
93  void ClearHandle() { m_handle = nullptr; }
94 
95  bool cancelled;
96  Handle *m_handle;
97 };
98 
99 // the queue management class. create one from the main thread, and feed your
100 // jobs do it. it will take care of the rest
101 class JobQueue {
102 public:
103  JobQueue() = default;
104  JobQueue(const JobQueue &) = delete;
105  JobQueue &operator=(const JobQueue &) = delete;
106 
107  // numRunners is the number of jobs to run in parallel. right now its the
108  // same as the number of threads, but there's no reason that it has to be
109  virtual ~JobQueue() {}
110 
111  // call from the main thread to add a job to the queue. the job should be
112  // allocated with new. the queue will delete it once its its completed
113  virtual Job::Handle Queue(Job *job, JobClient *client = nullptr) = 0;
114 
115  // call from the main thread to cancel a job. one of three things will happen
116  //
117  // - the job hasn't run yet. it will never be run, and neither OnFinished nor
118  // OnCancel will be called. the job will be deleted on the next call to
119  // FinishJobs
120  //
121  // - the job has finished. neither onFinished not onCancel will be called.
122  // the job will be deleted on the next call to FinishJobs
123  //
124  // - the job is running. OnCancel will be called
125  virtual void Cancel(Job *job) = 0;
126 
127  // call from the main loop. this will call OnFinish for any finished jobs,
128  // and then delete all finished and cancelled jobs. returns the number of
129  // finished jobs (not cancelled)
130  virtual Uint32 FinishJobs() = 0;
131 };
132 
133 // the queue management class. create one from the main thread, and feed your
134 // jobs do it. it will take care of the rest
135 class AsyncJobQueue : public JobQueue {
136 public:
137  // numRunners is the number of jobs to run in parallel. right now its the
138  // same as the number of threads, but there's no reason that it has to be
139  AsyncJobQueue(Uint32 numRunners);
140  virtual ~AsyncJobQueue();
141 
142  // call from the main thread to add a job to the queue. the job should be
143  // allocated with new. the queue will delete it once its its completed
144  virtual Job::Handle Queue(Job *job, JobClient *client = nullptr) override;
145 
146  // call from the main thread to cancel a job. one of three things will happen
147  //
148  // - the job hasn't run yet. it will never be run, and neither OnFinished nor
149  // OnCancel will be called. the job will be deleted on the next call to
150  // FinishJobs
151  //
152  // - the job has finished. neither onFinished not onCancel will be called.
153  // the job will be deleted on the next call to FinishJobs
154  //
155  // - the job is running. OnCancel will be called
156  virtual void Cancel(Job *job) override;
157 
158  // call from the main loop. this will call OnFinish for any finished jobs,
159  // and then delete all finished and cancelled jobs. returns the number of
160  // finished jobs (not cancelled)
161  virtual Uint32 FinishJobs() override;
162 
163 private:
164  // a runner wraps a single thread, and calls into the queue when its ready for
165  // a new job. no user-servicable parts inside!
166  class JobRunner {
167  public:
168  JobRunner(AsyncJobQueue *jq, const uint8_t idx);
169  ~JobRunner();
170  SDL_mutex *GetQueueDestroyingLock();
171  void SetQueueDestroyed();
172 
173  private:
174  static int Trampoline(void *);
175  void Main();
176 
177  AsyncJobQueue *m_jobQueue;
178 
179  Job *m_job;
180  SDL_mutex *m_jobLock;
181  SDL_mutex *m_queueDestroyingLock;
182 
183  SDL_Thread *m_threadId;
184 
185  uint8_t m_threadIdx;
186  std::string m_threadName;
187  bool m_queueDestroyed;
188  };
189 
190  Job *GetJob();
191  void Finish(Job *job, const uint8_t threadIdx);
192 
193  std::deque<Job *> m_queue;
194  SDL_mutex *m_queueLock;
195  SDL_cond *m_queueWaitCond;
196 
197  std::deque<Job *> m_finished[MAX_THREADS];
198  SDL_mutex *m_finishedLock[MAX_THREADS];
199 
200  std::vector<JobRunner *> m_runners;
201  // scratch space for finishing jobs on main thread
202  std::vector<Job *> m_finishedScratch;
203 
204  bool m_shutdown;
205 };
206 
207 class SyncJobQueue : public JobQueue {
208 public:
209  SyncJobQueue() = default;
210  virtual ~SyncJobQueue();
211 
212  // call from the main thread to add a job to the queue. the job should be
213  // allocated with new. the queue will delete it once its its completed
214  virtual Job::Handle Queue(Job *job, JobClient *client = nullptr) override;
215 
216  // call from the main thread to cancel a job. one of three things will happen
217  //
218  // - the job hasn't run yet. it will never be run, and neither OnFinished nor
219  // OnCancel will be called. the job will be deleted on the next call to
220  // FinishJobs
221  //
222  // - the job has finished. neither onFinished not onCancel will be called.
223  // the job will be deleted on the next call to FinishJobs
224  //
225  // - the job is running. OnCancel will be called
226  virtual void Cancel(Job *job) override;
227 
228  // call from the main loop. this will call OnFinish for any finished jobs,
229  // and then delete all finished and cancelled jobs. returns the number of
230  // finished jobs (not cancelled)
231  virtual Uint32 FinishJobs() override;
232 
233  Uint32 RunJobs(Uint32 count = 1);
234 
235 private:
236  std::deque<Job *> m_queue;
237  std::deque<Job *> m_finished;
238 };
239 
240 // JobClient is an abstraction to allow transparent management of job handles
241 class JobClient {
242 public:
243  virtual void Order(Job *job) = 0;
244  virtual void RemoveJob(Job::Handle *handle) = 0;
245  virtual ~JobClient() {}
246 };
247 
248 // JobSet provides an interface for "fire and forget" jobs - call Order with your job,
249 // and JobSet will keep the handle alive until the job has finished.
250 class JobSet : public JobClient {
251 public:
252  JobSet(JobQueue *queue) :
253  m_queue(queue) {}
254  JobSet(JobSet &&other) :
255  m_queue(other.m_queue),
256  m_jobs(std::move(other.m_jobs)) { other.m_queue = nullptr; }
258  {
259  m_queue = other.m_queue;
260  m_jobs = std::move(other.m_jobs);
261  other.m_queue = nullptr;
262  return *this;
263  }
264 
265  JobSet(const JobSet &) = delete;
266  JobSet &operator=(const JobSet &other) = delete;
267 
268  virtual void Order(Job *job)
269  {
270  auto x = m_jobs.insert(m_queue->Queue(job, this));
271  (void)x; // suppress unused variable warning
272  assert(x.second);
273  }
274  virtual void RemoveJob(Job::Handle *handle) { m_jobs.erase(*handle); }
275 
276  bool IsEmpty() const { return m_jobs.empty(); }
277 
278 private:
279  JobQueue *m_queue;
280  std::set<Job::Handle> m_jobs;
281 };
282 
283 #endif
Definition: JobQueue.h:135
virtual void Cancel(Job *job) override
Definition: JobQueue.cpp:242
virtual Uint32 FinishJobs() override
Definition: JobQueue.cpp:206
AsyncJobQueue(Uint32 numRunners)
Definition: JobQueue.cpp:93
virtual Job::Handle Queue(Job *job, JobClient *client=nullptr) override
Definition: JobQueue.cpp:151
virtual ~AsyncJobQueue()
Definition: JobQueue.cpp:108
Definition: JobQueue.h:241
virtual void Order(Job *job)=0
virtual ~JobClient()
Definition: JobQueue.h:245
virtual void RemoveJob(Job::Handle *handle)=0
Definition: JobQueue.h:101
JobQueue(const JobQueue &)=delete
virtual Uint32 FinishJobs()=0
JobQueue & operator=(const JobQueue &)=delete
virtual Job::Handle Queue(Job *job, JobClient *client=nullptr)=0
JobQueue()=default
virtual void Cancel(Job *job)=0
virtual ~JobQueue()
Definition: JobQueue.h:109
Definition: JobQueue.h:250
JobSet & operator=(const JobSet &other)=delete
bool IsEmpty() const
Definition: JobQueue.h:276
virtual void RemoveJob(Job::Handle *handle)
Definition: JobQueue.h:274
JobSet(JobQueue *queue)
Definition: JobQueue.h:252
virtual void Order(Job *job)
Definition: JobQueue.h:268
JobSet & operator=(JobSet &&other)
Definition: JobQueue.h:257
JobSet(JobSet &&other)
Definition: JobQueue.h:254
JobSet(const JobSet &)=delete
Definition: JobQueue.h:37
Handle & operator=(const Handle &)=delete
Job * GetJob() const
Definition: JobQueue.h:52
Handle()
Definition: JobQueue.h:39
bool operator<(const Handle &other) const
Definition: JobQueue.h:54
Handle & operator=(Handle &&other)
Definition: JobQueue.cpp:63
Handle(const Handle &)=delete
~Handle()
Definition: JobQueue.cpp:82
bool HasJob() const
Definition: JobQueue.h:51
Definition: JobQueue.h:31
virtual ~Job()
Definition: JobQueue.cpp:15
friend class JobRunner
Definition: JobQueue.h:88
Job & operator=(const Job &)=delete
Job()
Definition: JobQueue.h:73
virtual void OnFinish()=0
Job(const Job &)=delete
virtual void OnRun()=0
virtual void OnCancel()
Definition: JobQueue.h:83
Definition: JobQueue.h:207
Uint32 RunJobs(Uint32 count=1)
Definition: JobQueue.cpp:447
virtual ~SyncJobQueue()
Definition: JobQueue.cpp:382
virtual Job::Handle Queue(Job *job, JobClient *client=nullptr) override
Definition: JobQueue.cpp:391
SyncJobQueue()=default
virtual void Cancel(Job *job) override
Definition: JobQueue.cpp:420
virtual Uint32 FinishJobs() override
Definition: JobQueue.cpp:399