@@ -99,25 +99,26 @@ public: | |||
m_aabb.aa = m_position; | |||
m_aabb.bb = vec3((vec2)m_window_size, 0); | |||
#if LOL_FEATURE_THREADS | |||
/* Spawn worker threads and wait for their readiness. */ | |||
for (int i = 0; i < MAX_THREADS; i++) | |||
m_threads[i] = new thread(std::bind(&Fractal::DoWorkHelper, this, std::placeholders::_1)); | |||
for (int i = 0; i < MAX_THREADS; i++) | |||
m_spawnqueue.pop(); | |||
#endif | |||
if (has_threads()) | |||
{ | |||
// Spawn worker threads and wait for their readiness | |||
for (int i = 0; i < MAX_THREADS; i++) | |||
m_threads[i] = new thread(std::bind(&Fractal::DoWorkHelper, this, std::placeholders::_1)); | |||
for (int i = 0; i < MAX_THREADS; i++) | |||
m_spawnqueue.pop(); | |||
} | |||
} | |||
~Fractal() | |||
{ | |||
#if LOL_FEATURE_THREADS | |||
/* Signal worker threads for completion and wait for | |||
* them to quit. */ | |||
for (int i = 0; i < MAX_THREADS; i++) | |||
m_jobqueue.push(-1); | |||
for (int i = 0; i < MAX_THREADS; i++) | |||
m_donequeue.pop(); | |||
#endif | |||
if (has_threads()) | |||
{ | |||
// Signal worker threads for completion and wait for them to quit | |||
for (int i = 0; i < MAX_THREADS; i++) | |||
m_jobqueue.push(-1); | |||
for (int i = 0; i < MAX_THREADS; i++) | |||
m_donequeue.pop(); | |||
} | |||
Ticker::Unref(m_centertext); | |||
Ticker::Unref(m_mousetext); | |||
@@ -290,16 +291,14 @@ public: | |||
for (int i = 0; i < m_size.y; i += MAX_LINES * 2) | |||
{ | |||
#if LOL_FEATURE_THREADS | |||
m_jobqueue.push(i); | |||
#else | |||
DoWork(i); | |||
#endif | |||
if (has_threads()) | |||
m_jobqueue.push(i); | |||
else | |||
DoWork(i); | |||
} | |||
} | |||
} | |||
#if LOL_FEATURE_THREADS | |||
void DoWorkHelper(thread *) | |||
{ | |||
m_spawnqueue.push(0); | |||
@@ -313,7 +312,6 @@ public: | |||
} | |||
m_donequeue.push(0); | |||
}; | |||
#endif | |||
void DoWork(int line) | |||
{ | |||
@@ -475,10 +473,11 @@ public: | |||
if (m_dirty[m_frame]) | |||
{ | |||
#if LOL_FEATURE_THREADS | |||
for (int i = 0; i < m_size.y; i += MAX_LINES * 2) | |||
m_donequeue.pop(); | |||
#endif | |||
if (has_threads()) | |||
{ | |||
for (int i = 0; i < m_size.y; i += MAX_LINES * 2) | |||
m_donequeue.pop(); | |||
} | |||
m_dirty[m_frame]--; | |||
@@ -551,13 +550,11 @@ private: | |||
vec4 m_texel_settings, m_screen_settings; | |||
mat4 m_zoom_settings; | |||
#if LOL_FEATURE_THREADS | |||
/* Worker threads */ | |||
// Worker threads | |||
thread *m_threads[MAX_THREADS]; | |||
queue<int> m_spawnqueue, m_jobqueue, m_donequeue; | |||
#endif | |||
/* Debug information */ | |||
// Debug information | |||
Text *m_centertext, *m_mousetext, *m_zoomtext; | |||
}; | |||
@@ -88,7 +88,7 @@ liblol_core_sources = \ | |||
easymesh/shinydebuglighting.lolfx easymesh/shinydebugnormal.lolfx \ | |||
easymesh/shinydebugUV.lolfx easymesh/shiny_SK.lolfx \ | |||
\ | |||
base/assert.cpp base/log.cpp base/string.cpp \ | |||
base/assert.cpp base/features.cpp base/log.cpp base/string.cpp \ | |||
\ | |||
math/vector.cpp math/matrix.cpp math/transform.cpp math/half.cpp \ | |||
math/geometry.cpp math/real.cpp \ | |||
@@ -0,0 +1,26 @@ | |||
// | |||
// Lol Engine | |||
// | |||
// Copyright © 2010—2019 Sam Hocevar <sam@hocevar.net> | |||
// | |||
// Lol Engine is free software. It comes without any warranty, to | |||
// the extent permitted by applicable law. You can redistribute it | |||
// and/or modify it under the terms of the Do What the Fuck You Want | |||
// to Public License, Version 2, as published by the WTFPL Task Force. | |||
// See http://www.wtfpl.net/ for more details. | |||
// | |||
#include <lol/engine-internal.h> | |||
namespace lol | |||
{ | |||
bool has_threads() | |||
{ | |||
static char const *var = getenv("LOL_NOTHREADS"); | |||
static bool const disable_threads = var && var[0]; | |||
return !disable_threads && std::thread::hardware_concurrency() > 1; | |||
} | |||
} // namespace lol | |||
@@ -29,13 +29,14 @@ class ticker_data | |||
public: | |||
ticker_data() | |||
: DEPRECATED_nentities(0), | |||
m_frame(0), m_recording(0), deltatime(0), bias(0), fps(0), | |||
#if LOL_BUILD_DEBUG | |||
keepalive(0), | |||
#endif | |||
m_quit(0), m_quitframe(0), m_quitdelay(20), m_panic(0) | |||
{ | |||
if (has_threads()) | |||
{ | |||
gamethread = std::make_unique<thread>(std::bind(&ticker_data::GameThreadMain, this)); | |||
drawtick.push(1); | |||
diskthread = std::make_unique<thread>(std::bind(&ticker_data::DiskThreadMain, this)); | |||
} | |||
} | |||
~ticker_data() | |||
@@ -46,13 +47,14 @@ public: | |||
"still %d autoreleased entities\n", DEPRECATED_m_autolist.count()); | |||
msg::debug("%d frames required to quit\n", m_frame - m_quitframe); | |||
#if LOL_FEATURE_THREADS | |||
gametick.push(0); | |||
disktick.push(0); | |||
gamethread.release(); | |||
diskthread.release(); | |||
ASSERT(drawtick.size() == 0); | |||
#endif | |||
if (has_threads()) | |||
{ | |||
gametick.push(0); | |||
disktick.push(0); | |||
gamethread.release(); | |||
diskthread.release(); | |||
ASSERT(drawtick.size() == 0); | |||
} | |||
} | |||
void handle_shutdown(); | |||
@@ -68,14 +70,14 @@ private: | |||
array<entity *> DEPRECATED_m_todolist, DEPRECATED_m_todolist_delayed, DEPRECATED_m_autolist; | |||
array<entity *> DEPRECATED_m_list[(int)tickable::group::all::end]; | |||
array<int> DEPRECATED_m_scenes[(int)tickable::group::all::end]; | |||
int DEPRECATED_nentities; | |||
int DEPRECATED_nentities = 0; | |||
/* Fixed framerate management */ | |||
int m_frame, m_recording; | |||
int m_frame = 0, m_recording = 0; | |||
timer m_timer; | |||
float deltatime, bias, fps; | |||
float deltatime = 0.f, bias = 0.f, fps = 0.f; | |||
#if LOL_BUILD_DEBUG | |||
float keepalive; | |||
float keepalive = 0; | |||
#endif | |||
/* The three main functions (for now) */ | |||
@@ -83,17 +85,15 @@ private: | |||
static void DrawThreadTick(); | |||
static void DiskThreadTick(); | |||
#if LOL_FEATURE_THREADS | |||
/* The associated background threads */ | |||
void GameThreadMain(); | |||
void DrawThreadMain(); /* unused for now */ | |||
void DiskThreadMain(); | |||
std::unique_ptr<thread> gamethread, diskthread; | |||
queue<int> gametick, drawtick, disktick; | |||
#endif | |||
/* Shutdown management */ | |||
int m_quit, m_quitframe, m_quitdelay, m_panic; | |||
int m_quit = 0, m_quitframe = 0, m_quitdelay = 20, m_panic = 0; | |||
}; | |||
static std::unique_ptr<ticker_data> data; | |||
@@ -169,7 +169,6 @@ int Ticker::Unref(entity *entity) | |||
return --entity->m_ref; | |||
} | |||
#if LOL_FEATURE_THREADS | |||
void ticker_data::GameThreadMain() | |||
{ | |||
#if LOL_BUILD_DEBUG | |||
@@ -193,9 +192,7 @@ void ticker_data::GameThreadMain() | |||
msg::debug("ticker game thread terminated\n"); | |||
#endif | |||
} | |||
#endif /* LOL_FEATURE_THREADS */ | |||
#if LOL_FEATURE_THREADS | |||
void ticker_data::DrawThreadMain() /* unused */ | |||
{ | |||
#if LOL_BUILD_DEBUG | |||
@@ -217,15 +214,12 @@ void ticker_data::DrawThreadMain() /* unused */ | |||
msg::debug("ticker draw thread terminated\n"); | |||
#endif | |||
} | |||
#endif /* LOL_FEATURE_THREADS */ | |||
#if LOL_FEATURE_THREADS | |||
void ticker_data::DiskThreadMain() | |||
{ | |||
/* FIXME: temporary hack to avoid crashes on the PS3 */ | |||
disktick.pop(); | |||
} | |||
#endif /* LOL_FEATURE_THREADS */ | |||
//----------------------------------------------------------------------------- | |||
void ticker_data::GameThreadTick() | |||
@@ -565,13 +559,6 @@ void ticker::setup(float fps) | |||
{ | |||
data = std::make_unique<ticker_data>(); | |||
data->fps = fps; | |||
#if LOL_FEATURE_THREADS | |||
data->gamethread = std::make_unique<thread>(std::bind(&ticker_data::GameThreadMain, data.get())); | |||
data->drawtick.push(1); | |||
data->diskthread = std::make_unique<thread>(std::bind(&ticker_data::DiskThreadMain, data.get())); | |||
#endif | |||
} | |||
void ticker::teardown() | |||
@@ -581,24 +568,24 @@ void ticker::teardown() | |||
void ticker::tick_draw() | |||
{ | |||
#if LOL_FEATURE_THREADS | |||
int n = data->drawtick.pop(); | |||
if (n == 0) | |||
return; | |||
#else | |||
ticker_data::GameThreadTick(); | |||
#endif | |||
if (has_threads()) | |||
{ | |||
int n = data->drawtick.pop(); | |||
if (n == 0) | |||
return; | |||
} | |||
else | |||
ticker_data::GameThreadTick(); | |||
ticker_data::DrawThreadTick(); | |||
Profiler::Start(Profiler::STAT_TICK_BLIT); | |||
/* Signal game thread that it can carry on */ | |||
#if LOL_FEATURE_THREADS | |||
data->gametick.push(1); | |||
#else | |||
ticker_data::DiskThreadTick(); | |||
#endif | |||
if (has_threads()) | |||
data->gametick.push(1); | |||
else | |||
ticker_data::DiskThreadTick(); | |||
/* Clamp FPS */ | |||
Profiler::Stop(Profiler::STAT_TICK_BLIT); | |||
@@ -106,6 +106,7 @@ | |||
<ClCompile Include="audio\sample.cpp" /> | |||
<ClCompile Include="camera.cpp" /> | |||
<ClCompile Include="base\assert.cpp" /> | |||
<ClCompile Include="base\features.cpp" /> | |||
<ClCompile Include="base\log.cpp" /> | |||
<ClCompile Include="base\string.cpp" /> | |||
<ClCompile Include="debug\fps.cpp" /> | |||
@@ -14,6 +14,9 @@ | |||
<ClCompile Include="base\assert.cpp"> | |||
<Filter>base</Filter> | |||
</ClCompile> | |||
<ClCompile Include="base\features.cpp"> | |||
<Filter>base</Filter> | |||
</ClCompile> | |||
<ClCompile Include="base\log.cpp"> | |||
<Filter>base</Filter> | |||
</ClCompile> | |||
@@ -17,16 +17,6 @@ | |||
// ----------------------- | |||
// | |||
/* | |||
* System, CPU and compiler features. | |||
*/ | |||
#define LOL_FEATURE_THREADS 1 | |||
#if defined __EMSCRIPTEN__ | |||
# undef LOL_FEATURE_THREADS | |||
#endif | |||
/* | |||
* Check for C++11 and later features. | |||
*/ | |||
@@ -168,12 +158,15 @@ static inline int isnan(float f) | |||
#endif | |||
/* | |||
* A handy endianness test function | |||
*/ | |||
// | |||
// Some feature test functions | |||
// | |||
namespace lol | |||
{ | |||
extern bool has_threads(); | |||
// A handy endianness test function | |||
static inline bool is_big_endian() | |||
{ | |||
union { int i; char c; } u; | |||
@@ -19,13 +19,9 @@ | |||
#include <functional> | |||
#if LOL_FEATURE_THREADS | |||
# include <thread> | |||
# include <mutex> | |||
# include <condition_variable> | |||
#else | |||
/* Nothing */ | |||
#endif | |||
#include <thread> | |||
#include <mutex> | |||
#include <condition_variable> | |||
/* XXX: workaround for a bug in Visual Studio 2012 and 2013! | |||
* https://connect.microsoft.com/VisualStudio/feedback/details/747145 */ | |||
@@ -52,38 +48,18 @@ | |||
namespace lol | |||
{ | |||
// This is like std::mutex but we can add debug information to it | |||
class mutex | |||
{ | |||
public: | |||
// Will block the thread if another has already locked | |||
void lock() | |||
{ | |||
#if LOL_FEATURE_THREADS | |||
m_mutex.lock(); | |||
#endif | |||
} | |||
inline void lock() { m_mutex.lock(); } | |||
// Will not block if another thread has already locked | |||
bool try_lock() | |||
{ | |||
#if LOL_FEATURE_THREADS | |||
return m_mutex.try_lock(); | |||
#else | |||
return false; | |||
#endif | |||
} | |||
inline bool try_lock() { return m_mutex.try_lock(); } | |||
void unlock() | |||
{ | |||
#if LOL_FEATURE_THREADS | |||
m_mutex.unlock(); | |||
#endif | |||
} | |||
inline void unlock() { m_mutex.unlock(); } | |||
private: | |||
#if LOL_FEATURE_THREADS | |||
std::mutex m_mutex; | |||
#endif | |||
}; | |||
// A FIFO queue for threads | |||
@@ -91,58 +67,52 @@ template<typename T, int N = 128> | |||
class queue | |||
{ | |||
public: | |||
queue() | |||
: m_start(0), | |||
m_count(0) | |||
{} | |||
int size() const { return m_count; } | |||
// Will block the thread if another has already locked | |||
void push(T value) | |||
{ | |||
#if LOL_FEATURE_THREADS | |||
/* Wait for the mutex availability or non-fullness */ | |||
std::unique_lock<std::mutex> uni_lock(m_mutex); | |||
m_full_cond.wait(uni_lock, [&]{ return m_count < CAPACITY; }); | |||
#endif | |||
if (has_threads()) | |||
{ | |||
// Wait for the mutex availability or non-fullness | |||
m_full_cond.wait(uni_lock, [&]{ return m_count < CAPACITY; }); | |||
} | |||
do_push(value); /* Push value */ | |||
#if LOL_FEATURE_THREADS | |||
/* Release lock and notify empty condition var (in that order) */ | |||
uni_lock.unlock(); | |||
m_empty_cond.notify_one(); | |||
#endif | |||
if (has_threads()) | |||
{ | |||
// Release lock and notify empty condition var (in that order) | |||
uni_lock.unlock(); | |||
m_empty_cond.notify_one(); | |||
} | |||
} | |||
// Will not block if another has already locked | |||
bool try_push(T value) | |||
{ | |||
#if LOL_FEATURE_THREADS | |||
/* Same as Push(), except .... */ | |||
std::unique_lock<std::mutex> uni_lock(m_mutex, std::try_to_lock); | |||
/* Bail on fail try_lock fail */ | |||
if (!uni_lock.owns_lock()) | |||
return false; | |||
/* Bail on max CAPACITY */ | |||
if (m_count == CAPACITY) | |||
std::unique_lock<std::mutex> uni_lock(m_mutex, std::defer_lock); | |||
if (has_threads()) | |||
{ | |||
uni_lock.unlock(); | |||
return false; | |||
// Try to lock, bail out if we fail | |||
if (!uni_lock.try_lock()) | |||
return false; | |||
} | |||
#else | |||
if (m_count == CAPACITY) | |||
return false; | |||
#endif | |||
return false; // unlocks uni_lock | |||
do_push(value); /* Push value */ | |||
do_push(value); | |||
#if LOL_FEATURE_THREADS | |||
/* Release lock and notify empty condition var (in that order) */ | |||
uni_lock.unlock(); | |||
m_empty_cond.notify_one(); | |||
#endif | |||
if (has_threads()) | |||
{ | |||
// Release lock and notify empty condition var (in that order) | |||
uni_lock.unlock(); | |||
m_empty_cond.notify_one(); | |||
} | |||
return true; | |||
} | |||
@@ -150,21 +120,17 @@ public: | |||
// Will block the thread if another has already locked | |||
T pop() | |||
{ | |||
#if LOL_FEATURE_THREADS | |||
/* Wait for the mutex availability or non-emptiness */ | |||
ASSERT(has_threads(), "Pop should only be used with threads. Use try_pop instead."); | |||
// Wait for the mutex availability or non-emptiness | |||
std::unique_lock<std::mutex> uni_lock(m_mutex); | |||
m_empty_cond.wait(uni_lock, [&]{return m_count > 0; }); | |||
#else | |||
ASSERT(0, "Pop should only be used with threads. Use try_pop instead."); | |||
#endif | |||
T ret = do_pop(); /* Pop value */ | |||
T ret = do_pop(); | |||
#if LOL_FEATURE_THREADS | |||
/* Release lock and notify full condition var (in that order) */ | |||
// Release lock and notify full condition var (in that order) | |||
uni_lock.unlock(); | |||
m_full_cond.notify_one(); | |||
#endif | |||
return ret; | |||
} | |||
@@ -172,30 +138,25 @@ public: | |||
// Will not block if another has already locked | |||
bool try_pop(T &ret) | |||
{ | |||
#if LOL_FEATURE_THREADS | |||
/* Same as Pop(), except .... */ | |||
std::unique_lock<std::mutex> uni_lock(m_mutex, std::try_to_lock); | |||
/* Bail on fail try_lock fail */ | |||
if (!uni_lock.owns_lock()) | |||
return false; | |||
/* Bail on zero count */ | |||
if (m_count == 0) | |||
std::unique_lock<std::mutex> uni_lock(m_mutex, std::defer_lock); | |||
if (has_threads()) | |||
{ | |||
uni_lock.unlock(); | |||
return false; | |||
if (!uni_lock.try_lock()) | |||
return false; | |||
} | |||
#else | |||
if (m_count == 0) | |||
return false; | |||
#endif | |||
ret = do_pop(); /* Pop value */ | |||
#if LOL_FEATURE_THREADS | |||
/* Release lock and notify full condition var (in that order) */ | |||
uni_lock.unlock(); | |||
m_full_cond.notify_one(); | |||
#endif | |||
if (has_threads()) | |||
{ | |||
// Release lock and notify full condition var (in that order) | |||
uni_lock.unlock(); | |||
m_full_cond.notify_one(); | |||
} | |||
return true; | |||
} | |||
@@ -219,11 +180,10 @@ private: | |||
private: | |||
static int const CAPACITY = N; | |||
T m_values[CAPACITY]; | |||
int m_start, m_count; | |||
#if LOL_FEATURE_THREADS | |||
int m_start = 0, m_count = 0; | |||
std::mutex m_mutex; | |||
std::condition_variable m_empty_cond, m_full_cond; | |||
#endif | |||
}; | |||
// Base class for threads | |||
@@ -233,36 +193,28 @@ public: | |||
thread(std::function<void(thread*)> fn) | |||
: m_function(fn) | |||
{ | |||
#if LOL_FEATURE_THREADS | |||
m_thread = std::thread(trampoline, this); | |||
#endif | |||
} | |||
~thread() | |||
{ | |||
#if LOL_FEATURE_THREADS | |||
# if LOL_VISUAL_STUDIO_BUG_747145_WORKAROUND | |||
#if LOL_VISUAL_STUDIO_BUG_747145_WORKAROUND | |||
m_thread.detach(); | |||
# else | |||
#else | |||
m_thread.join(); | |||
# endif | |||
#endif | |||
} | |||
private: | |||
#if LOL_FEATURE_THREADS | |||
static void trampoline(thread *that) | |||
{ | |||
that->m_function(that); | |||
# if LOL_VISUAL_STUDIO_BUG_747145_WORKAROUND | |||
#if LOL_VISUAL_STUDIO_BUG_747145_WORKAROUND | |||
ExitThread(0); | |||
# endif | |||
} | |||
#endif | |||
} | |||
#if LOL_FEATURE_THREADS | |||
std::thread m_thread; | |||
#endif | |||
std::function<void(thread*)> m_function; | |||
}; | |||