| @@ -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; | |||
| }; | |||