diff options
Diffstat (limited to 'o3d/samples/o3d-webgl/counter.js')
-rw-r--r-- | o3d/samples/o3d-webgl/counter.js | 814 |
1 files changed, 814 insertions, 0 deletions
diff --git a/o3d/samples/o3d-webgl/counter.js b/o3d/samples/o3d-webgl/counter.js new file mode 100644 index 0000000..b3c3190 --- /dev/null +++ b/o3d/samples/o3d-webgl/counter.js @@ -0,0 +1,814 @@ +/* + * Copyright 2010, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Per-client list of counters/timers. + * @constructor + */ +o3d.CounterManager = function() { + /** + * Map from counter type to list of counters in this client. + * @private + * @type {!Object} + */ + this.counterMap_ = {}; +}; + +/** + * Contains the time of the last call to tick, so we know the amount to pass + * into advanceCounters_ + * lastUpdated is initialized on the first call to tick(). + * @type {Date} + */ +o3d.CounterManager.prototype.lastUpdated = null; + +/** + * @param {string} type Which counter_type_ to advance. + * @param {number} amount Amount to advance counters. + */ +o3d.CounterManager.prototype.advanceCounters = function(type, amount) { + var counterArrayRef = this.counterMap_[type]; + if (!counterArrayRef) + return; + var length = counterArrayRef.length; + var counterArray = []; + for (var i = 0; i < length; i++) { + counterArray[i] = counterArrayRef[i]; + } + for (var i = 0; i < length; ++i) { + if (counterArray[i].running) { + counterArray[i].advance(amount); + } + } +}; + +/** + * Static method to increments all global counters. RenderFrameCounter should + * not get updated here. + */ +o3d.CounterManager.prototype.tick = function() { + this.advanceCounters("TickCounter", 1.0); + var now = new Date(); + var deltaSeconds = 0; + if (this.lastUpdated != null) { + deltaSeconds = (now - this.lastUpdated) / 1000.0; + } + this.lastUpdated = now; + this.advanceCounters("SecondCounter", deltaSeconds); +}; + +/** + * Updates RenderFrameCounter objects by 1.0, similarly to TickCounter. + * As opposed to tick(), this should only be when the canvas is rendered, so + * any canvases which are not automatically rendererd may not advance this. + */ +o3d.CounterManager.prototype.advanceRenderFrameCounters = function() { + this.advanceCounters("RenderFrameCounter", 1.0); +}; + +/** + * Register a counter, using its className to decide which type of counter. + * @param {!o3d.Counter} counter Instance of some sub-type of o3d.Counter + */ +o3d.CounterManager.prototype.registerCounter = function(counter) { + var type = counter.counter_type_; + var arr = this.counterMap_[type]; + if (!arr) { + arr = this.counterMap_[type] = []; + } + arr.push(counter); +}; + +/** + * Unregister a counter previously registered by registerCounter. + * @param {!o3d.Counter} counter Instance of some sub-type of o3d.Counter + */ +o3d.CounterManager.prototype.unregisterCounter = function(counter) { + var type = counter.counter_type_; + var arr = this.counterMap_[type]; + if (arr) { + o3d.removeFromArray(arr, counter); + } +}; + +/** + * A Counter counts seconds, ticks or render frames depending on the type of + * counter. You can set where it starts counting from and where it stops + * counting at, whether or not it is running or paused and how it loops or does + * not loop. You can also give it callbacks to call at specific count values. + * @constructor + * @extends {o3d.ParamObject} + */ +o3d.Counter = function() { + o3d.ParamObject.call(this); + + /** + * Last called callback in the forward direction (start_count > end_count) + * @type {number} + * @private + */ + this.next_callback_ = -1; + + /** + * Last called callback in the backward direction (start_count < end_count) + * @type {number} + * @private + */ + this.prev_callback_ = -1; + + /** + * Keeps track of last count in case this.count_ is bound to another param. + * @type {number} + * @private + */ + this.old_count_ = 0; + + /** + * Keeps track of last end_count to prevent double-firing of callbacks if + * start_count is the same. + * @type {number} + * @private + */ + this.last_call_callbacks_end_count_ = 0; + + /** + * List of callbacks for this counter, in sorted order. + * There can only be one callback per timer value. + * @type {Array.<o3d.Counter.CallbackInfo>} + * @private + */ + this.callbacks_ = []; + + /** + * Pointer to CounterManager which owns this counter, so it can be added + * to the list of counters by setting this.running = true. + * @type {o3d.CounterManager} + */ + this.counter_manager_ = null; + + /** + * Cached copy of this.getParam('running') + * @type {o3d.ParamBoolean} + * @private + */ + this.running_param_ = this.createParam("running", "ParamBoolean"); + + /** + * Cached copy of this.getParam('count') + * @type {o3d.ParamFloat} + * @private + */ + this.count_param_ = this.createParam("count", "ParamFloat"); + + this.multiplier = 1.0; + + this.forward = true; + + this.countMode = o3d.Counter.CONTINUOUS; + + // this.running must be set in the sub class due to how o3d.inherit works. +}; +o3d.inherit('Counter', 'ParamObject'); + +/** + * Controls the direction of the counter. If false, Counter.callCallbacks_ + * will be called with start_count > end_count. + * @default true + * @type {boolean} + */ +o3d.ParamObject.setUpO3DParam_(o3d.Counter, "forward", "ParamBoolean"); + +/** + * The start value for this counter. + * @default 0 + * @type {number} + */ +o3d.ParamObject.setUpO3DParam_(o3d.Counter, "start", "ParamFloat"); + +/** + * The end value for this counter. + * @default 0 + * @type {number} + */ +o3d.ParamObject.setUpO3DParam_(o3d.Counter, "end", "ParamFloat"); + +/** + * Can be used to switch the direction of the counter or control what happens + * when end_count is reached. + * @see o3d.Counter.CONTINUOUS + * @see o3d.Counter.ONCE + * @see o3d.Counter.CYCLE + * @see o3d.Counter.OSCILLATE + * @default o3d.Counter.CONTINUOUS + * @type {o3d.Counter.CountMode} + */ +o3d.ParamObject.setUpO3DParam_(o3d.Counter, "countMode", "ParamInteger"); + +/** + * The time multiplier for this counter. + * @default 1 + * @type {number} + */ +o3d.ParamObject.setUpO3DParam_(o3d.Counter, "multiplier", "ParamFloat"); + +/** + * Whether or not this counter is running. + * Special setter to allow destroying a stopped Counter without leaking mem. + * Defaults to true in SecondCounter, TickCounter and RenderFrameCounter. + * @default false + * @type {boolean} + */ +o3d.Counter.prototype.__defineSetter__("running", + function(newRunning) { + var oldRunning = this.running_param_.value; + if (this.counter_manager_ && oldRunning != newRunning) { + if (newRunning == false) { + this.counter_manager_.unregisterCounter(this); + } else { + this.counter_manager_.registerCounter(this); + } + } + this.running_param_.value = newRunning; + } +); +o3d.Counter.prototype.__defineGetter__("running", + function() { + return this.running_param_.value; + } +); + +/** + * The current count for this counter. The setter is equivalent to calling + * setCount(value) + * @default 0 + * @type {number} + */ +o3d.Counter.prototype.__defineSetter__("count", + function(value) { + this.setCount(value); + } +); +o3d.Counter.prototype.__defineGetter__("count", + function() { + return this.count_param_.value; + } +); + +o3d.Counter.prototype.__defineSetter__("gl", + function(new_gl) { + var old_running = this.running; + this.running = false; + this.gl_ = new_gl; + if (this.gl_ && this.gl_.client) { + this.counter_manager_ = this.gl_.client.counter_manager_; + } + this.running = old_running; + } +); + +o3d.Counter.prototype.__defineGetter__("gl", + function() { + return this.gl_; + } +); + +/** + * @type {number} + */ +o3d.Counter.CountMode = goog.typedef; + +/** + * Continiuously goes up, ignoring the value of end_count. This is the default. + * @type {o3d.Counter.CountMode} + */ +o3d.Counter.CONTINUOUS = 0; +/** + * Sets running=false when end is reached and stops. + * @type {o3d.Counter.CountMode} + */ +o3d.Counter.ONCE = 1; +/** + * Resets count to start_count whenever end is reached. + * @type {o3d.Counter.CountMode} + */ +o3d.Counter.CYCLE = 2; +/** + * Switches direction whenever end or start is reached. + * @type {o3d.Counter.CountMode} + * @see direction + */ +o3d.Counter.OSCILLATE = 3; + +/** + * The counter type is used by o3d.CounterManager to decide which array to + * put the counter in, and how much to call advanceAmount by during each call + * to o3d.CounterManager.tick() or advanceRenderFrameCounters(). + * "Counter" means that it will only be advanced manually. + * @type {string} + */ +o3d.Counter.prototype.counter_type_ = "Counter"; + +/** + * Resets the counter back to the start or end time depending on the forward + * setting and also resets the Callback state. + * Note: Reset does not change the running state of the counter. + */ +o3d.Counter.prototype.reset = function() { + this.setCount(this.forward ? this.start : this.end); +}; + +/** + * Sets the current count value for this counter as well as the resetting the + * state of the callbacks. + * @param {number} count The current value of the counter + */ +o3d.Counter.prototype.setCount = function(value) { + this.count_param_.value = value; + this.old_count_ = value; + this.next_callback_ = -1; + this.prev_callback_ = -1; +}; + + +/** + * Advances the counter the given amount. The actual amount advanced depends on + * the forward and multiplier settings. The formula is + * new_count = count + advance_amount * multiplier * (forward ? 1.0 : -1.0); + * + * Any callbacks that fall in the range between the counter's current count and + * the amount advanced will be called. This function is normally called + * automatically by the client if the counter is set to running = true. but you + * can call it manually. + * + * @param {number} advance_amount Amount to advance count. + */ +o3d.Counter.prototype.advance = function(advance_amount) { + var queue = []; + var old_count = this.count_param_.value; + + // Update the count. + if (this.count_param_.inputConnection) { + this.callCallbacks_(this.old_count_, old_count, queue); + } else { + var direction = this.forward; + var start_count = this.start; + var end_count = this.end; + var multiplier = this.multiplier; + var delta = (direction ? advance_amount : -advance_amount) * multiplier; + var period = end_count - start_count; + + var mode = this.countMode; + if (period >= 0.0) { + // end > start + var new_count = old_count + delta; + if (delta >= 0.0) { + switch (mode) { + case o3d.Counter.ONCE: { + if (new_count >= end_count) { + new_count = end_count; + this.running = false; + } + break; + } + case o3d.Counter.CYCLE: { + while (new_count >= end_count) { + this.callCallbacks_(old_count, end_count, queue); + if (period == 0.0) { + break; + } + old_count = start_count; + new_count -= period; + } + break; + } + case o3d.Counter.OSCILLATE: { + while (delta > 0.0) { + new_count = old_count + delta; + if (new_count < end_count) { + break; + } + this.callCallbacks_(old_count, end_count, queue); + direction = !direction; + var amount = end_count - old_count; + delta -= amount; + old_count = end_count; + new_count = end_count; + if (delta <= 0.0 || period == 0.0) { + break; + } + new_count -= delta; + if (new_count > start_count) { + break; + } + this.callCallbacks_(old_count, start_count, queue); + direction = !direction; + amount = old_count - start_count; + delta -= amount; + old_count = start_count; + new_count = start_count; + } + this.forward = direction; + break; + } + case o3d.Counter.CONTINUOUS: + default: + break; + } + this.callCallbacks_(old_count, new_count, queue); + this.count_param_.value = new_count; + } else if (delta < 0.0) { + switch (mode) { + case o3d.Counter.ONCE: { + if (new_count <= start_count) { + new_count = start_count; + this.running = false ; + } + break; + } + case o3d.Counter.CYCLE: { + while (new_count <= start_count) { + this.callCallbacks_(old_count, start_count, queue); + if (period == 0.0) { + break; + } + old_count = end_count; + new_count += period; + } + break; + } + case o3d.Counter.OSCILLATE: { + while (delta < 0.0) { + new_count = old_count + delta; + if (new_count > start_count) { + break; + } + this.callCallbacks_(old_count, start_count, queue); + direction = !direction; + var amount = old_count - start_count; + delta += amount; + old_count = start_count; + new_count = start_count; + if (delta >= 0.0 || period == 0.0) { + break; + } + new_count -= delta; + if (new_count < end_count) { + break; + } + this.callCallbacks_(old_count, end_count, queue); + direction = !direction; + amount = end_count - old_count; + delta += amount; + old_count = end_count; + new_count = end_count; + } + this.forward = direction; + break; + } + case o3d.Counter.CONTINUOUS: + default: + break; + } + this.callCallbacks_(old_count, new_count, queue); + this.count_param_.value = new_count; + } + } else if (period < 0.0) { + // start > end + period = -period; + var new_count = old_count - delta; + if (delta > 0.0) { + switch (mode) { + case o3d.Counter.ONCE: { + if (new_count <= end_count) { + new_count = end_count; + this.running = false; + } + break; + } + case o3d.Counter.CYCLE: { + while (new_count <= end_count) { + this.callCallbacks_(old_count, end_count, queue); + old_count = start_count; + new_count += period; + } + break; + } + case o3d.Counter.OSCILLATE: { + while (delta > 0.0) { + new_count = old_count - delta; + if (new_count > end_count) { + break; + } + this.callCallbacks_(old_count, end_count, queue); + direction = !direction; + var amount = old_count - end_count; + delta -= amount; + old_count = end_count; + new_count = end_count; + if (delta <= 0.0) { + break; + } + new_count += delta; + if (new_count < start_count) { + break; + } + this.callCallbacks_(old_count, start_count, queue); + direction = !direction; + amount = start_count - old_count; + delta -= amount; + old_count = start_count; + new_count = start_count; + } + this.forward = direction; + break; + } + case o3d.Counter.CONTINUOUS: + default: + break; + } + this.callCallbacks_(old_count, new_count, queue); + this.count_param_.value = new_count; + } else if (delta < 0.0) { + switch (mode) { + case o3d.Counter.ONCE: { + if (new_count >= start_count) { + new_count = start_count; + this.running = false; + } + break; + } + case o3d.Counter.CYCLE: { + while (new_count >= start_count) { + this.callCallbacks_(old_count, start_count, queue); + old_count = end_count; + new_count -= period; + } + break; + } + case o3d.Counter.OSCILLATE: { + while (delta < 0.0) { + new_count = old_count - delta; + if (new_count < start_count) { + break; + } + this.callCallbacks_(old_count, start_count, queue); + direction = !direction; + var amount = start_count - old_count; + delta += amount; + old_count = start_count; + new_count = start_count; + if (delta >= 0.0) { + break; + } + new_count += delta; + if (new_count > end_count) { + break; + } + this.callCallbacks_(old_count, end_count, queue); + direction = !direction; + amount = old_count - end_count; + delta += amount; + old_count = end_count; + new_count = end_count; + } + this.forward = direction; + break; + } + case o3d.Counter.CONTINUOUS: + default: + break; + } + this.callCallbacks_(old_count, new_count, queue); + this.count_param_.value = new_count; + } + } + } + this.old_count_ = old_count; + for (var i = 0; i < queue.length; i++) { + queue[i](); + } +}; + +/** + * @param {number} start_count Starts exactly at this count. + * @param {number} end_count Calls callbacks up to but not including this. + * @private + */ +o3d.Counter.prototype.callCallbacks_ = function(start_count, end_count, queue) { + if (end_count > start_count) { + // Going forward. + // If next_callback is not valid, find the first possible callback. + if (this.next_callback_ < 0 || + start_count != this.last_call_callbacks_end_count_) { + this.next_callback_ = 0; + while (this.next_callback_ != this.callbacks_.length && + this.callbacks_[this.next_callback_].count < start_count) { + ++this.next_callback_; + } + } + + // add callbacks until we get to some callback past end_count. + while (this.next_callback_ < this.callbacks_.length) { + if (this.callbacks_[this.next_callback_].count > end_count) { + break; + } + queue.push(this.callbacks_[this.next_callback_].callback); + ++this.next_callback_; + } + this.prev_callback_ = -1; + this.last_call_callbacks_end_count_ = end_count; + } else if (end_count < start_count) { + // Going backward. + // If prev_callback is not valid, find the first possible callback. + if (this.prev_callback_ < 0 || + start_count != this.last_call_callbacks_end_count_) { + this.prev_callback_ = this.callbacks_.length - 1; + while (this.prev_callback_ >= 0 && + this.callbacks_[this.prev_callback_].count > start_count) { + --this.prev_callback_; + } + } + + // add callbacks until we get to some callback past end_count. + while (this.prev_callback_ >= 0) { + if (this.callbacks_[this.prev_callback_].count < end_count) { + break; + } + queue.push(this.callbacks_[this.prev_callback_].callback); + --this.prev_callback_; + } + + this.next_callback_ = -1; + this.last_call_callbacks_end_count_ = end_count; + } +}; + +/** + * Adds a callback for a given count value. Only one callback can be added to a + * specific count value. If another callback is added with the same count value + * the previous callback for that value will be replaced. Note: A callback at + * start will only get called when counting backward, a callback at end will + * only get called counting forward. + * @param {number} count Count at which to call callback. + * @param {function()} callback Callback to call at given count. + */ +o3d.Counter.prototype.addCallback = function(count, callback) { + this.next_callback_ = -1; + this.prev_callback_ = -1; + var end = this.callbacks_.length; + var iter = 0; + while (iter != end) { + var current = this.callbacks_[iter]; + if (current.count == count) { + // Did the o3d plugin overwrite existing callbacks here? + current.callback = callback; + return; + } else if (current.count > count) { + break; + } + ++iter; + } + var rest = this.callbacks_.splice(iter, this.callbacks_.length - iter); + this.callbacks_.push(new o3d.Counter.CallbackInfo(count, callback)); + this.callbacks_.push.apply(this.callbacks_, rest); +}; + +/** + * Removes a callback for a given count value. + * @param {number} count Count to remove callback for. + * @return {boolean} true if there was a callback for that count, false if + * there was not a callback for that count. + */ +o3d.Counter.prototype.removeCallback = function(count) { + var end = this.callbacks_.length; + for (var iter = 0; iter != end; ++iter) { + if (this.callbacks_[iter].count == count) { + this.next_callback_ = -1; + this.prev_callback_ = -1; + this.callbacks_.splice(iter, 1); + return true; + } + } + return false; +}; + +/** + * Removes all the callbacks on this counter. + * Does not affect running state: set running=false to allow this counter to be + * garbage collected. + */ +o3d.Counter.prototype.removeAllCallbacks = function() { + this.callbacks_ = []; + this.next_callback_ = -1; + this.prev_callback_ = -1; +}; + +/** + * Class to hold onto the state of a callback function at a specific count. + * @constructor + * @param {number} count Count at which this callback will get called. + * @param {function()} callback Function to call when count is reached. + */ +o3d.Counter.CallbackInfo = function(count, callback) { + /** + * Count at which this callback will get called. + * @type {number} + */ + this.count = count; + + /** + * Function to call when count is reached. + * @type {function()} + */ + this.callback = callback; + + /** + * Prevents recursion of the same callback, in case it decides to advance + * the counter. + * @type {boolean} + * @private + */ + this.called_ = false; +}; + +/** + * Run this callback without the possibility for recursion. + */ +o3d.Counter.CallbackInfo.prototype.run = function() { + if (!this.called_) { + this.called_ = true; + this.callback(); + this.called_ = false; + } +}; + +/** + * Counter to count seconds. Logic is implemented in CounterManager.tick() + * @constructor + * @extends {o3d.Counter} + */ +o3d.SecondCounter = function() { + o3d.Counter.call(this); + this.running = true; +}; + +o3d.inherit("SecondCounter", "Counter"); + +o3d.SecondCounter.prototype.counter_type_ = "SecondCounter"; + +/** + * Counter to count frames. Logic is implemented in + * CounterManager.advanceRenderFrameCounters() + * @constructor + * @extends {o3d.Counter} + */ +o3d.RenderFrameCounter = function() { + o3d.Counter.call(this); + this.running = true; +}; + +o3d.inherit("RenderFrameCounter", "Counter"); + +o3d.RenderFrameCounter.prototype.counter_type_ = "RenderFrameCounter"; + +/** + * Counter to count ticks. Logic is implemented in CounterManager.tick() + * @constructor + * @extends {o3d.Counter} + */ +o3d.TickCounter = function() { + o3d.Counter.call(this); + this.running = true; +}; + +o3d.inherit("TickCounter", "Counter"); + +o3d.TickCounter.prototype.counter_type_ = "TickCounter"; |