1. 4FipS.com
  2. Photos
  3. Videos
  4. Code
  5. Forums
  6. pfQuizzz
  7. About
/*
(c) 2013 +++ Filip Stoklas, aka FipS, http://www.4FipS.com +++
THIS CODE IS FREE - LICENSED UNDER THE MIT LICENSE
ARTICLE URL: http://forums.4fips.com/viewtopic.php?f=3&t=1081
*/

#include <thread>
#include <condition_variable>
#include <deque>
#include <cassert>

/// An auto-reset event.
class Event
{
 public:

    Event(): _signalled(false) {}

    void notify()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _signalled = true;
        _condvar.notify_one();
    }

    void wait()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _condvar.wait(lock, [&] { return _signalled; }); // also takes care about spurious wakeup
        _signalled = false; // auto-reset semantics
    }

 private:

    Event(const Event &); ///< ~ '= delete'.
    Event & operator = (const Event &); ///< ~ '= delete'.

    std::mutex _mutex;
    std::condition_variable _condvar;
    bool _signalled;
};

/// An unprotected shared command queue (resource).
class Command_queue
{
 public:

    enum { exit_command = 42 };

    void push(size_t value)
    {
        printf("+ %u\n", unsigned(value));
        _queue.emplace_back(value);
    }

    size_t pop()
    {
        const size_t value = _queue.front();
        printf("- %u\n", unsigned(value));
        _queue.pop_front();
        return value;
    }

    bool empty() const { return _queue.empty(); }

 private:

    std::deque<size_t> _queue;
};

/// A shared interface (protocol) between the render and update threads.
class Shared_state
{
 public:

    Shared_state() {}

    void notify_render_ready() { _render_ready_event.notify(); }
    void wait_for_render_ready() { _render_ready_event.wait(); }

    void notify_update_ready() { _update_ready_event.notify(); }
    void wait_for_update_ready() { _update_ready_event.wait(); }

    void notify_render_done() { _render_done_event.notify(); }
    void wait_for_render_done() { _render_done_event.wait(); }

    void notify_swap_done() { _swap_done_event.notify(); }
    void wait_for_swap_done() { _swap_done_event.wait(); }

    const Command_queue & update_queue() const { return _update_queue; }
    Command_queue & update_queue() { return _update_queue; }

    const Command_queue & render_queue() const { return _render_queue; }
    Command_queue & render_queue() { return _render_queue; }

    void swap_command_queues() { using namespace std; swap(_update_queue, _render_queue); }

 private:

    Shared_state(const Shared_state &); ///< ~ '= delete'.
    Shared_state & operator = (const Shared_state &); ///< ~ '= delete'.

    Event _render_ready_event;
    Event _update_ready_event;
    Event _render_done_event;
    Event _swap_done_event;

    Command_queue _update_queue;
    Command_queue _render_queue;
};

class Update_thread
{
 public:

    Update_thread(Shared_state &shared):
    _shared(shared),
    _counter(0)
    {
        _thread = std::thread(&Update_thread::loop, this);
    }

    ~Update_thread()
    {
        _thread.join();
    }

 private:

    Update_thread(const Update_thread &); ///< ~ '= delete'.
    Update_thread & operator = (const Update_thread &); ///< ~ '= delete'.

    void loop()
    {
        _shared.notify_update_ready();
        _shared.wait_for_render_ready();
        bool exit_request = false;

        while(!exit_request)
        {
            Command_queue &queue = _shared.update_queue();
            assert(queue.empty());

            for(size_t i = 0; i < 3; ++i, ++_counter)
            {
                queue.push(_counter);
                exit_request = (_counter == Command_queue::exit_command);

                if(exit_request)
                    break;
            }

            _shared.wait_for_render_done();
            {
                // this is the point of safe unprotected sync, the render thread is paused
                printf("swap\n");
                _shared.swap_command_queues();
            }
            _shared.notify_swap_done();

            if(exit_request)
                break;
        }
    }

    Shared_state &_shared;
    std::thread _thread;
    size_t _counter;
};

class Render_thread
{
 public:

    Render_thread(Shared_state &shared):
    _shared(shared)
    {
        _thread = std::thread(&Render_thread::loop, this);
    }

    ~Render_thread()
    {
        _thread.join();
    }

 private:

    Render_thread(const Render_thread &); ///< ~ '= delete'.
    Render_thread & operator = (const Render_thread &); ///< ~ '= delete'.

    void loop()
    {
        _shared.notify_render_ready();
        _shared.wait_for_update_ready();
        bool exit_request = false;

        for(;;)
        {
            Command_queue &queue = _shared.render_queue();

            while(!exit_request && !queue.empty())
            {
                const size_t value = queue.pop();
                exit_request = (value == Command_queue::exit_command);

                if(exit_request) // cooperative exit
                    break;
            }

            _shared.notify_render_done();
            _shared.wait_for_swap_done();

            if(exit_request)
                break;
        }
    }

    Shared_state &_shared;
    std::thread _thread;
};

int main()
{
    Shared_state shared;
    Update_thread updater(shared);
    Render_thread renderer(shared);

    return 0;
}

// Compiled under Visual C++ 2012, output:
// + 0
// + 1
// + 2
// swap
// + 3
// - 0
// + 4
// + 5
// - 1
// - 2
// swap
// + 6
// - 3
// + 7
// + 8
// - 4
// - 5
// swap
// + 9
// - 6
// + 10
// + 11
// - 7
// - 8
// swap
// + 12
// - 9
// + 13
// - 10
// - 11
// + 14
// swap
// + 15
// - 12
// + 16
// + 17
// - 13
// - 14
// swap
// + 18
// - 15
// + 19
// + 20
// - 16
// - 17
// swap
// + 21
// - 18
// + 22
// + 23
// - 19
// - 20
// swap
// + 24
// - 21
// + 25
// + 26
// - 22
// - 23
// swap
// + 27
// - 24
// + 28
// + 29
// - 25
// - 26
// swap
// + 30
// + 31
// + 32
// - 27
// - 28
// - 29
// swap
// + 33
// - 30
// - 31
// - 32
// + 34
// + 35
// swap
// + 36
// - 33
// + 37
// - 34
// + 38
// - 35
// swap
// + 39
// - 36
// + 40
// + 41
// - 37
// - 38
// swap
// + 42
// - 39
// - 40
// - 41
// swap
// - 42