// Filename: test_concurrency.cxx
// Created by:  drose (06Apr06)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University.  All rights reserved.
//
// All use of this software is subject to the terms of the revised BSD
// license.  You should have received a copy of this license along
// with this source code in a file named "LICENSE."
//
////////////////////////////////////////////////////////////////////

#include "pandabase.h"
#include "thread.h"
#include "pmutex.h"
#include "mutexHolder.h"
#include "pointerTo.h"
#include "trueClock.h"

// The number of iterations to spin within each thread, before
// printing output.
static const long long iterations_per_output = 100000000;

// The amount of time, in seconds, to wait between spawning threads.
static const double delay_between_threads = 2.0;

// The amount of time, in seconds, for each thread to run.
static const double thread_run_time = 15.0;

// The number of threads to spawn.
static const int number_of_threads = 4;

// Number of bytes to reserve between writable memory pointers.
static const int block_size = 64;

static Mutex _output_lock;

union MemBlock {
  char _pad[block_size];
  int _value;
};

volatile MemBlock memblock[number_of_threads];

#define OUTPUT(stuff) { \
  MutexHolder holder(_output_lock); \
  stuff; \
}

class MyThread : public Thread {
public:
  MyThread(const string &name, int index) : 
    Thread(name, name),
    _index(index)
  {
  }
    
  virtual void thread_main() {
    OUTPUT(nout << *this << " beginning.\n");
    
    double total_seconds = 0.0;
    TrueClock *clock = TrueClock::get_global_ptr();
    volatile int snarf;
    
    while (total_seconds < thread_run_time) {
      double start_time = clock->get_short_time();
      
      for (long long i = 0; i < iterations_per_output; ++i) {
        memblock[_index]._value = snarf;
      }

      double end_time = clock->get_short_time();
      
      double elapsed_seconds = end_time - start_time;
      double iterations_per_second = iterations_per_output / elapsed_seconds;
      OUTPUT(nout << *this << " achieving "
             << iterations_per_second / 1000000.0
             << " million iterations per second.\n");
      
      total_seconds += elapsed_seconds;
    }  
    
    OUTPUT(nout << *this << " exiting.\n");
  }

  int _index;
};

int
main(int argc, char *argv[]) {
  OUTPUT(nout << "Making " << number_of_threads << " threads.\n");

  typedef pvector< PT(MyThread) > Threads;
  Threads threads;

  PT(MyThread) thread = new MyThread("a", 0);
  threads.push_back(thread);
  thread->start(TP_normal, true);

  for (int i = 1; i < number_of_threads; ++i) {
    char name = 'a' + i;
    Thread::sleep(delay_between_threads);
    PT(MyThread) thread = new MyThread(string(1, name), i);
    threads.push_back(thread);
    thread->start(TP_normal, true);
  }

  // Now join all the threads.
  Threads::iterator ti;
  for (ti = threads.begin(); ti != threads.end(); ++ti) {
    (*ti)->join();
  }

  Thread::prepare_for_exit();
  return 0;
}
