// The contents of this file are in the public domain. See LICENSE_FOR_EXAMPLE_PROGRAMS.txt


/*
    This is an example showing how to use the type_safe_union and pipe object from
    from the dlib C++ Library to send messages between threads.

    In this example we will create a class with a single thread in it.  This thread
    will receive messages from a pipe object and simply print them to the screen.   
    The interesting thing about this example is that it shows how to use a pipe and
    type_safe_union to create a message channel between threads that can send many
    different types of objects in a type safe manner.
    


    Program output:
        got a float: 4.567
        got a string: string message
        got an int: 7
        got a string: yet another string message
*/


#include "dlib/threads.h"
#include "dlib/pipe.h"
#include "dlib/type_safe_union.h"
#include <iostream>

using namespace dlib;
using namespace std;

// ----------------------------------------------------------------------------------------

typedef type_safe_union<int, float, std::string> tsu_type;
/*  This is a typedef for the type_safe_union we will be using in this example.
    This type_safe_union object is a type-safe analogue of a union declared as follows:
        union our_union_type
        {
            int a;
            float b;
            std::string c;
        };
   
    Note that the above union isn't actually valid C++ code because it contains a
    non-POD type.  That is, you can't put a std::string or any non-trivial 
    C++ class in a union.   The type_safe_union, however, enables you to store non-POD 
    types such as the std::string.  
  
*/

// And here we have a typedef for the pipe we will be using
typedef dlib::pipe<tsu_type>::kernel_1a pipe_type;

// ----------------------------------------------------------------------------------------

class pipe_example : private threaded_object 
{
public:
    pipe_example(
    ) : 
        message_pipe(4) // This 4 here is the size of our message_pipe.  The significance is that
                    // if you try to enqueue more than 4 messages onto the pipe then enqueue() will
                    // block until there is room.  
    {
        // start the thread 
        start();
    }

    ~pipe_example (
    )
    {
        // wait for all the messages to be processed
        message_pipe.wait_until_empty();

        // Now disable the message_pipe.  Doing this will cause all calls to 
        // message_pipe.dequeue() to return false so our thread will terminate
        message_pipe.disable();

        // now block until our thread has terminated
        wait();
    }

    // Here we declare our pipe object.  It will contain our messages.
    pipe_type message_pipe;


    // When we call apply_to_contents() below these are the
    // functions which get called.   
    void operator() (int val)
    {
        cout << "got an int: " << val << endl;
    }

    void operator() (float val)
    {
        cout << "got a float: " << val << endl;
    }

    void operator() (std::string val)
    {
        cout << "got a string: " << val << endl;
    }

private:

    void thread ()
    {
        tsu_type msg;

        // Here we loop on messages from the message_pipe.  
        while (message_pipe.dequeue(msg))
        {
            // Tell the msg type_safe_union object to take whatever object
            // it contains and call (*this)(contained_object);   So what
            // happens here is one of the three above functions gets called
            // with the message we just got.  
            msg.apply_to_contents(*this);
        }
    }

};

// ----------------------------------------------------------------------------------------

int main()
{
    pipe_example pe;

    // Make one of our type_safe_union objects
    tsu_type msg;

    // Treat our msg as a float and assign it 4.567
    msg.get<float>() = 4.567f;
    // Now put the message into the pipe
    pe.message_pipe.enqueue(msg);

    // Put a string into the pipe
    msg.get<std::string>() = "string message";
    pe.message_pipe.enqueue(msg);

    // And now an int
    msg.get<int>() = 7;
    pe.message_pipe.enqueue(msg);

    // And another string
    msg.get<std::string>() = "yet another string message";
    pe.message_pipe.enqueue(msg);


    // the main function won't really terminate here.  It will call the destructor for pe
    // which will block until all the messages have been processed.
}

// ----------------------------------------------------------------------------------------

