Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds tickCap, cleans up timer module, adds docs #97

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/jsdoc-conf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": ["plugins/markdown", "jsdoc-plugin-jsio"]
}
71 changes: 71 additions & 0 deletions doc/jsdoc-plugin-jsio.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// based on:
// https://github.com/gameclosure/eslint-plugin-jsio/blob/master/lib/index.js

"use strict";

exports.handlers = {
beforeParse: function(e) {
e.source = e.source.replace(importExpr, rewriteImports);
}
};

var importExpr = /^(\s*)(import\s+[^=+*"'\r\n;\/]+|from\s+[^=+"'\r\n;\/ ]+\s+import\s+[^=+"'\r\n;\/]+)(;|\/|$)/gm;

function rewriteImports(raw, p1, p2, p3) {
if (!/\/\//.test(p1)) {
var declarations = getDeclarations(p2);
var vars = declarations.vars;
var requireStatements = declarations.requireStatements;
return p1
+ ((vars.length ? 'var ' + vars.map(function (declaration) {
return declaration.replace(/^\.+/, '').split('.')[0];
}).join(',') + ';' : '')
+ (requireStatements.length ? requireStatements.map(function (statement) {
return 'require(\'' + statement + '\');';
}).join('') : '')).replace(/;$/, '')
+ p3;
}

return raw;
}

function getDeclarations(importStatement) {
var vars = [];
var requireStatements = [];
// from myPackage import myFunc
// external myPackage import myFunc
var match = importStatement.match(/^\s*(from|external)\s+([\w.\-$]+)\s+(import|grab)\s+(.*)$/);
if (match) {
requireStatements.push(requireify(match[2]));
match[4].replace(/\s*([\w.\-$*]+)(?:\s+as\s+([\w.\-$]+))?/g, function(_, item, as) {
vars.push(as || item);
});
}

if (!match) {
match = importStatement.match(/^\s*import\s+(.*)$/);
if (match) {
match[1].replace(/\s*([\w.\-$]+)(?:\s+as\s+([\w.\-$]+))?,?/g, function(_, fullPath, as) {
requireStatements.push(requireify(fullPath));
vars.push(as || fullPath);
});
}
}

return {
requireStatements: requireStatements,
vars: vars
};
}

function requireify(path) {
var match = path.match(/^(\.*)(.*)$/);
var leadingDots = match[1];
var remainder = match[2];
return (leadingDots.length == 1
? './'
: leadingDots.length > 1
? leadingDots.substring(1).split('.').join('../')
: '')
+ remainder.replace(/\./g, '/');
}
162 changes: 119 additions & 43 deletions src/timer.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,68 +15,144 @@
*/

/**
* @module timer
* Provides an interface to the time for a timestep app. The interface can be
* queried by the app for information about elapsed time. It is consumed by the
* timestep {@link Engine} to drive the Engine's tick and render.
*
* Use {@linkcode module:timer.maxTick|maxTick} and {@linkcode
* module:timer.tickCap|tickCap} to control the behavior of the timer.
*
* For example, assume `maxTick = 100000` (10,000ms) and `tickCap = 100`
* (100ms). The following behavior will occur for various tick `dt` values:
* - `20 ⟶ onTick(20)`
* - `100 ⟶ onTick(100)`
* - `120 ⟶ onTick(100)`
* - `9999 ⟶ onTick(100)`
* - `10000 ⟶ onTick(10000)`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this supposed to be onTick(100) as well?

* - `10001 ⟶ onLargeTick(10001, 10000); onTick(1)`
* - `40000 ⟶ onLargeTick(40000, 10000); onTick(1)`
*
* Implements an independent, singleton timer for use by the environment.
* The Application Engine binds to this to generate the rendering tick.
* @module timer
*/

import device;

var Timer = device.get('Timer');

var MAX_TICK = 10000; // ticks over 10 seconds will be considered too large to process
exports.now = 0;
exports.frames = 0;
exports.reset = function () { this._last = null; }
exports.tick = function (dt) {
try {
if (dt > MAX_TICK) {
exports.onLargeTick(dt, MAX_TICK);
dt = 1;
}

exports.now += dt;
exports.frames++;
exports.onTick(dt);
ok = true;
} finally {
if (exports.debug && !ok) {
app.stopLoop()
}
}
}
// ticks over 10 seconds will be considered too large to forward to the app
var _maxTick = 10 * 1000;
var _tickCap = _maxTick;
var _now = 0;
var _frames = 0;

/**
* If our computer falls asleep, dt might be an insanely large number.
* If we're running a simulation of sorts, we don't want the computer
* to freeze while computing 1000s of simulation steps, so just drop
* this tick. Anyone who is interested can listen for a call to 'onLargeTick'
* Defines the maximum milliseconds for a tick before the tick is dropped and
* {@linkcode module:timer.onLargeTick|onLargeTick} is called. {@linkcode
* module:timer.onTick|onTick} will also be called with a `dt` of 1.
* @member module:timer.maxTick {integer}
* @default 10,000
*/
exports.onLargeTick = function (largeDt, threshold) {
logger.warn('Dropping large tick: ' + largeDt + '; Threshold is set at: ' + threshold);
}
Object.defineProperty(exports, 'maxTick', {
get: function () { return _maxTick; },
set: function (value) { _maxTick = value; }
});

/**
* Defines the maximum value of `dt`. If a tick `dt` is above the cap,
* {@linkcode module:timer.onTick|onTick} is called with `tickCap` rather than
* `dt`.
* @member module:timer.tickCap {integer}
* @default 10,000
*/
exports.tickCap;
Object.defineProperty(exports, 'tickCap', {
get: function () { return _tickCap; },
set: function (value) { _tickCap = value; }
});

exports.onTick = function (dt) {}
/**
* number of milliseconds since timer started
* @member module:timer.now {integer}
* @readonly
*/
Object.defineProperty(exports, 'now', {
get: function () { return _now; }
});

exports.debug = false;
/**
* number of frames since timer started
* @member module:timer.frames {integer}
* @readonly
*/
Object.defineProperty(exports, 'frames', {
get: function () { return _frames; }
});

/**
* resets the timer's properties `now` and `frames` (sets them to 0)
*/
exports.reset = function () {
_now = 0;
_frames = 0;
};

// TODO: <jsio>('from iOS import start as exports.start, stop as exports.stop');
exports.tick = function (dt) {
if (dt > _maxTick) {
exports.onLargeTick(dt, _maxTick);
dt = 1;
}

if (dt > _tickCap) {
dt = _tickCap;
}

_now += dt;
_frames++;

exports.onTick(dt);
};

/**
* Starts/resumes the timer
*/
exports.start = function (minDt) {
this.reset();
this.isRunning = true;
exports.isRunning = true;
device.get('Timer').start(exports.tick, minDt);
}
};

/**
* Stops/pauses the timer
*/
exports.stop = function () {
this.reset();
this.isRunning = false;
exports.isRunning = false;
device.get('Timer').stop();
}
};

/**
* Computes and returns the number of milliseconds into the current frame
* execution has progressed since the last tick (and before the next one)
*/
exports.getTickProgress = function () {
var now = +new Date;
return (-(Timer.last || now) + now);
}
var now = Date.now();
return now - (Timer.last || now);
};

/**
* Override this function to capture large ticks. Large ticks may happen if the
* app is backgrounded or paused. The default behavior is to log a warning with
* the value of the dropped tick.
* @param largeDt {integer} number of milliseconds in the large tick
* @param threshold {integer} current value of {@linkcode
* module:timer.maxTick|maxTick}
*/
exports.onLargeTick = function (largeDt, threshold) {
logger.warn('Dropping large tick: ' + largeDt + '; Threshold is set at: '
+ threshold);
};

/**
* Used internally by the timestep {@link Engine} for driving `tick` and
* `render`.
* @param _dt {integer} milliseconds since last tick
*/
exports.onTick = function (_dt) {};