From 32dc7df061b6abf1e08e56ae3e5feff3fbcc635a Mon Sep 17 00:00:00 2001 From: casperlamboo Date: Fri, 29 May 2015 10:41:44 +0200 Subject: [PATCH] added benchmark for web workers --- library/benchmark.js | 3919 +++++++++++++++++++++++++++++++++++ library/three.js | 15 + models/dom.stl | Bin 0 -> 195284 bytes settings/user_settings.json | 16 +- slice_test.html | 4 +- src/slicer.js | 12 +- src/slicerworker.js | 14 +- webworker/benchmark.js | 9 + webworker/worker.js | 20 +- webworker_benchmark.html | 74 + webworker_test.html | 3 + 11 files changed, 4060 insertions(+), 26 deletions(-) create mode 100644 library/benchmark.js create mode 100644 models/dom.stl create mode 100644 webworker/benchmark.js create mode 100644 webworker_benchmark.html diff --git a/library/benchmark.js b/library/benchmark.js new file mode 100644 index 0000000..c17b365 --- /dev/null +++ b/library/benchmark.js @@ -0,0 +1,3919 @@ +/*! + * Benchmark.js v1.0.0 + * Copyright 2010-2012 Mathias Bynens + * Based on JSLitmus.js, copyright Robert Kieffer + * Modified by John-David Dalton + * Available under MIT license + */ +;(function(window, undefined) { + 'use strict'; + + /** Used to assign each benchmark an incrimented id */ + var counter = 0; + + /** Detect DOM document object */ + var doc = isHostType(window, 'document') && document; + + /** Detect free variable `define` */ + var freeDefine = typeof define == 'function' && + typeof define.amd == 'object' && define.amd && define; + + /** Detect free variable `exports` */ + var freeExports = typeof exports == 'object' && exports && + (typeof global == 'object' && global && global == global.global && (window = global), exports); + + /** Detect free variable `require` */ + var freeRequire = typeof require == 'function' && require; + + /** Used to crawl all properties regardless of enumerability */ + var getAllKeys = Object.getOwnPropertyNames; + + /** Used to get property descriptors */ + var getDescriptor = Object.getOwnPropertyDescriptor; + + /** Used in case an object doesn't have its own method */ + var hasOwnProperty = {}.hasOwnProperty; + + /** Used to check if an object is extensible */ + var isExtensible = Object.isExtensible || function() { return true; }; + + /** Used to access Wade Simmons' Node microtime module */ + var microtimeObject = req('microtime'); + + /** Used to access the browser's high resolution timer */ + var perfObject = isHostType(window, 'performance') && performance; + + /** Used to call the browser's high resolution timer */ + var perfName = perfObject && ( + perfObject.now && 'now' || + perfObject.webkitNow && 'webkitNow' + ); + + /** Used to access Node's high resolution timer */ + var processObject = isHostType(window, 'process') && process; + + /** Used to check if an own property is enumerable */ + var propertyIsEnumerable = {}.propertyIsEnumerable; + + /** Used to set property descriptors */ + var setDescriptor = Object.defineProperty; + + /** Used to resolve a value's internal [[Class]] */ + var toString = {}.toString; + + /** Used to prevent a `removeChild` memory leak in IE < 9 */ + var trash = doc && doc.createElement('div'); + + /** Used to integrity check compiled tests */ + var uid = 'uid' + (+new Date); + + /** Used to avoid infinite recursion when methods call each other */ + var calledBy = {}; + + /** Used to avoid hz of Infinity */ + var divisors = { + '1': 4096, + '2': 512, + '3': 64, + '4': 8, + '5': 0 + }; + + /** + * T-Distribution two-tailed critical values for 95% confidence + * http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm + */ + var tTable = { + '1': 12.706,'2': 4.303, '3': 3.182, '4': 2.776, '5': 2.571, '6': 2.447, + '7': 2.365, '8': 2.306, '9': 2.262, '10': 2.228, '11': 2.201, '12': 2.179, + '13': 2.16, '14': 2.145, '15': 2.131, '16': 2.12, '17': 2.11, '18': 2.101, + '19': 2.093, '20': 2.086, '21': 2.08, '22': 2.074, '23': 2.069, '24': 2.064, + '25': 2.06, '26': 2.056, '27': 2.052, '28': 2.048, '29': 2.045, '30': 2.042, + 'infinity': 1.96 + }; + + /** + * Critical Mann-Whitney U-values for 95% confidence + * http://www.saburchill.com/IBbiology/stats/003.html + */ + var uTable = { + '5': [0, 1, 2], + '6': [1, 2, 3, 5], + '7': [1, 3, 5, 6, 8], + '8': [2, 4, 6, 8, 10, 13], + '9': [2, 4, 7, 10, 12, 15, 17], + '10': [3, 5, 8, 11, 14, 17, 20, 23], + '11': [3, 6, 9, 13, 16, 19, 23, 26, 30], + '12': [4, 7, 11, 14, 18, 22, 26, 29, 33, 37], + '13': [4, 8, 12, 16, 20, 24, 28, 33, 37, 41, 45], + '14': [5, 9, 13, 17, 22, 26, 31, 36, 40, 45, 50, 55], + '15': [5, 10, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64], + '16': [6, 11, 15, 21, 26, 31, 37, 42, 47, 53, 59, 64, 70, 75], + '17': [6, 11, 17, 22, 28, 34, 39, 45, 51, 57, 63, 67, 75, 81, 87], + '18': [7, 12, 18, 24, 30, 36, 42, 48, 55, 61, 67, 74, 80, 86, 93, 99], + '19': [7, 13, 19, 25, 32, 38, 45, 52, 58, 65, 72, 78, 85, 92, 99, 106, 113], + '20': [8, 14, 20, 27, 34, 41, 48, 55, 62, 69, 76, 83, 90, 98, 105, 112, 119, 127], + '21': [8, 15, 22, 29, 36, 43, 50, 58, 65, 73, 80, 88, 96, 103, 111, 119, 126, 134, 142], + '22': [9, 16, 23, 30, 38, 45, 53, 61, 69, 77, 85, 93, 101, 109, 117, 125, 133, 141, 150, 158], + '23': [9, 17, 24, 32, 40, 48, 56, 64, 73, 81, 89, 98, 106, 115, 123, 132, 140, 149, 157, 166, 175], + '24': [10, 17, 25, 33, 42, 50, 59, 67, 76, 85, 94, 102, 111, 120, 129, 138, 147, 156, 165, 174, 183, 192], + '25': [10, 18, 27, 35, 44, 53, 62, 71, 80, 89, 98, 107, 117, 126, 135, 145, 154, 163, 173, 182, 192, 201, 211], + '26': [11, 19, 28, 37, 46, 55, 64, 74, 83, 93, 102, 112, 122, 132, 141, 151, 161, 171, 181, 191, 200, 210, 220, 230], + '27': [11, 20, 29, 38, 48, 57, 67, 77, 87, 97, 107, 118, 125, 138, 147, 158, 168, 178, 188, 199, 209, 219, 230, 240, 250], + '28': [12, 21, 30, 40, 50, 60, 70, 80, 90, 101, 111, 122, 132, 143, 154, 164, 175, 186, 196, 207, 218, 228, 239, 250, 261, 272], + '29': [13, 22, 32, 42, 52, 62, 73, 83, 94, 105, 116, 127, 138, 149, 160, 171, 182, 193, 204, 215, 226, 238, 249, 260, 271, 282, 294], + '30': [13, 23, 33, 43, 54, 65, 76, 87, 98, 109, 120, 131, 143, 154, 166, 177, 189, 200, 212, 223, 235, 247, 258, 270, 282, 293, 305, 317] + }; + + /** + * An object used to flag environments/features. + * + * @static + * @memberOf Benchmark + * @type Object + */ + var support = {}; + + (function() { + + /** + * Detect Adobe AIR. + * + * @memberOf Benchmark.support + * @type Boolean + */ + support.air = isClassOf(window.runtime, 'ScriptBridgingProxyObject'); + + /** + * Detect if `arguments` objects have the correct internal [[Class]] value. + * + * @memberOf Benchmark.support + * @type Boolean + */ + support.argumentsClass = isClassOf(arguments, 'Arguments'); + + /** + * Detect if in a browser environment. + * + * @memberOf Benchmark.support + * @type Boolean + */ + support.browser = doc && isHostType(window, 'navigator'); + + /** + * Detect if strings support accessing characters by index. + * + * @memberOf Benchmark.support + * @type Boolean + */ + support.charByIndex = + // IE 8 supports indexes on string literals but not string objects + ('x'[0] + Object('x')[0]) == 'xx'; + + /** + * Detect if strings have indexes as own properties. + * + * @memberOf Benchmark.support + * @type Boolean + */ + support.charByOwnIndex = + // Narwhal, Rhino, RingoJS, IE 8, and Opera < 10.52 support indexes on + // strings but don't detect them as own properties + support.charByIndex && hasKey('x', '0'); + + /** + * Detect if Java is enabled/exposed. + * + * @memberOf Benchmark.support + * @type Boolean + */ + support.java = isClassOf(window.java, 'JavaPackage'); + + /** + * Detect if the Timers API exists. + * + * @memberOf Benchmark.support + * @type Boolean + */ + support.timeout = isHostType(window, 'setTimeout') && isHostType(window, 'clearTimeout'); + + /** + * Detect if functions support decompilation. + * + * @name decompilation + * @memberOf Benchmark.support + * @type Boolean + */ + try { + // Safari 2.x removes commas in object literals + // from Function#toString results + // http://webk.it/11609 + // Firefox 3.6 and Opera 9.25 strip grouping + // parentheses from Function#toString results + // http://bugzil.la/559438 + support.decompilation = Function( + 'return (' + (function(x) { return { 'x': '' + (1 + x) + '', 'y': 0 }; }) + ')' + )()(0).x === '1'; + } catch(e) { + support.decompilation = false; + } + + /** + * Detect ES5+ property descriptor API. + * + * @name descriptors + * @memberOf Benchmark.support + * @type Boolean + */ + try { + var o = {}; + support.descriptors = (setDescriptor(o, o, o), 'value' in getDescriptor(o, o)); + } catch(e) { + support.descriptors = false; + } + + /** + * Detect ES5+ Object.getOwnPropertyNames(). + * + * @name getAllKeys + * @memberOf Benchmark.support + * @type Boolean + */ + try { + support.getAllKeys = /\bvalueOf\b/.test(getAllKeys(Object.prototype)); + } catch(e) { + support.getAllKeys = false; + } + + /** + * Detect if own properties are iterated before inherited properties (all but IE < 9). + * + * @name iteratesOwnLast + * @memberOf Benchmark.support + * @type Boolean + */ + support.iteratesOwnFirst = (function() { + var props = []; + function ctor() { this.x = 1; } + ctor.prototype = { 'y': 1 }; + for (var prop in new ctor) { props.push(prop); } + return props[0] == 'x'; + }()); + + /** + * Detect if a node's [[Class]] is resolvable (all but IE < 9) + * and that the JS engine errors when attempting to coerce an object to a + * string without a `toString` property value of `typeof` "function". + * + * @name nodeClass + * @memberOf Benchmark.support + * @type Boolean + */ + try { + support.nodeClass = ({ 'toString': 0 } + '', toString.call(doc || 0) != '[object Object]'); + } catch(e) { + support.nodeClass = true; + } + }()); + + /** + * Timer object used by `clock()` and `Deferred#resolve`. + * + * @private + * @type Object + */ + var timer = { + + /** + * The timer namespace object or constructor. + * + * @private + * @memberOf timer + * @type Function|Object + */ + 'ns': Date, + + /** + * Starts the deferred timer. + * + * @private + * @memberOf timer + * @param {Object} deferred The deferred instance. + */ + 'start': null, // lazy defined in `clock()` + + /** + * Stops the deferred timer. + * + * @private + * @memberOf timer + * @param {Object} deferred The deferred instance. + */ + 'stop': null // lazy defined in `clock()` + }; + + /** Shortcut for inverse results */ + var noArgumentsClass = !support.argumentsClass, + noCharByIndex = !support.charByIndex, + noCharByOwnIndex = !support.charByOwnIndex; + + /** Math shortcuts */ + var abs = Math.abs, + floor = Math.floor, + max = Math.max, + min = Math.min, + pow = Math.pow, + sqrt = Math.sqrt; + + /*--------------------------------------------------------------------------*/ + + /** + * The Benchmark constructor. + * + * @constructor + * @param {String} name A name to identify the benchmark. + * @param {Function|String} fn The test to benchmark. + * @param {Object} [options={}] Options object. + * @example + * + * // basic usage (the `new` operator is optional) + * var bench = new Benchmark(fn); + * + * // or using a name first + * var bench = new Benchmark('foo', fn); + * + * // or with options + * var bench = new Benchmark('foo', fn, { + * + * // displayed by Benchmark#toString if `name` is not available + * 'id': 'xyz', + * + * // called when the benchmark starts running + * 'onStart': onStart, + * + * // called after each run cycle + * 'onCycle': onCycle, + * + * // called when aborted + * 'onAbort': onAbort, + * + * // called when a test errors + * 'onError': onError, + * + * // called when reset + * 'onReset': onReset, + * + * // called when the benchmark completes running + * 'onComplete': onComplete, + * + * // compiled/called before the test loop + * 'setup': setup, + * + * // compiled/called after the test loop + * 'teardown': teardown + * }); + * + * // or name and options + * var bench = new Benchmark('foo', { + * + * // a flag to indicate the benchmark is deferred + * 'defer': true, + * + * // benchmark test function + * 'fn': function(deferred) { + * // call resolve() when the deferred test is finished + * deferred.resolve(); + * } + * }); + * + * // or options only + * var bench = new Benchmark({ + * + * // benchmark name + * 'name': 'foo', + * + * // benchmark test as a string + * 'fn': '[1,2,3,4].sort()' + * }); + * + * // a test's `this` binding is set to the benchmark instance + * var bench = new Benchmark('foo', function() { + * 'My name is '.concat(this.name); // My name is foo + * }); + */ + function Benchmark(name, fn, options) { + var me = this; + + // allow instance creation without the `new` operator + if (me == null || me.constructor != Benchmark) { + return new Benchmark(name, fn, options); + } + // juggle arguments + if (isClassOf(name, 'Object')) { + // 1 argument (options) + options = name; + } + else if (isClassOf(name, 'Function')) { + // 2 arguments (fn, options) + options = fn; + fn = name; + } + else if (isClassOf(fn, 'Object')) { + // 2 arguments (name, options) + options = fn; + fn = null; + me.name = name; + } + else { + // 3 arguments (name, fn [, options]) + me.name = name; + } + setOptions(me, options); + me.id || (me.id = ++counter); + me.fn == null && (me.fn = fn); + me.stats = deepClone(me.stats); + me.times = deepClone(me.times); + } + + /** + * The Deferred constructor. + * + * @constructor + * @memberOf Benchmark + * @param {Object} clone The cloned benchmark instance. + */ + function Deferred(clone) { + var me = this; + if (me == null || me.constructor != Deferred) { + return new Deferred(clone); + } + me.benchmark = clone; + clock(me); + } + + /** + * The Event constructor. + * + * @constructor + * @memberOf Benchmark + * @param {String|Object} type The event type. + */ + function Event(type) { + var me = this; + return (me == null || me.constructor != Event) + ? new Event(type) + : (type instanceof Event) + ? type + : extend(me, { 'timeStamp': +new Date }, typeof type == 'string' ? { 'type': type } : type); + } + + /** + * The Suite constructor. + * + * @constructor + * @memberOf Benchmark + * @param {String} name A name to identify the suite. + * @param {Object} [options={}] Options object. + * @example + * + * // basic usage (the `new` operator is optional) + * var suite = new Benchmark.Suite; + * + * // or using a name first + * var suite = new Benchmark.Suite('foo'); + * + * // or with options + * var suite = new Benchmark.Suite('foo', { + * + * // called when the suite starts running + * 'onStart': onStart, + * + * // called between running benchmarks + * 'onCycle': onCycle, + * + * // called when aborted + * 'onAbort': onAbort, + * + * // called when a test errors + * 'onError': onError, + * + * // called when reset + * 'onReset': onReset, + * + * // called when the suite completes running + * 'onComplete': onComplete + * }); + */ + function Suite(name, options) { + var me = this; + + // allow instance creation without the `new` operator + if (me == null || me.constructor != Suite) { + return new Suite(name, options); + } + // juggle arguments + if (isClassOf(name, 'Object')) { + // 1 argument (options) + options = name; + } else { + // 2 arguments (name [, options]) + me.name = name; + } + setOptions(me, options); + } + + /*--------------------------------------------------------------------------*/ + + /** + * Note: Some array methods have been implemented in plain JavaScript to avoid + * bugs in IE, Opera, Rhino, and Mobile Safari. + * + * IE compatibility mode and IE < 9 have buggy Array `shift()` and `splice()` + * functions that fail to remove the last element, `object[0]`, of + * array-like-objects even though the `length` property is set to `0`. + * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` + * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. + * + * In Opera < 9.50 and some older/beta Mobile Safari versions using `unshift()` + * generically to augment the `arguments` object will pave the value at index 0 + * without incrimenting the other values's indexes. + * https://github.com/documentcloud/underscore/issues/9 + * + * Rhino and environments it powers, like Narwhal and RingoJS, may have + * buggy Array `concat()`, `reverse()`, `shift()`, `slice()`, `splice()` and + * `unshift()` functions that make sparse arrays non-sparse by assigning the + * undefined indexes a value of undefined. + * https://github.com/mozilla/rhino/commit/702abfed3f8ca043b2636efd31c14ba7552603dd + */ + + /** + * Creates an array containing the elements of the host array followed by the + * elements of each argument in order. + * + * @memberOf Benchmark.Suite + * @returns {Array} The new array. + */ + function concat() { + var value, + j = -1, + length = arguments.length, + result = slice.call(this), + index = result.length; + + while (++j < length) { + value = arguments[j]; + if (isClassOf(value, 'Array')) { + for (var k = 0, l = value.length; k < l; k++, index++) { + if (k in value) { + result[index] = value[k]; + } + } + } else { + result[index++] = value; + } + } + return result; + } + + /** + * Utility function used by `shift()`, `splice()`, and `unshift()`. + * + * @private + * @param {Number} start The index to start inserting elements. + * @param {Number} deleteCount The number of elements to delete from the insert point. + * @param {Array} elements The elements to insert. + * @returns {Array} An array of deleted elements. + */ + function insert(start, deleteCount, elements) { + // `result` should have its length set to the `deleteCount` + // see https://bugs.ecmascript.org/show_bug.cgi?id=332 + var deleteEnd = start + deleteCount, + elementCount = elements ? elements.length : 0, + index = start - 1, + length = start + elementCount, + object = this, + result = Array(deleteCount), + tail = slice.call(object, deleteEnd); + + // delete elements from the array + while (++index < deleteEnd) { + if (index in object) { + result[index - start] = object[index]; + delete object[index]; + } + } + // insert elements + index = start - 1; + while (++index < length) { + object[index] = elements[index - start]; + } + // append tail elements + start = index--; + length = max(0, (object.length >>> 0) - deleteCount + elementCount); + while (++index < length) { + if ((index - start) in tail) { + object[index] = tail[index - start]; + } else if (index in object) { + delete object[index]; + } + } + // delete excess elements + deleteCount = deleteCount > elementCount ? deleteCount - elementCount : 0; + while (deleteCount--) { + index = length + deleteCount; + if (index in object) { + delete object[index]; + } + } + object.length = length; + return result; + } + + /** + * Rearrange the host array's elements in reverse order. + * + * @memberOf Benchmark.Suite + * @returns {Array} The reversed array. + */ + function reverse() { + var upperIndex, + value, + index = -1, + object = Object(this), + length = object.length >>> 0, + middle = floor(length / 2); + + if (length > 1) { + while (++index < middle) { + upperIndex = length - index - 1; + value = upperIndex in object ? object[upperIndex] : uid; + if (index in object) { + object[upperIndex] = object[index]; + } else { + delete object[upperIndex]; + } + if (value != uid) { + object[index] = value; + } else { + delete object[index]; + } + } + } + return object; + } + + /** + * Removes the first element of the host array and returns it. + * + * @memberOf Benchmark.Suite + * @returns {Mixed} The first element of the array. + */ + function shift() { + return insert.call(this, 0, 1)[0]; + } + + /** + * Creates an array of the host array's elements from the start index up to, + * but not including, the end index. + * + * @memberOf Benchmark.Suite + * @param {Number} start The starting index. + * @param {Number} end The end index. + * @returns {Array} The new array. + */ + function slice(start, end) { + var index = -1, + object = Object(this), + length = object.length >>> 0, + result = []; + + start = toInteger(start); + start = start < 0 ? max(length + start, 0) : min(start, length); + start--; + end = end == null ? length : toInteger(end); + end = end < 0 ? max(length + end, 0) : min(end, length); + + while ((++index, ++start) < end) { + if (start in object) { + result[index] = object[start]; + } + } + return result; + } + + /** + * Allows removing a range of elements and/or inserting elements into the + * host array. + * + * @memberOf Benchmark.Suite + * @param {Number} start The start index. + * @param {Number} deleteCount The number of elements to delete. + * @param {Mixed} [val1, val2, ...] values to insert at the `start` index. + * @returns {Array} An array of removed elements. + */ + function splice(start, deleteCount) { + var object = Object(this), + length = object.length >>> 0; + + start = toInteger(start); + start = start < 0 ? max(length + start, 0) : min(start, length); + + // support the de-facto SpiderMonkey extension + // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice#Parameters + // https://bugs.ecmascript.org/show_bug.cgi?id=429 + deleteCount = arguments.length == 1 + ? length - start + : min(max(toInteger(deleteCount), 0), length - start); + + return insert.call(object, start, deleteCount, slice.call(arguments, 2)); + } + + /** + * Converts the specified `value` to an integer. + * + * @private + * @param {Mixed} value The value to convert. + * @returns {Number} The resulting integer. + */ + function toInteger(value) { + value = +value; + return value === 0 || !isFinite(value) ? value || 0 : value - (value % 1); + } + + /** + * Appends arguments to the host array. + * + * @memberOf Benchmark.Suite + * @returns {Number} The new length. + */ + function unshift() { + var object = Object(this); + insert.call(object, 0, 0, arguments); + return object.length; + } + + /*--------------------------------------------------------------------------*/ + + /** + * A generic `Function#bind` like method. + * + * @private + * @param {Function} fn The function to be bound to `thisArg`. + * @param {Mixed} thisArg The `this` binding for the given function. + * @returns {Function} The bound function. + */ + function bind(fn, thisArg) { + return function() { fn.apply(thisArg, arguments); }; + } + + /** + * Creates a function from the given arguments string and body. + * + * @private + * @param {String} args The comma separated function arguments. + * @param {String} body The function body. + * @returns {Function} The new function. + */ + function createFunction() { + // lazy define + createFunction = function(args, body) { + var result, + anchor = freeDefine ? define.amd : Benchmark, + prop = uid + 'createFunction'; + + runScript((freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '=function(' + args + '){' + body + '}'); + result = anchor[prop]; + delete anchor[prop]; + return result; + }; + // fix JaegerMonkey bug + // http://bugzil.la/639720 + createFunction = support.browser && (createFunction('', 'return"' + uid + '"') || noop)() == uid ? createFunction : Function; + return createFunction.apply(null, arguments); + } + + /** + * Delay the execution of a function based on the benchmark's `delay` property. + * + * @private + * @param {Object} bench The benchmark instance. + * @param {Object} fn The function to execute. + */ + function delay(bench, fn) { + bench._timerId = setTimeout(fn, bench.delay * 1e3); + } + + /** + * Destroys the given element. + * + * @private + * @param {Element} element The element to destroy. + */ + function destroyElement(element) { + trash.appendChild(element); + trash.innerHTML = ''; + } + + /** + * Iterates over an object's properties, executing the `callback` for each. + * Callbacks may terminate the loop by explicitly returning `false`. + * + * @private + * @param {Object} object The object to iterate over. + * @param {Function} callback The function executed per own property. + * @param {Object} options The options object. + * @returns {Object} Returns the object iterated over. + */ + function forProps() { + var forShadowed, + skipSeen, + forArgs = true, + shadowed = ['constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'valueOf']; + + (function(enumFlag, key) { + // must use a non-native constructor to catch the Safari 2 issue + function Klass() { this.valueOf = 0; }; + Klass.prototype.valueOf = 0; + // check various for-in bugs + for (key in new Klass) { + enumFlag += key == 'valueOf' ? 1 : 0; + } + // check if `arguments` objects have non-enumerable indexes + for (key in arguments) { + key == '0' && (forArgs = false); + } + // Safari 2 iterates over shadowed properties twice + // http://replay.waybackmachine.org/20090428222941/http://tobielangel.com/2007/1/29/for-in-loop-broken-in-safari/ + skipSeen = enumFlag == 2; + // IE < 9 incorrectly makes an object's properties non-enumerable if they have + // the same name as other non-enumerable properties in its prototype chain. + forShadowed = !enumFlag; + }(0)); + + // lazy define + forProps = function(object, callback, options) { + options || (options = {}); + + var result = object; + object = Object(object); + + var ctor, + key, + keys, + skipCtor, + done = !result, + which = options.which, + allFlag = which == 'all', + index = -1, + iteratee = object, + length = object.length, + ownFlag = allFlag || which == 'own', + seen = {}, + skipProto = isClassOf(object, 'Function'), + thisArg = options.bind; + + if (thisArg !== undefined) { + callback = bind(callback, thisArg); + } + // iterate all properties + if (allFlag && support.getAllKeys) { + for (index = 0, keys = getAllKeys(object), length = keys.length; index < length; index++) { + key = keys[index]; + if (callback(object[key], key, object) === false) { + break; + } + } + } + // else iterate only enumerable properties + else { + for (key in object) { + // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 + // (if the prototype or a property on the prototype has been set) + // incorrectly set a function's `prototype` property [[Enumerable]] value + // to `true`. Because of this we standardize on skipping the `prototype` + // property of functions regardless of their [[Enumerable]] value. + if ((done = + !(skipProto && key == 'prototype') && + !(skipSeen && (hasKey(seen, key) || !(seen[key] = true))) && + (!ownFlag || ownFlag && hasKey(object, key)) && + callback(object[key], key, object) === false)) { + break; + } + } + // in IE < 9 strings don't support accessing characters by index + if (!done && (forArgs && isArguments(object) || + ((noCharByIndex || noCharByOwnIndex) && isClassOf(object, 'String') && + (iteratee = noCharByIndex ? object.split('') : object)))) { + while (++index < length) { + if ((done = + callback(iteratee[index], String(index), object) === false)) { + break; + } + } + } + if (!done && forShadowed) { + // Because IE < 9 can't set the `[[Enumerable]]` attribute of an existing + // property and the `constructor` property of a prototype defaults to + // non-enumerable, we manually skip the `constructor` property when we + // think we are iterating over a `prototype` object. + ctor = object.constructor; + skipCtor = ctor && ctor.prototype && ctor.prototype.constructor === ctor; + for (index = 0; index < 7; index++) { + key = shadowed[index]; + if (!(skipCtor && key == 'constructor') && + hasKey(object, key) && + callback(object[key], key, object) === false) { + break; + } + } + } + } + return result; + }; + return forProps.apply(null, arguments); + } + + /** + * Gets the name of the first argument from a function's source. + * + * @private + * @param {Function} fn The function. + * @returns {String} The argument name. + */ + function getFirstArgument(fn) { + return (!hasKey(fn, 'toString') && + (/^[\s(]*function[^(]*\(([^\s,)]+)/.exec(fn) || 0)[1]) || ''; + } + + /** + * Computes the arithmetic mean of a sample. + * + * @private + * @param {Array} sample The sample. + * @returns {Number} The mean. + */ + function getMean(sample) { + return reduce(sample, function(sum, x) { + return sum + x; + }) / sample.length || 0; + } + + /** + * Gets the source code of a function. + * + * @private + * @param {Function} fn The function. + * @param {String} altSource A string used when a function's source code is unretrievable. + * @returns {String} The function's source code. + */ + function getSource(fn, altSource) { + var result = altSource; + if (isStringable(fn)) { + result = String(fn); + } else if (support.decompilation) { + // escape the `{` for Firefox 1 + result = (/^[^{]+\{([\s\S]*)}\s*$/.exec(fn) || 0)[1]; + } + // trim string + result = (result || '').replace(/^\s+|\s+$/g, ''); + + // detect strings containing only the "use strict" directive + return /^(?:\/\*+[\w|\W]*?\*\/|\/\/.*?[\n\r\u2028\u2029]|\s)*(["'])use strict\1;?$/.test(result) + ? '' + : result; + } + + /** + * Checks if a value is an `arguments` object. + * + * @private + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the value is an `arguments` object, else `false`. + */ + function isArguments() { + // lazy define + isArguments = function(value) { + return toString.call(value) == '[object Arguments]'; + }; + if (noArgumentsClass) { + isArguments = function(value) { + return hasKey(value, 'callee') && + !(propertyIsEnumerable && propertyIsEnumerable.call(value, 'callee')); + }; + } + return isArguments(arguments[0]); + } + + /** + * Checks if an object is of the specified class. + * + * @private + * @param {Mixed} value The value to check. + * @param {String} name The name of the class. + * @returns {Boolean} Returns `true` if the value is of the specified class, else `false`. + */ + function isClassOf(value, name) { + return value != null && toString.call(value) == '[object ' + name + ']'; + } + + /** + * Host objects can return type values that are different from their actual + * data type. The objects we are concerned with usually return non-primitive + * types of object, function, or unknown. + * + * @private + * @param {Mixed} object The owner of the property. + * @param {String} property The property to check. + * @returns {Boolean} Returns `true` if the property value is a non-primitive, else `false`. + */ + function isHostType(object, property) { + var type = object != null ? typeof object[property] : 'number'; + return !/^(?:boolean|number|string|undefined)$/.test(type) && + (type == 'object' ? !!object[property] : true); + } + + /** + * Checks if a given `value` is an object created by the `Object` constructor + * assuming objects created by the `Object` constructor have no inherited + * enumerable properties and that there are no `Object.prototype` extensions. + * + * @private + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the `value` is a plain `Object` object, else `false`. + */ + function isPlainObject(value) { + // avoid non-objects and false positives for `arguments` objects in IE < 9 + var result = false; + if (!(value && typeof value == 'object') || isArguments(value)) { + return result; + } + // IE < 9 presents DOM nodes as `Object` objects except they have `toString` + // methods that are `typeof` "string" and still can coerce nodes to strings. + // Also check that the constructor is `Object` (i.e. `Object instanceof Object`) + var ctor = value.constructor; + if ((support.nodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) && + (!isClassOf(ctor, 'Function') || ctor instanceof ctor)) { + // In most environments an object's own properties are iterated before + // its inherited properties. If the last iterated property is an object's + // own property then there are no inherited enumerable properties. + if (support.iteratesOwnFirst) { + forProps(value, function(subValue, subKey) { + result = subKey; + }); + return result === false || hasKey(value, result); + } + // IE < 9 iterates inherited properties before own properties. If the first + // iterated property is an object's own property then there are no inherited + // enumerable properties. + forProps(value, function(subValue, subKey) { + result = !hasKey(value, subKey); + return false; + }); + return result === false; + } + return result; + } + + /** + * Checks if a value can be safely coerced to a string. + * + * @private + * @param {Mixed} value The value to check. + * @returns {Boolean} Returns `true` if the value can be coerced, else `false`. + */ + function isStringable(value) { + return hasKey(value, 'toString') || isClassOf(value, 'String'); + } + + /** + * Wraps a function and passes `this` to the original function as the + * first argument. + * + * @private + * @param {Function} fn The function to be wrapped. + * @returns {Function} The new function. + */ + function methodize(fn) { + return function() { + var args = [this]; + args.push.apply(args, arguments); + return fn.apply(null, args); + }; + } + + /** + * A no-operation function. + * + * @private + */ + function noop() { + // no operation performed + } + + /** + * A wrapper around require() to suppress `module missing` errors. + * + * @private + * @param {String} id The module id. + * @returns {Mixed} The exported module or `null`. + */ + function req(id) { + try { + var result = freeExports && freeRequire(id); + } catch(e) { } + return result || null; + } + + /** + * Runs a snippet of JavaScript via script injection. + * + * @private + * @param {String} code The code to run. + */ + function runScript(code) { + var anchor = freeDefine ? define.amd : Benchmark, + script = doc.createElement('script'), + sibling = doc.getElementsByTagName('script')[0], + parent = sibling.parentNode, + prop = uid + 'runScript', + prefix = '(' + (freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '||function(){})();'; + + // Firefox 2.0.0.2 cannot use script injection as intended because it executes + // asynchronously, but that's OK because script injection is only used to avoid + // the previously commented JaegerMonkey bug. + try { + // remove the inserted script *before* running the code to avoid differences + // in the expected script element count/order of the document. + script.appendChild(doc.createTextNode(prefix + code)); + anchor[prop] = function() { destroyElement(script); }; + } catch(e) { + parent = parent.cloneNode(false); + sibling = null; + script.text = code; + } + parent.insertBefore(script, sibling); + delete anchor[prop]; + } + + /** + * A helper function for setting options/event handlers. + * + * @private + * @param {Object} bench The benchmark instance. + * @param {Object} [options={}] Options object. + */ + function setOptions(bench, options) { + options = extend({}, bench.constructor.options, options); + bench.options = forOwn(options, function(value, key) { + if (value != null) { + // add event listeners + if (/^on[A-Z]/.test(key)) { + forEach(key.split(' '), function(key) { + bench.on(key.slice(2).toLowerCase(), value); + }); + } else if (!hasKey(bench, key)) { + bench[key] = deepClone(value); + } + } + }); + } + + /*--------------------------------------------------------------------------*/ + + /** + * Handles cycling/completing the deferred benchmark. + * + * @memberOf Benchmark.Deferred + */ + function resolve() { + var me = this, + clone = me.benchmark, + bench = clone._original; + + if (bench.aborted) { + // cycle() -> clone cycle/complete event -> compute()'s invoked bench.run() cycle/complete + me.teardown(); + clone.running = false; + cycle(me); + } + else if (++me.cycles < clone.count) { + // continue the test loop + if (support.timeout) { + // use setTimeout to avoid a call stack overflow if called recursively + setTimeout(function() { clone.compiled.call(me, timer); }, 0); + } else { + clone.compiled.call(me, timer); + } + } + else { + timer.stop(me); + me.teardown(); + delay(clone, function() { cycle(me); }); + } + } + + /*--------------------------------------------------------------------------*/ + + /** + * A deep clone utility. + * + * @static + * @memberOf Benchmark + * @param {Mixed} value The value to clone. + * @returns {Mixed} The cloned value. + */ + function deepClone(value) { + var accessor, + circular, + clone, + ctor, + descriptor, + extensible, + key, + length, + markerKey, + parent, + result, + source, + subIndex, + data = { 'value': value }, + index = 0, + marked = [], + queue = { 'length': 0 }, + unmarked = []; + + /** + * An easily detectable decorator for cloned values. + */ + function Marker(object) { + this.raw = object; + } + + /** + * The callback used by `forProps()`. + */ + function forPropsCallback(subValue, subKey) { + // exit early to avoid cloning the marker + if (subValue && subValue.constructor == Marker) { + return; + } + // add objects to the queue + if (subValue === Object(subValue)) { + queue[queue.length++] = { 'key': subKey, 'parent': clone, 'source': value }; + } + // assign non-objects + else { + try { + // will throw an error in strict mode if the property is read-only + clone[subKey] = subValue; + } catch(e) { } + } + } + + /** + * Gets an available marker key for the given object. + */ + function getMarkerKey(object) { + // avoid collisions with existing keys + var result = uid; + while (object[result] && object[result].constructor != Marker) { + result += 1; + } + return result; + } + + do { + key = data.key; + parent = data.parent; + source = data.source; + clone = value = source ? source[key] : data.value; + accessor = circular = descriptor = false; + + // create a basic clone to filter out functions, DOM elements, and + // other non `Object` objects + if (value === Object(value)) { + // use custom deep clone function if available + if (isClassOf(value.deepClone, 'Function')) { + clone = value.deepClone(); + } else { + ctor = value.constructor; + switch (toString.call(value)) { + case '[object Array]': + clone = new ctor(value.length); + break; + + case '[object Boolean]': + clone = new ctor(value == true); + break; + + case '[object Date]': + clone = new ctor(+value); + break; + + case '[object Object]': + isPlainObject(value) && (clone = {}); + break; + + case '[object Number]': + case '[object String]': + clone = new ctor(value); + break; + + case '[object RegExp]': + clone = ctor(value.source, + (value.global ? 'g' : '') + + (value.ignoreCase ? 'i' : '') + + (value.multiline ? 'm' : '')); + } + } + // continue clone if `value` doesn't have an accessor descriptor + // http://es5.github.com/#x8.10.1 + if (clone && clone != value && + !(descriptor = source && support.descriptors && getDescriptor(source, key), + accessor = descriptor && (descriptor.get || descriptor.set))) { + // use an existing clone (circular reference) + if ((extensible = isExtensible(value))) { + markerKey = getMarkerKey(value); + if (value[markerKey]) { + circular = clone = value[markerKey].raw; + } + } else { + // for frozen/sealed objects + for (subIndex = 0, length = unmarked.length; subIndex < length; subIndex++) { + data = unmarked[subIndex]; + if (data.object === value) { + circular = clone = data.clone; + break; + } + } + } + if (!circular) { + // mark object to allow quickly detecting circular references and tie it to its clone + if (extensible) { + value[markerKey] = new Marker(clone); + marked.push({ 'key': markerKey, 'object': value }); + } else { + // for frozen/sealed objects + unmarked.push({ 'clone': clone, 'object': value }); + } + // iterate over object properties + forProps(value, forPropsCallback, { 'which': 'all' }); + } + } + } + if (parent) { + // for custom property descriptors + if (accessor || (descriptor && !(descriptor.configurable && descriptor.enumerable && descriptor.writable))) { + if ('value' in descriptor) { + descriptor.value = clone; + } + setDescriptor(parent, key, descriptor); + } + // for default property descriptors + else { + parent[key] = clone; + } + } else { + result = clone; + } + } while ((data = queue[index++])); + + // remove markers + for (index = 0, length = marked.length; index < length; index++) { + data = marked[index]; + delete data.object[data.key]; + } + return result; + } + + /** + * An iteration utility for arrays and objects. + * Callbacks may terminate the loop by explicitly returning `false`. + * + * @static + * @memberOf Benchmark + * @param {Array|Object} object The object to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} thisArg The `this` binding for the callback. + * @returns {Array|Object} Returns the object iterated over. + */ + function each(object, callback, thisArg) { + var result = object; + object = Object(object); + + var fn = callback, + index = -1, + length = object.length, + isSnapshot = !!(object.snapshotItem && (length = object.snapshotLength)), + isSplittable = (noCharByIndex || noCharByOwnIndex) && isClassOf(object, 'String'), + isConvertable = isSnapshot || isSplittable || 'item' in object, + origObject = object; + + // in Opera < 10.5 `hasKey(object, 'length')` returns `false` for NodeLists + if (length === length >>> 0) { + if (isConvertable) { + // the third argument of the callback is the original non-array object + callback = function(value, index) { + return fn.call(this, value, index, origObject); + }; + // in IE < 9 strings don't support accessing characters by index + if (isSplittable) { + object = object.split(''); + } else { + object = []; + while (++index < length) { + // in Safari 2 `index in object` is always `false` for NodeLists + object[index] = isSnapshot ? result.snapshotItem(index) : result[index]; + } + } + } + forEach(object, callback, thisArg); + } else { + forOwn(object, callback, thisArg); + } + return result; + } + + /** + * Copies enumerable properties from the source(s) object to the destination object. + * + * @static + * @memberOf Benchmark + * @param {Object} destination The destination object. + * @param {Object} [source={}] The source object. + * @returns {Object} The destination object. + */ + function extend(destination, source) { + // Chrome < 14 incorrectly sets `destination` to `undefined` when we `delete arguments[0]` + // http://code.google.com/p/v8/issues/detail?id=839 + var result = destination; + delete arguments[0]; + + forEach(arguments, function(source) { + forProps(source, function(value, key) { + result[key] = value; + }); + }); + return result; + } + + /** + * A generic `Array#filter` like method. + * + * @static + * @memberOf Benchmark + * @param {Array} array The array to iterate over. + * @param {Function|String} callback The function/alias called per iteration. + * @param {Mixed} thisArg The `this` binding for the callback. + * @returns {Array} A new array of values that passed callback filter. + * @example + * + * // get odd numbers + * Benchmark.filter([1, 2, 3, 4, 5], function(n) { + * return n % 2; + * }); // -> [1, 3, 5]; + * + * // get fastest benchmarks + * Benchmark.filter(benches, 'fastest'); + * + * // get slowest benchmarks + * Benchmark.filter(benches, 'slowest'); + * + * // get benchmarks that completed without erroring + * Benchmark.filter(benches, 'successful'); + */ + function filter(array, callback, thisArg) { + var result; + + if (callback == 'successful') { + // callback to exclude those that are errored, unrun, or have hz of Infinity + callback = function(bench) { return bench.cycles && isFinite(bench.hz); }; + } + else if (callback == 'fastest' || callback == 'slowest') { + // get successful, sort by period + margin of error, and filter fastest/slowest + result = filter(array, 'successful').sort(function(a, b) { + a = a.stats; b = b.stats; + return (a.mean + a.moe > b.mean + b.moe ? 1 : -1) * (callback == 'fastest' ? 1 : -1); + }); + result = filter(result, function(bench) { + return result[0].compare(bench) == 0; + }); + } + return result || reduce(array, function(result, value, index) { + return callback.call(thisArg, value, index, array) ? (result.push(value), result) : result; + }, []); + } + + /** + * A generic `Array#forEach` like method. + * Callbacks may terminate the loop by explicitly returning `false`. + * + * @static + * @memberOf Benchmark + * @param {Array} array The array to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} thisArg The `this` binding for the callback. + * @returns {Array} Returns the array iterated over. + */ + function forEach(array, callback, thisArg) { + var index = -1, + length = (array = Object(array)).length >>> 0; + + if (thisArg !== undefined) { + callback = bind(callback, thisArg); + } + while (++index < length) { + if (index in array && + callback(array[index], index, array) === false) { + break; + } + } + return array; + } + + /** + * Iterates over an object's own properties, executing the `callback` for each. + * Callbacks may terminate the loop by explicitly returning `false`. + * + * @static + * @memberOf Benchmark + * @param {Object} object The object to iterate over. + * @param {Function} callback The function executed per own property. + * @param {Mixed} thisArg The `this` binding for the callback. + * @returns {Object} Returns the object iterated over. + */ + function forOwn(object, callback, thisArg) { + return forProps(object, callback, { 'bind': thisArg, 'which': 'own' }); + } + + /** + * Converts a number to a more readable comma-separated string representation. + * + * @static + * @memberOf Benchmark + * @param {Number} number The number to convert. + * @returns {String} The more readable string representation. + */ + function formatNumber(number) { + number = String(number).split('.'); + return number[0].replace(/(?=(?:\d{3})+$)(?!\b)/g, ',') + + (number[1] ? '.' + number[1] : ''); + } + + /** + * Checks if an object has the specified key as a direct property. + * + * @static + * @memberOf Benchmark + * @param {Object} object The object to check. + * @param {String} key The key to check for. + * @returns {Boolean} Returns `true` if key is a direct property, else `false`. + */ + function hasKey() { + // lazy define for worst case fallback (not as accurate) + hasKey = function(object, key) { + var parent = object != null && (object.constructor || Object).prototype; + return !!parent && key in Object(object) && !(key in parent && object[key] === parent[key]); + }; + // for modern browsers + if (isClassOf(hasOwnProperty, 'Function')) { + hasKey = function(object, key) { + return object != null && hasOwnProperty.call(object, key); + }; + } + // for Safari 2 + else if ({}.__proto__ == Object.prototype) { + hasKey = function(object, key) { + var result = false; + if (object != null) { + object = Object(object); + object.__proto__ = [object.__proto__, object.__proto__ = null, result = key in object][0]; + } + return result; + }; + } + return hasKey.apply(this, arguments); + } + + /** + * A generic `Array#indexOf` like method. + * + * @static + * @memberOf Benchmark + * @param {Array} array The array to iterate over. + * @param {Mixed} value The value to search for. + * @param {Number} [fromIndex=0] The index to start searching from. + * @returns {Number} The index of the matched value or `-1`. + */ + function indexOf(array, value, fromIndex) { + var index = toInteger(fromIndex), + length = (array = Object(array)).length >>> 0; + + index = (index < 0 ? max(0, length + index) : index) - 1; + while (++index < length) { + if (index in array && value === array[index]) { + return index; + } + } + return -1; + } + + /** + * Modify a string by replacing named tokens with matching object property values. + * + * @static + * @memberOf Benchmark + * @param {String} string The string to modify. + * @param {Object} object The template object. + * @returns {String} The modified string. + */ + function interpolate(string, object) { + forOwn(object, function(value, key) { + // escape regexp special characters in `key` + string = string.replace(RegExp('#\\{' + key.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1') + '\\}', 'g'), value); + }); + return string; + } + + /** + * Invokes a method on all items in an array. + * + * @static + * @memberOf Benchmark + * @param {Array} benches Array of benchmarks to iterate over. + * @param {String|Object} name The name of the method to invoke OR options object. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. + * @returns {Array} A new array of values returned from each method invoked. + * @example + * + * // invoke `reset` on all benchmarks + * Benchmark.invoke(benches, 'reset'); + * + * // invoke `emit` with arguments + * Benchmark.invoke(benches, 'emit', 'complete', listener); + * + * // invoke `run(true)`, treat benchmarks as a queue, and register invoke callbacks + * Benchmark.invoke(benches, { + * + * // invoke the `run` method + * 'name': 'run', + * + * // pass a single argument + * 'args': true, + * + * // treat as queue, removing benchmarks from front of `benches` until empty + * 'queued': true, + * + * // called before any benchmarks have been invoked. + * 'onStart': onStart, + * + * // called between invoking benchmarks + * 'onCycle': onCycle, + * + * // called after all benchmarks have been invoked. + * 'onComplete': onComplete + * }); + */ + function invoke(benches, name) { + var args, + bench, + queued, + index = -1, + eventProps = { 'currentTarget': benches }, + options = { 'onStart': noop, 'onCycle': noop, 'onComplete': noop }, + result = map(benches, function(bench) { return bench; }); + + /** + * Invokes the method of the current object and if synchronous, fetches the next. + */ + function execute() { + var listeners, + async = isAsync(bench); + + if (async) { + // use `getNext` as the first listener + bench.on('complete', getNext); + listeners = bench.events.complete; + listeners.splice(0, 0, listeners.pop()); + } + // execute method + result[index] = isClassOf(bench && bench[name], 'Function') ? bench[name].apply(bench, args) : undefined; + // if synchronous return true until finished + return !async && getNext(); + } + + /** + * Fetches the next bench or executes `onComplete` callback. + */ + function getNext(event) { + var cycleEvent, + last = bench, + async = isAsync(last); + + if (async) { + last.off('complete', getNext); + last.emit('complete'); + } + // emit "cycle" event + eventProps.type = 'cycle'; + eventProps.target = last; + cycleEvent = Event(eventProps); + options.onCycle.call(benches, cycleEvent); + + // choose next benchmark if not exiting early + if (!cycleEvent.aborted && raiseIndex() !== false) { + bench = queued ? benches[0] : result[index]; + if (isAsync(bench)) { + delay(bench, execute); + } + else if (async) { + // resume execution if previously asynchronous but now synchronous + while (execute()) { } + } + else { + // continue synchronous execution + return true; + } + } else { + // emit "complete" event + eventProps.type = 'complete'; + options.onComplete.call(benches, Event(eventProps)); + } + // When used as a listener `event.aborted = true` will cancel the rest of + // the "complete" listeners because they were already called above and when + // used as part of `getNext` the `return false` will exit the execution while-loop. + if (event) { + event.aborted = true; + } else { + return false; + } + } + + /** + * Checks if invoking `Benchmark#run` with asynchronous cycles. + */ + function isAsync(object) { + // avoid using `instanceof` here because of IE memory leak issues with host objects + var async = args[0] && args[0].async; + return Object(object).constructor == Benchmark && name == 'run' && + ((async == null ? object.options.async : async) && support.timeout || object.defer); + } + + /** + * Raises `index` to the next defined index or returns `false`. + */ + function raiseIndex() { + var length = result.length; + if (queued) { + // if queued remove the previous bench and subsequent skipped non-entries + do { + ++index > 0 && shift.call(benches); + } while ((length = benches.length) && !('0' in benches)); + } + else { + while (++index < length && !(index in result)) { } + } + // if we reached the last index then return `false` + return (queued ? length : index < length) ? index : (index = false); + } + + // juggle arguments + if (isClassOf(name, 'String')) { + // 2 arguments (array, name) + args = slice.call(arguments, 2); + } else { + // 2 arguments (array, options) + options = extend(options, name); + name = options.name; + args = isClassOf(args = 'args' in options ? options.args : [], 'Array') ? args : [args]; + queued = options.queued; + } + + // start iterating over the array + if (raiseIndex() !== false) { + // emit "start" event + bench = result[index]; + eventProps.type = 'start'; + eventProps.target = bench; + options.onStart.call(benches, Event(eventProps)); + + // end early if the suite was aborted in an "onStart" listener + if (benches.aborted && benches.constructor == Suite && name == 'run') { + // emit "cycle" event + eventProps.type = 'cycle'; + options.onCycle.call(benches, Event(eventProps)); + // emit "complete" event + eventProps.type = 'complete'; + options.onComplete.call(benches, Event(eventProps)); + } + // else start + else { + if (isAsync(bench)) { + delay(bench, execute); + } else { + while (execute()) { } + } + } + } + return result; + } + + /** + * Creates a string of joined array values or object key-value pairs. + * + * @static + * @memberOf Benchmark + * @param {Array|Object} object The object to operate on. + * @param {String} [separator1=','] The separator used between key-value pairs. + * @param {String} [separator2=': '] The separator used between keys and values. + * @returns {String} The joined result. + */ + function join(object, separator1, separator2) { + var result = [], + length = (object = Object(object)).length, + arrayLike = length === length >>> 0; + + separator2 || (separator2 = ': '); + each(object, function(value, key) { + result.push(arrayLike ? value : key + separator2 + value); + }); + return result.join(separator1 || ','); + } + + /** + * A generic `Array#map` like method. + * + * @static + * @memberOf Benchmark + * @param {Array} array The array to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} thisArg The `this` binding for the callback. + * @returns {Array} A new array of values returned by the callback. + */ + function map(array, callback, thisArg) { + return reduce(array, function(result, value, index) { + result[index] = callback.call(thisArg, value, index, array); + return result; + }, Array(Object(array).length >>> 0)); + } + + /** + * Retrieves the value of a specified property from all items in an array. + * + * @static + * @memberOf Benchmark + * @param {Array} array The array to iterate over. + * @param {String} property The property to pluck. + * @returns {Array} A new array of property values. + */ + function pluck(array, property) { + return map(array, function(object) { + return object == null ? undefined : object[property]; + }); + } + + /** + * A generic `Array#reduce` like method. + * + * @static + * @memberOf Benchmark + * @param {Array} array The array to iterate over. + * @param {Function} callback The function called per iteration. + * @param {Mixed} accumulator Initial value of the accumulator. + * @returns {Mixed} The accumulator. + */ + function reduce(array, callback, accumulator) { + var noaccum = arguments.length < 3; + forEach(array, function(value, index) { + accumulator = noaccum ? (noaccum = false, value) : callback(accumulator, value, index, array); + }); + return accumulator; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Aborts all benchmarks in the suite. + * + * @name abort + * @memberOf Benchmark.Suite + * @returns {Object} The suite instance. + */ + function abortSuite() { + var event, + me = this, + resetting = calledBy.resetSuite; + + if (me.running) { + event = Event('abort'); + me.emit(event); + if (!event.cancelled || resetting) { + // avoid infinite recursion + calledBy.abortSuite = true; + me.reset(); + delete calledBy.abortSuite; + + if (!resetting) { + me.aborted = true; + invoke(me, 'abort'); + } + } + } + return me; + } + + /** + * Adds a test to the benchmark suite. + * + * @memberOf Benchmark.Suite + * @param {String} name A name to identify the benchmark. + * @param {Function|String} fn The test to benchmark. + * @param {Object} [options={}] Options object. + * @returns {Object} The benchmark instance. + * @example + * + * // basic usage + * suite.add(fn); + * + * // or using a name first + * suite.add('foo', fn); + * + * // or with options + * suite.add('foo', fn, { + * 'onCycle': onCycle, + * 'onComplete': onComplete + * }); + * + * // or name and options + * suite.add('foo', { + * 'fn': fn, + * 'onCycle': onCycle, + * 'onComplete': onComplete + * }); + * + * // or options only + * suite.add({ + * 'name': 'foo', + * 'fn': fn, + * 'onCycle': onCycle, + * 'onComplete': onComplete + * }); + */ + function add(name, fn, options) { + var me = this, + bench = Benchmark(name, fn, options), + event = Event({ 'type': 'add', 'target': bench }); + + if (me.emit(event), !event.cancelled) { + me.push(bench); + } + return me; + } + + /** + * Creates a new suite with cloned benchmarks. + * + * @name clone + * @memberOf Benchmark.Suite + * @param {Object} options Options object to overwrite cloned options. + * @returns {Object} The new suite instance. + */ + function cloneSuite(options) { + var me = this, + result = new me.constructor(extend({}, me.options, options)); + + // copy own properties + forOwn(me, function(value, key) { + if (!hasKey(result, key)) { + result[key] = value && isClassOf(value.clone, 'Function') + ? value.clone() + : deepClone(value); + } + }); + return result; + } + + /** + * An `Array#filter` like method. + * + * @name filter + * @memberOf Benchmark.Suite + * @param {Function|String} callback The function/alias called per iteration. + * @returns {Object} A new suite of benchmarks that passed callback filter. + */ + function filterSuite(callback) { + var me = this, + result = new me.constructor; + + result.push.apply(result, filter(me, callback)); + return result; + } + + /** + * Resets all benchmarks in the suite. + * + * @name reset + * @memberOf Benchmark.Suite + * @returns {Object} The suite instance. + */ + function resetSuite() { + var event, + me = this, + aborting = calledBy.abortSuite; + + if (me.running && !aborting) { + // no worries, `resetSuite()` is called within `abortSuite()` + calledBy.resetSuite = true; + me.abort(); + delete calledBy.resetSuite; + } + // reset if the state has changed + else if ((me.aborted || me.running) && + (me.emit(event = Event('reset')), !event.cancelled)) { + me.running = false; + if (!aborting) { + invoke(me, 'reset'); + } + } + return me; + } + + /** + * Runs the suite. + * + * @name run + * @memberOf Benchmark.Suite + * @param {Object} [options={}] Options object. + * @returns {Object} The suite instance. + * @example + * + * // basic usage + * suite.run(); + * + * // or with options + * suite.run({ 'async': true, 'queued': true }); + */ + function runSuite(options) { + var me = this; + + me.reset(); + me.running = true; + options || (options = {}); + + invoke(me, { + 'name': 'run', + 'args': options, + 'queued': options.queued, + 'onStart': function(event) { + me.emit(event); + }, + 'onCycle': function(event) { + var bench = event.target; + if (bench.error) { + me.emit({ 'type': 'error', 'target': bench }); + } + me.emit(event); + event.aborted = me.aborted; + }, + 'onComplete': function(event) { + me.running = false; + me.emit(event); + } + }); + return me; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Executes all registered listeners of the specified event type. + * + * @memberOf Benchmark, Benchmark.Suite + * @param {String|Object} type The event type or object. + * @returns {Mixed} Returns the return value of the last listener executed. + */ + function emit(type) { + var listeners, + me = this, + event = Event(type), + events = me.events, + args = (arguments[0] = event, arguments); + + event.currentTarget || (event.currentTarget = me); + event.target || (event.target = me); + delete event.result; + + if (events && (listeners = hasKey(events, event.type) && events[event.type])) { + forEach(listeners.slice(), function(listener) { + if ((event.result = listener.apply(me, args)) === false) { + event.cancelled = true; + } + return !event.aborted; + }); + } + return event.result; + } + + /** + * Returns an array of event listeners for a given type that can be manipulated + * to add or remove listeners. + * + * @memberOf Benchmark, Benchmark.Suite + * @param {String} type The event type. + * @returns {Array} The listeners array. + */ + function listeners(type) { + var me = this, + events = me.events || (me.events = {}); + + return hasKey(events, type) ? events[type] : (events[type] = []); + } + + /** + * Unregisters a listener for the specified event type(s), + * or unregisters all listeners for the specified event type(s), + * or unregisters all listeners for all event types. + * + * @memberOf Benchmark, Benchmark.Suite + * @param {String} [type] The event type. + * @param {Function} [listener] The function to unregister. + * @returns {Object} The benchmark instance. + * @example + * + * // unregister a listener for an event type + * bench.off('cycle', listener); + * + * // unregister a listener for multiple event types + * bench.off('start cycle', listener); + * + * // unregister all listeners for an event type + * bench.off('cycle'); + * + * // unregister all listeners for multiple event types + * bench.off('start cycle complete'); + * + * // unregister all listeners for all event types + * bench.off(); + */ + function off(type, listener) { + var me = this, + events = me.events; + + events && each(type ? type.split(' ') : events, function(listeners, type) { + var index; + if (typeof listeners == 'string') { + type = listeners; + listeners = hasKey(events, type) && events[type]; + } + if (listeners) { + if (listener) { + index = indexOf(listeners, listener); + if (index > -1) { + listeners.splice(index, 1); + } + } else { + listeners.length = 0; + } + } + }); + return me; + } + + /** + * Registers a listener for the specified event type(s). + * + * @memberOf Benchmark, Benchmark.Suite + * @param {String} type The event type. + * @param {Function} listener The function to register. + * @returns {Object} The benchmark instance. + * @example + * + * // register a listener for an event type + * bench.on('cycle', listener); + * + * // register a listener for multiple event types + * bench.on('start cycle', listener); + */ + function on(type, listener) { + var me = this, + events = me.events || (me.events = {}); + + forEach(type.split(' '), function(type) { + (hasKey(events, type) + ? events[type] + : (events[type] = []) + ).push(listener); + }); + return me; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Aborts the benchmark without recording times. + * + * @memberOf Benchmark + * @returns {Object} The benchmark instance. + */ + function abort() { + var event, + me = this, + resetting = calledBy.reset; + + if (me.running) { + event = Event('abort'); + me.emit(event); + if (!event.cancelled || resetting) { + // avoid infinite recursion + calledBy.abort = true; + me.reset(); + delete calledBy.abort; + + if (support.timeout) { + clearTimeout(me._timerId); + delete me._timerId; + } + if (!resetting) { + me.aborted = true; + me.running = false; + } + } + } + return me; + } + + /** + * Creates a new benchmark using the same test and options. + * + * @memberOf Benchmark + * @param {Object} options Options object to overwrite cloned options. + * @returns {Object} The new benchmark instance. + * @example + * + * var bizarro = bench.clone({ + * 'name': 'doppelganger' + * }); + */ + function clone(options) { + var me = this, + result = new me.constructor(extend({}, me, options)); + + // correct the `options` object + result.options = extend({}, me.options, options); + + // copy own custom properties + forOwn(me, function(value, key) { + if (!hasKey(result, key)) { + result[key] = deepClone(value); + } + }); + return result; + } + + /** + * Determines if a benchmark is faster than another. + * + * @memberOf Benchmark + * @param {Object} other The benchmark to compare. + * @returns {Number} Returns `-1` if slower, `1` if faster, and `0` if indeterminate. + */ + function compare(other) { + var critical, + zStat, + me = this, + sample1 = me.stats.sample, + sample2 = other.stats.sample, + size1 = sample1.length, + size2 = sample2.length, + maxSize = max(size1, size2), + minSize = min(size1, size2), + u1 = getU(sample1, sample2), + u2 = getU(sample2, sample1), + u = min(u1, u2); + + function getScore(xA, sampleB) { + return reduce(sampleB, function(total, xB) { + return total + (xB > xA ? 0 : xB < xA ? 1 : 0.5); + }, 0); + } + + function getU(sampleA, sampleB) { + return reduce(sampleA, function(total, xA) { + return total + getScore(xA, sampleB); + }, 0); + } + + function getZ(u) { + return (u - ((size1 * size2) / 2)) / sqrt((size1 * size2 * (size1 + size2 + 1)) / 12); + } + + // exit early if comparing the same benchmark + if (me == other) { + return 0; + } + // reject the null hyphothesis the two samples come from the + // same population (i.e. have the same median) if... + if (size1 + size2 > 30) { + // ...the z-stat is greater than 1.96 or less than -1.96 + // http://www.statisticslectures.com/topics/mannwhitneyu/ + zStat = getZ(u); + return abs(zStat) > 1.96 ? (zStat > 0 ? -1 : 1) : 0; + } + // ...the U value is less than or equal the critical U value + // http://www.geoib.com/mann-whitney-u-test.html + critical = maxSize < 5 || minSize < 3 ? 0 : uTable[maxSize][minSize - 3]; + return u <= critical ? (u == u1 ? 1 : -1) : 0; + } + + /** + * Reset properties and abort if running. + * + * @memberOf Benchmark + * @returns {Object} The benchmark instance. + */ + function reset() { + var data, + event, + me = this, + index = 0, + changes = { 'length': 0 }, + queue = { 'length': 0 }; + + if (me.running && !calledBy.abort) { + // no worries, `reset()` is called within `abort()` + calledBy.reset = true; + me.abort(); + delete calledBy.reset; + } + else { + // a non-recursive solution to check if properties have changed + // http://www.jslab.dk/articles/non.recursive.preorder.traversal.part4 + data = { 'destination': me, 'source': extend({}, me.constructor.prototype, me.options) }; + do { + forOwn(data.source, function(value, key) { + var changed, + destination = data.destination, + currValue = destination[key]; + + if (value && typeof value == 'object') { + if (isClassOf(value, 'Array')) { + // check if an array value has changed to a non-array value + if (!isClassOf(currValue, 'Array')) { + changed = currValue = []; + } + // or has changed its length + if (currValue.length != value.length) { + changed = currValue = currValue.slice(0, value.length); + currValue.length = value.length; + } + } + // check if an object has changed to a non-object value + else if (!currValue || typeof currValue != 'object') { + changed = currValue = {}; + } + // register a changed object + if (changed) { + changes[changes.length++] = { 'destination': destination, 'key': key, 'value': currValue }; + } + queue[queue.length++] = { 'destination': currValue, 'source': value }; + } + // register a changed primitive + else if (value !== currValue && !(value == null || isClassOf(value, 'Function'))) { + changes[changes.length++] = { 'destination': destination, 'key': key, 'value': value }; + } + }); + } + while ((data = queue[index++])); + + // if changed emit the `reset` event and if it isn't cancelled reset the benchmark + if (changes.length && (me.emit(event = Event('reset')), !event.cancelled)) { + forEach(changes, function(data) { + data.destination[data.key] = data.value; + }); + } + } + return me; + } + + /** + * Displays relevant benchmark information when coerced to a string. + * + * @name toString + * @memberOf Benchmark + * @returns {String} A string representation of the benchmark instance. + */ + function toStringBench() { + var me = this, + error = me.error, + hz = me.hz, + id = me.id, + stats = me.stats, + size = stats.sample.length, + pm = support.java ? '+/-' : '\xb1', + result = me.name || (isNaN(id) ? id : ''); + + if (error) { + result += ': ' + join(error); + } else { + result += ' x ' + formatNumber(hz.toFixed(hz < 100 ? 2 : 0)) + ' ops/sec ' + pm + + stats.rme.toFixed(2) + '% (' + size + ' run' + (size == 1 ? '' : 's') + ' sampled)'; + } + return result; + } + + /*--------------------------------------------------------------------------*/ + + /** + * Clocks the time taken to execute a test per cycle (secs). + * + * @private + * @param {Object} bench The benchmark instance. + * @returns {Number} The time taken. + */ + function clock() { + var applet, + options = Benchmark.options, + template = { 'begin': 's$=new n$', 'end': 'r$=(new n$-s$)/1e3', 'uid': uid }, + timers = [{ 'ns': timer.ns, 'res': max(0.0015, getRes('ms')), 'unit': 'ms' }]; + + // lazy define for hi-res timers + clock = function(clone) { + var deferred; + if (clone instanceof Deferred) { + deferred = clone; + clone = deferred.benchmark; + } + + var bench = clone._original, + fn = bench.fn, + fnArg = deferred ? getFirstArgument(fn) || 'deferred' : '', + stringable = isStringable(fn); + + var source = { + 'setup': getSource(bench.setup, preprocess('m$.setup()')), + 'fn': getSource(fn, preprocess('m$.fn(' + fnArg + ')')), + 'fnArg': fnArg, + 'teardown': getSource(bench.teardown, preprocess('m$.teardown()')) + }; + + var count = bench.count = clone.count, + decompilable = support.decompilation || stringable, + id = bench.id, + isEmpty = !(source.fn || stringable), + name = bench.name || (typeof id == 'number' ? '' : id), + ns = timer.ns, + result = 0; + + // init `minTime` if needed + clone.minTime = bench.minTime || (bench.minTime = bench.options.minTime = options.minTime); + + // repair nanosecond timer + // (some Chrome builds erase the `ns` variable after millions of executions) + if (applet) { + try { + ns.nanoTime(); + } catch(e) { + // use non-element to avoid issues with libs that augment them + ns = timer.ns = new applet.Packages.nano; + } + } + + // Compile in setup/teardown functions and the test loop. + // Create a new compiled test, instead of using the cached `bench.compiled`, + // to avoid potential engine optimizations enabled over the life of the test. + var compiled = bench.compiled = createFunction(preprocess('t$'), interpolate( + preprocess(deferred + ? 'var d$=this,#{fnArg}=d$,m$=d$.benchmark._original,f$=m$.fn,su$=m$.setup,td$=m$.teardown;' + + // when `deferred.cycles` is `0` then... + 'if(!d$.cycles){' + + // set `deferred.fn` + 'd$.fn=function(){var #{fnArg}=d$;if(typeof f$=="function"){try{#{fn}\n}catch(e$){f$(d$)}}else{#{fn}\n}};' + + // set `deferred.teardown` + 'd$.teardown=function(){d$.cycles=0;if(typeof td$=="function"){try{#{teardown}\n}catch(e$){td$()}}else{#{teardown}\n}};' + + // execute the benchmark's `setup` + 'if(typeof su$=="function"){try{#{setup}\n}catch(e$){su$()}}else{#{setup}\n};' + + // start timer + 't$.start(d$);' + + // execute `deferred.fn` and return a dummy object + '}d$.fn();return{}' + + : 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count,n$=t$.ns;#{setup}\n#{begin};' + + 'while(i$--){#{fn}\n}#{end};#{teardown}\nreturn{elapsed:r$,uid:"#{uid}"}'), + source + )); + + try { + if (isEmpty) { + // Firefox may remove dead code from Function#toString results + // http://bugzil.la/536085 + throw new Error('The test "' + name + '" is empty. This may be the result of dead code removal.'); + } + else if (!deferred) { + // pretest to determine if compiled code is exits early, usually by a + // rogue `return` statement, by checking for a return object with the uid + bench.count = 1; + compiled = (compiled.call(bench, timer) || {}).uid == uid && compiled; + bench.count = count; + } + } catch(e) { + compiled = null; + clone.error = e || new Error(String(e)); + bench.count = count; + } + // fallback when a test exits early or errors during pretest + if (decompilable && !compiled && !deferred && !isEmpty) { + compiled = createFunction(preprocess('t$'), interpolate( + preprocess( + (clone.error && !stringable + ? 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count' + : 'function f$(){#{fn}\n}var r$,s$,m$=this,i$=m$.count' + ) + + ',n$=t$.ns;#{setup}\n#{begin};m$.f$=f$;while(i$--){m$.f$()}#{end};' + + 'delete m$.f$;#{teardown}\nreturn{elapsed:r$}' + ), + source + )); + + try { + // pretest one more time to check for errors + bench.count = 1; + compiled.call(bench, timer); + bench.compiled = compiled; + bench.count = count; + delete clone.error; + } + catch(e) { + bench.count = count; + if (clone.error) { + compiled = null; + } else { + bench.compiled = compiled; + clone.error = e || new Error(String(e)); + } + } + } + // assign `compiled` to `clone` before calling in case a deferred benchmark + // immediately calls `deferred.resolve()` + clone.compiled = compiled; + // if no errors run the full test loop + if (!clone.error) { + result = compiled.call(deferred || bench, timer).elapsed; + } + return result; + }; + + /*------------------------------------------------------------------------*/ + + /** + * Gets the current timer's minimum resolution (secs). + */ + function getRes(unit) { + var measured, + begin, + count = 30, + divisor = 1e3, + ns = timer.ns, + sample = []; + + // get average smallest measurable time + while (count--) { + if (unit == 'us') { + divisor = 1e6; + if (ns.stop) { + ns.start(); + while (!(measured = ns.microseconds())) { } + } else if (ns[perfName]) { + divisor = 1e3; + measured = Function('n', 'var r,s=n.' + perfName + '();while(!(r=n.' + perfName + '()-s)){};return r')(ns); + } else { + begin = ns(); + while (!(measured = ns() - begin)) { } + } + } + else if (unit == 'ns') { + divisor = 1e9; + if (ns.nanoTime) { + begin = ns.nanoTime(); + while (!(measured = ns.nanoTime() - begin)) { } + } else { + begin = (begin = ns())[0] + (begin[1] / divisor); + while (!(measured = ((measured = ns())[0] + (measured[1] / divisor)) - begin)) { } + divisor = 1; + } + } + else { + begin = new ns; + while (!(measured = new ns - begin)) { } + } + // check for broken timers (nanoTime may have issues) + // http://alivebutsleepy.srnet.cz/unreliable-system-nanotime/ + if (measured > 0) { + sample.push(measured); + } else { + sample.push(Infinity); + break; + } + } + // convert to seconds + return getMean(sample) / divisor; + } + + /** + * Replaces all occurrences of `$` with a unique number and + * template tokens with content. + */ + function preprocess(code) { + return interpolate(code, template).replace(/\$/g, /\d+/.exec(uid)); + } + + /*------------------------------------------------------------------------*/ + + // detect nanosecond support from a Java applet + each(doc && doc.applets || [], function(element) { + return !(timer.ns = applet = 'nanoTime' in element && element); + }); + + // check type in case Safari returns an object instead of a number + try { + if (typeof timer.ns.nanoTime() == 'number') { + timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' }); + } + } catch(e) { } + + // detect Chrome's microsecond timer: + // enable benchmarking via the --enable-benchmarking command + // line switch in at least Chrome 7 to use chrome.Interval + try { + if ((timer.ns = new (window.chrome || window.chromium).Interval)) { + timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' }); + } + } catch(e) { } + + // detect `performance.now` microsecond resolution timer + if ((timer.ns = perfName && perfObject)) { + timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' }); + } + + // detect Node's nanosecond resolution timer available in Node >= 0.8 + if (processObject && typeof (timer.ns = processObject.hrtime) == 'function') { + timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' }); + } + + // detect Wade Simmons' Node microtime module + if (microtimeObject && typeof (timer.ns = microtimeObject.now) == 'function') { + timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' }); + } + + // pick timer with highest resolution + timer = reduce(timers, function(timer, other) { + return other.res < timer.res ? other : timer; + }); + + // remove unused applet + if (timer.unit != 'ns' && applet) { + applet = destroyElement(applet); + } + // error if there are no working timers + if (timer.res == Infinity) { + throw new Error('Benchmark.js was unable to find a working timer.'); + } + // use API of chosen timer + if (timer.unit == 'ns') { + if (timer.ns.nanoTime) { + extend(template, { + 'begin': 's$=n$.nanoTime()', + 'end': 'r$=(n$.nanoTime()-s$)/1e9' + }); + } else { + extend(template, { + 'begin': 's$=n$()', + 'end': 'r$=n$(s$);r$=r$[0]+(r$[1]/1e9)' + }); + } + } + else if (timer.unit == 'us') { + if (timer.ns.stop) { + extend(template, { + 'begin': 's$=n$.start()', + 'end': 'r$=n$.microseconds()/1e6' + }); + } else if (perfName) { + extend(template, { + 'begin': 's$=n$.' + perfName + '()', + 'end': 'r$=(n$.' + perfName + '()-s$)/1e3' + }); + } else { + extend(template, { + 'begin': 's$=n$()', + 'end': 'r$=(n$()-s$)/1e6' + }); + } + } + + // define `timer` methods + timer.start = createFunction(preprocess('o$'), + preprocess('var n$=this.ns,#{begin};o$.elapsed=0;o$.timeStamp=s$')); + + timer.stop = createFunction(preprocess('o$'), + preprocess('var n$=this.ns,s$=o$.timeStamp,#{end};o$.elapsed=r$')); + + // resolve time span required to achieve a percent uncertainty of at most 1% + // http://spiff.rit.edu/classes/phys273/uncert/uncert.html + options.minTime || (options.minTime = max(timer.res / 2 / 0.01, 0.05)); + return clock.apply(null, arguments); + } + + /*--------------------------------------------------------------------------*/ + + /** + * Computes stats on benchmark results. + * + * @private + * @param {Object} bench The benchmark instance. + * @param {Object} options The options object. + */ + function compute(bench, options) { + options || (options = {}); + + var async = options.async, + elapsed = 0, + initCount = bench.initCount, + minSamples = bench.minSamples, + queue = [], + sample = bench.stats.sample; + + /** + * Adds a clone to the queue. + */ + function enqueue() { + queue.push(bench.clone({ + '_original': bench, + 'events': { + 'abort': [update], + 'cycle': [update], + 'error': [update], + 'start': [update] + } + })); + } + + /** + * Updates the clone/original benchmarks to keep their data in sync. + */ + function update(event) { + var clone = this, + type = event.type; + + if (bench.running) { + if (type == 'start') { + // Note: `clone.minTime` prop is inited in `clock()` + clone.count = bench.initCount; + } + else { + if (type == 'error') { + bench.error = clone.error; + } + if (type == 'abort') { + bench.abort(); + bench.emit('cycle'); + } else { + event.currentTarget = event.target = bench; + bench.emit(event); + } + } + } else if (bench.aborted) { + // clear abort listeners to avoid triggering bench's abort/cycle again + clone.events.abort.length = 0; + clone.abort(); + } + } + + /** + * Determines if more clones should be queued or if cycling should stop. + */ + function evaluate(event) { + var critical, + df, + mean, + moe, + rme, + sd, + sem, + variance, + clone = event.target, + done = bench.aborted, + now = +new Date, + size = sample.push(clone.times.period), + maxedOut = size >= minSamples && (elapsed += now - clone.times.timeStamp) / 1e3 > bench.maxTime, + times = bench.times, + varOf = function(sum, x) { return sum + pow(x - mean, 2); }; + + // exit early for aborted or unclockable tests + if (done || clone.hz == Infinity) { + maxedOut = !(size = sample.length = queue.length = 0); + } + + if (!done) { + // sample mean (estimate of the population mean) + mean = getMean(sample); + // sample variance (estimate of the population variance) + variance = reduce(sample, varOf, 0) / (size - 1) || 0; + // sample standard deviation (estimate of the population standard deviation) + sd = sqrt(variance); + // standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean) + sem = sd / sqrt(size); + // degrees of freedom + df = size - 1; + // critical value + critical = tTable[Math.round(df) || 1] || tTable.infinity; + // margin of error + moe = sem * critical; + // relative margin of error + rme = (moe / mean) * 100 || 0; + + extend(bench.stats, { + 'deviation': sd, + 'mean': mean, + 'moe': moe, + 'rme': rme, + 'sem': sem, + 'variance': variance + }); + + // Abort the cycle loop when the minimum sample size has been collected + // and the elapsed time exceeds the maximum time allowed per benchmark. + // We don't count cycle delays toward the max time because delays may be + // increased by browsers that clamp timeouts for inactive tabs. + // https://developer.mozilla.org/en/window.setTimeout#Inactive_tabs + if (maxedOut) { + // reset the `initCount` in case the benchmark is rerun + bench.initCount = initCount; + bench.running = false; + done = true; + times.elapsed = (now - times.timeStamp) / 1e3; + } + if (bench.hz != Infinity) { + bench.hz = 1 / mean; + times.cycle = mean * bench.count; + times.period = mean; + } + } + // if time permits, increase sample size to reduce the margin of error + if (queue.length < 2 && !maxedOut) { + enqueue(); + } + // abort the invoke cycle when done + event.aborted = done; + } + + // init queue and begin + enqueue(); + invoke(queue, { + 'name': 'run', + 'args': { 'async': async }, + 'queued': true, + 'onCycle': evaluate, + 'onComplete': function() { bench.emit('complete'); } + }); + } + + /*--------------------------------------------------------------------------*/ + + /** + * Cycles a benchmark until a run `count` can be established. + * + * @private + * @param {Object} clone The cloned benchmark instance. + * @param {Object} options The options object. + */ + function cycle(clone, options) { + options || (options = {}); + + var deferred; + if (clone instanceof Deferred) { + deferred = clone; + clone = clone.benchmark; + } + + var clocked, + cycles, + divisor, + event, + minTime, + period, + async = options.async, + bench = clone._original, + count = clone.count, + times = clone.times; + + // continue, if not aborted between cycles + if (clone.running) { + // `minTime` is set to `Benchmark.options.minTime` in `clock()` + cycles = ++clone.cycles; + clocked = deferred ? deferred.elapsed : clock(clone); + minTime = clone.minTime; + + if (cycles > bench.cycles) { + bench.cycles = cycles; + } + if (clone.error) { + event = Event('error'); + event.message = clone.error; + clone.emit(event); + if (!event.cancelled) { + clone.abort(); + } + } + } + + // continue, if not errored + if (clone.running) { + // time taken to complete last test cycle + bench.times.cycle = times.cycle = clocked; + // seconds per operation + period = bench.times.period = times.period = clocked / count; + // ops per second + bench.hz = clone.hz = 1 / period; + // avoid working our way up to this next time + bench.initCount = clone.initCount = count; + // do we need to do another cycle? + clone.running = clocked < minTime; + + if (clone.running) { + // tests may clock at `0` when `initCount` is a small number, + // to avoid that we set its count to something a bit higher + if (!clocked && (divisor = divisors[clone.cycles]) != null) { + count = floor(4e6 / divisor); + } + // calculate how many more iterations it will take to achive the `minTime` + if (count <= clone.count) { + count += Math.ceil((minTime - clocked) / period); + } + clone.running = count != Infinity; + } + } + // should we exit early? + event = Event('cycle'); + clone.emit(event); + if (event.aborted) { + clone.abort(); + } + // figure out what to do next + if (clone.running) { + // start a new cycle + clone.count = count; + if (deferred) { + clone.compiled.call(deferred, timer); + } else if (async) { + delay(clone, function() { cycle(clone, options); }); + } else { + cycle(clone); + } + } + else { + // fix TraceMonkey bug associated with clock fallbacks + // http://bugzil.la/509069 + if (support.browser) { + runScript(uid + '=1;delete ' + uid); + } + // done + clone.emit('complete'); + } + } + + /*--------------------------------------------------------------------------*/ + + /** + * Runs the benchmark. + * + * @memberOf Benchmark + * @param {Object} [options={}] Options object. + * @returns {Object} The benchmark instance. + * @example + * + * // basic usage + * bench.run(); + * + * // or with options + * bench.run({ 'async': true }); + */ + function run(options) { + var me = this, + event = Event('start'); + + // set `running` to `false` so `reset()` won't call `abort()` + me.running = false; + me.reset(); + me.running = true; + + me.count = me.initCount; + me.times.timeStamp = +new Date; + me.emit(event); + + if (!event.cancelled) { + options = { 'async': ((options = options && options.async) == null ? me.async : options) && support.timeout }; + + // for clones created within `compute()` + if (me._original) { + if (me.defer) { + Deferred(me); + } else { + cycle(me, options); + } + } + // for original benchmarks + else { + compute(me, options); + } + } + return me; + } + + /*--------------------------------------------------------------------------*/ + + // Firefox 1 erroneously defines variable and argument names of functions on + // the function itself as non-configurable properties with `undefined` values. + // The bugginess continues as the `Benchmark` constructor has an argument + // named `options` and Firefox 1 will not assign a value to `Benchmark.options`, + // making it non-writable in the process, unless it is the first property + // assigned by for-in loop of `extend()`. + extend(Benchmark, { + + /** + * The default options copied by benchmark instances. + * + * @static + * @memberOf Benchmark + * @type Object + */ + 'options': { + + /** + * A flag to indicate that benchmark cycles will execute asynchronously + * by default. + * + * @memberOf Benchmark.options + * @type Boolean + */ + 'async': false, + + /** + * A flag to indicate that the benchmark clock is deferred. + * + * @memberOf Benchmark.options + * @type Boolean + */ + 'defer': false, + + /** + * The delay between test cycles (secs). + * @memberOf Benchmark.options + * @type Number + */ + 'delay': 0.005, + + /** + * Displayed by Benchmark#toString when a `name` is not available + * (auto-generated if absent). + * + * @memberOf Benchmark.options + * @type String + */ + 'id': undefined, + + /** + * The default number of times to execute a test on a benchmark's first cycle. + * + * @memberOf Benchmark.options + * @type Number + */ + 'initCount': 1, + + /** + * The maximum time a benchmark is allowed to run before finishing (secs). + * + * Note: Cycle delays aren't counted toward the maximum time. + * + * @memberOf Benchmark.options + * @type Number + */ + 'maxTime': 5, + + /** + * The minimum sample size required to perform statistical analysis. + * + * @memberOf Benchmark.options + * @type Number + */ + 'minSamples': 5, + + /** + * The time needed to reduce the percent uncertainty of measurement to 1% (secs). + * + * @memberOf Benchmark.options + * @type Number + */ + 'minTime': 0, + + /** + * The name of the benchmark. + * + * @memberOf Benchmark.options + * @type String + */ + 'name': undefined, + + /** + * An event listener called when the benchmark is aborted. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onAbort': undefined, + + /** + * An event listener called when the benchmark completes running. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onComplete': undefined, + + /** + * An event listener called after each run cycle. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onCycle': undefined, + + /** + * An event listener called when a test errors. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onError': undefined, + + /** + * An event listener called when the benchmark is reset. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onReset': undefined, + + /** + * An event listener called when the benchmark starts running. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onStart': undefined + }, + + /** + * Platform object with properties describing things like browser name, + * version, and operating system. + * + * @static + * @memberOf Benchmark + * @type Object + */ + 'platform': req('platform') || window.platform || { + + /** + * The platform description. + * + * @memberOf Benchmark.platform + * @type String + */ + 'description': window.navigator && navigator.userAgent || null, + + /** + * The name of the browser layout engine. + * + * @memberOf Benchmark.platform + * @type String|Null + */ + 'layout': null, + + /** + * The name of the product hosting the browser. + * + * @memberOf Benchmark.platform + * @type String|Null + */ + 'product': null, + + /** + * The name of the browser/environment. + * + * @memberOf Benchmark.platform + * @type String|Null + */ + 'name': null, + + /** + * The name of the product's manufacturer. + * + * @memberOf Benchmark.platform + * @type String|Null + */ + 'manufacturer': null, + + /** + * The name of the operating system. + * + * @memberOf Benchmark.platform + * @type String|Null + */ + 'os': null, + + /** + * The alpha/beta release indicator. + * + * @memberOf Benchmark.platform + * @type String|Null + */ + 'prerelease': null, + + /** + * The browser/environment version. + * + * @memberOf Benchmark.platform + * @type String|Null + */ + 'version': null, + + /** + * Return platform description when the platform object is coerced to a string. + * + * @memberOf Benchmark.platform + * @type Function + * @returns {String} The platform description. + */ + 'toString': function() { + return this.description || ''; + } + }, + + /** + * The semantic version number. + * + * @static + * @memberOf Benchmark + * @type String + */ + 'version': '1.0.0', + + // an object of environment/feature detection flags + 'support': support, + + // clone objects + 'deepClone': deepClone, + + // iteration utility + 'each': each, + + // augment objects + 'extend': extend, + + // generic Array#filter + 'filter': filter, + + // generic Array#forEach + 'forEach': forEach, + + // generic own property iteration utility + 'forOwn': forOwn, + + // converts a number to a comma-separated string + 'formatNumber': formatNumber, + + // generic Object#hasOwnProperty + // (trigger hasKey's lazy define before assigning it to Benchmark) + 'hasKey': (hasKey(Benchmark, ''), hasKey), + + // generic Array#indexOf + 'indexOf': indexOf, + + // template utility + 'interpolate': interpolate, + + // invokes a method on each item in an array + 'invoke': invoke, + + // generic Array#join for arrays and objects + 'join': join, + + // generic Array#map + 'map': map, + + // retrieves a property value from each item in an array + 'pluck': pluck, + + // generic Array#reduce + 'reduce': reduce + }); + + /*--------------------------------------------------------------------------*/ + + extend(Benchmark.prototype, { + + /** + * The number of times a test was executed. + * + * @memberOf Benchmark + * @type Number + */ + 'count': 0, + + /** + * The number of cycles performed while benchmarking. + * + * @memberOf Benchmark + * @type Number + */ + 'cycles': 0, + + /** + * The number of executions per second. + * + * @memberOf Benchmark + * @type Number + */ + 'hz': 0, + + /** + * The compiled test function. + * + * @memberOf Benchmark + * @type Function|String + */ + 'compiled': undefined, + + /** + * The error object if the test failed. + * + * @memberOf Benchmark + * @type Object + */ + 'error': undefined, + + /** + * The test to benchmark. + * + * @memberOf Benchmark + * @type Function|String + */ + 'fn': undefined, + + /** + * A flag to indicate if the benchmark is aborted. + * + * @memberOf Benchmark + * @type Boolean + */ + 'aborted': false, + + /** + * A flag to indicate if the benchmark is running. + * + * @memberOf Benchmark + * @type Boolean + */ + 'running': false, + + /** + * Compiled into the test and executed immediately **before** the test loop. + * + * @memberOf Benchmark + * @type Function|String + * @example + * + * // basic usage + * var bench = Benchmark({ + * 'setup': function() { + * var c = this.count, + * element = document.getElementById('container'); + * while (c--) { + * element.appendChild(document.createElement('div')); + * } + * }, + * 'fn': function() { + * element.removeChild(element.lastChild); + * } + * }); + * + * // compiles to something like: + * var c = this.count, + * element = document.getElementById('container'); + * while (c--) { + * element.appendChild(document.createElement('div')); + * } + * var start = new Date; + * while (count--) { + * element.removeChild(element.lastChild); + * } + * var end = new Date - start; + * + * // or using strings + * var bench = Benchmark({ + * 'setup': '\ + * var a = 0;\n\ + * (function() {\n\ + * (function() {\n\ + * (function() {', + * 'fn': 'a += 1;', + * 'teardown': '\ + * }())\n\ + * }())\n\ + * }())' + * }); + * + * // compiles to something like: + * var a = 0; + * (function() { + * (function() { + * (function() { + * var start = new Date; + * while (count--) { + * a += 1; + * } + * var end = new Date - start; + * }()) + * }()) + * }()) + */ + 'setup': noop, + + /** + * Compiled into the test and executed immediately **after** the test loop. + * + * @memberOf Benchmark + * @type Function|String + */ + 'teardown': noop, + + /** + * An object of stats including mean, margin or error, and standard deviation. + * + * @memberOf Benchmark + * @type Object + */ + 'stats': { + + /** + * The margin of error. + * + * @memberOf Benchmark#stats + * @type Number + */ + 'moe': 0, + + /** + * The relative margin of error (expressed as a percentage of the mean). + * + * @memberOf Benchmark#stats + * @type Number + */ + 'rme': 0, + + /** + * The standard error of the mean. + * + * @memberOf Benchmark#stats + * @type Number + */ + 'sem': 0, + + /** + * The sample standard deviation. + * + * @memberOf Benchmark#stats + * @type Number + */ + 'deviation': 0, + + /** + * The sample arithmetic mean. + * + * @memberOf Benchmark#stats + * @type Number + */ + 'mean': 0, + + /** + * The array of sampled periods. + * + * @memberOf Benchmark#stats + * @type Array + */ + 'sample': [], + + /** + * The sample variance. + * + * @memberOf Benchmark#stats + * @type Number + */ + 'variance': 0 + }, + + /** + * An object of timing data including cycle, elapsed, period, start, and stop. + * + * @memberOf Benchmark + * @type Object + */ + 'times': { + + /** + * The time taken to complete the last cycle (secs). + * + * @memberOf Benchmark#times + * @type Number + */ + 'cycle': 0, + + /** + * The time taken to complete the benchmark (secs). + * + * @memberOf Benchmark#times + * @type Number + */ + 'elapsed': 0, + + /** + * The time taken to execute the test once (secs). + * + * @memberOf Benchmark#times + * @type Number + */ + 'period': 0, + + /** + * A timestamp of when the benchmark started (ms). + * + * @memberOf Benchmark#times + * @type Number + */ + 'timeStamp': 0 + }, + + // aborts benchmark (does not record times) + 'abort': abort, + + // creates a new benchmark using the same test and options + 'clone': clone, + + // compares benchmark's hertz with another + 'compare': compare, + + // executes listeners + 'emit': emit, + + // get listeners + 'listeners': listeners, + + // unregister listeners + 'off': off, + + // register listeners + 'on': on, + + // reset benchmark properties + 'reset': reset, + + // runs the benchmark + 'run': run, + + // pretty print benchmark info + 'toString': toStringBench + }); + + /*--------------------------------------------------------------------------*/ + + extend(Deferred.prototype, { + + /** + * The deferred benchmark instance. + * + * @memberOf Benchmark.Deferred + * @type Object + */ + 'benchmark': null, + + /** + * The number of deferred cycles performed while benchmarking. + * + * @memberOf Benchmark.Deferred + * @type Number + */ + 'cycles': 0, + + /** + * The time taken to complete the deferred benchmark (secs). + * + * @memberOf Benchmark.Deferred + * @type Number + */ + 'elapsed': 0, + + /** + * A timestamp of when the deferred benchmark started (ms). + * + * @memberOf Benchmark.Deferred + * @type Number + */ + 'timeStamp': 0, + + // cycles/completes the deferred benchmark + 'resolve': resolve + }); + + /*--------------------------------------------------------------------------*/ + + extend(Event.prototype, { + + /** + * A flag to indicate if the emitters listener iteration is aborted. + * + * @memberOf Benchmark.Event + * @type Boolean + */ + 'aborted': false, + + /** + * A flag to indicate if the default action is cancelled. + * + * @memberOf Benchmark.Event + * @type Boolean + */ + 'cancelled': false, + + /** + * The object whose listeners are currently being processed. + * + * @memberOf Benchmark.Event + * @type Object + */ + 'currentTarget': undefined, + + /** + * The return value of the last executed listener. + * + * @memberOf Benchmark.Event + * @type Mixed + */ + 'result': undefined, + + /** + * The object to which the event was originally emitted. + * + * @memberOf Benchmark.Event + * @type Object + */ + 'target': undefined, + + /** + * A timestamp of when the event was created (ms). + * + * @memberOf Benchmark.Event + * @type Number + */ + 'timeStamp': 0, + + /** + * The event type. + * + * @memberOf Benchmark.Event + * @type String + */ + 'type': '' + }); + + /*--------------------------------------------------------------------------*/ + + /** + * The default options copied by suite instances. + * + * @static + * @memberOf Benchmark.Suite + * @type Object + */ + Suite.options = { + + /** + * The name of the suite. + * + * @memberOf Benchmark.Suite.options + * @type String + */ + 'name': undefined + }; + + /*--------------------------------------------------------------------------*/ + + extend(Suite.prototype, { + + /** + * The number of benchmarks in the suite. + * + * @memberOf Benchmark.Suite + * @type Number + */ + 'length': 0, + + /** + * A flag to indicate if the suite is aborted. + * + * @memberOf Benchmark.Suite + * @type Boolean + */ + 'aborted': false, + + /** + * A flag to indicate if the suite is running. + * + * @memberOf Benchmark.Suite + * @type Boolean + */ + 'running': false, + + /** + * An `Array#forEach` like method. + * Callbacks may terminate the loop by explicitly returning `false`. + * + * @memberOf Benchmark.Suite + * @param {Function} callback The function called per iteration. + * @returns {Object} The suite iterated over. + */ + 'forEach': methodize(forEach), + + /** + * An `Array#indexOf` like method. + * + * @memberOf Benchmark.Suite + * @param {Mixed} value The value to search for. + * @returns {Number} The index of the matched value or `-1`. + */ + 'indexOf': methodize(indexOf), + + /** + * Invokes a method on all benchmarks in the suite. + * + * @memberOf Benchmark.Suite + * @param {String|Object} name The name of the method to invoke OR options object. + * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. + * @returns {Array} A new array of values returned from each method invoked. + */ + 'invoke': methodize(invoke), + + /** + * Converts the suite of benchmarks to a string. + * + * @memberOf Benchmark.Suite + * @param {String} [separator=','] A string to separate each element of the array. + * @returns {String} The string. + */ + 'join': [].join, + + /** + * An `Array#map` like method. + * + * @memberOf Benchmark.Suite + * @param {Function} callback The function called per iteration. + * @returns {Array} A new array of values returned by the callback. + */ + 'map': methodize(map), + + /** + * Retrieves the value of a specified property from all benchmarks in the suite. + * + * @memberOf Benchmark.Suite + * @param {String} property The property to pluck. + * @returns {Array} A new array of property values. + */ + 'pluck': methodize(pluck), + + /** + * Removes the last benchmark from the suite and returns it. + * + * @memberOf Benchmark.Suite + * @returns {Mixed} The removed benchmark. + */ + 'pop': [].pop, + + /** + * Appends benchmarks to the suite. + * + * @memberOf Benchmark.Suite + * @returns {Number} The suite's new length. + */ + 'push': [].push, + + /** + * Sorts the benchmarks of the suite. + * + * @memberOf Benchmark.Suite + * @param {Function} [compareFn=null] A function that defines the sort order. + * @returns {Object} The sorted suite. + */ + 'sort': [].sort, + + /** + * An `Array#reduce` like method. + * + * @memberOf Benchmark.Suite + * @param {Function} callback The function called per iteration. + * @param {Mixed} accumulator Initial value of the accumulator. + * @returns {Mixed} The accumulator. + */ + 'reduce': methodize(reduce), + + // aborts all benchmarks in the suite + 'abort': abortSuite, + + // adds a benchmark to the suite + 'add': add, + + // creates a new suite with cloned benchmarks + 'clone': cloneSuite, + + // executes listeners of a specified type + 'emit': emit, + + // creates a new suite of filtered benchmarks + 'filter': filterSuite, + + // get listeners + 'listeners': listeners, + + // unregister listeners + 'off': off, + + // register listeners + 'on': on, + + // resets all benchmarks in the suite + 'reset': resetSuite, + + // runs all benchmarks in the suite + 'run': runSuite, + + // array methods + 'concat': concat, + + 'reverse': reverse, + + 'shift': shift, + + 'slice': slice, + + 'splice': splice, + + 'unshift': unshift + }); + + /*--------------------------------------------------------------------------*/ + + // expose Deferred, Event and Suite + extend(Benchmark, { + 'Deferred': Deferred, + 'Event': Event, + 'Suite': Suite + }); + + // expose Benchmark + // some AMD build optimizers, like r.js, check for specific condition patterns like the following: + if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) { + // define as an anonymous module so, through path mapping, it can be aliased + define(function() { + return Benchmark; + }); + } + // check for `exports` after `define` in case a build optimizer adds an `exports` object + else if (freeExports) { + // in Node.js or RingoJS v0.8.0+ + if (typeof module == 'object' && module && module.exports == freeExports) { + (module.exports = Benchmark).Benchmark = Benchmark; + } + // in Narwhal or RingoJS v0.7.0- + else { + freeExports.Benchmark = Benchmark; + } + } + // in a browser or Rhino + else { + // use square bracket notation so Closure Compiler won't munge `Benchmark` + // http://code.google.com/closure/compiler/docs/api-tutorial3.html#export + window['Benchmark'] = Benchmark; + } + + // trigger clock's lazy define early to avoid a security error + if (support.air) { + clock({ '_original': { 'fn': noop, 'count': 1, 'options': {} } }); + } +}(this)); \ No newline at end of file diff --git a/library/three.js b/library/three.js index 905568f..f589aff 100644 --- a/library/three.js +++ b/library/three.js @@ -9342,6 +9342,21 @@ THREE.BufferGeometry.prototype = { this.dispatchEvent( { type: 'dispose' } ); + }, + + getBuffers: function () { + + var buffers = []; + + for ( var i = 0; i < this.attributesKeys.length; i ++ ) { + + var key = this.attributesKeys[ i ]; + buffers.push( this.attributes[ key ].array.buffer ); + + } + + return buffers; + } }; diff --git a/models/dom.stl b/models/dom.stl new file mode 100644 index 0000000000000000000000000000000000000000..b5dc7a5e8639f65ae31b6e6a9e71857f8b8c1234 GIT binary patch literal 195284 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 literal 0 HcmV?d00001 diff --git a/settings/user_settings.json b/settings/user_settings.json index c18586b..1b71721 100644 --- a/settings/user_settings.json +++ b/settings/user_settings.json @@ -1,18 +1,18 @@ { "printer.normalFlowRate": 1.0, "printer.bottomFlowRate": 2.0, - "printer.bottomLayerSpeed": 35, + "printer.bottomLayerSpeed": 35.0, "printer.bottomThickness": 0.4, - "printer.fillSize": 5, + "printer.fillSize": 10.0, "printer.firstLayerSlow": true, - "printer.layerHeight": 0.2, - "printer.retraction.amount": 3, + "printer.layerHeight": 0.2, + "printer.retraction.amount": 3.0, "printer.retraction.enabled": true, "printer.retraction.speed": 50, - "printer.retraction.minDistance": 5, + "printer.retraction.minDistance": 5.0, "printer.shellThickness": 0.4, - "printer.speed": 50, - "printer.temperature": 230, + "printer.speed": 50.0, + "printer.temperature": 230.0, "printer.topThickness": 0.8, - "printer.travelSpeed": 200 + "printer.travelSpeed": 200.0 } \ No newline at end of file diff --git a/slice_test.html b/slice_test.html index b227cc9..618988f 100644 --- a/slice_test.html +++ b/slice_test.html @@ -30,12 +30,12 @@ function init () { var scene = createScene(); var localIp = location.hash.substring(1); - //doodleBox = new D3D.Box(localIp); + doodleBox = new D3D.Box(localIp); var printer = new D3D.Printer().updateConfig(USER_SETTINGS).updateConfig(PRINTER_SETTINGS["ultimaker"]); var loader = new THREE.STLLoader(); - loader.load("models/pokemon/pikachu.stl", function (geometry) { + loader.load("models/dom.stl", function (geometry) { //var geometry = new THREE.BoxGeometry(10, 10, 10, 1, 1, 1); //var geometry = new THREE.SphereGeometry(10, 20, 10); //var geometry = new THREE.TorusGeometry(20, 10, 30, 30); diff --git a/src/slicer.js b/src/slicer.js index f09be9b..2b43f42 100644 --- a/src/slicer.js +++ b/src/slicer.js @@ -19,11 +19,12 @@ D3D.Slicer.prototype.setMesh = function (geometry, matrix) { "use strict"; //convert buffergeometry to geometry; - if (geometry instanceof THREE.BufferGeometry) { - geometry = new THREE.Geometry().fromBufferGeometry(geometry); - } + //if (geometry instanceof THREE.BufferGeometry) { + // geometry = new THREE.Geometry().fromBufferGeometry(geometry); + //} //remove duplicate vertices; + /* for (var i = 0; i < geometry.vertices.length; i ++) { var vertexA = geometry.vertices[i]; @@ -35,6 +36,7 @@ D3D.Slicer.prototype.setMesh = function (geometry, matrix) { } } } + */ geometry.mergeVertices(); //apply mesh matrix on geometry; @@ -111,7 +113,7 @@ D3D.Slicer.prototype.slice = function (layerHeight, height) { var max = Math.floor(Math.max(line.start.y, line.end.y) / layerHeight); for (var layerIndex = min; layerIndex <= max; layerIndex ++) { - if (layerIndex >= 0) { + if (layerIndex >= 0 && layerIndex < height / layerHeight) { if (layersIntersections[layerIndex] === undefined) { layersIntersections[layerIndex] = []; } @@ -127,7 +129,7 @@ D3D.Slicer.prototype.slice = function (layerHeight, height) { for (var layer = 1; layer < layersIntersections.length-1; layer ++) { //for (var layer = 0; layer < layersIntersections.length; layer ++) { var layerIntersections = layersIntersections[layer]; - var y = layer*layerHeight; + var y = layer * layerHeight; var intersections = []; for (var i = 0; i < layerIntersections.length; i ++) { diff --git a/src/slicerworker.js b/src/slicerworker.js index 181610c..1ac0b0a 100644 --- a/src/slicerworker.js +++ b/src/slicerworker.js @@ -5,6 +5,7 @@ D3D.SlicerWorker = function () { this.worker.addEventListener('message', function (event) { console.log(event); + gcode = event.data; }, false); } D3D.SlicerWorker.prototype.setSettings = function (USER_SETTINGS, PRINTER_SETTINGS) { @@ -23,16 +24,23 @@ D3D.SlicerWorker.prototype.setMesh = function (mesh) { var geometry = new THREE.BufferGeometry().fromGeometry(mesh.geometry); } else { - var geometry = mesh.geometry; + var geometry = mesh.geometry.clone(); + } + + var buffers = []; + + for (var i = 0; i < geometry.attributesKeys.length; i ++) { + var key = geometry.attributesKeys[i]; + buffers.push(geometry.attributes[key].array.buffer); } mesh.updateMatrix(); this.worker.postMessage({ "cmd": "SET_MESH", - "geometry": geometry.toJSON().data, + "geometry": geometry, "matrix": mesh.matrix.toArray() - }); + }, buffers); }; D3D.SlicerWorker.prototype.slice = function () { "use strict"; diff --git a/webworker/benchmark.js b/webworker/benchmark.js new file mode 100644 index 0000000..2ae3288 --- /dev/null +++ b/webworker/benchmark.js @@ -0,0 +1,9 @@ +self.addEventListener("message", function (event) { + "use strict"; + + //console.log(event.data); + + if (event.data === "close") { + self.close(); + } +}); \ No newline at end of file diff --git a/webworker/worker.js b/webworker/worker.js index ecfba26..af28510 100644 --- a/webworker/worker.js +++ b/webworker/worker.js @@ -8,34 +8,38 @@ importScripts("../src/slicer.js"); var printer = new D3D.Printer(); var slicer = new D3D.Slicer(); -self.addEventListener('message', function (event) { +self.addEventListener("message", function (event) { "use strict"; switch (event.data["cmd"]) { case "SET_MESH": - var loader = new THREE.BufferGeometryLoader(); - var geometry = loader.parse(event.data["geometry"]); - + + var geometry = new THREE.Geometry().fromBufferGeometry(event.data["geometry"]); var matrix = new THREE.Matrix4().fromArray(event.data["matrix"]); slicer.setMesh(geometry, matrix); + break; case "SET_SETTINGS": printer.updateConfig(event.data["USER_SETTINGS"]); printer.updateConfig(event.data["PRINTER_SETTINGS"]); - - console.log(printer); break; case "SLICE": var gcode = slicer.getGcode(printer); - self.postMessage('gcode generated'); + self.postMessage(gcode); break; - case "CLOSE": + case "CLOSE": self.close(); break; + + default: + + //console.log(event); + + break; } }); \ No newline at end of file diff --git a/webworker_benchmark.html b/webworker_benchmark.html new file mode 100644 index 0000000..038b7ab --- /dev/null +++ b/webworker_benchmark.html @@ -0,0 +1,74 @@ + + + + + + +Doedel Drie Dee || Webworker Benchmark + + + + + + + + \ No newline at end of file diff --git a/webworker_test.html b/webworker_test.html index 2d3e70b..fa3b32b 100644 --- a/webworker_test.html +++ b/webworker_test.html @@ -42,6 +42,7 @@ function init () { mesh.rotation.x = -Math.PI/2; mesh.scale.x = mesh.scale.y = mesh.scale.z = 1; + mesh.position.y = -9.260265119962973e-17; mesh.position.x = 100; mesh.position.z = 100; @@ -49,6 +50,8 @@ function init () { slicer.setMesh(mesh); slicer.slice(); + + slicer.close(); }); }