Index: src/object-observe.js |
diff --git a/src/object-observe.js b/src/object-observe.js |
index 1c147d95e30f9e82d45797d500b2d32bb198b064..c9b1c88363ae65524cfa878424c2feb2f77314d2 100644 |
--- a/src/object-observe.js |
+++ b/src/object-observe.js |
@@ -67,51 +67,131 @@ function CreateObjectInfo(object) { |
changeObservers: new InternalArray, |
notifier: null, |
inactiveObservers: new InternalArray, |
- performing: { __proto__: null }, |
+ performing: 0, |
performingCount: 0, |
}; |
objectInfoMap.set(object, info); |
return info; |
} |
-var defaultAcceptTypes = { |
- __proto__: null, |
- 'new': true, |
- 'updated': true, |
- 'deleted': true, |
- 'prototype': true, |
- 'reconfigured': true |
-}; |
+var typeConstants = { __proto__: null }; |
+var nextTypeConstant = 1; |
+var MAX_BITFIELD_CONSTANT = 1 << 30; |
+ |
+function NormalizeTypeMap(typeMap) { |
+ if (IS_NUMBER(typeMap)) { |
+ var bitfield = typeMap; |
+ var typeMap = []; |
+ var constant = 1; |
+ while (true) { |
+ if (bitfield & constant) |
+ typeMap[constant] = 1; |
+ if (constant == MAX_BITFIELD_CONSTANT) |
+ break; |
+ constant = constant << 1; |
+ } |
+ } |
-function CreateObserver(callback, accept) { |
- var observer = { |
- __proto__: null, |
- callback: callback, |
- accept: defaultAcceptTypes |
- }; |
+ return typeMap; |
+} |
- if (IS_UNDEFINED(accept)) |
- return observer; |
+function GetTypeConstant(type) { |
+ var constant = typeConstants[type]; |
+ if (!constant) { |
+ constant = nextTypeConstant; |
+ typeConstants[type] = constant; |
+ nextTypeConstant = nextTypeConstant < MAX_BITFIELD_CONSTANT ? |
+ nextTypeConstant << 1 : nextTypeConstant + 1; |
+ } |
- var acceptMap = { __proto__: null }; |
- for (var i = 0; i < accept.length; i++) |
- acceptMap[accept[i]] = true; |
+ return constant; |
+} |
+ |
+function AddToTypeMap(typeMap, type, ignoreDuplicate) { |
+ var typeConstant = GetTypeConstant(type); |
+ if (IS_NUMBER(typeMap) && |
+ typeConstant <= MAX_BITFIELD_CONSTANT && |
+ (ignoreDuplicate || !(typeMap & typeConstant))) { |
+ typeMap |= typeConstant; |
+ return typeMap; |
+ } |
- observer.accept = acceptMap; |
- return observer; |
+ typeMap = NormalizeTypeMap(typeMap); |
+ var value = ignoreDuplicate ? 1 : (typeMap[typeConstant] || 0) + 1; |
+ typeMap[typeConstant] = value; |
+ return typeMap; |
} |
-function ObserverIsActive(observer, objectInfo) { |
- if (objectInfo.performingCount === 0) |
- return true; |
+function RemoveFromTypeMap(typeMap, type) { |
+ var typeConstant = GetTypeConstant(type); |
+ if (IS_NUMBER(typeMap)) { |
+ if (typeConstant <= MAX_BITFIELD_CONSTANT && (typeMap & typeConstant)) |
+ typeMap -= typeConstant; |
+ } else if (typeMap[typeConstant] > 0) { |
+ typeMap[typeConstant]--; |
+ } |
- var performing = objectInfo.performing; |
- for (var type in performing) { |
- if (performing[type] > 0 && observer.accept[type]) |
- return false; |
+ return typeMap; |
+} |
+ |
+function CreateTypeMap(typeList) { |
+ var typeMap = 0; |
+ for (var i = 0; i < typeList.length; i++) { |
+ typeMap = AddToTypeMap(typeMap, typeList[i], true); |
} |
- return true; |
+ return typeMap; |
+} |
+ |
+function TypeMapHasType(typeMap, type) { |
+ return TypeMapHasConstant(typeMap, GetTypeConstant(type)); |
+} |
+ |
+function TypeMapHasConstant(typeMap, constant) { |
+ if (IS_NUMBER(typeMap)) |
+ return constant > MAX_BITFIELD_CONSTANT ? false : typeMap & constant; |
+ else |
+ return typeMap[constant] > 0; |
+} |
+ |
+function TypeMapsIntersect(typeMap1, typeMap2) { |
+ if (IS_NUMBER(typeMap1) && IS_NUMBER(typeMap2)) |
+ return typeMap1 & typeMap2; |
+ |
+ var checkMap = IS_NUMBER(typeMap1) ? typeMap1 : typeMap2; |
+ var iterateMap = IS_NUMBER(typeMap1) ? typeMap2 : typeMap1; |
+ |
+ for (var constant in iterateMap) { |
+ if (TypeMapHasConstant(checkMap, constant)) |
+ return true; |
+ } |
+ |
+ return false; |
+} |
+ |
+var defaultAcceptTypes = CreateTypeMap([ |
+ 'new', |
+ 'updated', |
+ 'deleted', |
+ 'prototype', |
+ 'reconfigured' |
+]); |
+ |
+GetTypeConstant('splice'); |
+ |
+ |
+function CreateObserver(callback, acceptList) { |
+ return { |
+ __proto__: null, |
+ callback: callback, |
+ acceptMap: IS_UNDEFINED(acceptList) ? |
+ defaultAcceptTypes : CreateTypeMap(acceptList) |
+ }; |
+} |
+ |
+function ObserverIsActive(observer, objectInfo) { |
+ return objectInfo.performingCount === 0 ? |
+ true : !TypeMapsIntersect(objectInfo.performing, observer.acceptMap); |
} |
function ObserverIsInactive(observer, objectInfo) { |
@@ -148,22 +228,47 @@ function RepartitionObservers(conditionFn, from, to, objectInfo) { |
RemoveNullElements(from); |
} |
-function BeginPerformChange(objectInfo, type) { |
- objectInfo.performing[type] = (objectInfo.performing[type] || 0) + 1; |
+function ObjectAddPerformingType(objectInfo, type) { |
+ if (!objectInfo.performing) { |
+ objectInfo.performing = AddToTypeMap(0, type); |
+ objectInfo.performingCount = 1; |
+ return true; |
+ } |
+ |
+ var hadType = TypeMapHasType(objectInfo.performing, type); |
+ objectInfo.performing = AddToTypeMap(objectInfo.performing, type); |
objectInfo.performingCount++; |
- RepartitionObservers(ObserverIsInactive, |
- objectInfo.changeObservers, |
- objectInfo.inactiveObservers, |
- objectInfo); |
+ return !hadType; |
} |
-function EndPerformChange(objectInfo, type) { |
- objectInfo.performing[type]--; |
+function ObjectRemovePerformingType(objectInfo, type) { |
+ if (objectInfo.performingCount == 1) { |
+ objectInfo.performing = 0; |
+ objectInfo.performingCount = 0; |
+ return true; |
+ } |
+ |
+ objectInfo.performing = RemoveFromTypeMap(objectInfo.performing, type); |
objectInfo.performingCount--; |
- RepartitionObservers(ObserverIsActive, |
- objectInfo.inactiveObservers, |
- objectInfo.changeObservers, |
- objectInfo); |
+ return !TypeMapHasType(objectInfo.performing, type); |
+} |
+ |
+function BeginPerformChange(objectInfo, type) { |
+ if (ObjectAddPerformingType(objectInfo, type)) { |
+ RepartitionObservers(ObserverIsInactive, |
+ objectInfo.changeObservers, |
+ objectInfo.inactiveObservers, |
+ objectInfo); |
+ } |
+} |
+ |
+function EndPerformChange(objectInfo, type) { |
+ if (ObjectRemovePerformingType(objectInfo, type)) { |
+ RepartitionObservers(ObserverIsActive, |
+ objectInfo.inactiveObservers, |
+ objectInfo.changeObservers, |
+ objectInfo); |
+ } |
} |
function EnsureObserverRemoved(objectInfo, callback) { |
@@ -214,14 +319,14 @@ function NormalizeCallbackInfo(callback) { |
return callbackInfo; |
} |
-function ObjectObserve(object, callback, accept) { |
+function ObjectObserve(object, callback, acceptList) { |
if (!IS_SPEC_OBJECT(object)) |
throw MakeTypeError("observe_non_object", ["observe"]); |
if (!IS_SPEC_FUNCTION(callback)) |
throw MakeTypeError("observe_non_function", ["observe"]); |
if (ObjectIsFrozen(callback)) |
throw MakeTypeError("observe_callback_frozen"); |
- if (!AcceptArgIsValid(accept)) |
+ if (!AcceptArgIsValid(acceptList)) |
throw MakeTypeError("observe_accept_invalid"); |
EnsureCallbackPriority(callback); |
@@ -234,7 +339,7 @@ function ObjectObserve(object, callback, accept) { |
EnsureObserverRemoved(objectInfo, callback); |
- var observer = CreateObserver(callback, accept); |
+ var observer = CreateObserver(callback, acceptList); |
if (ObserverIsActive(observer, objectInfo)) |
objectInfo.changeObservers.push(observer); |
else |
@@ -275,16 +380,18 @@ function EnqueueToCallback(callback, changeRecord) { |
%SetObserverDeliveryPending(); |
} |
+function ObserverAcceptsType(observer, type) { |
+ return TypeMapHasType(observer.acceptMap, type); |
+} |
+ |
function EnqueueChangeRecord(changeRecord, observers) { |
// TODO(rossberg): adjust once there is a story for symbols vs proxies. |
if (IS_SYMBOL(changeRecord.name)) return; |
for (var i = 0; i < observers.length; i++) { |
var observer = observers[i]; |
- if (IS_UNDEFINED(observer.accept[changeRecord.type])) |
- continue; |
- |
- EnqueueToCallback(observer.callback, changeRecord); |
+ if (ObserverAcceptsType(observer, changeRecord.type)) |
+ EnqueueToCallback(observer.callback, changeRecord); |
} |
} |