Thread.h
4.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#pragma once
#include "../C/Baselib_Thread.h"
#include "Time.h"
#include <memory>
#if !COMPILER_SUPPORTS_GENERIC_LAMBDA_EXPRESSIONS
#include <functional>
#endif
namespace baselib
{
BASELIB_CPP_INTERFACE
{
/*
This class is not supposed to be used as-is.
Instead separate thread class should be created to explicitely define thread lifetime.
This is useful to avoid having timeout constants all over the codebase.
class ApplicationThread : public baselib::Thread
{
public:
// Expose base class constructors.
using baselib::Thread::Thread;
void Join()
{
// Thread must join with-in 10 seconds, or this is an error.
// Use application specific methods to report error and/or try again.
assert(baselib::Thread::TryJoin(10 * 1000) == true);
}
};
*/
class BASELIB_API Thread
{
public:
// Default constructor does nothing, useful when declaring thread as field in classes/structs
Thread() = default;
// Generic Constructor
template<class FunctionType , class ... Args>
Thread(FunctionType && f, Args && ... args)
{
#if COMPILER_SUPPORTS_GENERIC_LAMBDA_EXPRESSIONS
// This generates cleaner and nicer-to-debug code
auto wrapped = [ = ] {f(args ...);};
#else
auto wrapped = std::bind(f, args ...);
#endif
using Container = decltype(wrapped);
// Small object optimization.
constexpr bool smallObject = (sizeof(Container) <= sizeof(void*)) && (alignof(Container) <= alignof(void*));
if (smallObject)
{
union
{
// sizeof(void*) will trigger placement new errors
// even if code path is not executed
char buf[sizeof(Container)];
void* smallObject;
};
smallObject = nullptr; // to avoid -Wmaybe-uninitialized
// We have to move it to pointer, otherwise wrapped destructor will be called
new(buf) Container(std::move(wrapped));
thread = CreateThread(ThreadProxySmallObject<Container>, smallObject);
}
else
{
std::unique_ptr<Container> ptr(new Container(std::move(wrapped)));
thread = CreateThread(ThreadProxyHeap<Container>, ptr.get());
if (thread)
ptr.release();
}
}
// Thread has to be joined before destructor is called
~Thread();
// Non-copyable
Thread(const Thread&) = delete;
Thread& operator=(const Thread&) = delete;
// Movable
Thread(Thread&& other);
Thread& operator=(Thread&& other);
// Return true if threads are supported
static bool SupportsThreads();
// Return true if join succeeded
COMPILER_WARN_UNUSED_RESULT bool TryJoin(timeout_ms timeout);
// Yields execution
static inline void YieldExecution()
{
Baselib_Thread_YieldExecution();
}
// Returns thread id
inline Baselib_Thread_Id GetId()
{
return Baselib_Thread_GetId(thread);
}
// Returns current thread id
static inline Baselib_Thread_Id GetCurrentId()
{
return Baselib_Thread_GetCurrentThreadId();
}
private:
Baselib_Thread* thread = nullptr;
static Baselib_Thread* CreateThread(Baselib_Thread_EntryPointFunction function, void* arg);
template<class T>
static void ThreadProxyHeap(void* data)
{
std::unique_ptr<T> ptr(reinterpret_cast<T*>(data));
(*ptr)();
}
template<class T>
static void ThreadProxySmallObject(void* data)
{
T* ptr = reinterpret_cast<T*>(&data);
(*ptr)();
ptr->~T();
}
};
}
}