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

    /*
        This is an example illustrating the use of the threading API and pipe object
        from the dlib C++ Library.

        In this example we will create three threads that will read "jobs" off the end of
        a pipe object and process them.  It shows you how you can use the pipe object
        to communicate between threads.

        Example program output:
        0 INFO  [0] pipe_example: Add job 0 to pipe
        0 INFO  [0] pipe_example: Add job 1 to pipe
        0 INFO  [0] pipe_example: Add job 2 to pipe
        0 INFO  [0] pipe_example: Add job 3 to pipe
        0 INFO  [0] pipe_example: Add job 4 to pipe
        0 INFO  [0] pipe_example: Add job 5 to pipe
        0 INFO  [1] pipe_example: got job 0
        0 INFO  [0] pipe_example: Add job 6 to pipe
        0 INFO  [2] pipe_example: got job 1
        0 INFO  [0] pipe_example: Add job 7 to pipe
        0 INFO  [3] pipe_example: got job 2
      103 INFO  [0] pipe_example: Add job 8 to pipe
      103 INFO  [1] pipe_example: got job 3
      103 INFO  [0] pipe_example: Add job 9 to pipe
      103 INFO  [2] pipe_example: got job 4
      103 INFO  [0] pipe_example: Add job 10 to pipe
      103 INFO  [3] pipe_example: got job 5
      207 INFO  [0] pipe_example: Add job 11 to pipe
      207 INFO  [1] pipe_example: got job 6
      207 INFO  [0] pipe_example: Add job 12 to pipe
      207 INFO  [2] pipe_example: got job 7
      207 INFO  [0] pipe_example: Add job 13 to pipe
      207 INFO  [3] pipe_example: got job 8
      311 INFO  [1] pipe_example: got job 9
      311 INFO  [2] pipe_example: got job 10
      311 INFO  [3] pipe_example: got job 11
      311 INFO  [0] pipe_example: Add job 14 to pipe
      311 INFO  [0] pipe_example: main ending
      311 INFO  [0] pipe_example: destructing pipe object: wait for job_pipe to be empty
      415 INFO  [1] pipe_example: got job 12
      415 INFO  [2] pipe_example: got job 13
      415 INFO  [3] pipe_example: got job 14
      415 INFO  [0] pipe_example: destructing pipe object: job_pipe is empty
      519 INFO  [1] pipe_example: thread ending
      519 INFO  [2] pipe_example: thread ending
      519 INFO  [3] pipe_example: thread ending
      519 INFO  [0] pipe_example: destructing pipe object: all threads have ended

      The first column is the number of milliseconds since program start, the second
      column is the logging level, the third column is the thread id, and the rest
      is the log message.
    */

    #include <dlib/threads.h>
    #include <dlib/misc_api.h>  // for dlib::sleep
    #include <dlib/pipe.h>
    #include <dlib/logger.h>

    using namespace dlib;

    struct job
    {
        /*
            This object represents the jobs we are going to send out to our threads.
        */
        int id;
    };

    dlib::logger dlog("pipe_example");

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

    class pipe_example : private multithreaded_object
    {
    public:
        pipe_example(
        ) :
            job_pipe(4) // This 4 here is the size of our job_pipe.  The significance is that
                        // if you try to enqueue more than 4 jobs onto the pipe then enqueue() will
                        // block until there is room.
        {
            // register 3 threads
            register_thread(*this,&pipe_example::thread);
            register_thread(*this,&pipe_example::thread);
            register_thread(*this,&pipe_example::thread);

            // start the 3 threads we registered above
            start();
        }

        ~pipe_example (
        )
        {
            dlog << LINFO << "destructing pipe object: wait for job_pipe to be empty";
            // wait for all the jobs to be processed
            job_pipe.wait_until_empty();

            dlog << LINFO << "destructing pipe object: job_pipe is empty";

            // now disable the job_pipe.  doing this will cause all calls to
            // job_pipe.dequeue() to return false so our threads will terminate
            job_pipe.disable();

            // now block until all the threads have terminated
            wait();
            dlog << LINFO << "destructing pipe object: all threads have ended";
        }

        // Here we declare our pipe object.  It will contain our job objects.
        // There are only two requirements on the type of objects you can use in a
        // pipe, first they must have a default constructor and second they must
        // be swappable by a global swap().
        dlib::pipe<job> job_pipe;

    private:
        void thread ()
        {
            job j;
            // Here we loop on jobs from the job_pipe.
            while (job_pipe.dequeue(j))
            {
                // process our job j in some way.
                dlog << LINFO << "got job " << j.id;

                // sleep for 0.1 seconds
                dlib::sleep(100);
            }
            dlog << LINFO << "thread ending";
        }

    };

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

    int main()
    {
        // Set the dlog object so that it logs everything.
        dlog.set_level(LALL);

        pipe_example pe;

        for (int i = 0; i < 15; ++i)
        {
            dlog << LINFO << "Add job " << i << " to pipe";
            job j;
            j.id = i;

            // Add this job to the pipe.  One of our three threads will get it and process it.
            // It should also be pointed out that the enqueue() function uses the global
            // swap function to move jobs into the pipe.  This means that it modifies the
            // jobs we are passing in to it.  This allows you to implement a fast swap
            // operator for your jobs.  For example, std::vector objects have a global
            // swap and it can execute in constant time by just swapping pointers inside
            // std::vector.  This means that the dlib::pipe is effectively a zero-copy
            // message passing system if you setup global swap for your jobs.
            pe.job_pipe.enqueue(j);
        }

        dlog << LINFO << "main ending";

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

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