/*
 * ============================================================================
 *  Title:    Speed control
 *  Author:   J. Zbiciak
 *  $Id: speed.c,v 1.10 1999/10/10 08:44:30 im14u2c Exp $
 * ============================================================================
 *  SPEED_T      -- Speed-control peripheral
 *  SPEED_TK     -- Main throttling agent.
 *  SPEED_SET    -- Set desired speed as a fraction out of 256.
 *  SPEED_GET    -- Query current average running speed, tick rate, etc.
 * ============================================================================
 *  Attempts to control speed of emulation.  Invoked as a peripheral.
 *  This code will tend to be very platform dependent, I'm guessing.
 *  The initial implementation will be a busy-loop that calls gettimeofday
 *  repeatedly
 * ============================================================================
 */

static const char rcs_id[]="$Id: speed.c,v 1.10 1999/10/10 08:44:30 im14u2c Exp $";

#ifndef macintosh
# include <SDL/SDL.h>
#endif

#include "config.h"
#include "periph/periph.h"
#include "gfx/gfx.h"
#include "speed.h"

#define LATE_TOLERANCE     (256)
#define MICROSEC_PER_FRAME (16686)
#define DELAY_THRESH       (4000)

/*
 * ============================================================================
 *  SPEED_TK         -- Main throttling agent.
 * ============================================================================
 */
uint_32 speed_tk(periph_t *p, uint_32 len)
{
    speed_t         *speed = (speed_t*)p;
    uint_32         usec, elapsed, thresh;
    struct timeval  now, then;

    /* -------------------------------------------------------------------- */
    /*  The value of 'len' says how long we've gone w/out a tick.  We       */
    /*  compare this against the amount of real time that's elapsed, and    */
    /*  act accordingly:                                                    */
    /*                                                                      */
    /*   -- If it appears "no" real time has passed, then we're being       */
    /*      called too often, so adjust our min tick rate outwards.         */
    /*      We don't let this get larger than max_tick, though, which is    */
    /*      set to correspond to 60Hz.                                      */
    /*                                                                      */
    /*   -- If too little real time has passed, sit in a busy loop and      */
    /*      for real time to catch up to simulation time.                   */
    /*                                                                      */
    /*   -- If too much time has passed, do nothing.  (We could adjust our  */
    /*      frame rate, audio quality, etc.)                                */
    /* -------------------------------------------------------------------- */


    thresh   = speed->threshold;
    len      = speed->periph.now + len - speed->tick;
    usec     = (double)len * (4.0 / 3.579545);


    gettimeofday(&now, NULL);
    then     = speed->tv;           /* When will THEN be NOW???  SOON!!!    */
    elapsed  = (now.tv_usec - then.tv_usec) + 
               (now.tv_sec  - then.tv_sec ) * 1000000;

    if (elapsed <= 1) elapsed = 2;
    
    /* -------------------------------------------------------------------- */
    /*  If more than one full second has elapsed, assume that we've gotten  */
    /*  majorly pre-empted by the OS and just slip time so that we resync.  */
    /* -------------------------------------------------------------------- */
    if (now.tv_sec - then.tv_sec > 1 || elapsed > 1000000)
    {
        speed_resync(speed);
        return len;
    }

    /* -------------------------------------------------------------------- */
    /*  If we're warming up, then just record the time and move on.         */
    /* -------------------------------------------------------------------- */
    if (speed->warmup > 0)
    {
        speed->warmup--;

        len = elapsed * 0.89488625;
        speed->tv    = now;
        speed->tick += len;
        return len;
    }

    /* -------------------------------------------------------------------- */
    /*  No time has passed.  Adjust and move on.                            */
    /* -------------------------------------------------------------------- */
    if (elapsed == 0)
    {   
        if (speed->periph.min_tick < speed->periph.max_tick)
            speed->periph.min_tick++;

        return 0;
    }

    /* -------------------------------------------------------------------- */
    /*  Too much time has elapsed.  Return actual elasped time as a number  */
    /*  of simulation ticks that has elapsed.                               */
    /* -------------------------------------------------------------------- */
    if (elapsed + thresh >= usec)
    {
        if (elapsed + thresh >= usec + LATE_TOLERANCE)
        {
            int drop = elapsed + thresh - usec - LATE_TOLERANCE;

            speed->gfx->drop_frame += drop >> 14;

            if (speed->threshold <=
                (speed->periph.max_tick - speed->periph.min_tick))
                speed->threshold += drop >> 11;

            if (speed->periph.min_tick > (speed->periph.max_tick >> 3))
                speed->periph.min_tick -= drop >> 11;
        } else 
        {
            uint_32 new_thresh = speed->threshold - (speed->threshold >> 4) - 1;
            speed->threshold = new_thresh < speed->threshold ? new_thresh : 0;

            if (speed->periph.min_tick < speed->periph.max_tick)
                speed->periph.min_tick += 
                    1 + ((speed->periph.max_tick-speed->periph.min_tick)>>4);
        }

        len = elapsed * 0.89488625;
        speed->tv    = now;
        speed->tick += len;
        return len;
    } else if (elapsed >= usec)
    {
        uint_32 new_thresh = speed->threshold - (speed->threshold >> 3) - 1;
        speed->threshold = new_thresh < speed->threshold ? new_thresh : 0;
    }
    

    /* -------------------------------------------------------------------- */
    /*  Too little time has elapsed.  Sit in a busy loop until enough       */
    /*  time has gone by.  If the difference is too large, then don't       */
    /*  make it up all at once.                                             */
    /* -------------------------------------------------------------------- */

    if (usec - elapsed > MICROSEC_PER_FRAME)
        usec = elapsed + MICROSEC_PER_FRAME;

    if (usec > DELAY_THRESH) 
        plat_delay(usec / 1000);
        
    do 
    {
        gettimeofday(&now, NULL);
        elapsed = (now.tv_usec - then.tv_usec) + 
                  (now.tv_sec  - then.tv_sec ) * 1000000;
    } while (elapsed + thresh < usec);

    len = elapsed * 0.89488625;
    speed->tv    = now;
    speed->tick += len;

    return len;
}

/*
 * ============================================================================
 *  SPEED_RESYNC     -- Resynchronizes speed-control, slipping time.
 * ============================================================================
 */
void speed_resync(speed_t *speed)
{
    struct timeval  tv;
    gettimeofday(&tv, NULL);
    speed->warmup = 10;
    speed->tv     = tv;
}

/*
 * ============================================================================
 *  SPEED_INIT       -- Initializes a speed-control object.
 * ============================================================================
 */
int speed_init(speed_t *speed, gfx_t *gfx)
{
    struct timeval  tv;

    /* -------------------------------------------------------------------- */
    /*  Set up tick values.  Who knows if these work?                       */
    /* -------------------------------------------------------------------- */
    gettimeofday(&tv, NULL);


    /* -------------------------------------------------------------------- */
    /*  Set up the speed_t structure.                                       */
    /* -------------------------------------------------------------------- */
    speed->periph.read      = NULL;
    speed->periph.write     = NULL;
    speed->periph.peek      = NULL;
    speed->periph.poke      = NULL;
    speed->periph.tick      = speed_tk;
    speed->periph.min_tick  = 114;
    speed->periph.max_tick  = 14934/2;
    speed->gfx              = gfx;
    speed->threshold        = 0;
    speed->warmup           = 10;
    speed->tv               = tv;

    return 0;
}

/* ======================================================================== */
/*  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., 675 Mass Ave, Cambridge, MA 02139, USA.               */
/* ======================================================================== */
/*                 Copyright (c) 1998-1999, Joseph Zbiciak                  */
/* ======================================================================== */

