Index: lib/typedObjects/objectTypes.js |
=================================================================== |
--- a/lib/typedObjects/objectTypes.js |
+++ b/lib/typedObjects/objectTypes.js |
@@ -106,94 +106,175 @@ function createSetter(typeId, offset) |
{ |
typeId = typeId | 0; |
offset = offset | 0; |
let views = Array.prototype.slice.call(arguments, 2); |
let reference = new Reference(types, views); |
return function(value) |
{ |
- if (value && value.typeId != typeId) |
+ if (value && !isInstance(typeId, value)) |
throw new Error("Incompatible type"); |
reference.bufferIndex = this.bufferIndex | 0; |
reference.byteOffset = (this.byteOffset | 0) + offset; |
if (value) |
{ |
reference.typeId = value.typeId; |
reference.targetBufferIndex = value.bufferIndex; |
reference.targetByteOffset = value.byteOffset; |
} |
else |
reference.typeId = -1; |
}; |
} |
-function ObjectType(properties, meta) |
+/** |
+ * Overridden methods get the respective method of the superclass as the first |
+ * parameter. This function will create a wrapper function for the method that |
+ * forwards all arguments to the actual methods but also injects super as first |
+ * parameter. |
+ */ |
+function createSubclassMethod(method, super_) |
+{ |
+ return function() |
+ { |
+ let args = [].slice.apply(arguments); |
+ args.unshift(() => super_.apply(this, arguments)); |
+ return method.apply(this, args); |
+ }; |
+} |
+ |
+function extend(parentTypeInfo, typeDescriptor, meta) |
{ |
if (typeof meta != "object" || meta == null) |
meta = {}; |
- let propList = []; |
- let proto = {}; |
+ let properties = Object.create(parentTypeInfo && parentTypeInfo.properties); |
+ |
+ // Methods have to be actually copied here, prototypes won't work correctly |
+ // with Object.defineProperties(). |
+ let methods = Object.create(null); |
+ if (parentTypeInfo) |
+ for (let key in parentTypeInfo.methods) |
+ methods[key] = parentTypeInfo.methods[key]; |
+ |
let maxReferenceLength = TypedReference.byteLength | 0; |
- for (let name in properties) |
+ for (let name in typeDescriptor) |
{ |
- let type = properties[name]; |
+ let type = typeDescriptor[name]; |
if (type && typeof type.referenceLength == "number") |
{ |
+ if (name in methods) |
+ throw new Error("Property " + name + " masks a method with the same name"); |
+ if (name in properties) |
+ { |
+ if (properties[name] == type) |
+ continue; |
+ else |
+ throw new Error("Cannot redefine type of property " + name + " in subclass"); |
+ } |
+ |
// Property with type |
- propList.push([name, type]); |
+ properties[name] = type; |
let referenceLength = type.referenceLength | 0; |
if (referenceLength > maxReferenceLength) |
maxReferenceLength = referenceLength; |
} |
else if (typeof type == "function") |
{ |
// Method |
- Object.defineProperty(proto, name, fixedPropertyDescriptor(type)); |
+ if (name in properties) |
+ throw new Error("Method " + name + " masks a property with the same name"); |
+ |
+ if (name in methods) |
+ type = createSubclassMethod(type, methods[name].value); |
+ methods[name] = fixedPropertyDescriptor(type); |
} |
else |
throw new Error("Unrecognized type " + type + " given for property " + name); |
} |
+ let proto = {}; |
let buffers = []; |
let viewTypes = []; |
let views = []; |
- let byteLength = defineProperties(proto, propList, viewTypes, views, 0); |
+ let byteLength = defineProperties(proto, properties, viewTypes, views, 0); |
+ Object.defineProperties(proto, methods); |
// Round up to be a multiple of the maximal property size |
byteLength = ((byteLength - 1) | (maxReferenceLength - 1)) + 1; |
// We need to be able to store a typed reference in the object's buffer |
byteLength = Math.max(byteLength, TypedReference.byteLength) | 0; |
let typedReferenceViews = getViewsForType(TypedReference, viewTypes, views); |
+ // Take constructor from meta parameter, allow calling superclass constructor. |
+ let constructor = parentTypeInfo && parentTypeInfo.constructor; |
+ if (meta.hasOwnProperty("constructor") && typeof meta.constructor == "function") |
+ { |
+ if (constructor) |
+ constructor = createSubclassMethod(meta.constructor, constructor); |
+ else |
+ constructor = meta.constructor; |
+ } |
+ |
let typeId = types.length | 0; |
let typeInfo = { |
byteLength: byteLength, |
bufferSize: "bufferSize" in meta ? Math.max(meta.bufferSize | 0, 2) : 128, |
firstFree: new TypedReference(typeId, typedReferenceViews), |
proto: proto, |
+ properties: properties, |
+ methods: methods, |
buffers: buffers, |
viewTypes: viewTypes, |
views: views, |
typeId: typeId, |
- constructor: (meta.hasOwnProperty("constructor") && typeof meta.constructor == "function" ? meta.constructor : null) |
+ parentTypeInfo: parentTypeInfo, |
+ constructor: constructor |
}; |
let result = create.bind(typeInfo); |
Object.defineProperties(result, { |
byteLength: fixedPropertyDescriptor(byteLength), |
referenceLength: fixedPropertyDescriptor(Reference.byteLength), |
viewTypes: fixedPropertyDescriptor(Reference.viewTypes), |
typeId: fixedPropertyDescriptor(typeId), |
+ extend: fixedPropertyDescriptor(extend.bind(null, typeInfo)), |
+ isInstance: fixedPropertyDescriptor(isInstance.bind(null, typeId)), |
createGetter: fixedPropertyDescriptor(createGetter), |
createSetter: fixedPropertyDescriptor(createSetter.bind(null, typeId)) |
}); |
types.push(typeInfo); |
return result; |
} |
-exports.ObjectType = ObjectType; |
+ |
+function isInstance(typeId, obj) |
+{ |
+ typeId = typeId | 0; |
+ |
+ // TODO: This could be optimized by compiling the list of all subclasses for |
+ // each type up front. Question is whether this is worth it. |
+ let typeInfo = types[obj.typeId | 0]; |
+ while (typeInfo) |
+ { |
+ if ((typeInfo.typeId | 0) == typeId) |
+ return true; |
+ typeInfo = typeInfo.parentTypeInfo; |
+ } |
+ return false; |
+} |
+ |
+let ObjectBase = exports.ObjectBase = extend(null, { |
+ equals: function(obj) |
+ { |
+ if (!obj) |
+ return false; |
+ return this.typeId == obj.typeId && this.bufferIndex == obj.bufferIndex && this.byteOffset == obj.byteOffset; |
+ } |
+}, null); |
+ |
+exports.ObjectType = ObjectBase.extend; |