136 lines
4.4 KiB
C++
136 lines
4.4 KiB
C++
#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();
|
|
}
|
|
};
|
|
}
|
|
}
|