diff --git a/doc/tutorial/11_fractal.cpp b/doc/tutorial/11_fractal.cpp index 4f7a4f1c..84082e00 100644 --- a/doc/tutorial/11_fractal.cpp +++ b/doc/tutorial/11_fractal.cpp @@ -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 m_spawnqueue, m_jobqueue, m_donequeue; -#endif - /* Debug information */ + // Debug information Text *m_centertext, *m_mousetext, *m_zoomtext; }; diff --git a/src/Makefile.am b/src/Makefile.am index ecd5dc24..1d348505 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 \ diff --git a/src/base/features.cpp b/src/base/features.cpp new file mode 100644 index 00000000..eb9bb007 --- /dev/null +++ b/src/base/features.cpp @@ -0,0 +1,26 @@ +// +// Lol Engine +// +// Copyright © 2010—2019 Sam Hocevar +// +// 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 + +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 + diff --git a/src/engine/ticker.cpp b/src/engine/ticker.cpp index 4a60e057..0dc04bf1 100644 --- a/src/engine/ticker.cpp +++ b/src/engine/ticker.cpp @@ -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(std::bind(&ticker_data::GameThreadMain, this)); + drawtick.push(1); + + diskthread = std::make_unique(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 DEPRECATED_m_todolist, DEPRECATED_m_todolist_delayed, DEPRECATED_m_autolist; array DEPRECATED_m_list[(int)tickable::group::all::end]; array 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 gamethread, diskthread; queue 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 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(); data->fps = fps; - -#if LOL_FEATURE_THREADS - data->gamethread = std::make_unique(std::bind(&ticker_data::GameThreadMain, data.get())); - data->drawtick.push(1); - - data->diskthread = std::make_unique(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); diff --git a/src/lol-core.vcxproj b/src/lol-core.vcxproj index a116f25d..53b3decf 100644 --- a/src/lol-core.vcxproj +++ b/src/lol-core.vcxproj @@ -106,6 +106,7 @@ + diff --git a/src/lol-core.vcxproj.filters b/src/lol-core.vcxproj.filters index f811bee7..88320b6d 100644 --- a/src/lol-core.vcxproj.filters +++ b/src/lol-core.vcxproj.filters @@ -14,6 +14,9 @@ base + + base + base diff --git a/src/lol/base/features.h b/src/lol/base/features.h index 1452c48b..497da7bd 100644 --- a/src/lol/base/features.h +++ b/src/lol/base/features.h @@ -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; diff --git a/src/lol/sys/thread.h b/src/lol/sys/thread.h index 501edefc..f52e320d 100644 --- a/src/lol/sys/thread.h +++ b/src/lol/sys/thread.h @@ -19,13 +19,9 @@ #include -#if LOL_FEATURE_THREADS -# include -# include -# include -#else -/* Nothing */ -#endif +#include +#include +#include /* 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 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 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 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 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 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 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 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 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 m_function; };