diff --git a/doc/jsdoc-conf.json b/doc/jsdoc-conf.json new file mode 100644 index 00000000..d3120efe --- /dev/null +++ b/doc/jsdoc-conf.json @@ -0,0 +1,3 @@ +{ + "plugins": ["plugins/markdown", "jsdoc-plugin-jsio"] +} diff --git a/doc/jsdoc-plugin-jsio.js b/doc/jsdoc-plugin-jsio.js new file mode 100644 index 00000000..b17cf35b --- /dev/null +++ b/doc/jsdoc-plugin-jsio.js @@ -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, '/'); +} diff --git a/src/timer.js b/src/timer.js index d74c423b..6869f2a5 100644 --- a/src/timer.js +++ b/src/timer.js @@ -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)` + * - `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: ('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) {};