| 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 = null; |
| + if (meta.hasOwnProperty("constructor") && typeof meta.constructor == "function") |
| + { |
| + if (parentTypeInfo && parentTypeInfo.constructor) |
| + constructor = createSubclassMethod(meta.constructor, parentTypeInfo.constructor); |
| + else |
| + constructor = meta.constructor; |
| + } |
|
Wladimir Palant
2014/04/28 07:03:37
The logic here was wrong: if no constructor is def
|
| + |
| 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; |