/* XRACER (C) 1999-2000 Richard W.M. Jones <rich@annexia.org> and other AUTHORS
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: sound.c,v 1.4 2000/01/18 22:29:57 rich Exp $
 */

/* This is, roughly speaking, the third attempt to write a sound
 * driver for XRacer.
 *
 * As with previous versions, the sound driver is divided into
 * two distinct parts: the parent process, and a sound process
 * child which is forked off at initialization.
 *
 * The parent and child communicate using a circular queue of
 * commands stored in shared memory, a shared memory status buffer
 * and signals.
 *
 * When an event is triggered in the parent, the parent will
 * add the appropriate sound command to the queue of commands
 * and send a SIGUSR1 to the child. If the queue of commands fills
 * up, then the parent prints a warning and starts dropping
 * sound commands.
 *
 * The status buffer stores the state of various continuous
 * quantities, such as speed / wind noise, and various other
 * miscellaneous status signals.
 *
 * The parent process listens for SIGCHLD, indicating that the
 * child process has died or exited. When it receives this signal,
 * it disables sound permanently.
 *
 * Before the parent exits, it writes a special QUIT command to the
 * child, which causes the child to exit cleanly.
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef HAVE_SCHED_H
#include <sched.h>
#endif

#include "xracer.h"
#include "xracer-sound.h"
#include "xracer-sound-process.h"
#include "xracer-arch.h"
#include "xracer-log.h"

/* If there is a sound process running, this contains the process
 * ID, else it contains -1.
 */
static int pid = -1;

/* Address of shared memory. */
static struct xrSoundSharedMemory *shm = 0;

static void sound_died (void);
static void send_command_no_arg (int code);

/* Program-level initializations. Here we create the child process
 * (initially the sound state is always disabled).
 */
void
xrSoundInit ()
{
  size_t shm_size = sizeof (struct xrSoundSharedMemory);

  /* Create the shared memory. */
  shm = (struct xrSoundSharedMemory *) xrArchCreateSharedMemory (shm_size);
  if (shm == (struct xrSoundSharedMemory *) 0)
    {
      xrLogPerror ("create shared memory");
      return;
    }

  /* Initialize the shared memory. */
  shm->insert_position = 0;
  shm->remove_position = 0;

  /* Prepare to be signalled if the sound process dies. */
  if (xrArchSetCallbackOnChildTerminationSignal (sound_died) < 0)
    {
      xrArchDeleteSharedMemory (shm, shm_size);
      xrLogPerror ("signal");
      return;
    }

  /* Create the sound subprocess. */
  pid = xrArchCreateChild (xrSoundProcessRun, shm);
  if (pid == -1)
    {
      xrArchSetCallbackOnChildTerminationSignal (0);
      xrArchDeleteSharedMemory (shm, shm_size);
      xrLogPerror ("create child process");
      return;
    }

  xrLog (LOG_DEBUG, "created sound process: pid = %d, shm = %p", pid, shm);
}

/* Program-level cleanup. Here we tell the child process to exit cleanly,
 * if indeed it is still running.
 */
void
xrSoundCleanup ()
{
  if (pid == -1) return;

  send_command_no_arg (XR_SOUND_COMMAND_CODE_QUIT);

  /* Busy wait for sound process to die. */
  while (pid >= 0)
#ifdef HAVE_SCHED_YIELD
    sched_yield ()
#endif
      ;

  xrLog (LOG_DEBUG, "cleaned up sound process");
}

/* This function is called if the child dies unexpectedly. */
static void
sound_died ()
{
#if defined(linux) && 1
  /* Debug SIGCHLD signals. */
  const char *s = "Received child termination signal.\n";
  int len = strlen (s);

  write (2, s, len);
#endif

  pid = -1;

  xrArchSetCallbackOnChildTerminationSignal (0);
  xrArchDeleteSharedMemory (shm, sizeof (struct xrSoundSharedMemory));

  xrArchWaitChild ();
}

/* Send a general command to the child process. */
static void
send_command (const struct xrSoundCommandBuffer *command)
{
  int next_slot = (shm->insert_position + 1) & (XR_SOUND_NR_COMMANDS - 1);

  /* Have we run out of space in the command queue? */
  if (shm->remove_position == next_slot)
    {
      xrLog (LOG_ERROR, "sound: no more sound command slots");
      return;
    }

  /* Push the command onto the queue. */
  memcpy (&shm->command[shm->insert_position],
	  command,
	  sizeof *command);

  /* Update pointer. */
  shm->insert_position = next_slot;

  /* Send the process a signal. */
  xrArchSendUserSignal (pid);
}

/* Send a simple command with no argument. */
static void
send_command_no_arg (int code)
{
  struct xrSoundCommandBuffer command;

  command.code = code;
  send_command (&command);
}
