Skip to content

Commit

Permalink
general typescript review, #404
Browse files Browse the repository at this point in the history
  • Loading branch information
zepumph committed Feb 28, 2022
1 parent ffdca43 commit 010ffb4
Show file tree
Hide file tree
Showing 42 changed files with 275 additions and 798 deletions.
59 changes: 29 additions & 30 deletions js/common/model/RAPModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,30 +37,40 @@ const TOTAL_RANGE = rapConstants.TOTAL_RATIO_TERM_VALUE_RANGE;

class RAPModel {

// the current state of the ratio (value of terms, if its locked, etc)
ratio: RAPRatio;

// The desired ratio of the antecedent as compared to the consequent. As in 1:2. Initialized to default ratio
// so that we always start in-proportion.
targetRatioProperty: NumberProperty;

// How "correct" the proportion currently is. Max is RATIO_FITNESS_RANGE.max, but the min depends on the range of the
// ratio terms (see RAPRatio), and the current targetRatio value. Thus using this Property should likely be used with
// `RAPModel.getMinFitness()`. In most cases, this should not be used, since it isn't normalized. See
// `this.ratioFitnessProperty` for the preferred method of monitoring ratio fitness. This Property can be useful
// if you need to map feedback based on the entire range of fitness, and not just when the current ratio gets
// "close enough" to the target (since negative values in this Property are all clamped to 0 in this.ratioFitnessProperty).
unclampedFitnessProperty: IReadOnlyProperty<number>;

// How "correct" the proportion currently is. clamped within RATIO_FITNESS_RANGE. If at max (1), the proportion of
// the two ratio terms is exactly the value of the targetRatioProperty. If min (0), it is at or outside the tolerance
// threshold for some feedback in the view (like color/sound); in that case those will be omitted until the ratio is
// closer to the target. In general, this Property should be used to listen to the fitness of the current ratio. It
// is preferable to the unclampedFitnessProperty because it is normalized, and simpler when comparing the current ratio
// to the target ratio.
ratioFitnessProperty: IReadOnlyProperty<number>;

// whether the model is in its "in-proportion" state.
inProportionProperty: IReadOnlyProperty<boolean>;

public constructor( tandem: Tandem ) {
constructor( tandem: Tandem ) {

// @public - the current state of the ratio (value of terms, if its locked, etc)
this.ratio = new RAPRatio( 0.2, 0.4, tandem.createTandem( 'ratio' ) );

// @public - The desired ratio of the antecedent as compared to the consequent. As in 1:2. Initialized to default ratio
// so that we always start in-proportion.
this.targetRatioProperty = new NumberProperty( this.ratio.currentRatio, {
tandem: tandem.createTandem( 'targetRatioProperty' )
} );

// @public {DerivedProperty.<number>}
// How "correct" the proportion currently is. Max is RATIO_FITNESS_RANGE.max, but the min depends on the range of the
// ratio terms (see RAPRatio), and the current targetRatio value. Thus using this Property should likely be used with
// `RAPModel.getMinFitness()`. In most cases, this should not be used, since it isn't normalized. See
// `this.ratioFitnessProperty` for the preferred method of monitoring ratio fitness. This Property can be useful
// if you need to map feedback based on the entire range of fitness, and not just when the current ratio gets
// "close enough" to the target (since negative values in this Property are all clamped to 0 in this.ratioFitnessProperty).
this.unclampedFitnessProperty = new DerivedProperty( [
this.ratio.tupleProperty,
this.targetRatioProperty,
Expand Down Expand Up @@ -104,19 +114,11 @@ unclampedFitness: ${unclampedFitness}
phetioType: DerivedProperty.DerivedPropertyIO( NumberIO )
} );

// @public {DerivedProperty.<number>}
// How "correct" the proportion currently is. clamped within RATIO_FITNESS_RANGE. If at max (1), the proportion of
// the two ratio terms is exactly the value of the targetRatioProperty. If min (0), it is at or outside the tolerance
// threshold for some feedback in the view (like color/sound); in that case those will be omitted until the ratio is
// closer to the target. In general, this Property should be used to listen to the fitness of the current ratio. It
// is preferable to the unclampedFitnessProperty because it is normalized, and simpler when comparing the current ratio
// to the target ratio.
this.ratioFitnessProperty = new DerivedProperty( [ this.unclampedFitnessProperty ],
( unclampedFitness: number ) => Utils.clamp( unclampedFitness, rapConstants.RATIO_FITNESS_RANGE.min, rapConstants.RATIO_FITNESS_RANGE.max ), {
isValidValue: ( value: number ) => rapConstants.RATIO_FITNESS_RANGE.contains( value )
} );

// @public - whether or not the model is in its "in proportion" state.
this.inProportionProperty = new DerivedProperty<boolean, [ number, boolean ]>( [
this.unclampedFitnessProperty,
this.ratio.movingInDirectionProperty
Expand Down Expand Up @@ -188,7 +190,7 @@ unclampedFitness: ${unclampedFitness}
/**
* Get the minimum fitness value (unclamped) for the provided target ratio, based on the range of the ratio terms.
*/
public getMinFitness( ratio = this.targetRatioProperty.value ): number {
getMinFitness( ratio = this.targetRatioProperty.value ): number {
const minRatioFitness = Math.min( this.calculateFitness( TOTAL_RANGE.min, TOTAL_RANGE.max, ratio ),
this.calculateFitness( TOTAL_RANGE.min, TOTAL_RANGE.min, ratio ) );
const maxRatioFitness = Math.min( this.calculateFitness( TOTAL_RANGE.min, TOTAL_RANGE.max, ratio ),
Expand All @@ -200,13 +202,13 @@ unclampedFitness: ${unclampedFitness}
* If either value is smaller than a threshold, then the fitness cannot be at its max, "in-proportion" state. This function
* will return true when the model is in that state. When true, one or both value is too small to allow for a success state.
*/
public valuesTooSmallForInProportion(): boolean {
valuesTooSmallForInProportion(): boolean {
const currentTuple = this.ratio.tupleProperty.value;
return currentTuple.antecedent < rapConstants.NO_SUCCESS_VALUE_THRESHOLD ||
currentTuple.consequent < rapConstants.NO_SUCCESS_VALUE_THRESHOLD;
}

public getInProportionThreshold(): number {
getInProportionThreshold(): number {
let threshold = rapConstants.IN_PROPORTION_FITNESS_THRESHOLD;
if ( this.ratio.movingInDirectionProperty.value ) {
threshold = rapConstants.MOVING_IN_PROPORTION_FITNESS_THRESHOLD;
Expand All @@ -218,22 +220,19 @@ unclampedFitness: ${unclampedFitness}
* This is the sim's definition of if the ratio is in the "success" metric, what we call "in proportion." This changes
* based on if moving in proportion (bimodal interaction), or not. If fitness is provided, calculate if this fitness is in proportion
*/
public inProportion( fitness = this.ratioFitnessProperty.value ): boolean {
inProportion( fitness = this.ratioFitnessProperty.value ): boolean {
return fitness > rapConstants.RATIO_FITNESS_RANGE.max - this.getInProportionThreshold();
}

/**
* @override
*/
public step(): void {
step(): void {
this.ratio.step();
}

/**
* Given a ratioTerm, determine how the provided RatioTerm should change to, to make the current ratio equal to
* the target ratio.
*/
public getIdealValueForTerm( ratioTerm: RatioTerm ): number {
getIdealValueForTerm( ratioTerm: RatioTerm ): number {
if ( ratioTerm === RatioTerm.ANTECEDENT ) {
return this.targetRatioProperty.value * this.ratio.tupleProperty.value.consequent;
}
Expand All @@ -248,15 +247,15 @@ unclampedFitness: ${unclampedFitness}
* A special case in the model where the target ratio is not 1, but both ratio terms are even. This case is worth
* its own function because it often produces weird bugs in the view's output, see https://github.com/phetsims/ratio-and-proportion/issues/297 and https://github.com/phetsims/ratio-and-proportion/issues/299
*/
public ratioEvenButNotAtTarget(): boolean {
ratioEvenButNotAtTarget(): boolean {
return this.targetRatioProperty.value !== 1 &&
this.ratio.tupleProperty.value.antecedent === this.ratio.tupleProperty.value.consequent;
}

/**
* Resets the model.
*/
public reset(): void {
reset(): void {
this.ratio.reset(); // do this first

this.targetRatioProperty.reset();
Expand Down
92 changes: 31 additions & 61 deletions js/common/model/RAPRatio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,31 +38,35 @@ const LOCK_RATIO_RANGE_MIN = rapConstants.NO_SUCCESS_VALUE_THRESHOLD + Number.EP
class RAPRatio {

enabledRatioTermsRangeProperty: Property<Range>;

// Central Property that holds the value of the ratio. Using a tuple that holds
// both the antecedent and consequent values as a single data structure is vital for changing both hands at once, and
// in supporting the "locked ratio" state. Otherwise there are complicated reentrant cases where changing the
// antecedent cascades to the consequent to snap it back into ratio. Thus the creation of RAPRatioTuple.
tupleProperty: Property<RAPRatioTuple>;

// when true, moving one ratio value will maintain the current ratio by updating the other value Property
lockedProperty: BooleanProperty;
antecedentVelocityTracker: VelocityTracker;
consequentVelocityTracker: VelocityTracker;
private antecedentVelocityTracker: VelocityTracker;
private consequentVelocityTracker: VelocityTracker;

// if the ratio is in the "moving in direction" state: whether or not the two hands are moving fast
// enough together in the same direction. This indicates, among other things a bimodal interaction.
movingInDirectionProperty: IReadOnlyProperty<boolean>;
ratioLockListenerEnabled: boolean;

// To avoid an infinite loop as setting the tupleProperty from inside its lock-ratio-support
// listener. This is predominately needed because even same antecedent/consequent values get wrapped in a new
// RAPRatioTuple instance.
private ratioLockListenerEnabled: boolean;

/**
* @param {number} initialAntecedent
* @param {number} initialConsequent
* @param {Tandem} tandem
*/
constructor( initialAntecedent: number, initialConsequent: number, tandem: Tandem ) {

// @public (read-only) {Property.<Range>}
// TODO: public readonly, https://github.com/phetsims/ratio-and-proportion/issues/404
this.enabledRatioTermsRangeProperty = new Property( DEFAULT_TERM_VALUE_RANGE, {
tandem: tandem.createTandem( 'enabledRatioTermsRangeProperty' ),
phetioType: Property.PropertyIO( Range.RangeIO )
} );

// @public {Property.<RAPRatioTuple>} - Central Property that holds the value of the ratio. Using a tuple that holds
// both the antecedent and consequent values as a single data structure is vital for changing both hands at once, and
// in supporting the "locked ratio" state. Otherwise there are complicated reentrant cases where changing the
// antecedent cascades to the consequent to snap it back into ratio. Thus the creation of RAPRatioTuple.
this.tupleProperty = new Property( new RAPRatioTuple( initialAntecedent, initialConsequent ), {
valueType: RAPRatioTuple,
useDeepEquality: true,
Expand All @@ -73,15 +77,11 @@ class RAPRatio {
phetioType: Property.PropertyIO( RAPRatioTuple.RAPRatioTupleIO )
} );

// @public - when true, moving one ratio value will maintain the current ratio by updating the other value Property
this.lockedProperty = new BooleanProperty( false, { tandem: tandem.createTandem( 'lockedProperty' ) } );

// @private
this.antecedentVelocityTracker = new VelocityTracker( this.lockedProperty );
this.consequentVelocityTracker = new VelocityTracker( this.lockedProperty );

// @public - if the ratio is in the "moving in direction" state: whether or not the two hands are moving fast
// enough together in the same direction. This indicates, among other things a bimodal interaction.
this.movingInDirectionProperty = new DerivedProperty( [
this.antecedentVelocityTracker.currentVelocityProperty,
this.consequentVelocityTracker.currentVelocityProperty,
Expand All @@ -102,9 +102,6 @@ class RAPRatio {
phetioType: DerivedProperty.DerivedPropertyIO( BooleanIO )
} );

// @private - To avoid an infinite loop as setting the tupleProperty from inside its lock-ratio-support
// listener. This is predominately needed because even same antecedent/consequent values get wrapped in a new
// RAPRatioTuple instance.
this.ratioLockListenerEnabled = true;

// Listener that will handle keeping both ratio tuple values in sync when the ratio is locked.
Expand Down Expand Up @@ -157,15 +154,10 @@ class RAPRatio {
}

/**
* While keeping the same ratio, make sure that both ratio terms are within the provided range
* @private
* @param {number} antecedent
* @param {number} consequent
* @param {number} ratio - to base clamping on
* @param {Range} [range]
* @returns {RAPRatioTuple} - a new RAPRatioTuple, not mutated
* While keeping the same ratio, make sure that both ratio terms are within the provided range. Returns a new
* RAPRatioTuple, not mutated.
*/
clampRatioTupleValuesInRange( antecedent: number, consequent: number, ratio: number, range: Range = this.enabledRatioTermsRangeProperty.value ): RAPRatioTuple {
private clampRatioTupleValuesInRange( antecedent: number, consequent: number, ratio: number, range: Range = this.enabledRatioTermsRangeProperty.value ): RAPRatioTuple {

// Handle if the antecedent is out of range
if ( !range.contains( antecedent ) ) {
Expand All @@ -184,10 +176,6 @@ class RAPRatio {
return new RAPRatioTuple( antecedent, consequent );
}

/**
* @param {number} targetRatio
* @public
*/
setRatioToTarget( targetRatio: number ): void {
const currentRatioTuple = this.tupleProperty.value;

Expand All @@ -213,26 +201,16 @@ class RAPRatio {
this.ratioLockListenerEnabled = true;
}

/**
* @public
* @returns {number}
*/
get currentRatio(): number {
return this.tupleProperty.value.getRatio();
}

/**
* @public
*/
step(): void {
const currentTuple = this.tupleProperty.value;
this.antecedentVelocityTracker.step( currentTuple.antecedent );
this.consequentVelocityTracker.step( currentTuple.consequent );
}

/**
* @public
*/
reset(): void {

// it is easiest if this is reset first
Expand All @@ -250,42 +228,34 @@ class RAPRatio {
// Private class to keep details about tracking the velocity of each ratio term encapsulated.
class VelocityTracker {

ratioLockedProperty: Property<boolean>;
previousValues: number[];
earliestTime: number;
private ratioLockedProperty: Property<boolean>;

// keep track of previous values to calculate the change, only unique values are appended to this
private previousValues: number[];
private earliestTime: number;

// The change in ratio values since last capture. The frequency (or granularity) of this value
// is determined by STEP_FRAME_GRANULARITY.
currentVelocityProperty: NumberProperty;
stepCountTracker: number;

// Used for keeping track of how often dVelocity is checked.
private stepCountTracker: number;

constructor( ratioLockedProperty: Property<boolean> ) {

// @private
this.ratioLockedProperty = ratioLockedProperty;

// @private - keep track of previous values to calculate the change, only unique values are appended to this
this.previousValues = [];
this.earliestTime = 0;

// @private - The change in ratio values since last capture. The frequency (or granularity) of this value
// is determined by STEP_FRAME_GRANULARITY.
this.currentVelocityProperty = new NumberProperty( 0 );

// @private - Used for keeping track of how often dVelocity is checked.
this.stepCountTracker = 0;
}

/**
* @public
*/
reset(): void {
this.currentVelocityProperty.reset();
this.stepCountTracker = 0;
this.previousValues.length = 0;
}

/**
* @public
* @param {number} currentValue
*/
step( currentValue: number ): void {
this.stepCountTracker++;

Expand Down
Loading

0 comments on commit 010ffb4

Please sign in to comment.