Skip to content

Commit 111f83e

Browse files
committed
Change ReferenceProperty to return undefined when asked for values and the target entity (or target property) does not exist.
This differs from the previous behavior, which was to throw. Throwing can lead to situations where it is effectively impossible to delete and re-create a property which is referenced. In general, visualizers have always needed to be written to expect the values to properties to suddenly become undefined, which they generally deal with by hiding or removing the related primitives.
1 parent 0a45aef commit 111f83e

File tree

3 files changed

+110
-80
lines changed

3 files changed

+110
-80
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Change Log
2525
* Reduced Cesium bundle size by avoiding unnecessarily importing `Cesium3DTileset` in `Picking.js` [#8532](https://github.com/AnalyticalGraphicsInc/cesium/pull/8532)
2626
* Fixed WebGL warning message about `EXT_float_blend` being implicitly enabled. [#8534](https://github.com/AnalyticalGraphicsInc/cesium/pull/8534)
2727
* Fixed a bug where toggling point cloud classification visibility would result in a grey screen on Linux / Nvidia [#8538](https://github.com/AnalyticalGraphicsInc/cesium/pull/8538)
28+
* Fixed a crash when deleting and re-creating polylines from CZML. `ReferenceProperty` now returns undefined when the target entity or property does not exist, instead of throwing. [#8544](https://github.com/AnalyticalGraphicsInc/cesium/pull/8544)
2829

2930
### 1.65.0 - 2020-01-06
3031

Source/DataSources/ReferenceProperty.js

+37-54
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,37 @@ import defined from '../Core/defined.js';
22
import defineProperties from '../Core/defineProperties.js';
33
import DeveloperError from '../Core/DeveloperError.js';
44
import Event from '../Core/Event.js';
5-
import RuntimeError from '../Core/RuntimeError.js';
65
import Property from './Property.js';
76

8-
function resolveEntity(that) {
9-
var entityIsResolved = true;
10-
if (that._resolveEntity) {
11-
var targetEntity = that._targetCollection.getById(that._targetId);
7+
function resolve(that) {
8+
var targetProperty = that._targetProperty;
129

13-
if (defined(targetEntity)) {
14-
targetEntity.definitionChanged.addEventListener(ReferenceProperty.prototype._onTargetEntityDefinitionChanged, that);
15-
that._targetEntity = targetEntity;
16-
that._resolveEntity = false;
17-
} else {
18-
//The property has become detached. It has a valid value but is not currently resolved to an entity in the collection
19-
targetEntity = that._targetEntity;
20-
entityIsResolved = false;
21-
}
10+
if (!defined(targetProperty)) {
11+
var targetEntity = that._targetEntity;
2212

2313
if (!defined(targetEntity)) {
24-
throw new RuntimeError('target entity "' + that._targetId + '" could not be resolved.');
25-
}
26-
}
27-
return entityIsResolved;
28-
}
14+
targetEntity = that._targetCollection.getById(that._targetId);
2915

30-
function resolve(that) {
31-
var targetProperty = that._targetProperty;
16+
if (!defined(targetEntity)) {
17+
// target entity not found
18+
that._targetEntity = that._targetProperty = undefined;
19+
return;
20+
}
3221

33-
if (that._resolveProperty) {
34-
var entityIsResolved = resolveEntity(that);
22+
// target entity was found. listen for changes to entity definition
23+
targetEntity.definitionChanged.addEventListener(ReferenceProperty.prototype._onTargetEntityDefinitionChanged, that);
24+
that._targetEntity = targetEntity;
25+
}
3526

36-
var names = that._targetPropertyNames;
27+
// walk the list of property names and resolve properties
28+
var targetPropertyNames = that._targetPropertyNames;
3729
targetProperty = that._targetEntity;
38-
var length = names.length;
39-
for (var i = 0; i < length && defined(targetProperty); i++) {
40-
targetProperty = targetProperty[names[i]];
30+
for (var i = 0, len = targetPropertyNames.length; i < len && defined(targetProperty); ++i) {
31+
targetProperty = targetProperty[targetPropertyNames[i]];
4132
}
4233

43-
if (defined(targetProperty)) {
44-
that._targetProperty = targetProperty;
45-
that._resolveProperty = !entityIsResolved;
46-
} else if (!defined(that._targetProperty)) {
47-
throw new RuntimeError('targetProperty "' + that._targetId + '.' + names.join('.') + '" could not be resolved.');
48-
}
34+
// target property may or may not be defined, depending on if it was found
35+
that._targetProperty = targetProperty;
4936
}
5037

5138
return targetProperty;
@@ -118,8 +105,6 @@ import Property from './Property.js';
118105
this._targetProperty = undefined;
119106
this._targetEntity = undefined;
120107
this._definitionChanged = new Event();
121-
this._resolveEntity = true;
122-
this._resolveProperty = true;
123108

124109
targetCollection.collectionChanged.addEventListener(ReferenceProperty.prototype._onCollectionChanged, this);
125110
}
@@ -157,7 +142,8 @@ import Property from './Property.js';
157142
*/
158143
referenceFrame : {
159144
get : function() {
160-
return resolve(this).referenceFrame;
145+
var target = resolve(this);
146+
return defined(target) ? target.referenceFrame : undefined;
161147
}
162148
},
163149
/**
@@ -267,7 +253,8 @@ import Property from './Property.js';
267253
* @returns {Object} The modified result parameter or a new instance if the result parameter was not supplied.
268254
*/
269255
ReferenceProperty.prototype.getValue = function(time, result) {
270-
return resolve(this).getValue(time, result);
256+
var target = resolve(this);
257+
return defined(target) ? target.getValue(time, result) : undefined;
271258
};
272259

273260
/**
@@ -280,7 +267,8 @@ import Property from './Property.js';
280267
* @returns {Cartesian3} The modified result parameter or a new instance if the result parameter was not supplied.
281268
*/
282269
ReferenceProperty.prototype.getValueInReferenceFrame = function(time, referenceFrame, result) {
283-
return resolve(this).getValueInReferenceFrame(time, referenceFrame, result);
270+
var target = resolve(this);
271+
return defined(target) ? target.getValueInReferenceFrame(time, referenceFrame, result) : undefined;
284272
};
285273

286274
/**
@@ -291,7 +279,8 @@ import Property from './Property.js';
291279
* @returns {String} The type of material.
292280
*/
293281
ReferenceProperty.prototype.getType = function(time) {
294-
return resolve(this).getType(time);
282+
var target = resolve(this);
283+
return defined(target) ? target.getType(time) : undefined;
295284
};
296285

297286
/**
@@ -326,27 +315,21 @@ import Property from './Property.js';
326315
};
327316

328317
ReferenceProperty.prototype._onTargetEntityDefinitionChanged = function(targetEntity, name, value, oldValue) {
329-
if (this._targetPropertyNames[0] === name) {
330-
this._resolveProperty = true;
318+
if (defined(this._targetProperty) && this._targetPropertyNames[0] === name) {
319+
this._targetProperty = undefined;
331320
this._definitionChanged.raiseEvent(this);
332321
}
333322
};
334323

335324
ReferenceProperty.prototype._onCollectionChanged = function(collection, added, removed) {
336325
var targetEntity = this._targetEntity;
337-
if (defined(targetEntity)) {
338-
if (removed.indexOf(targetEntity) !== -1) {
339-
targetEntity.definitionChanged.removeEventListener(ReferenceProperty.prototype._onTargetEntityDefinitionChanged, this);
340-
this._resolveEntity = true;
341-
this._resolveProperty = true;
342-
} else if (this._resolveEntity) {
343-
//If targetEntity is defined but resolveEntity is true, then the entity is detached
344-
//and any change to the collection needs to incur an attempt to resolve in order to re-attach.
345-
//without this if block, a reference that becomes re-attached will not signal definitionChanged
346-
resolve(this);
347-
if (!this._resolveEntity) {
348-
this._definitionChanged.raiseEvent(this);
349-
}
326+
if (defined(targetEntity) && removed.indexOf(targetEntity) !== -1) {
327+
targetEntity.definitionChanged.removeEventListener(ReferenceProperty.prototype._onTargetEntityDefinitionChanged, this);
328+
this._targetEntity = this._targetProperty = undefined;
329+
} else if (!defined(targetEntity)) {
330+
targetEntity = resolve(this);
331+
if (defined(targetEntity)) {
332+
this._definitionChanged.raiseEvent(this);
350333
}
351334
}
352335
};

Specs/DataSources/ReferencePropertySpec.js

+72-26
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ describe('DataSources/ReferenceProperty', function() {
5858
var collection = new EntityCollection();
5959
collection.add(testObject);
6060

61-
//Basic property resolution
61+
// Basic property resolution
6262
var property = ReferenceProperty.fromString(collection, 'testId#billboard.scale');
6363
expect(property.referenceFrame).toBeUndefined();
6464
expect(property.isConstant).toEqual(true);
@@ -68,37 +68,37 @@ describe('DataSources/ReferenceProperty', function() {
6868
var listener = jasmine.createSpy('listener');
6969
property.definitionChanged.addEventListener(listener);
7070

71-
//Change to exist target property is reflected in reference.
71+
// A change to exist target property is reflected in reference.
7272
testObject.billboard.scale.setValue(6);
7373
expect(listener).toHaveBeenCalledWith(property);
7474
expect(property.isConstant).toEqual(true);
7575
expect(property.getValue(time)).toEqual(6);
7676
listener.calls.reset();
7777

78-
//Assignment of new leaf property to existing target is reflected in reference.
78+
// Assignment of new leaf property to existing target is reflected in reference.
7979
testObject.billboard.scale = new ConstantProperty(7);
8080
expect(listener).toHaveBeenCalledWith(property);
8181
expect(property.isConstant).toEqual(true);
8282
expect(property.getValue(time)).toEqual(7);
8383
listener.calls.reset();
8484

85-
//Assignment of non-leaf property to existing target is reflected in reference.
85+
// Assignment of non-leaf property to existing target is reflected in reference.
8686
testObject.billboard = new BillboardGraphics();
8787
testObject.billboard.scale = new ConstantProperty(8);
8888
expect(listener).toHaveBeenCalledWith(property);
8989
expect(property.isConstant).toEqual(true);
9090
expect(property.getValue(time)).toEqual(8);
9191
listener.calls.reset();
9292

93-
//Removing an object should cause the reference to be severed but maintain last value
93+
// Removing an object should cause the reference to be severed.
9494
collection.remove(testObject);
9595

96-
expect(listener).not.toHaveBeenCalledWith();
96+
expect(listener).not.toHaveBeenCalled();
9797
expect(property.isConstant).toEqual(true);
98-
expect(property.getValue(time)).toEqual(8);
98+
expect(property.getValue(time)).toBeUndefined();
9999
listener.calls.reset();
100100

101-
//adding a new object should re-wire the reference.
101+
// Adding a new object should re-wire the reference.
102102
var testObject2 = new Entity({
103103
id : 'testId'
104104
});
@@ -108,6 +108,18 @@ describe('DataSources/ReferenceProperty', function() {
108108
expect(listener).toHaveBeenCalledWith(property);
109109
expect(property.isConstant).toEqual(true);
110110
expect(property.getValue(time)).toEqual(9);
111+
112+
// setting the target property to undefined should cause the reference to be severed.
113+
testObject2.billboard.scale = undefined;
114+
expect(listener).toHaveBeenCalledWith(property);
115+
expect(property.isConstant).toEqual(true);
116+
expect(property.getValue(time)).toBeUndefined();
117+
118+
// Assigning a valid property should re-connect the reference.
119+
testObject2.billboard.scale = new ConstantProperty(10);
120+
expect(listener).toHaveBeenCalledWith(property);
121+
expect(property.isConstant).toEqual(true);
122+
expect(property.getValue(time)).toEqual(10);
111123
});
112124

113125
it('works with position properties', function() {
@@ -119,12 +131,18 @@ describe('DataSources/ReferenceProperty', function() {
119131
var collection = new EntityCollection();
120132
collection.add(testObject);
121133

122-
//Basic property resolution
134+
// Basic property resolution
123135
var property = ReferenceProperty.fromString(collection, 'testId#position');
124136
expect(property.isConstant).toEqual(true);
125137
expect(property.referenceFrame).toEqual(ReferenceFrame.FIXED);
126138
expect(property.getValue(time)).toEqual(testObject.position.getValue(time));
127139
expect(property.getValueInReferenceFrame(time, ReferenceFrame.INERTIAL)).toEqual(testObject.position.getValueInReferenceFrame(time, ReferenceFrame.INERTIAL));
140+
141+
property = ReferenceProperty.fromString(collection, 'nonExistent#position');
142+
expect(property.isConstant).toEqual(true);
143+
expect(property.referenceFrame).toBeUndefined();
144+
expect(property.getValue(time)).toBeUndefined();
145+
expect(property.getValueInReferenceFrame(time, ReferenceFrame.INERTIAL)).toBeUndefined();
128146
});
129147

130148
it('works with material properties', function() {
@@ -137,11 +155,17 @@ describe('DataSources/ReferenceProperty', function() {
137155
var collection = new EntityCollection();
138156
collection.add(testObject);
139157

140-
//Basic property resolution
158+
// Basic property resolution
141159
var property = ReferenceProperty.fromString(collection, 'testId#testMaterial');
142160
expect(property.isConstant).toEqual(true);
143161
expect(property.getType(time)).toEqual(testObject.testMaterial.getType(time));
144162
expect(property.getValue(time)).toEqual(testObject.testMaterial.getValue(time));
163+
164+
property = ReferenceProperty.fromString(collection, 'nonExistent#testMaterial');
165+
expect(property.isConstant).toEqual(true);
166+
expect(property.referenceFrame).toBeUndefined();
167+
expect(property.getType(time)).toBeUndefined();
168+
expect(property.getValue(time)).toBeUndefined();
145169
});
146170

147171
it('equals works', function() {
@@ -151,19 +175,19 @@ describe('DataSources/ReferenceProperty', function() {
151175
var right = ReferenceProperty.fromString(entityCollection, 'objectId#foo.bar');
152176
expect(left.equals(right)).toEqual(true);
153177

154-
//collection differs
178+
// collection differs
155179
right = ReferenceProperty.fromString(new EntityCollection(), 'objectId#foo.bar');
156180
expect(left.equals(right)).toEqual(false);
157181

158-
//target id differs
182+
// target id differs
159183
right = ReferenceProperty.fromString(entityCollection, 'otherObjectId#foo.bar');
160184
expect(left.equals(right)).toEqual(false);
161185

162-
//number of sub-properties differ
186+
// number of sub-properties differ
163187
right = ReferenceProperty.fromString(entityCollection, 'objectId#foo');
164188
expect(left.equals(right)).toEqual(false);
165189

166-
//sub-properties of same length differ
190+
// sub-properties of same length differ
167191
right = ReferenceProperty.fromString(entityCollection, 'objectId#foo.baz');
168192
expect(left.equals(right)).toEqual(false);
169193
});
@@ -195,6 +219,34 @@ describe('DataSources/ReferenceProperty', function() {
195219
expect(listener).not.toHaveBeenCalled();
196220
});
197221

222+
it('attaches to a target entity created later', function() {
223+
var collection = new EntityCollection();
224+
225+
var property = ReferenceProperty.fromString(collection, 'testId#billboard.scale');
226+
expect(property.resolvedProperty).toBeUndefined();
227+
228+
var listener = jasmine.createSpy('listener');
229+
property.definitionChanged.addEventListener(listener);
230+
231+
var otherObject = new Entity({
232+
id : 'other'
233+
});
234+
collection.add(otherObject);
235+
236+
expect(listener).not.toHaveBeenCalled();
237+
expect(property.resolvedProperty).toBeUndefined();
238+
239+
var testObject = new Entity({
240+
id : 'testId'
241+
});
242+
testObject.billboard = new BillboardGraphics();
243+
testObject.billboard.scale = new ConstantProperty(5);
244+
collection.add(testObject);
245+
246+
expect(listener).toHaveBeenCalledWith(property);
247+
expect(property.resolvedProperty).toBe(testObject.billboard.scale);
248+
});
249+
198250
it('constructor throws with undefined targetCollection', function() {
199251
expect(function() {
200252
return new ReferenceProperty(undefined, 'objectid', ['property']);
@@ -251,15 +303,13 @@ describe('DataSources/ReferenceProperty', function() {
251303
}).toThrowDeveloperError();
252304
});
253305

254-
it('throws RuntimeError if targetId can not be resolved', function() {
306+
it('getValue returns undefined if target entity can not be resolved', function() {
255307
var collection = new EntityCollection();
256308
var property = ReferenceProperty.fromString(collection, 'testId#foo.bar');
257-
expect(function() {
258-
property.getValue(time);
259-
}).toThrowRuntimeError();
309+
expect(property.getValue(time)).toBeUndefined();
260310
});
261311

262-
it('throws RuntimeError if property can not be resolved', function() {
312+
it('getValue returns undefined if target property can not be resolved', function() {
263313
var collection = new EntityCollection();
264314

265315
var testObject = new Entity({
@@ -268,12 +318,10 @@ describe('DataSources/ReferenceProperty', function() {
268318
collection.add(testObject);
269319

270320
var property = ReferenceProperty.fromString(collection, 'testId#billboard');
271-
expect(function() {
272-
property.getValue(time);
273-
}).toThrowRuntimeError();
321+
expect(property.getValue(time)).toBeUndefined();
274322
});
275323

276-
it('throws RuntimeError if sub-property can not be resolved', function() {
324+
it('getValue returns undefined if sub-property of target property can not be resolved', function() {
277325
var collection = new EntityCollection();
278326

279327
var testObject = new Entity({
@@ -283,8 +331,6 @@ describe('DataSources/ReferenceProperty', function() {
283331
collection.add(testObject);
284332

285333
var property = ReferenceProperty.fromString(collection, 'testId#billboard.foo');
286-
expect(function() {
287-
property.getValue(time);
288-
}).toThrowRuntimeError();
334+
expect(property.getValue(time)).toBeUndefined();
289335
});
290336
});

0 commit comments

Comments
 (0)