| Index: lib/typedObjects/utils.js | 
| =================================================================== | 
| --- a/lib/typedObjects/utils.js | 
| +++ b/lib/typedObjects/utils.js | 
| @@ -82,28 +82,66 @@ let getViewsForType = exports.getViewsFo | 
| index = (viewTypes.push(viewType) | 0) - 1; | 
| views.push([]); | 
| } | 
| result.push(views[index]); | 
| } | 
| return result; | 
| }; | 
|  | 
| + /** | 
| + * Creates a wrapper function for a setter that will call the watcher function | 
| + * with the new value of the property before executing the actual setter. | 
| + */ | 
| +function watchSetter(/**Function*/ setter, /**Function*/ watcher) /**Function*/ | 
| +{ | 
| +  return function(value) | 
| +  { | 
| +    setter.call(this, watcher.call(this, value)); | 
| +  } | 
| +} | 
| + | 
| +/** | 
| + * Creates a parameter-less wrapper function around a getter that will get | 
| + * bufferIndex and byteOffset parameters from object properties. | 
| + */ | 
| +function wrapGetter(/**Function*/ getter) /**Function*/ | 
| +{ | 
| +  return function() | 
| +  { | 
| +    return getter.call(this, this.bufferIndex, this.byteOffset); | 
| +  } | 
| +} | 
| + | 
| +/** | 
| + * Creates a wrapper function around a setter with value as the only parameter, | 
| + * the bufferIndex and byteOffset parameters will be retrieved from object | 
| + * properties. | 
| + */ | 
| +function wrapSetter(/**Function*/ setter) /**Function*/ | 
| +{ | 
| +  return function(value) | 
| +  { | 
| +    return setter.call(this, this.bufferIndex, this.byteOffset, value); | 
| +  } | 
| +} | 
| + | 
| /** | 
| * Defines properties with given name and type on an object. | 
| * | 
| * @param obj object to define properties on | 
| * @param properties object mapping property names to their respective types | 
| * @param viewTypes see getViewsForType() | 
| * @param views see getViewsForType() | 
| * @param [offset] byte array offset at which the properties should start | 
| - * @param [cleanupValues] array of property/value combinations to be set when the object is created or destroyed | 
| + * @param [watchers] map of watcher functions to be called when a particular property is being set | 
| + * @param [initialValues] array of property/value combinations to be set when the object is created or destroyed | 
| * @return new start offset for additional properties | 
| */ | 
| -exports.defineProperties = function defineProperties(obj, properties, viewTypes, views, offset, cleanupValues) | 
| +exports.defineProperties = function defineProperties(obj, properties, viewTypes, views, offset, watchers, initialValues) | 
| { | 
| offset = offset | 0; | 
|  | 
| let propList = []; | 
| for (let name in properties) | 
| propList.push([name, properties[name]]); | 
|  | 
| // Put larger properties first to make sure alignment requirements are met. | 
| @@ -115,23 +153,125 @@ exports.defineProperties = function defi | 
| // Generates getters and setters for each property. | 
| let descriptors = {}; | 
| for (let i = 0, l = propList.length | 0; i < l; i++) | 
| { | 
| let [name, type] = propList[i]; | 
|  | 
| let viewParams = getViewsForType(type, viewTypes, views); | 
| descriptors[name] = { | 
| -      get: type.createGetter.apply(type, [offset].concat(viewParams)), | 
| -      set: type.createSetter.apply(type, [offset].concat(viewParams)), | 
| +      get: wrapGetter(type.createGetter.apply(type, [offset].concat(viewParams))), | 
| +      set: wrapSetter(type.createSetter.apply(type, [offset].concat(viewParams))), | 
| configurable: false, | 
| enumerable: true | 
| }; | 
| + | 
| +    if (watchers && typeof watchers[name] == "function") | 
| +      descriptors[name].set = watchSetter(descriptors[name].set, watchers[name]); | 
| + | 
| offset += type.referenceLength; | 
| -    if (cleanupValues && typeof type.cleanupValue != "undefined") | 
| -      cleanupValues.push([name, type.cleanupValue]); | 
| +    if (initialValues && typeof type.initialValue != "undefined") | 
| +      initialValues.push([name, type.initialValue]); | 
| } | 
|  | 
| // Define properties | 
| Object.defineProperties(obj, descriptors); | 
|  | 
| return offset; | 
| }; | 
| + | 
| +/** | 
| + * Creates a new array buffer and adds the necessary views. | 
| + * | 
| + * @param {Integer} byteSize  bytes to allocate for the buffer | 
| + * @param {Array} buffers  existing buffers (will be modified) | 
| + * @param {Array} viewTypes  view types for the buffers | 
| + * @param {Array} views  existing buffer views (will be modified) | 
| + * @result {Integer} index of the buffer created | 
| + */ | 
| +let addBuffer = exports.addBuffer = function(byteSize, buffers, viewTypes, views) | 
| +{ | 
| +  let buffer = new ArrayBuffer(byteSize | 0); | 
| +  buffers.push(buffer); | 
| +  for (let i = 0, l = viewTypes.length | 0; i < l; i++) | 
| +    views[i].push(new viewTypes[i](buffer)); | 
| +  return (buffers.length | 0) - 1; | 
| +} | 
| + | 
| +/** | 
| + * Releases an array buffer. | 
| + * | 
| + * @param {Integer} bufferIndex  index of the buffer to be released. | 
| + * @param {Array} buffers  existing buffers (will be modified) | 
| + * @param {Array} views  existing buffer views (will be modified) | 
| + */ | 
| +exports.removeBuffer = function(bufferIndex, buffers, views) | 
| +{ | 
| +  delete buffers[bufferIndex]; | 
| +  for (let i = 0, l = views.length | 0; i < l; i++) | 
| +    delete views[i][bufferIndex]; | 
| +} | 
| + | 
| +/** | 
| + * Allocates a new fixed-size element. It will return the first available free | 
| + * block or create a new buffer if the existing ones have no space left. | 
| + * | 
| + * @param {TypedReference} firstFree  head of the linked list pointing to unallocated elements | 
| + * @param {Integer} byteLength  size of an element | 
| + * @param {Integer} bufferSize  number of elements in a buffer | 
| + * @param {Array} buffers  existing buffers (might be modified in necessary) | 
| + * @param {Array} viewTypes  view types for the buffers | 
| + * @param {Array} views  existing buffer views (might be modified if necessary) | 
| + * @result {Array} [bufferIndex, byteOffset] parameters of the newly allocated block | 
| + */ | 
| +exports.alloc = function(firstFree, byteLength, bufferSize, buffers, viewTypes, views) | 
| +{ | 
| +  let bufferIndex = firstFree.bufferIndex | 0; | 
| +  let byteOffset = firstFree.byteOffset | 0; | 
| +  if (bufferIndex >= 0) | 
| +  { | 
| +    // There is still a free spot, simply move on firstFree reference | 
| +    [firstFree.bufferIndex, firstFree.byteOffset] = | 
| +        [firstFree.targetBufferIndex, firstFree.targetByteOffset]; | 
| +  } | 
| +  else | 
| +  { | 
| +    byteLength = byteLength | 0; | 
| +    bufferSize = bufferSize | 0; | 
| + | 
| +    // Create new buffer and use the first element of it | 
| +    bufferIndex = addBuffer(byteLength * bufferSize, buffers, viewTypes, views); | 
| +    byteOffset = 0; | 
| + | 
| +    // Mark last element of the new buffer as the last free spot | 
| +    firstFree.bufferIndex = bufferIndex; | 
| +    firstFree.byteOffset = (bufferSize - 1) * byteLength; | 
| +    firstFree.targetBufferIndex = -1; | 
| + | 
| +    // Make each remaining element of the new buffer point to the next one | 
| +    for (let i = bufferSize - 2; i >= 1; i--) | 
| +    { | 
| +      let nextByteOffset = firstFree.byteOffset; | 
| +      firstFree.byteOffset = nextByteOffset - byteLength; | 
| +      firstFree.targetBufferIndex = bufferIndex; | 
| +      firstFree.targetByteOffset = nextByteOffset; | 
| +    } | 
| +  } | 
| +  return [bufferIndex, byteOffset]; | 
| +}; | 
| + | 
| +/** | 
| + * Releases the block at given offset so that it can be allocated again. | 
| + * | 
| + * @param {TypedReference} firstFree  head of the linked list pointing to unallocated elements | 
| + * @param {Integer} bufferIndex  buffer index of the block to be released | 
| + * @param {Integer} byteOffset  byte offset o fthe block to be released | 
| + */ | 
| +exports.dealloc = function(firstFree, bufferIndex, byteOffset) | 
| +{ | 
| +  let oldFreeBufferIndex = firstFree.bufferIndex | 0; | 
| +  let oldFreeByteOffset = firstFree.byteOffset | 0; | 
| + | 
| +  firstFree.bufferIndex = bufferIndex | 0; | 
| +  firstFree.byteOffset = byteOffset | 0; | 
| +  firstFree.targetBufferIndex = oldFreeBufferIndex; | 
| +  firstFree.targetByteOffset = oldFreeByteOffset; | 
| +} | 
|  |