-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Prefer children
to addChild
.
#44
Comments
@pixelzoom follow up question to this. On the BeakerNode review you mentioned that super.mutate is not very efficient either. Would setting |
What BeakerNode is doing now is most efficient. It's setting In general, calling So I would only use As for what you asked about specifically... Doing |
Mmm... Okay. @pixelzoom what is the correct pattern to follow when super needs to be called before children are initialized? For example: In |
A useful general pattern is to instantiate as a local export default class IntroScreenView extends MeanShareAndBalanceScreenView {
// Keeps track of pipeNode's and their respective pipeModel for disposal.
private readonly pipeMap: Map<PipeModel, PipeNode>;
public constructor( model: IntroModel, providedOptions: LevelingOutScreenViewOptions ) {
...
const pipeMap = new Map<PipeModel, PipeNode>();
...
super( model, options );
...
this.pipeMap = pipeMap;
}
...
} One case where this pattern doesn't work is when a superclass field is require to instantiate/initialize something. I.e., the super constructor needs to run first, so that you can use an inherited field to instantiate a Node (or something else) in your subclass. In that case, call For example: // Superclass.ts
type SelfOptions = {...};
export type SuperclassOptions = SelfOptions & NodeOptions;
export default class Superclass extends Node {
protected readonly someProperty: IProperty<...>;
constructor( providedOptions?: ... ) {
super( providedOptions );
this.someProperty = new Property( ... );
}
}
// Subclass.ts
type SelfOptions = {...};
// We'll be setting this.children, so do not allow the caller to provide children.
export type SubclassOptions = SelfOptions & StrictOmit< SuperclassOptions, 'children'>;
export class Subclass extends Superclass {
private readonly someNode: Node;
constructor( providedOptions?: ... ) {
// We need this.someProperty to exist to instantiate this.someNode, so call super first.
super( options );
// We now have everything needed to instantiate this.someNode.
this.someNode = new MyNode( this.someProperty, ... );
// Instantiate other child Nodes ...
....
// .. then set all children at once, instead of making multiple addChild calls.
this.children = [ this.someNode, ... ];
}
} |
I should also reiterate... For a small sim, you're unlikely to see much (or any) perceptible difference in performance between |
Ran into Index: js/intro/view/IntroScreenView.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/intro/view/IntroScreenView.ts b/js/intro/view/IntroScreenView.ts
--- a/js/intro/view/IntroScreenView.ts (revision 2025b41c92ca6bd9a348c9a201dcd483d16499af)
+++ b/js/intro/view/IntroScreenView.ts (date 1661452579544)
@@ -26,12 +26,13 @@
import Dimension2 from '../../../../dot/js/Dimension2.js';
import ValveNode from './ValveNode.js';
import TableNode from './TableNode.js';
+import { ScreenViewOptions } from '../../../../joist/js/ScreenView.js';
+import { combineOptions } from '../../../../phet-core/js/optionize.js';
type LevelingOutScreenViewOptions = MeanShareAndBalanceScreenViewOptions;
export default class IntroScreenView extends MeanShareAndBalanceScreenView {
- private readonly pipeNodes: PipeNode[];
public readonly predictMeanVisibleProperty: Property<boolean>;
public readonly meanVisibleProperty: Property<boolean>;
public readonly tickMarksVisibleProperty: Property<boolean>;
@@ -39,17 +40,16 @@
public constructor( model: IntroModel, providedOptions: LevelingOutScreenViewOptions ) {
const options = providedOptions;
- super( model, meanShareAndBalanceStrings.introQuestionProperty, MeanShareAndBalanceColors.introQuestionBarColorProperty, model.numberOfCupsProperty, options );
- this.predictMeanVisibleProperty = new BooleanProperty( false, {
+ const predictMeanVisibleProperty = new BooleanProperty( false, {
// phet-io
tandem: options.tandem.createTandem( 'predictMeanVisibleProperty' )
} );
- this.meanVisibleProperty = new BooleanProperty( false, {
+ const meanVisibleProperty = new BooleanProperty( false, {
// phet-io
tandem: options.tandem.createTandem( 'meanVisibleProperty' )
} );
- this.tickMarksVisibleProperty = new BooleanProperty( false, {
+ const tickMarksVisibleProperty = new BooleanProperty( false, {
// phet-io
tandem: options.tandem.createTandem( 'tickMarksVisibleProperty' )
} );
@@ -57,14 +57,12 @@
const modelViewTransform2DCups = ModelViewTransform2.createSinglePointScaleInvertedYMapping( new Vector2( 0, 0 ), new Vector2( 0, MeanShareAndBalanceConstants.CUPS_2D_CENTER_Y ), MeanShareAndBalanceConstants.CUP_HEIGHT );
const modelViewTransform3DCups = ModelViewTransform2.createSinglePointScaleInvertedYMapping( new Vector2( 0, 0 ), new Vector2( 0, MeanShareAndBalanceConstants.CUPS_3D_CENTER_Y ), MeanShareAndBalanceConstants.CUP_HEIGHT );
- model.numberOfCupsProperty.lazyLink( () => this.interruptSubtreeInput );
-
//Predict Mean Line that acts as a slider for alternative input.
const predictMeanSlider = new PredictMeanSlider(
model.meanPredictionProperty, model.dragRange,
model.numberOfCupsProperty, () => model.getActive2DCups(),
modelViewTransform2DCups, {
- visibleProperty: this.predictMeanVisibleProperty,
+ visibleProperty: predictMeanVisibleProperty,
valueProperty: model.meanPredictionProperty,
// Constant range
@@ -81,32 +79,42 @@
excludeInvisibleChildrenFromBounds: true
} );
- // 2D/3D water cup nodes addition and removal
- // Center 2D & 3D cups
- const checkboxGroupWidthOffset = ( MeanShareAndBalanceConstants.MAX_CONTROLS_TEXT_WIDTH + MeanShareAndBalanceConstants.CONTROLS_HORIZONTAL_MARGIN ) / 2;
- const cupsAreaCenterX = this.layoutBounds.centerX - checkboxGroupWidthOffset;
- const centerWaterCupLayerNode = () => {
- waterCupLayerNode.centerX = cupsAreaCenterX;
- predictMeanSlider.x = waterCupLayerNode.x - 12.5;
- };
+ // Configure layout
+ const controlPanel = new IntroControlPanel( tickMarksVisibleProperty, meanVisibleProperty, predictMeanVisibleProperty, options.tandem );
+
+ // Pipe toggle
+ const pipeSwitch = new ABSwitch( model.arePipesOpenProperty,
+ false, new ValveNode( new Vector2( 0, 0 ), new Property( 0 ), options.tandem.createTandem( 'closedValveIcon' ) ),
+ true, new ValveNode( new Vector2( 0, 0 ), new Property( Math.PI / 2 ), options.tandem.createTandem( 'openValveIcon' ) ), {
+ toggleSwitchOptions: {
+ size: new Dimension2( 40, 20 )
+ },
+
+ // phet-io
+ tandem: options.tandem.createTandem( 'pipeSwitch' )
+ } );
+
+ const tableNode = new TableNode( { centerX: 6, y: 200 } );
+
+ const combinedOptions = combineOptions<ScreenViewOptions>( { children: [ tableNode, waterCupLayerNode, predictMeanSlider ] }, options );
+
+ super( model, meanShareAndBalanceStrings.introQuestionProperty, MeanShareAndBalanceColors.introQuestionBarColorProperty, model.numberOfCupsProperty, combinedOptions );
// add all cup nodes to the view
model.waterCup2DArray.forEach( ( cupModel, index ) => {
- const cupNode = new WaterCup2DNode( cupModel, model.waterCup3DArray[ index ], modelViewTransform2DCups, model.meanProperty, this.tickMarksVisibleProperty,
- this.meanVisibleProperty, { tandem: options.tandem.createTandem( `waterCup2DNode${cupModel.linePlacement}` ) } );
+ const cupNode = new WaterCup2DNode( cupModel, model.waterCup3DArray[ index ], modelViewTransform2DCups, model.meanProperty, tickMarksVisibleProperty,
+ meanVisibleProperty, { tandem: options.tandem.createTandem( `waterCup2DNode${cupModel.linePlacement}` ) } );
waterCupLayerNode.addChild( cupNode );
- centerWaterCupLayerNode();
} );
model.waterCup3DArray.forEach( cupModel => {
- const cupNode = new WaterCup3DNode( this.tickMarksVisibleProperty, model, cupModel, modelViewTransform3DCups, {
+ const cupNode = new WaterCup3DNode( tickMarksVisibleProperty, model, cupModel, modelViewTransform3DCups, {
tandem: options.tandem.createTandem( `waterCup3DNode${cupModel.linePlacement}` )
} );
waterCupLayerNode.addChild( cupNode );
- centerWaterCupLayerNode();
} );
- this.pipeNodes = model.pipeArray.map( pipeModel => {
+ model.pipeArray.map( pipeModel => {
const index = model.pipeArray.indexOf( pipeModel );
const pipeNode = new PipeNode( pipeModel, model.arePipesOpenProperty, modelViewTransform2DCups,
{ tandem: options.tandem.createTandem( `pipeNode${index}` ) } );
@@ -114,29 +122,22 @@
return pipeNode;
} );
+ // Center waterCups as they are activated and de-activated
+ const checkboxGroupWidthOffset = ( MeanShareAndBalanceConstants.MAX_CONTROLS_TEXT_WIDTH + MeanShareAndBalanceConstants.CONTROLS_HORIZONTAL_MARGIN ) / 2;
+ const cupsAreaCenterX = this.layoutBounds.centerX - checkboxGroupWidthOffset;
+ const centerWaterCupLayerNode = () => {
+ waterCupLayerNode.centerX = cupsAreaCenterX;
+ predictMeanSlider.x = waterCupLayerNode.x - 12.5;
+ tableNode.centerX = waterCupLayerNode.centerX;
+ tableNode.y = waterCupLayerNode.y - 28;
+ };
+
model.numberOfCupsProperty.link( centerWaterCupLayerNode );
+ model.numberOfCupsProperty.lazyLink( () => this.interruptSubtreeInput );
- // Configure layout
- const controlPanel = new IntroControlPanel( this.tickMarksVisibleProperty, this.meanVisibleProperty, this.predictMeanVisibleProperty, options.tandem );
this.controlsVBox.addChild( controlPanel );
-
- // Pipe toggle
- const pipeSwitch = new ABSwitch( model.arePipesOpenProperty,
- false, new ValveNode( new Vector2( 0, 0 ), new Property( 0 ), options.tandem.createTandem( 'closedValveIcon' ) ),
- true, new ValveNode( new Vector2( 0, 0 ), new Property( Math.PI / 2 ), options.tandem.createTandem( 'openValveIcon' ) ), {
- toggleSwitchOptions: {
- size: new Dimension2( 40, 20 )
- },
-
- // phet-io
- tandem: options.tandem.createTandem( 'pipeSwitch' )
- } );
this.dataStateVBox.addChild( pipeSwitch );
- this.addChild( new TableNode( { centerX: waterCupLayerNode.centerX - 6, y: waterCupLayerNode.bottom - 28 } ) );
- this.addChild( waterCupLayerNode );
- this.addChild( predictMeanSlider );
-
this.pdomPlayAreaNode.pdomOrder = [
waterCupLayerNode,
controlPanel,
@@ -146,6 +147,10 @@
this.pdomControlAreaNode.pdomOrder = [
this.resetAllButton
];
+
+ this.predictMeanVisibleProperty = predictMeanVisibleProperty;
+ this.meanVisibleProperty = meanVisibleProperty;
+ this.tickMarksVisibleProperty = tickMarksVisibleProperty;
}
public override reset(): void {
Index: js/common/view/MeanShareAndBalanceScreenView.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/common/view/MeanShareAndBalanceScreenView.ts b/js/common/view/MeanShareAndBalanceScreenView.ts
--- a/js/common/view/MeanShareAndBalanceScreenView.ts (revision 2025b41c92ca6bd9a348c9a201dcd483d16499af)
+++ b/js/common/view/MeanShareAndBalanceScreenView.ts (date 1661450593131)
@@ -138,6 +138,7 @@
topMargin: MeanShareAndBalanceConstants.CONTROLS_VERTICAL_MARGIN
} );
+ // refactoring this to use children is inefficient. Too many of the elements rely on the layoutBounds of the class instance
this.addChild( this.questionBar );
this.addChild( this.resetAllButton );
this.addChild( this.controlsAlignBox ); |
I have converted all the instances of @pixelzoom, can you review and close if all seems good? |
Looks good overall. But there are a few places where Closing. |
For code review #41 ...
There's a significant performance difference between Node's
children
option and itsaddChild
method that I thought would be worth bringing to your attention.The current implementation of
SyncIcon
is:A more efficient implementation would use the
children
option instead ofaddChild
:There's no significant performance difference in this specific case, because so few Nodes are involved. But (according to @jonathanolson) in general,
children
is much faster thanaddChild
, particularly for anything that cares about children (e.g. layout containers). So if you get in the habit of preferringchildren
toaddChild
across your entire code base, you can realize a performance improvement.There's nothing that you need to change, just wanted you to be aware of this.
The text was updated successfully, but these errors were encountered: