]> git.cworth.org Git - vogl/blob - src/voglcore/vogl_threading_win32.h
Initial vogl checkin
[vogl] / src / voglcore / vogl_threading_win32.h
1 /**************************************************************************
2  *
3  * Copyright 2013-2014 RAD Game Tools and Valve Software
4  * Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC
5  * All Rights Reserved.
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a copy
8  * of this software and associated documentation files (the "Software"), to deal
9  * in the Software without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11  * copies of the Software, and to permit persons to whom the Software is
12  * furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in
15  * all copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23  * THE SOFTWARE.
24  *
25  **************************************************************************/
26
27 // File: vogl_win32_threading.h
28 #pragma once
29
30 #include "vogl_core.h"
31
32 #include "vogl_atomics.h"
33 #if VOGL_NO_ATOMICS
34 #error No atomic operations defined in vogl_platform.h!
35 #endif
36
37 namespace vogl
38 {
39     // g_number_of_processors defaults to 1. Will be higher on multicore machines.
40     extern uint g_number_of_processors;
41
42     void vogl_threading_init();
43
44     typedef uint64_t vogl_thread_id_t;
45     vogl_thread_id_t vogl_get_current_thread_id();
46
47     void vogl_sleep(unsigned int milliseconds);
48
49     uint vogl_get_max_helper_threads();
50
51     class mutex
52     {
53         VOGL_NO_COPY_OR_ASSIGNMENT_OP(mutex);
54
55     public:
56         mutex(unsigned int spin_count = 0);
57         ~mutex();
58         void lock();
59         void unlock();
60         void set_spin_count(unsigned int count);
61
62     private:
63         int m_buf[12];
64
65 #ifdef VOGL_BUILD_DEBUG
66         unsigned int m_lock_count;
67 #endif
68     };
69
70     class scoped_mutex
71     {
72         scoped_mutex(const scoped_mutex &);
73         scoped_mutex &operator=(const scoped_mutex &);
74
75     public:
76         inline scoped_mutex(mutex &m)
77             : m_mutex(m)
78         {
79             m_mutex.lock();
80         }
81         inline ~scoped_mutex()
82         {
83             m_mutex.unlock();
84         }
85
86     private:
87         mutex &m_mutex;
88     };
89
90     // Simple non-recursive spinlock.
91     class spinlock
92     {
93         VOGL_NO_COPY_OR_ASSIGNMENT_OP(spinlock);
94
95     public:
96         inline spinlock()
97             : m_flag(0)
98         {
99         }
100
101         void lock(uint32 max_spins = 4096, bool yielding = true);
102
103         inline void lock_no_barrier(uint32 max_spins = 4096, bool yielding = true)
104         {
105             lock(max_spins, yielding);
106         }
107
108         void unlock();
109
110         inline void unlock_no_barrier()
111         {
112             m_flag = VOGL_FALSE;
113         }
114
115     private:
116         volatile int32 m_flag;
117     };
118
119     class scoped_spinlock
120     {
121         scoped_spinlock(const scoped_spinlock &);
122         scoped_spinlock &operator=(const scoped_spinlock &);
123
124     public:
125         inline scoped_spinlock(spinlock &lock)
126             : m_lock(lock)
127         {
128             m_lock.lock();
129         }
130         inline ~scoped_spinlock()
131         {
132             m_lock.unlock();
133         }
134
135     private:
136         spinlock &m_lock;
137     };
138
139     class semaphore
140     {
141         VOGL_NO_COPY_OR_ASSIGNMENT_OP(semaphore);
142
143     public:
144         // TODO: Change to uint32
145         semaphore(int32 initialCount, int32 maximumCount, const char *pName = NULL);
146
147         ~semaphore();
148
149         inline HANDLE get_handle(void) const
150         {
151             return m_handle;
152         }
153
154         void release(int32 releaseCount = 1, int32 *pPreviousCount = NULL);
155         bool try_release(int32 releaseCount = 1, int32 *pPreviousCount = NULL);
156
157         bool wait(uint32 milliseconds = cUINT32_MAX);
158
159     private:
160         HANDLE m_handle;
161     };
162
163     template <typename T>
164     class tsstack
165     {
166         VOGL_NO_COPY_OR_ASSIGNMENT_OP(tsstack);
167
168     public:
169         inline tsstack(bool use_freelist = true)
170             : m_use_freelist(use_freelist)
171         {
172             VOGL_VERIFY(((ptr_bits_t) this & (VOGL_GET_ALIGNMENT(tsstack) - 1)) == 0);
173             InitializeSListHead(&m_stack_head);
174             InitializeSListHead(&m_freelist_head);
175         }
176
177         inline ~tsstack()
178         {
179             clear();
180         }
181
182         inline void clear()
183         {
184             for (;;)
185             {
186                 node *pNode = (node *)InterlockedPopEntrySList(&m_stack_head);
187                 if (!pNode)
188                     break;
189
190                 VOGL_MEMORY_IMPORT_BARRIER
191
192                 helpers::destruct(&pNode->m_obj);
193
194                 vogl_free(pNode);
195             }
196
197             flush_freelist();
198         }
199
200         inline void flush_freelist()
201         {
202             if (!m_use_freelist)
203                 return;
204
205             for (;;)
206             {
207                 node *pNode = (node *)InterlockedPopEntrySList(&m_freelist_head);
208                 if (!pNode)
209                     break;
210
211                 VOGL_MEMORY_IMPORT_BARRIER
212
213                 vogl_free(pNode);
214             }
215         }
216
217         inline bool try_push(const T &obj)
218         {
219             node *pNode = alloc_node();
220             if (!pNode)
221                 return false;
222
223             helpers::construct(&pNode->m_obj, obj);
224
225             VOGL_MEMORY_EXPORT_BARRIER
226
227             InterlockedPushEntrySList(&m_stack_head, &pNode->m_slist_entry);
228
229             return true;
230         }
231
232         inline bool pop(T &obj)
233         {
234             node *pNode = (node *)InterlockedPopEntrySList(&m_stack_head);
235             if (!pNode)
236                 return false;
237
238             VOGL_MEMORY_IMPORT_BARRIER
239
240             obj = pNode->m_obj;
241
242             helpers::destruct(&pNode->m_obj);
243
244             free_node(pNode);
245
246             return true;
247         }
248
249     private:
250         SLIST_HEADER m_stack_head;
251         SLIST_HEADER m_freelist_head;
252
253         struct node
254         {
255             SLIST_ENTRY m_slist_entry;
256             T m_obj;
257         };
258
259         bool m_use_freelist;
260
261         inline node *alloc_node()
262         {
263             node *pNode = m_use_freelist ? (node *)InterlockedPopEntrySList(&m_freelist_head) : NULL;
264
265             if (!pNode)
266                 pNode = (node *)vogl_malloc(sizeof(node));
267
268             return pNode;
269         }
270
271         inline void free_node(node *pNode)
272         {
273             if (m_use_freelist)
274                 InterlockedPushEntrySList(&m_freelist_head, &pNode->m_slist_entry);
275             else
276                 vogl_free(pNode);
277         }
278     };
279
280     // Simple multithreaded task pool. This class assumes a single global thread will be issuing tasks and joining.
281     class task_pool
282     {
283         VOGL_NO_COPY_OR_ASSIGNMENT_OP(task_pool);
284
285     public:
286         task_pool();
287         task_pool(uint num_threads);
288         ~task_pool();
289
290         enum
291         {
292             cMaxThreads = 16
293         };
294         bool init(uint num_threads);
295         void deinit();
296
297         inline uint get_num_threads() const
298         {
299             return m_num_threads;
300         }
301         inline uint32 get_num_outstanding_tasks() const
302         {
303             return m_total_submitted_tasks - m_total_completed_tasks;
304         }
305
306         // C-style task callback
307         typedef void (*task_callback_func)(uint64_t data, void *pData_ptr);
308         bool queue_task(task_callback_func pFunc, uint64_t data = 0, void *pData_ptr = NULL);
309
310         class executable_task
311         {
312         public:
313             virtual void execute_task(uint64_t data, void *pData_ptr) = 0;
314         };
315
316         // It's the caller's responsibility to delete pObj within the execute_task() method, if needed!
317         bool queue_task(executable_task *pObj, uint64_t data = 0, void *pData_ptr = NULL);
318
319         template <typename S, typename T>
320         inline bool queue_object_task(S *pObject, T pObject_method, uint64_t data = 0, void *pData_ptr = NULL);
321
322         template <typename S, typename T>
323         inline bool queue_multiple_object_tasks(S *pObject, T pObject_method, uint64_t first_data, uint num_tasks, void *pData_ptr = NULL);
324
325         // Waits for all outstanding tasks (if any) to complete.
326         // The calling thread will steal any outstanding tasks from worker threads, if possible.
327         void join();
328
329     private:
330         struct task
331         {
332             //inline task() : m_data(0), m_pData_ptr(NULL), m_pObj(NULL), m_flags(0) { }
333
334             uint64_t m_data;
335             void *m_pData_ptr;
336
337             union
338             {
339                 task_callback_func m_callback;
340                 executable_task *m_pObj;
341             };
342
343             uint m_flags;
344         };
345
346         typedef tsstack<task> ts_task_stack_t;
347         ts_task_stack_t *m_pTask_stack;
348
349         uint m_num_threads;
350         HANDLE m_threads[cMaxThreads];
351
352         // Signalled whenever a task is queued up.
353         semaphore m_tasks_available;
354
355         // Signalled when all outstanding tasks are completed.
356         semaphore m_all_tasks_completed;
357
358         enum task_flags
359         {
360             cTaskFlagObject = 1
361         };
362
363         volatile atomic32_t m_total_submitted_tasks;
364         volatile atomic32_t m_total_completed_tasks;
365         volatile atomic32_t m_exit_flag;
366
367         void process_task(task &tsk);
368
369         static unsigned __stdcall thread_func(void *pContext);
370     };
371
372     enum object_task_flags
373     {
374         cObjectTaskFlagDefault = 0,
375         cObjectTaskFlagDeleteAfterExecution = 1
376     };
377
378     template <typename T>
379     class object_task : public task_pool::executable_task
380     {
381     public:
382         object_task(uint flags = cObjectTaskFlagDefault)
383             : m_pObject(NULL),
384               m_pMethod(NULL),
385               m_flags(flags)
386         {
387         }
388
389         typedef void (T::*object_method_ptr)(uint64_t data, void *pData_ptr);
390
391         object_task(T *pObject, object_method_ptr pMethod, uint flags = cObjectTaskFlagDefault)
392             : m_pObject(pObject),
393               m_pMethod(pMethod),
394               m_flags(flags)
395         {
396             VOGL_ASSERT(pObject && pMethod);
397         }
398
399         void init(T *pObject, object_method_ptr pMethod, uint flags = cObjectTaskFlagDefault)
400         {
401             VOGL_ASSERT(pObject && pMethod);
402
403             m_pObject = pObject;
404             m_pMethod = pMethod;
405             m_flags = flags;
406         }
407
408         T *get_object() const
409         {
410             return m_pObject;
411         }
412         object_method_ptr get_method() const
413         {
414             return m_pMethod;
415         }
416
417         virtual void execute_task(uint64_t data, void *pData_ptr)
418         {
419             (m_pObject->*m_pMethod)(data, pData_ptr);
420
421             if (m_flags & cObjectTaskFlagDeleteAfterExecution)
422                 vogl_delete(this);
423         }
424
425     protected:
426         T *m_pObject;
427
428         object_method_ptr m_pMethod;
429
430         uint m_flags;
431     };
432
433     template <typename S, typename T>
434     inline bool task_pool::queue_object_task(S *pObject, T pObject_method, uint64_t data, void *pData_ptr)
435     {
436         object_task<S> *pTask = vogl_new<object_task<S> >(pObject, pObject_method, cObjectTaskFlagDeleteAfterExecution);
437         if (!pTask)
438             return false;
439         return queue_task(pTask, data, pData_ptr);
440     }
441
442     template <typename S, typename T>
443     inline bool task_pool::queue_multiple_object_tasks(S *pObject, T pObject_method, uint64_t first_data, uint num_tasks, void *pData_ptr)
444     {
445         VOGL_ASSERT(pObject);
446         VOGL_ASSERT(num_tasks);
447         if (!num_tasks)
448             return true;
449
450         bool status = true;
451
452         uint i;
453         for (i = 0; i < num_tasks; i++)
454         {
455             task tsk;
456
457             tsk.m_pObj = vogl_new<object_task<S> >(pObject, pObject_method, cObjectTaskFlagDeleteAfterExecution);
458             if (!tsk.m_pObj)
459             {
460                 status = false;
461                 break;
462             }
463
464             tsk.m_data = first_data + i;
465             tsk.m_pData_ptr = pData_ptr;
466             tsk.m_flags = cTaskFlagObject;
467
468             atomic_increment32(&m_total_submitted_tasks);
469
470             if (!m_pTask_stack->try_push(tsk))
471             {
472                 atomic_increment32(&m_total_completed_tasks);
473
474                 status = false;
475                 break;
476             }
477         }
478
479         if (i)
480         {
481             m_tasks_available.release(i);
482         }
483
484         return status;
485     }
486
487 } // namespace vogl