define(["./Util", "./Constants"], function (Util, constants) {
"use strict";
var utf8Decode = Util.utf8Decode,
utf8Encode = Util.utf8Encode,
DEBUG = constants._DEBUG,
fail = Util.fail,
paddingArray = new Uint8Array(4);
/**
* Chunk data format object
* @class ChunkData
* @namespace kick.core
* @constructor
*/
return function () {
var MAGIC_NUMBER = 0xF001,
VERSION_NUMBER = 1,
Float32ArrayType = 1,
Float64ArrayType = 2,
Int16ArrayType = 3,
Int32ArrayType = 4,
Int8ArrayType = 5,
Uint16ArrayType = 6,
Uint32ArrayType = 7,
Uint8ArrayType = 8,
Chunk = function (chunkId, chunkType, chunkDataLength, data) {
var thisObj = this;
this.chunkId = chunkId;
this.chunkType = chunkType;
this.chunkDataLength = chunkDataLength; // contains the actual data
this.data = data; // data is assumed to have the length
Object.defineProperties(this, {
paddingSize: {
get: function () {
var dataSize = thisObj.data.length * thisObj.data.BYTES_PER_ELEMENT,
dataSizeMod4 = dataSize % 8;
if (dataSizeMod4) {
return 8 - dataSizeMod4;
}
return 0;
}
},
paddingData: {
get: function () {
return paddingArray.subarray(0, thisObj.paddingSize);
}
}
});
},
thisObj = this,
chunks = [],
/**
* Return header size in bytes
* @method getHeaderSize
* @private
*/
getHeaderSize = function () {
return 2 + // magic number
2 + // version number
4; // number of chunks
},
/**
* Return chunks size in bytes
* @method
* @private
*/
getChunksSize = function () {
var sum = 0,
chunkHeaderLength = 8,
i;
for (i = 0; i < chunks.length; i++) {
sum += chunks[i].chunkDataLength +
chunkHeaderLength +
chunks[i].paddingSize;
}
return sum;
},
getTypeEnum = function (array) {
if (array instanceof Float32Array) { return Float32ArrayType; }
if (array instanceof Float64Array) { return Float64ArrayType; }
if (array instanceof Int16Array) { return Int16ArrayType; }
if (array instanceof Int32Array) { return Int32ArrayType; }
if (array instanceof Int8Array) { return Int8ArrayType; }
if (array instanceof Uint16Array) { return Uint16ArrayType; }
if (array instanceof Uint8Array) { return Uint8ArrayType; }
return null;
},
getTypeClass = function (id) {
if (id === Float32ArrayType) { return Float32Array; }
if (id === Float64ArrayType) { return Float64Array; }
if (id === Int16ArrayType) { return Int16Array; }
if (id === Int32ArrayType) { return Int32Array; }
if (id === Int8ArrayType) { return Int8Array; }
if (id === Uint16ArrayType) { return Uint16Array; }
if (id === Uint8ArrayType) { return Uint8Array; }
return null;
};
/**
* Size of chunkdata in bytes. Note that the data is added padding so it always fit into a double array.
* @method getSize
*/
this.getSize = function () {
var size = getHeaderSize() + getChunksSize(),
remainder = size % 8;
if (remainder !== 0) {
size += 8 - remainder;
}
return size;
};
/**
* @method serialize
* @return ArrayBuffer
*/
this.serialize = function () {
var output = new ArrayBuffer(thisObj.getSize()),
byteOffset = 0,
uint8View = new Uint8Array(output, 0),
uint16View = new Uint16Array(output, byteOffset),
uint32View,
i,
ViewType,
view;
uint16View[0] = MAGIC_NUMBER;
uint16View[1] = VERSION_NUMBER;
byteOffset += 4;
uint32View = new Uint32Array(output, byteOffset);
uint32View[0] = chunks.length;
byteOffset += 4;
for (i = 0; i < chunks.length; i++) {
uint16View = new Uint16Array(output, byteOffset);
uint16View[0] = chunks[i].chunkId;
uint16View[1] = chunks[i].chunkType;
byteOffset += 4;
uint32View = new Uint32Array(output, byteOffset);
uint32View[0] = chunks[i].chunkDataLength;
byteOffset += 4;
ViewType = getTypeClass(chunks[i].chunkType);
view = new ViewType(output);
view.set(chunks[i].data, byteOffset / view.BYTES_PER_ELEMENT);
byteOffset += chunks[i].chunkDataLength;
uint8View.set(chunks[i].paddingData, byteOffset); // write padding data
byteOffset += chunks[i].paddingSize;
}
return output;
};
/**
* @method get
* @param {Number} chunkid
* @return TypedArrayView[Number]
*/
this.get = function (chunkid) {
var i;
for (i = 0; i < chunks.length; i++) {
if (chunks[i].chunkId === chunkid) {
return chunks[i].data;
}
}
return null;
};
/**
* @method getString
* @param {Number} chunkid
* @return String or null
*/
this.getString = function (chunkid) {
var value = thisObj.get(chunkid);
if (value) {
return utf8Decode(value);
}
return null;
};
/**
* @method getNumber
* @param {Number} chunkid
* @return String or null
*/
this.getNumber = function (chunkid) {
var value = thisObj.get(chunkid);
if (value) {
return value[0];
}
return null;
};
/**
* @method getArrayBuffer
* @param {Number} chunkid
* @return ArrayBuffer or null if not found
*/
this.getArrayBuffer = function (chunkid) {
var value = thisObj.get(chunkid),
arrayBuffer,
res;
if (value) {
arrayBuffer = new ArrayBuffer(value.length * value.BYTES_PER_ELEMENT);
res = new Uint8Array(arrayBuffer);
res.set(value);
return arrayBuffer;
}
return null;
};
/**
* @method remove
* @param {Number} chunkid
* @return Boolean true when deleted
*/
this.remove = function (chunkid) {
var i;
for (i = 0; i < chunks.length; i++) {
if (chunks[i].chunkId === chunkid) {
chunks = chunks.splice(i, 1);
return true;
}
}
return false;
};
/**
* @method setString
* @param {Number} chunkId
* @param {String} str
*/
this.setString = function (chunkId, str) {
var array = utf8Encode(str);
thisObj.set(chunkId, array);
};
/**
* Uses a Float32Array for storing the number. Note that potentially precision can get lost.
* @method setNumber
* @param {Number} chunkId
* @param {Number} num
*/
this.setNumber = function (chunkId, num) {
var array = new Float32Array([num]);
thisObj.set(chunkId, array);
};
/**
* @method setArrayBuffer
* @param {Number} chunkId
* @param {ArrayBuffer} arrayBuffer
*/
this.setArrayBuffer = function (chunkId, arrayBuffer) {
thisObj.set(chunkId, new Uint8Array(arrayBuffer));
};
/**
* Note that this method saves a reference to the array (it does not copy data)
* @method set
* @param {Number} chunkId
* @param {TypedArrayView} array
*/
this.set = function (chunkId, array) {
thisObj.remove(chunkId);
var chunkType = getTypeEnum(array),
lengthBytes;
if (chunkType) {
lengthBytes = array.length * array.BYTES_PER_ELEMENT;
chunks.push(new Chunk(chunkId, chunkType, lengthBytes, array));
} else if (DEBUG) {
fail("Unsupported array type");
}
};
/**
* Loads the binary data into the object
* @method deserialize
* @param {ArrayBuffer} binaryData
* @return {boolean} success
*/
this.deserialize = function (binaryData) {
if (!(binaryData instanceof ArrayBuffer)) {
if (DEBUG) {
fail("binaryData is not instanceof ArrayBuffer");
}
return false;
}
var newChunks = [],
byteOffset = 0,
uint16View = new Uint16Array(binaryData, byteOffset),
uint32View,
chunksLength,
i,
chunkId,
chunkType,
chunkDataLength,
DataType,
data,
chunk;
if (uint16View[0] !== MAGIC_NUMBER || uint16View[1] !== VERSION_NUMBER) {
if (DEBUG) {
if (uint16View[0] !== MAGIC_NUMBER) {
fail("Invalid magic number");
} else {
fail("Unsupported version number");
}
}
return false;
}
byteOffset += 4;
uint32View = new Uint32Array(binaryData, byteOffset);
chunksLength = uint32View[0];
byteOffset += 4;
for (i = 0; i < chunksLength; i++) {
uint16View = new Uint16Array(binaryData, byteOffset);
chunkId = uint16View[0];
chunkType = uint16View[1];
byteOffset += 4;
uint32View = new Uint32Array(binaryData, byteOffset);
chunkDataLength = uint32View[0];
byteOffset += 4;
DataType = getTypeClass(chunkType);
data = new DataType(binaryData, byteOffset, chunkDataLength / DataType.BYTES_PER_ELEMENT);
chunk = new Chunk(chunkId, chunkType, chunkDataLength, data);
newChunks.push(chunk);
byteOffset += chunkDataLength;
byteOffset += chunk.paddingSize; // skip padding data
}
chunks = newChunks;
return true;
};
};
});