diff --git a/js/coins/view/CoinsExperimentSceneView.ts b/js/coins/view/CoinsExperimentSceneView.ts index 65f5d86..2f4840e 100644 --- a/js/coins/view/CoinsExperimentSceneView.ts +++ b/js/coins/view/CoinsExperimentSceneView.ts @@ -33,6 +33,7 @@ import CoinExperimentPreparationArea from './CoinExperimentPreparationArea.js'; import CoinNode from './CoinNode.js'; import InitialCoinStateSelectorNode from './InitialCoinStateSelectorNode.js'; import SmallCoinNode from './SmallCoinNode.js'; +import isSettingPhetioStateProperty from '../../../../tandem/js/isSettingPhetioStateProperty.js'; type SelfOptions = EmptySelfOptions; export type CoinsExperimentSceneViewOptions = SelfOptions & WithRequired; @@ -133,11 +134,14 @@ export default class CoinsExperimentSceneView extends Node { this.dividerMovementAnimation.stop(); } + // Set the animation time, but use zero if phet-io state is being set. + const dividerAnimationDuration = isSettingPhetioStateProperty.value ? 0 : 0.5; + // Start an animation to move the divider and the center positions of the activity areas. this.dividerMovementAnimation = new Animation( { property: this.dividerXPositionProperty, to: preparingExperiment ? DIVIDER_X_POSITION_DURING_PREPARATION : DIVIDER_X_POSITION_DURING_MEASUREMENT, - duration: 0.5, + duration: dividerAnimationDuration, easing: Easing.CUBIC_OUT } ); diff --git a/js/coins/view/MultiCoinTestBox.ts b/js/coins/view/MultiCoinTestBox.ts index a18f49a..537e066 100644 --- a/js/coins/view/MultiCoinTestBox.ts +++ b/js/coins/view/MultiCoinTestBox.ts @@ -22,6 +22,7 @@ import quantumMeasurement from '../../quantumMeasurement.js'; import { ExperimentMeasurementState } from '../model/ExperimentMeasurementState.js'; import SmallCoinNode from './SmallCoinNode.js'; import { MAX_COINS, MULTI_COIN_EXPERIMENT_QUANTITIES } from '../model/CoinsExperimentSceneModel.js'; +import isSettingPhetioStateProperty from '../../../../tandem/js/isSettingPhetioStateProperty.js'; type SelfOptions = EmptySelfOptions; export type MultiCoinTestBoxOptions = SelfOptions & PickRequired; @@ -92,46 +93,62 @@ export default class MultiCoinTestBox extends HBox { TEST_BOX_CONTENTS_HIDDEN_FILL; // Update the appearance of the coin nodes. - this.residentCoinNodes.forEach( ( coinNode, index ) => { - - if ( measurementState === 'measuredAndRevealed' ) { - - if ( coinNode.isFlipping ) { - coinNode.stopFlipping(); - } - - // Reveal the coin's state. - const state = coinSet.measuredValues[ index ]; - if ( state === null ) { - coinNode.displayModeProperty.value = 'masked'; - } - else if ( state === 'up' ) { - coinNode.displayModeProperty.value = 'up'; - } - else if ( state === 'down' ) { - coinNode.displayModeProperty.value = 'down'; - } - else if ( state === 'heads' ) { - coinNode.displayModeProperty.value = 'heads'; - } - else if ( state === 'tails' ) { - coinNode.displayModeProperty.value = 'tails'; - } - } - else if ( measurementState === 'preparingToBeMeasured' ) { + this.updateCoinNodes( coinSet, measurementState ); + } ); - assert && assert( !coinNode.isFlipping, 'coin node should not already be flipping' ); + // When phet-io state is being set, it is possible for the coin states to change without there being any change in + // the state of the experiment. The following code makes sure that coin nodes are updated if necessary. + isSettingPhetioStateProperty.lazyLink( isSettingPhetioState => { + if ( !isSettingPhetioState && measurementStateProperty.value === 'measuredAndRevealed' ) { + this.updateCoinNodes( coinSet, measurementStateProperty.value ); + } + } ); + } - coinNode.displayModeProperty.value = 'masked'; - coinNode.startFlipping(); + /** + * Update the appearance of the coin nodes. This is done in a batch rather than having separate model elements and + * nodes that automatically update because there can be many thousands of coins, so this approach is needed to get + * reasonable performance. + */ + private updateCoinNodes( coinSet: TwoStateSystemSet, measurementState: ExperimentMeasurementState ): void { + this.residentCoinNodes.forEach( ( coinNode, index ) => { + + if ( measurementState === 'measuredAndRevealed' ) { + + if ( coinNode.isFlipping ) { + coinNode.stopFlipping(); } - else if ( measurementState === 'readyToBeMeasured' ) { - if ( coinNode.isFlipping ) { - coinNode.stopFlipping(); - } + + const state = coinSet.measuredValues[ index ]; + if ( state === null ) { coinNode.displayModeProperty.value = 'masked'; } - } ); + else if ( state === 'up' ) { + coinNode.displayModeProperty.value = 'up'; + } + else if ( state === 'down' ) { + coinNode.displayModeProperty.value = 'down'; + } + else if ( state === 'heads' ) { + coinNode.displayModeProperty.value = 'heads'; + } + else if ( state === 'tails' ) { + coinNode.displayModeProperty.value = 'tails'; + } + } + else if ( measurementState === 'preparingToBeMeasured' ) { + + assert && assert( !coinNode.isFlipping, 'coin node should not already be flipping' ); + + coinNode.displayModeProperty.value = 'masked'; + coinNode.startFlipping(); + } + else if ( measurementState === 'readyToBeMeasured' ) { + if ( coinNode.isFlipping ) { + coinNode.stopFlipping(); + } + coinNode.displayModeProperty.value = 'masked'; + } } ); } diff --git a/js/coins/view/MultipleCoinAnimations.ts b/js/coins/view/MultipleCoinAnimations.ts index 687a7ec..8eea99a 100644 --- a/js/coins/view/MultipleCoinAnimations.ts +++ b/js/coins/view/MultipleCoinAnimations.ts @@ -19,6 +19,7 @@ import CoinExperimentMeasurementArea from './CoinExperimentMeasurementArea.js'; import CoinsExperimentSceneView from './CoinsExperimentSceneView.js'; import MultiCoinTestBox from './MultiCoinTestBox.js'; import SmallCoinNode from './SmallCoinNode.js'; +import isSettingPhetioStateProperty from '../../../../tandem/js/isSettingPhetioStateProperty.js'; const COIN_TRAVEL_ANIMATION_DURATION = MEASUREMENT_PREPARATION_TIME * 0.95; @@ -115,12 +116,15 @@ public readonly startIngressAnimationForCoinSet: ( forReprepare: boolean ) => vo // position. const destinationAtBoxEdge = leftOfTestAreaInParentCoords.plusXY( 0, finalDestinationOffset.y ); + // Determine the total animation duration, but use zero if phet-io state is being set. + const totalAnimationDuration = isSettingPhetioStateProperty.value ? 0 : COIN_TRAVEL_ANIMATION_DURATION; + // Create an animation that will move this coin node to the edge of the multi-coin test box. const animationToTestBoxEdge = new Animation( { setValue: value => { coinNode.center = value; }, getValue: () => coinNode.center, to: destinationAtBoxEdge, - duration: COIN_TRAVEL_ANIMATION_DURATION / 2, + duration: totalAnimationDuration / 2, easing: Easing.LINEAR } ); animationsToEdgeOfMultiCoinTestBox.push( animationToTestBoxEdge ); @@ -133,7 +137,7 @@ public readonly startIngressAnimationForCoinSet: ( forReprepare: boolean ) => vo setValue: value => { coinNode.center = value; }, getValue: () => coinNode.center, to: boxCenter.plus( finalDestinationOffset ), - duration: COIN_TRAVEL_ANIMATION_DURATION / 2, + duration: totalAnimationDuration / 2, easing: Easing.CUBIC_OUT } ); animationsFromEdgeOfMultiCoinBoxToInside.push( animationFromTestBoxEdgeToInside ); diff --git a/js/coins/view/SingleCoinAnimations.ts b/js/coins/view/SingleCoinAnimations.ts index 79ee16b..ba32c1b 100644 --- a/js/coins/view/SingleCoinAnimations.ts +++ b/js/coins/view/SingleCoinAnimations.ts @@ -30,6 +30,7 @@ import CoinNode from './CoinNode.js'; import CoinsExperimentSceneView from './CoinsExperimentSceneView.js'; import InitialCoinStateSelectorNode from './InitialCoinStateSelectorNode.js'; import QuantumCoinNode from './QuantumCoinNode.js'; +import isSettingPhetioStateProperty from '../../../../tandem/js/isSettingPhetioStateProperty.js'; const SINGLE_COIN_TEST_BOX_SIZE = new Dimension2( 165, 145 ); const COIN_FLIP_RATE = 3; // full flips per second @@ -132,6 +133,9 @@ export default class SingleCoinAnimations { // Make sure the coin mask is outside the test box so that it isn't visible until it slides into the test box. coinMask.x = -SINGLE_COIN_TEST_BOX_SIZE.width * 2; + // Determine the total animation duration, but use zero if phet-io state is being set. + const totalAnimationDuration = isSettingPhetioStateProperty.value ? 0 : COIN_TRAVEL_ANIMATION_DURATION; + // Create and start an animation to move the single coin to the side of the single coin test box. The entire // process consists of two animations, one to move the coin to the left edge of the test box while the test box is // potentially also moving, then a second one to move the coin into the box. The durations must be set up such @@ -143,7 +147,7 @@ export default class SingleCoinAnimations { setValue: value => { singleCoinNode!.center = value; }, getValue: () => singleCoinNode!.center, to: leftOfTestAreaInParentCoords, - duration: COIN_TRAVEL_ANIMATION_DURATION / 2, + duration: totalAnimationDuration / 2, easing: Easing.LINEAR } ); singleCoinAnimationFromPrepAreaToEdgeOfTestBox.finishEmitter.addListener( () => { @@ -167,7 +171,7 @@ export default class SingleCoinAnimations { }, getValue: () => assuredSingleCoinNode.center, to: measurementArea.localToParentPoint( singleCoinMeasurementArea.localToParentPoint( singleCoinTestBox.center ) ), - duration: COIN_TRAVEL_ANIMATION_DURATION / 2, + duration: totalAnimationDuration / 2, easing: Easing.CUBIC_OUT } ); singleCoinAnimationFromEdgeOfTestBoxToInside.finishEmitter.addListener( () => { diff --git a/js/common/model/TwoStateSystemSet.ts b/js/common/model/TwoStateSystemSet.ts index 049e016..0f72dfd 100644 --- a/js/common/model/TwoStateSystemSet.ts +++ b/js/common/model/TwoStateSystemSet.ts @@ -47,8 +47,7 @@ export default class TwoStateSystemSet extends PhetioObject { // valid values for a measurement public readonly validValues: readonly T[]; - // REVIEW: Any reason why here it's Array<> and above is T[]? https://github.com/phetsims/quantum-measurement/issues/20 - // the values of most recent measurement, null indicates indeterminate + // the values of most recent measurement, null indicates superposed (which only applies to quantum systems) public readonly measuredValues: Array; // The number of systems that will be measured each time a measurement is made. @@ -79,6 +78,7 @@ export default class TwoStateSystemSet extends PhetioObject { this.validValues = stateValues; + // TODO: This isn't how we should do this. See https://github.com/phetsims/quantum-measurement/issues/43. const initialNumberOfActiveSystems = options.maxNumberOfSystems === 1 ? 1 : MULTI_COIN_EXPERIMENT_QUANTITIES[ 1 ]; this.numberOfActiveSystemsProperty = new NumberProperty( initialNumberOfActiveSystems, { @@ -201,9 +201,6 @@ export default class TwoStateSystemSet extends PhetioObject { valueType: TwoStateSystemSet, stateSchema: { measuredValues: ArrayIO( NullableIO( StringIO ) ) - }, - applyState: ( twoStateSystemSet: TwoStateSystemSet, stateObject: TwoStateSystemSetStateObject ) => { - stateObject.measuredValues.forEach( ( value, i ) => { twoStateSystemSet.measuredValues[ i ] = value; } ); } } );