The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#ifdef __cplusplus
extern "C" {
#endif

#define MIN_PERL_DEFINE 1

#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#ifdef __cplusplus
}
#endif

/* Is time() portable everywhere?  Hope so!  XXX */

static double fallback_NVtime()
{ return time(0); }

static void fallback_U2time(U32 *ret)
{
  ret[0]=time(0);
  ret[1]=0;
}

/*-----------------*/

static int    Installed=0;
static double (*realNVtime)();
static void   (*realU2time)(U32 *);

static double Lost;    /** time relative to now */
static double Zero;    /** apply Scale from when? */
static double Scale;   /** speed of time (.5 == half speed) */

static void reset_warp()
{
    Lost=0;
    Zero=(*realNVtime)();
    Scale=1;
}

/*-----------------*/

static double warped_NVtime()
{
    double now = (*realNVtime)() - Lost;
    double delta = now - Zero;
    delta *= Scale;
    return Zero + delta;
}

static void warped_U2time(U32 *ret)
{
    /* performance doesn't matter enough for a native
       non-float implementation */
    double now = warped_NVtime();
    U32 unow = now;
    ret[0] = unow;
    ret[1] = (now - unow) * 1000000;
}

MODULE = Time::Warp            PACKAGE = Time::Warp

PROTOTYPES: ENABLE

void
install_time_api()
	CODE:
{
    SV **svp;
    if (Installed) {
	warn("Time::Warp::install_time_api() called more than once");
	return;
    }
    Installed=1;
    svp = hv_fetch(PL_modglobal, "Time::NVtime", 12, 0);
    if (!svp) {
	warn("Time::Warp: Time::HiRes is not loaded --\n\tat best 1s time accuracy is available");
	hv_store(PL_modglobal, "Time::NVtime", 12,
		 newSViv((IV) fallback_NVtime), 0);
	hv_store(PL_modglobal, "Time::U2time", 12,
		 newSViv((IV) fallback_U2time), 0);
    }
    svp = hv_fetch(PL_modglobal, "Time::NVtime", 12, 0);
    if (!SvIOK(*svp)) croak("Time::NVtime isn't a function pointer");
    realNVtime = (double(*)()) SvIV(*svp);
    svp = hv_fetch(PL_modglobal, "Time::U2time", 12, 0);
    if (!SvIOK(*svp)) croak("Time::U2time isn't a function pointer");
    realU2time = (void(*)(U32*)) SvIV(*svp);
    hv_store(PL_modglobal, "Time::NVtime", 12,
	     newSViv((IV) warped_NVtime), 0);
    hv_store(PL_modglobal, "Time::U2time", 12,
	     newSViv((IV) warped_U2time), 0);

    reset_warp();
}

void
reset()
	CODE:
	reset_warp();

void
to(when)
     double when
     CODE:
{
    Lost += (warped_NVtime() - when) / Scale;
}

void
scale(...)
     PPCODE:
{
    if (items == 0) {
	XPUSHs(sv_2mortal(newSVnv(Scale)));
    } else {
	Zero = warped_NVtime();
	Lost = 0;
	Scale = SvNV(ST(0));
	if (Scale < 0) {
	    warn("Sorry, Time::Warp cannot go backwards");
	    Scale = 1;
	}
	else if (Scale < .001) {
	    warn("Sorry, Time::Warp cannot stop time");
	    Scale = .001;
	}
    }
}

void
time()
     PPCODE:
{
    XPUSHs(sv_2mortal(newSVnv(warped_NVtime())));
}