Browse Source

Make the check for threading support runtime to make debugging easier.

legacy
Sam Hocevar 5 years ago
parent
commit
2c5a1be8b3
8 changed files with 153 additions and 194 deletions
  1. +27
    -30
      doc/tutorial/11_fractal.cpp
  2. +1
    -1
      src/Makefile.am
  3. +26
    -0
      src/base/features.cpp
  4. +32
    -45
      src/engine/ticker.cpp
  5. +1
    -0
      src/lol-core.vcxproj
  6. +3
    -0
      src/lol-core.vcxproj.filters
  7. +6
    -13
      src/lol/base/features.h
  8. +57
    -105
      src/lol/sys/thread.h

+ 27
- 30
doc/tutorial/11_fractal.cpp View File

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



+ 1
- 1
src/Makefile.am View File

@@ -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 \


+ 26
- 0
src/base/features.cpp View File

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


+ 32
- 45
src/engine/ticker.cpp View File

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


+ 1
- 0
src/lol-core.vcxproj View File

@@ -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" />


+ 3
- 0
src/lol-core.vcxproj.filters View File

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


+ 6
- 13
src/lol/base/features.h View File

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


+ 57
- 105
src/lol/sys/thread.h View File

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



Loading…
Cancel
Save