// Filename: clientBase.cxx
// Created by:  jason (04Aug00)
//
////////////////////////////////////////////////////////////////////
//
// 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 "clientBase.h"
#include "config_device.h"

TypeHandle ClientBase::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: ClientBase::constructor
//       Access: Protected
//  Description:
////////////////////////////////////////////////////////////////////
ClientBase::
ClientBase() {
  _forked = false;
  _last_poll_time = 0.0f;
  _last_poll_frame = 0;
  _cs = CS_default;

#ifdef OLD_HAVE_IPC
  _client_thread = (thread *)NULL;
  _shutdown = false;
#endif
}


////////////////////////////////////////////////////////////////////
//     Function: ClientBase::destructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
ClientBase::
~ClientBase() {
  // We have to disconnect all of our devices before destructing.
  Devices::iterator di;
  Devices devices_copy = _devices;
  for (di = devices_copy.begin(); di != devices_copy.end(); ++di) {
    DevicesByName &dbn = (*di).second;
    DevicesByName::iterator dbni;
    for (dbni = dbn.begin(); dbni != dbn.end(); ++dbni) {
      ClientDevice *device = (*dbni).second;
      device->disconnect();
    }
  }

#ifdef OLD_HAVE_IPC
  if (_forked) {
    _shutdown = true;

    // Join the loader thread - calling process blocks until the loader
    // thread returns.
    void *ret;
    _client_thread->join(&ret);
  }
#endif
}

////////////////////////////////////////////////////////////////////
//     Function: ClientBase::fork_asynchronous_thread
//       Access: Public
//  Description: Forks a separate thread to do all the polling of
//               connected devices.  The forked thread will poll after
//               every poll_time seconds has elapsed.  Returns true if
//               the fork was successful, or false otherwise (for
//               instance, because we were already forked, or because
//               asynchronous threads are disabled).
////////////////////////////////////////////////////////////////////
bool ClientBase::
fork_asynchronous_thread(double poll_time) {
#ifdef OLD_HAVE_IPC
  if (_forked) {
    device_cat.error()
      << "Attempt to fork client thread twice.\n";
    return false;
  }

  if (asynchronous_clients) {
    _sleep_time = (int)(1000000 * poll_time);

    _client_thread = thread::create(&st_callback, this);
    _forked = true;
    if (device_cat.is_debug()) {
      device_cat.debug()
        << "fork_asynchronous_thread() - forking client thread"
        << endl;
    }
    return true;
  }
#endif

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: ClientBase::get_device
//       Access: Public
//  Description: Returns a ClientDevice pointer that corresponds to
//               the named device of the indicated device type.  The
//               device_type should be one of ClientTrackerDevice,
//               ClientAnalogDevice, etc.; the device_name is
//               implementation defined.
//
//               Normally, the user does not need to call this
//               function directly; it is called automatically by
//               creating a TrackerNode or AnalogNode or some such
//               data graph node.
//
//               The return value is the pointer to the created device
//               (which might be the same pointer returned by a
//               previous call to this function with the same
//               parameters).  When the pointer destructs (i.e. its
//               reference count reaches zero) it will automatically
//               be disconnected.
//
//               If the named device does not exist or cannot be
//               connected for some reason, NULL is returned.
////////////////////////////////////////////////////////////////////
PT(ClientDevice) ClientBase::
get_device(TypeHandle device_type, const string &device_name) {
  DevicesByName &dbn = _devices[device_type];

  DevicesByName::iterator dbni;
  dbni = dbn.find(device_name);
  if (dbni != dbn.end()) {
    // This device was previously connected.  Return it again.
    return (*dbni).second;
  }

  // We need to create a new device for this name.
  PT(ClientDevice) device = make_device(device_type, device_name);

  if (device != (ClientDevice *)NULL) {
    dbn.insert(DevicesByName::value_type(device_name, device));
    device->_is_connected = true;
  }

  return device;
}

////////////////////////////////////////////////////////////////////
//     Function: ClientBase::disconnect_device
//       Access: Protected, Virtual
//  Description: Removes the device, which is presumably about to
//               destruct, from the list of connected devices, and
//               frees any data required to support it.  This device
//               will no longer receive automatic updates with each
//               poll.
//
//               The return value is true if the device was
//               disconnected, or false if it was unknown (e.g. it was
//               disconnected previously).
////////////////////////////////////////////////////////////////////
bool ClientBase::
disconnect_device(TypeHandle device_type, const string &device_name,
                  ClientDevice *device) {
  DevicesByName &dbn = _devices[device_type];

  DevicesByName::iterator dbni;
  dbni = dbn.find(device_name);
  if (dbni != dbn.end()) {
    if ((*dbni).second == device) {
      // We found it!
      dbn.erase(dbni);
      return true;
    }
  }

  // The device was unknown.
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: ClientBase::do_poll
//       Access: Protected, Virtual
//  Description: Implements the polling and updating of connected
//               devices, if the ClientBase requires this.  This may
//               be called in a sub-thread if
//               fork_asynchronous_thread() was called; otherwise, it
//               will be called once per frame.
////////////////////////////////////////////////////////////////////
void ClientBase::
do_poll() {
  ClockObject *global_clock = ClockObject::get_global_clock();
  _last_poll_frame = global_clock->get_frame_count();
  _last_poll_time = global_clock->get_frame_time();
}

#ifdef OLD_HAVE_IPC
////////////////////////////////////////////////////////////////////
//     Function: ClientBase::st_callback
//       Access: Private, static
//  Description: Call back function for thread (if thread has been
//               spawned).  A call back function must be static, so
//               this merely calls the non-static member callback In
//               addition, the function has a void* return type even
//               though we don't actually return anything.  This is
//               necessary because ipc assumes a function that does
//               not return anything indicates that the associated
//               thread should  be created as unjoinable (detached).
////////////////////////////////////////////////////////////////////
void *ClientBase::
st_callback(void *arg) {
  nassertr(arg != NULL, NULL);
  ((ClientBase *)arg)->callback();
  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: ClientBase::callback
//       Access: Private
//  Description: This is the main body of the sub-thread.  It sleeps
//               a certain time and then polls all devices currently
//               being watched
////////////////////////////////////////////////////////////////////
void ClientBase::
callback() {
  while (true) {
    if (_shutdown) {
      break;
    }
    do_poll();
    ipc_traits::sleep(0, _sleep_time);
  }
}
#endif // OLD_HAVE_IPC
