From 6cde9b757ba5a50f7a13e15e66afb7589cb1d821 Mon Sep 17 00:00:00 2001 From: pixelzoom Date: Mon, 5 Dec 2022 16:28:58 -0700 Subject: [PATCH] improve performance of ParticlesNode (Canvas), https://github.com/phetsims/beers-law-lab/issues/209 --- js/common/model/Solute.ts | 9 ++++- js/concentration/model/ConcentrationModel.ts | 2 +- js/concentration/model/Particles.ts | 40 ++++++++++++++++++++ js/concentration/model/Precipitate.ts | 7 +++- js/concentration/model/ShakerParticles.ts | 18 +++++++-- 5 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 js/concentration/model/Particles.ts diff --git a/js/common/model/Solute.ts b/js/common/model/Solute.ts index 657ec39d..c1f09182 100644 --- a/js/common/model/Solute.ts +++ b/js/common/model/Solute.ts @@ -64,7 +64,11 @@ export default class Solute extends PhetioObject { // percent concentration [0,100] of stock solution, see https://github.com/phetsims/beers-law-lab/issues/149 public readonly stockSolutionPercentConcentration: number; - public constructor( providedOptions: SoluteOptions ) { + // Precomputed for rendering performance with Canvas. + public readonly fillStyle: string; + public readonly strokeStyle: string; + + private constructor( providedOptions: SoluteOptions ) { assert && assert( Solute.SoluteIO, 'SoluteIO and Solute instances are statics, so make sure SoluteIO exists' ); @@ -107,6 +111,9 @@ export default class Solute extends PhetioObject { ( Solvent.WATER.density + ( options.molarMass * options.stockSolutionConcentration ) ); assert && assert( this.stockSolutionPercentConcentration >= 0 && this.stockSolutionPercentConcentration <= 100 ); + this.fillStyle = options.particleColor.getCanvasStyle(); + this.strokeStyle = options.particleColor.darkerColor().getCanvasStyle(); + this.addLinkedElement( options.nameProperty, { tandem: options.tandem.createTandem( 'nameProperty' ) } ); diff --git a/js/concentration/model/ConcentrationModel.ts b/js/concentration/model/ConcentrationModel.ts index db46bb8b..4b41ba30 100644 --- a/js/concentration/model/ConcentrationModel.ts +++ b/js/concentration/model/ConcentrationModel.ts @@ -93,7 +93,7 @@ export default class ConcentrationModel implements TModel { tandem: tandem.createTandem( 'shaker' ) } ); - this.shakerParticles = new ShakerParticles( this.shaker, this.solution, this.beaker, { + this.shakerParticles = new ShakerParticles( this.solution, this.beaker, this.shaker, { tandem: tandem.createTandem( 'shakerParticles' ) } ); diff --git a/js/concentration/model/Particles.ts b/js/concentration/model/Particles.ts new file mode 100644 index 00000000..e2eaf6d3 --- /dev/null +++ b/js/concentration/model/Particles.ts @@ -0,0 +1,40 @@ +// Copyright 2022, University of Colorado Boulder + +/** + * Particles is the base class for all systems of particles. + * + * @author Chris Malley (PixelZoom, Inc.) + */ + +import Solute from '../../common/model/Solute.js'; +import TReadOnlyProperty from '../../../../axon/js/TReadOnlyProperty.js'; + +export default class Particles { + + protected readonly soluteProperty: TReadOnlyProperty; + + protected constructor( soluteProperty: TReadOnlyProperty ) { + this.soluteProperty = soluteProperty; + } + + /** + * Gets the size of all particles in the system. Particles are square. + */ + public getParticleSize(): number { + return this.soluteProperty.value.particleSize; + } + + /** + * Gets the Canvas fillStyle for all particles in the system. + */ + public getFillStyle(): string { + return this.soluteProperty.value.fillStyle; + } + + /** + * Gets the Canvas strokeStyle for all particles in the system. + */ + public getStrokeStyle(): string { + return this.soluteProperty.value.strokeStyle; + } +} \ No newline at end of file diff --git a/js/concentration/model/Precipitate.ts b/js/concentration/model/Precipitate.ts index bf2d3101..e2d642fe 100644 --- a/js/concentration/model/Precipitate.ts +++ b/js/concentration/model/Precipitate.ts @@ -15,13 +15,14 @@ import { PhetioObjectOptions } from '../../../../tandem/js/PhetioObject.js'; import beersLawLab from '../../beersLawLab.js'; import Beaker from './Beaker.js'; import ConcentrationSolution from './ConcentrationSolution.js'; +import Particles from './Particles.js'; import PrecipitateParticleGroup from './PrecipitateParticleGroup.js'; type SelfOptions = EmptySelfOptions; type PrecipitateOptions = SelfOptions & PickRequired; -export default class Precipitate { +export default class Precipitate extends Particles { private readonly solution: ConcentrationSolution; private readonly beaker: Beaker; @@ -29,6 +30,8 @@ export default class Precipitate { public constructor( solution: ConcentrationSolution, beaker: Beaker, providedOptions: PrecipitateOptions ) { + super( solution.soluteProperty ); + this.solution = solution; this.beaker = beaker; @@ -40,7 +43,7 @@ export default class Precipitate { this.solution.precipitateMolesProperty.link( () => this.updateParticles() ); // when the solute changes, remove all particles and create new particles for the solute - this.solution.soluteProperty.link( () => { + this.solution.soluteProperty.link( solute => { // Remove all particles, unless solute was being restored by PhET-iO. Particles will be restored by particleGroup. if ( !phet.joist.sim.isSettingPhetioStateProperty.value ) { diff --git a/js/concentration/model/ShakerParticles.ts b/js/concentration/model/ShakerParticles.ts index 5b51776f..237046ac 100644 --- a/js/concentration/model/ShakerParticles.ts +++ b/js/concentration/model/ShakerParticles.ts @@ -18,6 +18,7 @@ import beersLawLab from '../../beersLawLab.js'; import BLLConstants from '../../common/BLLConstants.js'; import Beaker from './Beaker.js'; import ConcentrationSolution from './ConcentrationSolution.js'; +import Particles from './Particles.js'; import Shaker from './Shaker.js'; import ShakerParticleGroup from './ShakerParticleGroup.js'; @@ -33,16 +34,25 @@ type SelfOptions = EmptySelfOptions; type ShakerParticlesOptions = SelfOptions & PickRequired; -export default class ShakerParticles { +export default class ShakerParticles extends Particles { + private readonly solution: ConcentrationSolution; + private readonly beaker: Beaker; + private readonly shaker: Shaker; public readonly particleGroup: ShakerParticleGroup; public readonly particlesMovedEmitter: Emitter; // emits on step if one or more particles has moved - public constructor( private readonly shaker: Shaker, - private readonly solution: ConcentrationSolution, - private readonly beaker: Beaker, + public constructor( solution: ConcentrationSolution, + beaker: Beaker, + shaker: Shaker, providedOptions: ShakerParticlesOptions ) { + super( solution.soluteProperty ); + + this.solution = solution; + this.beaker = beaker; + this.shaker = shaker; + this.particleGroup = new ShakerParticleGroup( { tandem: providedOptions.tandem.createTandem( 'particleGroup' ) } );