diff --git a/anvil_rules/templates/simstate_js.mako b/anvil_rules/templates/simstate_js.mako index a14f9fb..6cc9a44 100644 --- a/anvil_rules/templates/simstate_js.mako +++ b/anvil_rules/templates/simstate_js.mako @@ -23,7 +23,7 @@ goog.require('${state.super}'); */ ${state.name} = function(entity, opt_variableTable) { var variableTable = opt_variableTable || gf.sim.EntityState.getVariableTable( - ${state.name}.declareVariables); + ${state.name}.declareVariables, this); goog.base(this, entity, variableTable); % for i, var in enumerate(state.vars): diff --git a/src/gf/net/packetreader.js b/src/gf/net/packetreader.js index 0f36a37..13f6266 100644 --- a/src/gf/net/packetreader.js +++ b/src/gf/net/packetreader.js @@ -292,6 +292,21 @@ gf.net.PacketReader.prototype.readVec3 = function(value) { }; +/** + * Reads a value from the buffer. + * The result of this function will be reset on the next read operation and must + * only be used to copy the value to some other structure. + * @return {!goog.vec.Vec3.Float32} Value pointer. + */ +gf.net.PacketReader.prototype.readVec3Temp = function() { + goog.asserts.assert(this.offset + 3 * 4 <= this.buffer.length); + for (var n = 0; n < 3 * 4; n++) { + this.float32byte_[n] = this.buffer[this.offset++]; + } + return this.float32_; +}; + + /** * Reads a value from the buffer. * @param {!goog.vec.Vec4.Float32} value Value to receive the contents. @@ -305,6 +320,21 @@ gf.net.PacketReader.prototype.readVec4 = function(value) { }; +/** + * Reads a value from the buffer. + * The result of this function will be reset on the next read operation and must + * only be used to copy the value to some other structure. + * @return {!goog.vec.Vec3.Float32} Value pointer. + */ +gf.net.PacketReader.prototype.readVec4Temp = function() { + goog.asserts.assert(this.offset + 4 * 4 <= this.buffer.length); + for (var n = 0; n < 4 * 4; n++) { + this.float32byte_[n] = this.buffer[this.offset++]; + } + return this.float32_; +}; + + /** * Reads a value from the buffer. * @param {!goog.vec.Mat3.Type} value Value to receive the contents. @@ -318,6 +348,21 @@ gf.net.PacketReader.prototype.readMat3 = function(value) { }; +/** + * Reads a value from the buffer. + * The result of this function will be reset on the next read operation and must + * only be used to copy the value to some other structure. + * @return {!goog.vec.Vec3.Float32} Value pointer. + */ +gf.net.PacketReader.prototype.readMat3Temp = function() { + goog.asserts.assert(this.offset + 3 * 3 * 4 <= this.buffer.length); + for (var n = 0; n < 3 * 3 * 4; n++) { + this.float32byte_[n] = this.buffer[this.offset++]; + } + return this.float32_; +}; + + /** * Reads a value from the buffer. * @param {!goog.vec.Mat4.Type} value Value to receive the contents. @@ -331,6 +376,21 @@ gf.net.PacketReader.prototype.readMat4 = function(value) { }; +/** + * Reads a value from the buffer. + * The result of this function will be reset on the next read operation and must + * only be used to copy the value to some other structure. + * @return {!goog.vec.Vec3.Float32} Value pointer. + */ +gf.net.PacketReader.prototype.readMat4Temp = function() { + goog.asserts.assert(this.offset + 4 * 4 * 4 <= this.buffer.length); + for (var n = 0; n < 4 * 4 * 4; n++) { + this.float32byte_[n] = this.buffer[this.offset++]; + } + return this.float32_; +}; + + /** * Reads a value from the buffer. * @param {Uint8Array=} opt_target Target value, used if the size matches. diff --git a/src/gf/sim/entitystate.js b/src/gf/sim/entitystate.js index 12fd25d..ac99ff0 100644 --- a/src/gf/sim/entitystate.js +++ b/src/gf/sim/entitystate.js @@ -20,6 +20,7 @@ goog.provide('gf.sim.EntityState'); +goog.require('gf.sim.Variable'); goog.require('gf.sim.VariableTable'); goog.require('goog.asserts'); @@ -92,11 +93,12 @@ gf.sim.EntityState = function(entity, variableTable) { * Entity state variable declaration function. * @return {!gf.sim.VariableTable} A shared variable table. */ -gf.sim.EntityState.getVariableTable = function(declarationFunction) { +gf.sim.EntityState.getVariableTable = function(declarationFunction, obj) { if (!declarationFunction.variableTable_) { var variableList = []; declarationFunction(variableList); - declarationFunction.variableTable_ = new gf.sim.VariableTable(variableList); + declarationFunction.variableTable_ = new gf.sim.VariableTable( + variableList, obj); } return declarationFunction.variableTable_; }; @@ -155,37 +157,15 @@ gf.sim.EntityState.prototype.read = function(reader) { */ gf.sim.EntityState.prototype.readDelta = function(reader) { // Read the first 32 variables - this.readDeltaVariables_(reader, 0); + var presentBits00_31 = reader.readVarUint(); + this.variableTable_.readPresentVariables( + 0, presentBits00_31, this, reader); // Write the next 32, if present if (this.variableTable_.getCount() > 31) { - this.readDeltaVariables_(reader, 32); - } -}; - - -/** - * Reads a range of delta variables. - * This function is designed to be called on a subset of the variable range. - * For example, the first 32 variables, second 32, etc. - * @private - * @param {!gf.net.PacketReader} reader Packet reader. - * @param {number} startingOrdinal Ordinal this range starts at. - */ -gf.sim.EntityState.prototype.readDeltaVariables_ = function( - reader, startingOrdinal) { - // Read bits indicating which variables are present - var presentBits = reader.readVarUint(); - - // For each bit that is present, read the value - var ordinal = startingOrdinal; - while (presentBits) { - if (presentBits & 1) { - // Variable at is present and needs reading - this.variableTable_.readVariable(ordinal, this, reader); - } - presentBits >>= 1; - ordinal++; + var presentBits32_63 = reader.readVarUint(); + this.variableTable_.readPresentVariables( + 32, presentBits32_63, this, reader); } }; @@ -214,38 +194,15 @@ gf.sim.EntityState.prototype.writeDelta = function(writer) { // delta // Write the first 32 variables - this.writeDeltaVariables_(writer, this.dirtyBits00_31_, 0); + writer.writeVarUint(this.dirtyBits00_31_); + this.variableTable_.writePresentVariables( + 0, this.dirtyBits00_31_, this, writer); // Write the next 32, if present if (this.dirtyBits32_63_ && this.variableTable_.getCount() > 31) { - this.writeDeltaVariables_(writer, this.dirtyBits32_63_, 32); - } -}; - - -/** - * Writes a range of delta variables. - * This function is designed to be called on a subset of the variable range. - * For example, the first 32 variables, second 32, etc. - * @private - * @param {!gf.net.PacketWriter} writer Packet writer. - * @param {number} presentBits Bit field indicating which variables are present. - * @param {number} startingOrdinal Ordinal this range starts at. - */ -gf.sim.EntityState.prototype.writeDeltaVariables_ = function( - writer, presentBits, startingOrdinal) { - // Write dirty bits - writer.writeVarUint(presentBits); - - // For each bit that is dirty, write the value - var ordinal = startingOrdinal; - while (presentBits) { - if (presentBits & 1) { - // Variable at is dirty and needs writing - this.variableTable_.writeVariable(ordinal, this, writer); - } - presentBits >>= 1; - ordinal++; + writer.writeVarUint(this.dirtyBits32_63_); + this.variableTable_.writePresentVariables( + 32, this.dirtyBits32_63_, this, writer); } }; @@ -300,3 +257,52 @@ gf.sim.EntityState.prototype.interpolate = function( vtable.interpolateVariables(sourceState, targetState, t, this); } }; + + +// TODO(benvanik): find a way to remove these - point at an indirection table? +/** + * Scratch Vec3 for math. + * This is currently used by the variable table system. + * @protected + * @type {!goog.vec.Vec3.Float32} + */ +gf.sim.EntityState.prototype.tmpVec3 = gf.sim.Variable.tmpVec3; + + +/** + * Scratch Quaternion for math. + * This is currently used by the variable table system. + * @protected + * @type {!goog.vec.Quaternion.Float32} + */ +gf.sim.EntityState.prototype.tmpQuat = gf.sim.Variable.tmpQuat; + + +/** + * Quaternion slerp. + * This is currently used by the variable table system. + * @protected + * @type {!Function} + */ +gf.sim.EntityState.prototype.qslerp = goog.vec.Quaternion.slerp; + + +/** + * Color lerp. + * This is currently used by the variable table system. + * @protected + * @type {!Function} + */ +gf.sim.EntityState.prototype.colorLerp = gf.vec.Color.lerpUint32; + +// HACK: ensure things are included +goog.scope(function() { + gf.sim.EntityState.prototype.tmpVec3[0] = + gf.sim.EntityState.prototype.tmpVec3[1]; + gf.sim.EntityState.prototype.qslerp( + gf.sim.EntityState.prototype.tmpQuat, + gf.sim.EntityState.prototype.tmpQuat, + 0, + gf.sim.EntityState.prototype.tmpQuat); + gf.sim.EntityState.prototype.colorLerp(0, 0, 0); +}); diff --git a/src/gf/sim/variable.js b/src/gf/sim/variable.js index bea7f0d..e34b1fd 100644 --- a/src/gf/sim/variable.js +++ b/src/gf/sim/variable.js @@ -21,6 +21,9 @@ goog.provide('gf.sim.Variable'); goog.provide('gf.sim.VariableFlag'); +goog.require('gf.net.PacketReader'); +goog.require('gf.net.PacketWriter'); +goog.require('gf.vec.Color'); goog.require('goog.vec.Quaternion'); goog.require('goog.vec.Vec3'); @@ -72,37 +75,47 @@ gf.sim.Variable.prototype.clone = goog.abstractMethod; /** - * Reads the variable. - * @param {!Object} target Target object. - * @param {!gf.net.PacketReader} reader Packet reader. + * Gets a source code statement for read. + * Used by the JIT system in the variable table. + * @param {!Object} obj Representative object. + * @return {string} Source statement. */ -gf.sim.Variable.prototype.read = goog.abstractMethod; +gf.sim.Variable.prototype.getReadSource = goog.abstractMethod; /** - * Writes the variable. - * @param {!Object} target Target object. - * @param {!gf.net.PacketWriter} writer Packet writer. + * Gets a source code statement for write. + * Used by the JIT system in the variable table. + * @param {!Object} obj Representative object. + * @return {string} Source statement. */ -gf.sim.Variable.prototype.write = goog.abstractMethod; +gf.sim.Variable.prototype.getWriteSource = goog.abstractMethod; /** - * Copies the value from one object to another. - * @param {!Object} source Source object. - * @param {!Object} target Target object. + * Gets a source code statement for copy. + * Used by the JIT system in the variable table. + * @param {!Object} obj Representative object. + * @return {string} Source statement. */ -gf.sim.Variable.prototype.copy = goog.abstractMethod; +gf.sim.Variable.prototype.getCopySource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + return 'target.' + setter + '(source.' + getter + '());'; +}; /** - * Interpolates the value between the given two states. - * @param {!Object} source Interpolation source object. - * @param {!Object} target Interpolation target object. - * @param {number} t Interpolation coefficient, [0-1]. - * @param {!Object} result Storage object. + * Gets a source code statement for interpolate. + * Used by the JIT system in the variable table. + * @param {!Object} obj Representative object. + * @return {string} Source statement. */ -gf.sim.Variable.prototype.interpolate = goog.abstractMethod; +gf.sim.Variable.prototype.getInterpolateSource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + return 'result.' + setter + '(target.' + getter + '());'; +}; /** @@ -138,6 +151,25 @@ gf.sim.Variable.sortByPriority = function(a, b) { }; +/** + * Gets the compiled name of a member on an object. + * This looks up by member value, so only use with known-good values. + * @private + * @param {!Object} obj Representative object. + * @param {!Object} memberValue Member value. + * @return {string?} Member name, if found. + */ +gf.sim.Variable.getCompiledFunctionName_ = function(obj, memberValue) { + for (var name in obj) { + if (obj[name] === memberValue) { + return name; + } + } + goog.asserts.fail('member not found'); + return null; +}; + + /** * Bitmask values describing the behavior of variables. * @enum {number} @@ -210,36 +242,47 @@ gf.sim.Variable.Integer.prototype.clone = function() { /** * @override */ -gf.sim.Variable.Integer.prototype.read = function(target, reader) { - this.setter_.call(target, reader.readVarInt()); +gf.sim.Variable.Integer.prototype.getReadSource = function(obj) { + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + var reader = gf.net.PacketReader.getSharedReader(); + var readFn = gf.sim.Variable.getCompiledFunctionName_( + reader, reader.readVarInt); + return 'target.' + setter + '(reader.' + readFn + '());'; }; /** * @override */ -gf.sim.Variable.Integer.prototype.write = function(target, writer) { - writer.writeVarInt(this.getter_.call(target) | 0); +gf.sim.Variable.Integer.prototype.getWriteSource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var writer = gf.net.PacketWriter.getSharedWriter(); + var writeFn = gf.sim.Variable.getCompiledFunctionName_( + writer, writer.writeVarInt); + return 'writer.' + writeFn + '(target.' + getter + '() | 0);'; }; /** * @override */ -gf.sim.Variable.Integer.prototype.copy = function(source, target) { - this.setter_.call(target, this.getter_.call(source) | 0); +gf.sim.Variable.Integer.prototype.getCopySource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + return 'target.' + setter + '(source.' + getter + '() | 0);'; }; /** * @override */ -gf.sim.Variable.Integer.prototype.interpolate = function(source, target, t, - result) { - var sourceValue = this.getter_.call(source) | 0; - var targetValue = this.getter_.call(target) | 0; - this.setter_.call(result, - (sourceValue + t * (targetValue - sourceValue)) | 0); +gf.sim.Variable.Integer.prototype.getInterpolateSource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + return '' + + 'var _s = source.' + getter + '();' + + 'var _t = target.' + getter + '();' + + 'target.' + setter + '((_s + t * (_t - _s)) | 0);'; }; @@ -284,35 +327,37 @@ gf.sim.Variable.Float.prototype.clone = function() { /** * @override */ -gf.sim.Variable.Float.prototype.read = function(target, reader) { - this.setter_.call(target, reader.readFloat32()); -}; - - -/** - * @override - */ -gf.sim.Variable.Float.prototype.write = function(target, writer) { - writer.writeFloat32(this.getter_.call(target)); +gf.sim.Variable.Float.prototype.getReadSource = function(obj) { + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + var reader = gf.net.PacketReader.getSharedReader(); + var readFn = gf.sim.Variable.getCompiledFunctionName_( + reader, reader.readFloat32); + return 'target.' + setter + '(reader.' + readFn + '());'; }; /** * @override */ -gf.sim.Variable.Float.prototype.copy = function(source, target) { - this.setter_.call(target, this.getter_.call(source)); +gf.sim.Variable.Float.prototype.getWriteSource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var writer = gf.net.PacketWriter.getSharedWriter(); + var writeFn = gf.sim.Variable.getCompiledFunctionName_( + writer, writer.writeFloat32); + return 'writer.' + writeFn + '(target.' + getter + '());'; }; /** * @override */ -gf.sim.Variable.Float.prototype.interpolate = function(source, target, t, - result) { - var sourceValue = this.getter_.call(source); - var targetValue = this.getter_.call(target); - this.setter_.call(result, sourceValue + t * (targetValue - sourceValue)); +gf.sim.Variable.Float.prototype.getInterpolateSource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + return '' + + 'var _s = source.' + getter + '();' + + 'var _t = target.' + getter + '();' + + 'result.' + setter + '((_s + t * (_t - _s)));'; }; @@ -359,52 +404,46 @@ gf.sim.Variable.Vec3.prototype.clone = function() { /** * @override */ -gf.sim.Variable.Vec3.prototype.read = function(target, reader) { - var v = gf.sim.Variable.Vec3.tmp_; - reader.readVec3(v); - this.setter_.call(target, v); -}; - - -/** - * @override - */ -gf.sim.Variable.Vec3.prototype.write = function(target, writer) { - writer.writeVec3(this.getter_.call(target)); +gf.sim.Variable.Vec3.prototype.getReadSource = function(obj) { + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + var reader = gf.net.PacketReader.getSharedReader(); + var readFn = gf.sim.Variable.getCompiledFunctionName_( + reader, reader.readVec3Temp); + return 'target.' + setter + '(reader.' + readFn + '());'; }; /** * @override */ -gf.sim.Variable.Vec3.prototype.copy = function(source, target) { - this.setter_.call(target, this.getter_.call(source)); +gf.sim.Variable.Vec3.prototype.getWriteSource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var writer = gf.net.PacketWriter.getSharedWriter(); + var writeFn = gf.sim.Variable.getCompiledFunctionName_( + writer, writer.writeVec3); + return 'writer.' + writeFn + '(target.' + getter + '());'; }; /** * @override */ -gf.sim.Variable.Vec3.prototype.interpolate = function(source, target, t, - result) { - var v = gf.sim.Variable.Vec3.tmp_; - goog.vec.Vec3.lerp( - this.getter_.call(source), - this.getter_.call(target), - t, - v); - this.setter_.call(result, v); +gf.sim.Variable.Vec3.prototype.getInterpolateSource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + var tmpVec3 = gf.sim.Variable.getCompiledFunctionName_(obj, + gf.sim.Variable.tmpVec3); + return '' + + 'var _sv3 = source.' + getter + '();' + + 'var _tv3 = target.' + getter + '();' + + 'var _rv3 = source.' + tmpVec3 + ';' + + '_rv3[0] = (_tv3[0] - _sv3[0]) * t + _sv3[0];' + + '_rv3[1] = (_tv3[1] - _sv3[1]) * t + _sv3[1];' + + '_rv3[2] = (_tv3[2] - _sv3[2]) * t + _sv3[2];' + + 'result.' + setter + '(_rv3);'; }; -/** - * Scratch Vec3 for math. - * @private - * @type {!goog.vec.Vec3.Float32} - */ -gf.sim.Variable.Vec3.tmp_ = goog.vec.Vec3.createFloat32(); - - /** * Variable containing a floating-point quaternion. @@ -458,63 +497,45 @@ gf.sim.Variable.Quaternion.prototype.clone = function() { /** * @override */ -gf.sim.Variable.Quaternion.prototype.read = function(target, reader) { - var q = gf.sim.Variable.Quaternion.tmp_; - // if (this.normalized_) { - // // Reconstruct w - // reader.readVec3(q); - // // Trick is from http://www.gamedev.net/topic/461253-compressed-quaternions/ - // // Known to have issues - may not be worth it - // q[3] = Math.sqrt(1 - q[0] * q[0] + q[1] * q[1] + q[2] * q[2]); - // } else { - reader.readVec4(q); - this.setter_.call(target, q); -}; - - -/** - * @override - */ -gf.sim.Variable.Quaternion.prototype.write = function(target, writer) { - // if (this.normalized_) { - // // Just ignore w - // writer.writeVec3(this.getter_.call(target)); - // } else { - writer.writeVec4(this.getter_.call(target)); +gf.sim.Variable.Quaternion.prototype.getReadSource = function(obj) { + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + var reader = gf.net.PacketReader.getSharedReader(); + var readFn = gf.sim.Variable.getCompiledFunctionName_( + reader, reader.readVec4Temp); + return 'target.' + setter + '(reader.' + readFn + '());'; }; /** * @override */ -gf.sim.Variable.Quaternion.prototype.copy = function(source, target) { - this.setter_.call(target, this.getter_.call(source)); +gf.sim.Variable.Quaternion.prototype.getWriteSource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var writer = gf.net.PacketWriter.getSharedWriter(); + var writeFn = gf.sim.Variable.getCompiledFunctionName_( + writer, writer.writeVec4); + return 'writer.' + writeFn + '(target.' + getter + '());'; }; /** * @override */ -gf.sim.Variable.Quaternion.prototype.interpolate = function(source, target, t, - result) { - var q = gf.sim.Variable.Quaternion.tmp_; - goog.vec.Quaternion.slerp( - this.getter_.call(source), - this.getter_.call(target), - t, - q); - this.setter_.call(result, q); +gf.sim.Variable.Quaternion.prototype.getInterpolateSource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + var tmpQuat = gf.sim.Variable.getCompiledFunctionName_(obj, + gf.sim.Variable.tmpQuat); + var slerp = gf.sim.Variable.getCompiledFunctionName_(obj, + goog.vec.Quaternion.slerp); + return '' + + 'var _rq = source.' + tmpQuat + ';' + + 'source.' + slerp + + '(source.' + getter + '(), target.' + getter + '(), t, _rq);' + + 'result.' + setter + '(_rq);'; }; -/** - * Scratch Quaternion for math. - * @private - * @type {!goog.vec.Quaternion.Float32} - */ -gf.sim.Variable.Quaternion.tmp_ = goog.vec.Quaternion.createFloat32(); - - /** * Variable containing an ARGB color. @@ -556,50 +577,38 @@ gf.sim.Variable.Color.prototype.clone = function() { /** * @override */ -gf.sim.Variable.Color.prototype.read = function(target, reader) { - this.setter_.call(target, reader.readFloat32()); +gf.sim.Variable.Color.prototype.getReadSource = function(obj) { + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + var reader = gf.net.PacketReader.getSharedReader(); + var readFn = gf.sim.Variable.getCompiledFunctionName_( + reader, reader.readUint32); + return 'target.' + setter + '(reader.' + readFn + '());'; }; /** * @override */ -gf.sim.Variable.Color.prototype.write = function(target, writer) { - writer.writeFloat32(this.getter_.call(target)); +gf.sim.Variable.Color.prototype.getWriteSource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var writer = gf.net.PacketWriter.getSharedWriter(); + var writeFn = gf.sim.Variable.getCompiledFunctionName_( + writer, writer.writeUint32); + return 'writer.' + writeFn + '(target.' + getter + '());'; }; /** * @override */ -gf.sim.Variable.Color.prototype.copy = function(source, target) { - this.setter_.call(target, this.getter_.call(source)); -}; - - -/** - * @override - */ -gf.sim.Variable.Color.prototype.interpolate = function(source, target, t, - result) { - // There has got to be a better way... - // Knowing that t = [0,1], I'm sure it's possible to do this in two mults - var sourceValue = this.getter_.call(source); - var sourceA = (sourceValue >> 24) & 0xFF; - var sourceB = (sourceValue >> 16) & 0xFF; - var sourceG = (sourceValue >> 8) & 0xFF; - var sourceR = sourceValue & 0xFF; - var targetValue = this.getter_.call(target); - var targetA = (sourceValue >> 24) & 0xFF; - var targetB = (sourceValue >> 16) & 0xFF; - var targetG = (sourceValue >> 8) & 0xFF; - var targetR = sourceValue & 0xFF; - var value = - ((sourceA + t * (targetA - sourceA)) & 0xFF) << 24 | - ((sourceB + t * (targetB - sourceB)) & 0xFF) << 16 | - ((sourceG + t * (targetG - sourceG)) & 0xFF) << 8 | - ((sourceR + t * (targetR - sourceR)) & 0xFF); - this.setter_.call(result, value); +gf.sim.Variable.Color.prototype.getInterpolateSource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + var lerp = gf.sim.Variable.getCompiledFunctionName_(obj, + gf.vec.Color.lerpUint32); + return '' + + 'result.' + setter + '(source.' + lerp + '(' + + 'source.' + getter + '(), target.' + getter + '(), t));'; }; @@ -644,34 +653,24 @@ gf.sim.Variable.String.prototype.clone = function() { /** * @override */ -gf.sim.Variable.String.prototype.read = function(target, reader) { - this.setter_.call(target, reader.readString()); -}; - - -/** - * @override - */ -gf.sim.Variable.String.prototype.write = function(target, writer) { - writer.writeString(this.getter_.call(target)); -}; - - -/** - * @override - */ -gf.sim.Variable.String.prototype.copy = function(source, target) { - this.setter_.call(target, this.getter_.call(source)); +gf.sim.Variable.String.prototype.getReadSource = function(obj) { + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + var reader = gf.net.PacketReader.getSharedReader(); + var readFn = gf.sim.Variable.getCompiledFunctionName_( + reader, reader.readString); + return 'target.' + setter + '(reader.' + readFn + '());'; }; /** * @override */ -gf.sim.Variable.String.prototype.interpolate = function(source, target, t, - result) { - // Instantaneous to target - this.setter_.call(result, this.getter_.call(target)); +gf.sim.Variable.String.prototype.getWriteSource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var writer = gf.net.PacketWriter.getSharedWriter(); + var writeFn = gf.sim.Variable.getCompiledFunctionName_( + writer, writer.writeString); + return 'writer.' + writeFn + '(target.' + getter + '());'; }; @@ -716,34 +715,24 @@ gf.sim.Variable.UserID.prototype.clone = function() { /** * @override */ -gf.sim.Variable.UserID.prototype.read = function(target, reader) { - this.setter_.call(target, reader.readString()); +gf.sim.Variable.UserID.prototype.getReadSource = function(obj) { + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + var reader = gf.net.PacketReader.getSharedReader(); + var readFn = gf.sim.Variable.getCompiledFunctionName_( + reader, reader.readString); + return 'target.' + setter + '(reader.' + readFn + '());'; }; /** * @override */ -gf.sim.Variable.UserID.prototype.write = function(target, writer) { - writer.writeString(this.getter_.call(target)); -}; - - -/** - * @override - */ -gf.sim.Variable.UserID.prototype.copy = function(source, target) { - this.setter_.call(target, this.getter_.call(source)); -}; - - -/** - * @override - */ -gf.sim.Variable.UserID.prototype.interpolate = function(source, target, t, - result) { - // Instantaneous to target - this.setter_.call(result, this.getter_.call(target)); +gf.sim.Variable.UserID.prototype.getWriteSource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var writer = gf.net.PacketWriter.getSharedWriter(); + var writeFn = gf.sim.Variable.getCompiledFunctionName_( + writer, writer.writeString); + return 'writer.' + writeFn + '(target.' + getter + '());'; }; @@ -788,32 +777,37 @@ gf.sim.Variable.EntityID.prototype.clone = function() { /** * @override */ -gf.sim.Variable.EntityID.prototype.read = function(target, reader) { - this.setter_.call(target, reader.readVarUint()); +gf.sim.Variable.EntityID.prototype.getReadSource = function(obj) { + var setter = gf.sim.Variable.getCompiledFunctionName_(obj, this.setter_); + var reader = gf.net.PacketReader.getSharedReader(); + var readFn = gf.sim.Variable.getCompiledFunctionName_( + reader, reader.readVarUint); + return 'target.' + setter + '(reader.' + readFn + '());'; }; /** * @override */ -gf.sim.Variable.EntityID.prototype.write = function(target, writer) { - writer.writeVarUint(this.getter_.call(target)); +gf.sim.Variable.EntityID.prototype.getWriteSource = function(obj) { + var getter = gf.sim.Variable.getCompiledFunctionName_(obj, this.getter_); + var writer = gf.net.PacketWriter.getSharedWriter(); + var writeFn = gf.sim.Variable.getCompiledFunctionName_( + writer, writer.writeVarUint); + return 'writer.' + writeFn + '(target.' + getter + '());'; }; + /** - * @override + * Scratch Vec3 for math. + * @type {!goog.vec.Vec3.Float32} */ -gf.sim.Variable.EntityID.prototype.copy = function(source, target) { - this.setter_.call(target, this.getter_.call(source)); -}; +gf.sim.Variable.tmpVec3 = goog.vec.Vec3.createFloat32(); /** - * @override + * Scratch Quaternion for math. + * @type {!goog.vec.Quaternion.Float32} */ -gf.sim.Variable.EntityID.prototype.interpolate = function(source, target, t, - result) { - // Instantaneous to target - this.setter_.call(result, this.getter_.call(target)); -}; +gf.sim.Variable.tmpQuat = goog.vec.Quaternion.createFloat32(); diff --git a/src/gf/sim/variabletable.js b/src/gf/sim/variabletable.js index 068f5ae..531081d 100644 --- a/src/gf/sim/variabletable.js +++ b/src/gf/sim/variabletable.js @@ -37,8 +37,10 @@ goog.require('goog.asserts'); * * @constructor * @param {!Array.} variableList A list of all variables. + * @param {!Object} obj A representative object with an initialized prototype + * chain. Used for JITing. */ -gf.sim.VariableTable = function(variableList) { +gf.sim.VariableTable = function(variableList, obj) { // Sort by priority in a stable way // We do this with a hack of using the ordinals if the two priorities are the // same @@ -57,34 +59,36 @@ gf.sim.VariableTable = function(variableList) { this.ordinalLookup_ = {}; /** - * Variables that are neither interpolated or predicted. + * A table of variable read functions. + * These are JITed functions that read variables into a value. + * Array order matches variable order. * @private - * @type {!Array.} + * @type {!Array.} */ - this.immediateVariables_ = []; + this.readTable_ = new Array(variableList.length); /** - * Variables that have their {@see gf.sim.VariableFlag#PREDICTED} bit set. + * A table of variable write functions. + * These are JITed functions that write variables into a value. + * Array order matches variable order. * @private - * @type {!Array.} + * @type {!Array.} */ - this.predictedVariables_ = []; - - /** - * Variables that have their {@see gf.sim.VariableFlag#INTERPOLATED} bit set. - * @private - * @type {!Array.} - */ - this.interpolatedVariables_ = []; - - /** - * Variables that have their {@see gf.sim.VariableFlag#INTERPOLATED} bit set - * but not their {@see gf.sim.VariableFlag#PREDICTED} bit set. This is used - * for interpolating variables on clients that have prediction enabled. - * @private - * @type {!Array.} - */ - this.interpolatedNotPredictedVariables_ = []; + this.writeTable_ = new Array(variableList.length); + + // Here lie black magic + // This code carefully constructs Functions while being mindful of the Closure + // Compiler renaming rules - it does this through some clever hacks that will + // likely break with ambiguation enabled - I hope that doesn't happen. + // This should be fairly efficient, and since vtables are shared across all + // entities of a given type this is a one-time per-entity type hit. + var readAllVariablesFn = ''; + var writeAllVariablesFn = ''; + var copyVariablesFn = ''; + var copyImmediateVariablesFn = ''; + var copyPredictedVariablesFn = ''; + var interpolateVariablesFn = ''; + var interpolateUnpredictedVariablesFn = ''; /** * List, in sorted ordinal order, of all variables. @@ -101,21 +105,104 @@ gf.sim.VariableTable = function(variableList) { // Assign ordinal v.ordinal = n; - // Add to fast access arrays + // Read/write table + this.readTable_[n] = new Function( + 'target', 'reader', v.getReadSource(obj)); + this.writeTable_[n] = new Function( + 'target', 'writer', v.getWriteSource(obj)); + + // Add function JIT per-variable statements + readAllVariablesFn += v.getReadSource(obj); + writeAllVariablesFn += v.getWriteSource(obj); + var copySource = v.getCopySource(obj); + copyVariablesFn += copySource; if (!(v.flags & ( gf.sim.VariableFlag.PREDICTED | gf.sim.VariableFlag.INTERPOLATED))) { - this.immediateVariables_.push(v); + copyImmediateVariablesFn += copySource; } if (v.flags & gf.sim.VariableFlag.PREDICTED) { - this.predictedVariables_.push(v); + copyPredictedVariablesFn += copySource; } if (v.flags & gf.sim.VariableFlag.INTERPOLATED) { - this.interpolatedVariables_.push(v); + var interpolateSource = v.getInterpolateSource(obj); + interpolateVariablesFn += interpolateSource; if (!(v.flags & gf.sim.VariableFlag.PREDICTED)) { - this.interpolatedNotPredictedVariables_.push(v); + interpolateUnpredictedVariablesFn += interpolateSource; } } } + + /** + * Reads all variables. + * @param {!Object} target Target object. + * @param {!gf.net.PacketReader} reader Packet reader. + */ + this.readAllVariables = new Function( + 'target', 'reader', readAllVariablesFn); + + /** + * Writes all variables. + * @param {!Object} target Target object. + * @param {!gf.net.PacketWriter} writer Packet writer. + */ + this.writeAllVariables = new Function( + 'target', 'writer', writeAllVariablesFn); + + /** + * Copies all variables from one object to another. + * @param {!Object} source Source object. + * @param {!Object} target Target object. + */ + this.copyVariables = new Function( + 'source', 'target', copyVariablesFn); + + /** + * Copies all immediate variables from one object to another (those variables + * that are not predicted/interpolated). + * @param {!Object} source Source object. + * @param {!Object} target Target object. + */ + this.copyImmediateVariables = new Function( + 'source', 'target', copyImmediateVariablesFn); + + /** + * Copies values of all predicted variables from one object to another. + * All values in the target with {@see gf.sim.VariableFlag#PREDICTED} set will + * get overwritten with the source values. + * @param {!Object} source Source object. + * @param {!Object} target Target object. + */ + this.copyPredictedVariables = new Function( + 'source', 'target', copyPredictedVariablesFn); + + /** + * Interpolates variables between two states. + * All values with {@see gf.sim.VariableFlag#INTERPOLATED} set will be + * interpolated between the source and target by the given time. The result + * will be stored on the given result object. + * @param {!Object} source Interpolation source object. + * @param {!Object} target Interpolation target object. + * @param {number} t Interpolation coefficient, [0-1]. + * @param {!Object} result Storage object. + */ + this.interpolateVariables = new Function( + 'source', 'target', 't', 'result', interpolateVariablesFn); + + /** + * Interpolates unpredicted variables between two states. + * All values with {@see gf.sim.VariableFlag#INTERPOLATED} set will be + * interpolated between the source and target by the given time. The result + * will be stored on the given result object. + * This version will ignore variables that also have + * {@see gf.sim.VariableFlag#PREDICTED} set on them, preventing interpolation + * from messing with the prediction system. + * @param {!Object} source Interpolation source object. + * @param {!Object} target Interpolation target object. + * @param {number} t Interpolation coefficient, [0-1]. + * @param {!Object} result Storage object. + */ + this.interpolateUnpredictedVariables = new Function( + 'source', 'target', 't', 'result', interpolateUnpredictedVariablesFn); }; @@ -141,140 +228,51 @@ gf.sim.VariableTable.prototype.getOrdinal = function(tag) { /** - * Reads the given variable. - * @param {number} ordinal Variable ordinal. - * @param {!Object} target Target object. - * @param {!gf.net.PacketReader} reader Packet reader. - */ -gf.sim.VariableTable.prototype.readVariable = function( - ordinal, target, reader) { - var v = this.variables_[ordinal]; - // NOTE: must validate here, as clients could send up bogus info - if (v) { - v.read(target, reader); - } -}; - - -/** - * Reads all variables. + * Reads a range of delta variables. + * This function is designed to be called on a subset of the variable range. + * For example, the first 32 variables, second 32, etc. + * @param {number} startingOrdinal Ordinal this range starts at. + * @param {number} presentBits Bit field indicating which variables are present. * @param {!Object} target Target object. * @param {!gf.net.PacketReader} reader Packet reader. */ -gf.sim.VariableTable.prototype.readAllVariables = function(target, reader) { - var vars = this.variables_; - for (var n = 0; n < vars.length; n++) { - vars[n].read(target, reader); +gf.sim.VariableTable.prototype.readPresentVariables = function( + startingOrdinal, presentBits, target, reader) { + var readTable = this.readTable_; + var ordinal = startingOrdinal; + while (presentBits) { + if (presentBits & 1) { + // Variable at is present and needs reading + // NOTE: must validate here, as clients could send up bogus info + if (readTable[ordinal]) { + readTable[ordinal](target, reader); + } + } + presentBits >>= 1; + ordinal++; } }; /** - * Writes the given variable. - * @param {number} ordinal Variable ordinal. + * Writes a range of delta variables. + * This function is designed to be called on a subset of the variable range. + * For example, the first 32 variables, second 32, etc. + * @param {number} startingOrdinal Ordinal this range starts at. + * @param {number} presentBits Bit field indicating which variables are present. * @param {!Object} target Target object. * @param {!gf.net.PacketWriter} writer Packet writer. */ -gf.sim.VariableTable.prototype.writeVariable = function( - ordinal, target, writer) { - var v = this.variables_[ordinal]; - v.write(target, writer); -}; - - -/** - * Writes all variables. - * @param {!Object} target Target object. - * @param {!gf.net.PacketWriter} writer Packet writer. - */ -gf.sim.VariableTable.prototype.writeAllVariables = function(target, writer) { - var vars = this.variables_; - for (var n = 0; n < vars.length; n++) { - vars[n].write(target, writer); - } -}; - - -/** - * Copies all variables from one object to another. - * @param {!Object} source Source object. - * @param {!Object} target Target object. - */ -gf.sim.VariableTable.prototype.copyVariables = function(source, target) { - var vars = this.variables_; - for (var n = 0; n < vars.length; n++) { - vars[n].copy(source, target); - } -}; - - -/** - * Copies all immediate variables from one object to another (those variables - * that are not predicted/interpolated). - * @param {!Object} source Source object. - * @param {!Object} target Target object. - */ -gf.sim.VariableTable.prototype.copyImmediateVariables = function( - source, target) { - var vars = this.immediateVariables_; - for (var n = 0; n < vars.length; n++) { - vars[n].copy(source, target); - } -}; - - -/** - * Copies values of all predicted variables from one object to another. - * All values in the target with {@see gf.sim.VariableFlag#PREDICTED} set will - * get overwritten with the source values. - * @param {!Object} source Source object. - * @param {!Object} target Target object. - */ -gf.sim.VariableTable.prototype.copyPredictedVariables = function( - source, target) { - var vars = this.predictedVariables_; - for (var n = 0; n < vars.length; n++) { - vars[n].copy(source, target); - } -}; - - -/** - * Interpolates variables between two states. - * All values with {@see gf.sim.VariableFlag#INTERPOLATED} set will be - * interpolated between the source and target by the given time. The result - * will be stored on the given result object. - * @param {!Object} source Interpolation source object. - * @param {!Object} target Interpolation target object. - * @param {number} t Interpolation coefficient, [0-1]. - * @param {!Object} result Storage object. - */ -gf.sim.VariableTable.prototype.interpolateVariables = function( - source, target, t, result) { - var vars = this.interpolatedVariables_; - for (var n = 0; n < vars.length; n++) { - vars[n].interpolate(source, target, t, result); - } -}; - - -/** - * Interpolates unpredicted variables between two states. - * All values with {@see gf.sim.VariableFlag#INTERPOLATED} set will be - * interpolated between the source and target by the given time. The result - * will be stored on the given result object. - * This version will ignore variables that also have - * {@see gf.sim.VariableFlag#PREDICTED} set on them, preventing interpolation - * from messing with the prediction system. - * @param {!Object} source Interpolation source object. - * @param {!Object} target Interpolation target object. - * @param {number} t Interpolation coefficient, [0-1]. - * @param {!Object} result Storage object. - */ -gf.sim.VariableTable.prototype.interpolateUnpredictedVariables = function( - source, target, t, result) { - var vars = this.interpolatedNotPredictedVariables_; - for (var n = 0; n < vars.length; n++) { - vars[n].interpolate(source, target, t, result); +gf.sim.VariableTable.prototype.writePresentVariables = function( + startingOrdinal, presentBits, target, writer) { + var writeTable = this.writeTable_; + var ordinal = startingOrdinal; + while (presentBits) { + if (presentBits & 1) { + // Variable at is dirty and needs writing + writeTable[ordinal](target, writer); + } + presentBits >>= 1; + ordinal++; } }; diff --git a/src/gf/vec/color.js b/src/gf/vec/color.js index a28ee84..418783c 100644 --- a/src/gf/vec/color.js +++ b/src/gf/vec/color.js @@ -35,3 +35,30 @@ gf.vec.Color.toUint32 = function(value) { var a = Math.min(255, value[3] * 255); return (a << 24) | (b << 16) | (g << 8) | r; }; + + +/** + * Linear interpolation between two color values. + * @param {number} source Source Uint32 color as ABGR. + * @param {number} target Target Uint32 color as ABGR. + * @param {number} t Interpolation value, [0-1]. + * @return {number} Result ABGR. + */ +gf.vec.Color.lerpUint32 = function(source, target, t) { + // There has got to be a better way... + // Knowing that t = [0,1], I'm sure it's possible to do this in two mults + var sourceA = (source >> 24) & 0xFF; + var sourceB = (source >> 16) & 0xFF; + var sourceG = (source >> 8) & 0xFF; + var sourceR = source & 0xFF; + var targetA = (target >> 24) & 0xFF; + var targetB = (target >> 16) & 0xFF; + var targetG = (target >> 8) & 0xFF; + var targetR = target & 0xFF; + var result = + ((sourceA + t * (targetA - sourceA)) & 0xFF) << 24 | + ((sourceB + t * (targetB - sourceB)) & 0xFF) << 16 | + ((sourceG + t * (targetG - sourceG)) & 0xFF) << 8 | + ((sourceR + t * (targetR - sourceR)) & 0xFF); + return result; +};