Skip to content

Commit

Permalink
refine phet-io state handling, see #42
Browse files Browse the repository at this point in the history
  • Loading branch information
jbphet committed Sep 28, 2024
1 parent 9759958 commit 7ff909b
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 45 deletions.
6 changes: 5 additions & 1 deletion js/coins/view/CoinsExperimentSceneView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<NodeOptions, 'tandem'>;
Expand Down Expand Up @@ -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
} );

Expand Down
87 changes: 52 additions & 35 deletions js/coins/view/MultiCoinTestBox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<HBoxOptions, 'tandem'>;
Expand Down Expand Up @@ -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<string>, 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';
}
} );
}

Expand Down
8 changes: 6 additions & 2 deletions js/coins/view/MultipleCoinAnimations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 );
Expand All @@ -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 );
Expand Down
8 changes: 6 additions & 2 deletions js/coins/view/SingleCoinAnimations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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( () => {
Expand All @@ -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( () => {
Expand Down
7 changes: 2 additions & 5 deletions js/common/model/TwoStateSystemSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ export default class TwoStateSystemSet<T extends string> 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<T | null>;

// The number of systems that will be measured each time a measurement is made.
Expand Down Expand Up @@ -79,6 +78,7 @@ export default class TwoStateSystemSet<T extends string> 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, {
Expand Down Expand Up @@ -201,9 +201,6 @@ export default class TwoStateSystemSet<T extends string> extends PhetioObject {
valueType: TwoStateSystemSet,
stateSchema: {
measuredValues: ArrayIO( NullableIO( StringIO ) )
},
applyState: ( twoStateSystemSet: TwoStateSystemSet<string>, stateObject: TwoStateSystemSetStateObject ) => {
stateObject.measuredValues.forEach( ( value, i ) => { twoStateSystemSet.measuredValues[ i ] = value; } );
}
}
);
Expand Down

0 comments on commit 7ff909b

Please sign in to comment.