Skip to content
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

CT tried to removeListener on something that wasn't a listener #67

Closed
KatieWoe opened this issue Jun 6, 2022 · 22 comments
Closed

CT tried to removeListener on something that wasn't a listener #67

KatieWoe opened this issue Jun 6, 2022 · 22 comments

Comments

@KatieWoe
Copy link
Contributor

KatieWoe commented Jun 6, 2022

buoyancy : fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/buoyancy/buoyancy_en.html?continuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654306543783%7D&brand=phet&ea&fuzz&memoryLimit=1000
Query: brand=phet&ea&fuzz&memoryLimit=1000
Uncaught Error: Assertion failed: tried to removeListener on something that wasn't a listener
Error: Assertion failed: tried to removeListener on something that wasn't a listener
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (TinyEmitter.ts:139:6)
at removeListener (TinyProperty.ts:155:9)
at unlink (AbstractProperty.ts:379:22)
at unlink (DynamicProperty.ts:247:38)
at listener (TinyEmitter.ts:94:8)
at emit (AbstractProperty.ts:255:22)
at _notifyListeners (AbstractProperty.ts:206:13)
at set (Property.ts:67:10)
at set (Property.ts:53:9)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : multitouch-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/buoyancy/buoyancy_en.html?continuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22multitouch-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654527155349%7D&brand=phet&ea&fuzz&fuzzPointers=2&memoryLimit=1000&supportsPanAndZoom=false
Query: brand=phet&ea&fuzz&fuzzPointers=2&memoryLimit=1000&supportsPanAndZoom=false
Uncaught Error: Assertion failed: tried to removeListener on something that wasn't a listener
Error: Assertion failed: tried to removeListener on something that wasn't a listener
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (TinyEmitter.ts:139:6)
at removeListener (TinyProperty.ts:155:9)
at unlink (AbstractProperty.ts:379:22)
at unlink (DynamicProperty.ts:247:38)
at listener (TinyEmitter.ts:94:8)
at emit (AbstractProperty.ts:255:22)
at _notifyListeners (AbstractProperty.ts:206:13)
at set (Property.ts:67:10)
at set (Property.ts:53:9)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/buoyancy/buoyancy_en.html?continuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654339438249%7D&ea&brand=phet-io&phetioStandalone&fuzz&memoryLimit=1000
Query: ea&brand=phet-io&phetioStandalone&fuzz&memoryLimit=1000
Uncaught Error: Assertion failed: All descendants must be disposed by the next frame: buoyancy.shapesScreen.model.primaryMass.radiusProperty
Error: Assertion failed: All descendants must be disposed by the next frame: buoyancy.shapesScreen.model.primaryMass.radiusProperty
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioObject.ts:640:18)
at Array.forEach
at forEach (PhetioObject.ts:639:18)
at listener (Timer.ts:33:10)
at (TinyEmitter.ts:108:42)
at emit (Sim.ts:964:24)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/buoyancy/buoyancy_en.html?continuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654429775494%7D&ea&brand=phet-io&phetioStandalone&fuzz&memoryLimit=1000
Query: ea&brand=phet-io&phetioStandalone&fuzz&memoryLimit=1000
Uncaught Error: Assertion failed: All descendants must be disposed by the next frame: buoyancy.shapesScreen.model.primaryMass.radiusProperty
Error: Assertion failed: All descendants must be disposed by the next frame: buoyancy.shapesScreen.model.primaryMass.radiusProperty
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioObject.ts:640:18)
at Array.forEach
at forEach (PhetioObject.ts:639:18)
at listener (Timer.ts:33:10)
at (TinyEmitter.ts:108:42)
at emit (Sim.ts:964:24)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/buoyancy/buoyancy_en.html?continuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654452264856%7D&ea&brand=phet-io&phetioStandalone&fuzz&memoryLimit=1000
Query: ea&brand=phet-io&phetioStandalone&fuzz&memoryLimit=1000
Uncaught Error: Assertion failed: All descendants must be disposed by the next frame: buoyancy.shapesScreen.model.primaryMass.radiusProperty
Error: Assertion failed: All descendants must be disposed by the next frame: buoyancy.shapesScreen.model.primaryMass.radiusProperty
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioObject.ts:640:18)
at Array.forEach
at forEach (PhetioObject.ts:639:18)
at listener (Timer.ts:33:10)
at listener (TinyEmitter.ts:94:8)
at emit (Sim.ts:964:24)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/buoyancy/buoyancy_en.html?continuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654503193520%7D&ea&brand=phet-io&phetioStandalone&fuzz&memoryLimit=1000
Query: ea&brand=phet-io&phetioStandalone&fuzz&memoryLimit=1000
Uncaught Error: Assertion failed: All descendants must be disposed by the next frame: buoyancy.shapesScreen.model.primaryMass.radiusProperty
Error: Assertion failed: All descendants must be disposed by the next frame: buoyancy.shapesScreen.model.primaryMass.radiusProperty
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioObject.ts:640:18)
at Array.forEach
at forEach (PhetioObject.ts:639:18)
at listener (Timer.ts:33:10)
at listener (TinyEmitter.ts:94:8)
at emit (Sim.ts:964:24)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/buoyancy/buoyancy_en.html?continuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654527565209%7D&ea&brand=phet-io&phetioStandalone&fuzz&memoryLimit=1000
Query: ea&brand=phet-io&phetioStandalone&fuzz&memoryLimit=1000
Uncaught Error: Assertion failed: empty volume should be non-negative
Error: Assertion failed: empty volume should be non-negative
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (Basin.ts:140:14)
at getEmptyVolume (BoatView.ts:150:39)
at callback (Multilink.ts:111:10)
at listener (TinyEmitter.ts:94:8)
at emit (AbstractProperty.ts:255:22)
at _notifyListeners (AbstractProperty.ts:206:13)
at set (Property.ts:67:10)
at set (Property.ts:53:9)
at (DensityBuoyancyModel.ts:401:8)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654297854811%7D
Uncaught Error: Uncaught Error: Assertion failed: Impossible set state from iterate; unset state:
[object Object]
Error: Assertion failed: Impossible set state from iterate; unset state:
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:333:18)
at iterate (PhetioStateEngine.js:246:30)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654311876243%7D
Uncaught Error: Uncaught Error: Assertion failed: Impossible set state from iterate; unset state:
[object Object]
Error: Assertion failed: Impossible set state from iterate; unset state:
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:333:18)
at iterate (PhetioStateEngine.js:246:30)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654324967154%7D
Uncaught Error: Uncaught Error: Assertion failed: Impossible set state from iterate; unset state:
[object Object]
Error: Assertion failed: Impossible set state from iterate; unset state:
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:333:18)
at iterate (PhetioStateEngine.js:246:30)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654341820023%7D
Uncaught Error: Uncaught Error: Assertion failed: cannot set state while setting state
Error: Assertion failed: cannot set state while setting state
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:224:14)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
at processCommands (phetioCommandProcessor.js:113:15)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654354028997%7D
Uncaught Error: Uncaught Error: Assertion failed: Impossible set state from iterate; unset state:
[object Object]
Error: Assertion failed: Impossible set state from iterate; unset state:
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:333:18)
at iterate (PhetioStateEngine.js:246:30)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654370801959%7D
Uncaught Error: Uncaught Error: Assertion failed: Impossible set state from iterate; unset state:
[object Object]
Error: Assertion failed: Impossible set state from iterate; unset state:
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:333:18)
at iterate (PhetioStateEngine.js:246:30)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654384082501%7D
Uncaught Error: Uncaught Error: Assertion failed: Impossible set state from iterate; unset state:
[object Object]
Error: Assertion failed: Impossible set state from iterate; unset state:
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:333:18)
at iterate (PhetioStateEngine.js:246:30)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654396840512%7D
Uncaught Error: Uncaught Error: Assertion failed: Impossible set state from iterate; unset state:
[object Object]
Error: Assertion failed: Impossible set state from iterate; unset state:
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:333:18)
at iterate (PhetioStateEngine.js:246:30)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654412052138%7D
Uncaught Error: Uncaught Error: Assertion failed: cannot set state while setting state
Error: Assertion failed: cannot set state while setting state
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:224:14)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
at processCommands (phetioCommandProcessor.js:113:15)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654433259741%7D
Uncaught Error: Uncaught Error: Assertion failed: Impossible set state from iterate; unset state:
[object Object]
Error: Assertion failed: Impossible set state from iterate; unset state:
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:333:18)
at iterate (PhetioStateEngine.js:246:30)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654450435293%7D
Uncaught Error: Uncaught Error: Assertion failed: Impossible set state from iterate; unset state:
[object Object]
Error: Assertion failed: Impossible set state from iterate; unset state:
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:333:18)
at iterate (PhetioStateEngine.js:246:30)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654492643740%7D
Uncaught Error: Uncaught Error: Assertion failed: cannot set state while setting state
Error: Assertion failed: cannot set state while setting state
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:224:14)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
at processCommands (phetioCommandProcessor.js:113:15)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654504185026%7D
Uncaught Error: Uncaught Error: Assertion failed: cannot set state while setting state
Error: Assertion failed: cannot set state while setting state
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:224:14)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
at processCommands (phetioCommandProcessor.js:113:15)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654516876098%7D
Uncaught Error: Uncaught Error: Assertion failed: cannot set state while setting state
Error: Assertion failed: cannot set state while setting state
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:224:14)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
at processCommands (phetioCommandProcessor.js:113:15)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM

----------------------------------

buoyancy : phet-io-state-fuzz : unbuilt
https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/phet-io-wrappers/state/?sim=buoyancy&phetioDebug=true&fuzz&wrapperContinuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22phet-io-state-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1654295157038%22%2C%22timestamp%22%3A1654527294900%7D
Uncaught Error: Uncaught Error: Assertion failed: Impossible set state from iterate; unset state:
[object Object]
Error: Assertion failed: Impossible set state from iterate; unset state:
at window.assertions.assertFunction (https://bayes.colorado.edu/continuous-testing/ct-snapshots/1654295157038/assert/js/assert.js:28:13)
at assert (PhetioStateEngine.js:333:18)
at iterate (PhetioStateEngine.js:246:30)
at setState (PhetioStateEngine.js:263:9)
at setFullState (phetioEngine.js:1105:31)
at apply (phetioCommandProcessor.js:304:50)
at processCommand (phetioCommandProcessor.js:179:35)
at getReturn (phetioCommandProcessor.js:183:15)
at Array.map
at map (phetioCommandProcessor.js:177:29)
id: Bayes Puppeteer
Snapshot from 6/3/2022, 4:25:57 PM
@KatieWoe
Copy link
Contributor Author

KatieWoe commented May 9, 2023

Seeing this again

buoyancy : fuzz : unbuilt
http://127.0.0.1/continuous-testing/ct-snapshots/1683626850194/buoyancy/buoyancy_en.html?continuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1683626850194%22%2C%22timestamp%22%3A1683627329248%7D&brand=phet&ea&fuzz
Query: brand=phet&ea&fuzz
Error: Assertion failed: tried to removeListener on something that wasn't a listener
window.assertions.assertFunction<@http://127.0.0.1/continuous-testing/ct-snapshots/1683626850194/assert/js/assert.js:28:13
at window.assertions.assertFunction< (http://127.0.0.1/continuous-testing/ct-snapshots/1683626850194/assert/js/assert.js:28:13)
at (TinyEmitter.ts:170:14)
at removeListener (TinyProperty.ts:150:9)
at unlink (ReadOnlyProperty.ts:429:22)
at unlink (DynamicProperty.ts:246:38)
at (TinyEmitter.ts:119:18)
at emit (ReadOnlyProperty.ts:315:22)
at _notifyListeners (ReadOnlyProperty.ts:266:13)
at unguardedSet (ReadOnlyProperty.ts:250:11)
at set (Property.ts:54:10)
at inverseMap (DynamicProperty.ts:273:68)
at (TinyEmitter.ts:119:18)
at emit (ReadOnlyProperty.ts:315:22)
at _notifyListeners (ReadOnlyProperty.ts:266:13)
at unguardedSet (ReadOnlyProperty.ts:250:11)
at set (DynamicProperty.ts:311:10)
at set (DynamicProperty.ts:329:9)
at property (ComboBoxListBox.ts:104:6)
at apply (PhetioAction.ts:158:16)
at execute (ComboBoxListBox.ts:126:19)
at inputEvent (Input.ts:1899:69)
at dispatchToListeners (Input.ts:1939:11)
at dispatchToTargets (Input.ts:1851:9)
at dispatchEvent (Input.ts:1653:9)
at upEvent (Input.ts:518:13)
at apply (PhetioAction.ts:158:16)
at execute (Input.ts:1291:24)
at touchEnd (InputFuzzer.js:242:24)
at touchEnd (InputFuzzer.js:55:11)
at action (InputFuzzer.js:107:6)
at fuzzEvents (SimDisplay.ts:211:23)
at fuzzInputEvents (Sim.ts:998:44)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
id: Sparky Playwright Firefox
Snapshot from 5/9/2023, 4:07:30 AM

----------------------------------

buoyancy : multitouch-fuzz : unbuilt
http://127.0.0.1/continuous-testing/ct-snapshots/1683626850194/buoyancy/buoyancy_en.html?continuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22multitouch-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1683626850194%22%2C%22timestamp%22%3A1683628425725%7D&brand=phet&ea&fuzz&fuzzPointers=2&supportsPanAndZoom=false
Query: brand=phet&ea&fuzz&fuzzPointers=2&supportsPanAndZoom=false
Error: Assertion failed: tried to removeListener on something that wasn't a listener
window.assertions.assertFunction<@http://127.0.0.1/continuous-testing/ct-snapshots/1683626850194/assert/js/assert.js:28:13
at window.assertions.assertFunction< (http://127.0.0.1/continuous-testing/ct-snapshots/1683626850194/assert/js/assert.js:28:13)
at (TinyEmitter.ts:170:14)
at removeListener (TinyProperty.ts:150:9)
at unlink (ReadOnlyProperty.ts:429:22)
at unlink (DynamicProperty.ts:246:38)
at (TinyEmitter.ts:119:18)
at emit (ReadOnlyProperty.ts:315:22)
at _notifyListeners (ReadOnlyProperty.ts:266:13)
at unguardedSet (ReadOnlyProperty.ts:250:11)
at set (Property.ts:54:10)
at createCustomSolidMaterial (MaterialMassVolumeControlNode.ts:208:44)
at (TinyEmitter.ts:119:18)
at emit (ReadOnlyProperty.ts:315:22)
at _notifyListeners (ReadOnlyProperty.ts:266:13)
at unguardedSet (ReadOnlyProperty.ts:250:11)
at set (Property.ts:65:10)
at set (Slider.ts:463:24)
at (TinyEmitter.ts:119:18)
at emit (ReadOnlyProperty.ts:315:22)
at _notifyListeners (ReadOnlyProperty.ts:266:13)
at unguardedSet (ReadOnlyProperty.ts:250:11)
at set (DerivedProperty.ts:158:12)
at (TinyEmitter.ts:119:18)
at emit (ReadOnlyProperty.ts:315:22)
at _notifyListeners (ReadOnlyProperty.ts:266:13)
at unguardedSet (ReadOnlyProperty.ts:250:11)
at set (Property.ts:54:10)
at inverseMap (DynamicProperty.ts:273:68)
at (TinyEmitter.ts:119:18)
at emit (ReadOnlyProperty.ts:315:22)
at _notifyListeners (ReadOnlyProperty.ts:266:13)
at unguardedSet (ReadOnlyProperty.ts:250:11)
at set (DynamicProperty.ts:311:10)
at set (DynamicProperty.ts:329:9)
at property (ComboBoxListBox.ts:104:6)
at apply (PhetioAction.ts:158:16)
at execute (ComboBoxListBox.ts:126:19)
at inputEvent (Input.ts:1899:69)
at dispatchToListeners (Input.ts:1939:11)
at dispatchToTargets (Input.ts:1851:9)
at dispatchEvent (Input.ts:1653:9)
at upEvent (Input.ts:401:11)
at apply (PhetioAction.ts:158:16)
at execute (Input.ts:1219:23)
at mouseUp (InputFuzzer.js:275:26)
at mouseToggle (InputFuzzer.js:40:11)
at action (InputFuzzer.js:107:6)
at fuzzEvents (SimDisplay.ts:211:23)
at fuzzInputEvents (Sim.ts:998:44)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
at requestAnimationFrame (Sim.ts:990:11)
id: Sparky Playwright Firefox
Snapshot from 5/9/2023, 4:07:30 AM

@KatieWoe
Copy link
Contributor Author

May be related:

buoyancy : multitouch-fuzz : unbuilt
http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/buoyancy/buoyancy_en.html?continuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22multitouch-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1684762546721%22%2C%22timestamp%22%3A1684763878935%7D&brand=phet&ea&fuzz&fuzzPointers=2&supportsPanAndZoom=false
Query: brand=phet&ea&fuzz&fuzzPointers=2&supportsPanAndZoom=false
Uncaught Error: Assertion failed
Error: Assertion failed
at window.assertions.assertFunction (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/assert/js/assert.js:28:13)
at assert (Material.ts:101:14)
at (Material.ts:118:11)
at createCustomMaterial (Material.ts:138:20)
at createCustomSolidMaterial (MaterialMassVolumeControlNode.ts:208:44)
at listener (TinyEmitter.ts:119:8)
at emit (ReadOnlyProperty.ts:315:22)
at _notifyListeners (ReadOnlyProperty.ts:266:13)
at unguardedSet (ReadOnlyProperty.ts:250:11)
at set (Property.ts:65:10)
[URL] http://127.0.0.1/continuous-testing/aqua/html/sim-test.html?url=..%2F..%2Fct-snapshots%2F1684762546721%2Fbuoyancy%2Fbuoyancy_en.html&simQueryParameters=brand%3Dphet%26ea%26fuzz%26fuzzPointers%3D2%26supportsPanAndZoom%3Dfalse&testInfo=%7B%22test%22%3A%5B%22buoyancy%22%2C%22multitouch-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1684762546721%22%2C%22timestamp%22%3A1684763878935%7D
[NAVIGATED] http://127.0.0.1/continuous-testing/aqua/html/sim-test.html?url=..%2F..%2Fct-snapshots%2F1684762546721%2Fbuoyancy%2Fbuoyancy_en.html&simQueryParameters=brand%3Dphet%26ea%26fuzz%26fuzzPointers%3D2%26supportsPanAndZoom%3Dfalse&testInfo=%7B%22test%22%3A%5B%22buoyancy%22%2C%22multitouch-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1684762546721%22%2C%22timestamp%22%3A1684763878935%7D
[NAVIGATED] about:blank
[NAVIGATED] http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/buoyancy/buoyancy_en.html?continuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22multitouch-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1684762546721%22%2C%22timestamp%22%3A1684763878935%7D&brand=phet&ea&fuzz&fuzzPointers=2&supportsPanAndZoom=false
[CONSOLE] enabling assert
[CONSOLE] continuous-test-load
[CONSOLE] Assertion failed
[PAGE ERROR] Error: Error: Assertion failed
at window.assertions.assertFunction (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/assert/js/assert.js:28:13)
at new Material (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/density-buoyancy-common/js/common/model/Material.js:40:15)
at Material.createCustomMaterial (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/density-buoyancy-common/js/common/model/Material.js:56:12)
at Material.createCustomSolidMaterial (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/density-buoyancy-common/js/common/model/Material.js:76:21)
at http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/density-buoyancy-common/js/common/view/MaterialMassVolumeControlNode.js:166:45
at TinyProperty.emit (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/TinyEmitter.js:95:9)
at NumberProperty._notifyListeners (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/ReadOnlyProperty.js:237:23)
at NumberProperty.unguardedSet (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/ReadOnlyProperty.js:189:14)
at NumberProperty.set (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/ReadOnlyProperty.js:174:12)
at NumberProperty.set (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/Property.js:58:11)
[CONSOLE] continuous-test-error

id: "Sparky Node Puppeteer"
Snapshot from 5/22/2023, 7:35:46 AM

----------------------------------

buoyancy : pan-and-zoom-fuzz : unbuilt
http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/buoyancy/buoyancy_en.html?continuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22pan-and-zoom-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1684762546721%22%2C%22timestamp%22%3A1684767650400%7D&brand=phet&ea&fuzz&fuzzPointers=2&supportsPanAndZoom=true
Query: brand=phet&ea&fuzz&fuzzPointers=2&supportsPanAndZoom=true
Uncaught Error: Assertion failed: tried to removeListener on something that wasn't a listener
Error: Assertion failed: tried to removeListener on something that wasn't a listener
at window.assertions.assertFunction (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/assert/js/assert.js:28:13)
at assert (TinyEmitter.ts:170:6)
at removeListener (TinyProperty.ts:150:9)
at unlink (ReadOnlyProperty.ts:429:22)
at unlink (DynamicProperty.ts:246:38)
at listener (TinyEmitter.ts:119:8)
at emit (ReadOnlyProperty.ts:315:22)
at _notifyListeners (ReadOnlyProperty.ts:266:13)
at unguardedSet (ReadOnlyProperty.ts:250:11)
at set (Property.ts:54:10)
[URL] http://127.0.0.1/continuous-testing/aqua/html/sim-test.html?url=..%2F..%2Fct-snapshots%2F1684762546721%2Fbuoyancy%2Fbuoyancy_en.html&simQueryParameters=brand%3Dphet%26ea%26fuzz%26fuzzPointers%3D2%26supportsPanAndZoom%3Dtrue&testInfo=%7B%22test%22%3A%5B%22buoyancy%22%2C%22pan-and-zoom-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1684762546721%22%2C%22timestamp%22%3A1684767650400%7D
[NAVIGATED] http://127.0.0.1/continuous-testing/aqua/html/sim-test.html?url=..%2F..%2Fct-snapshots%2F1684762546721%2Fbuoyancy%2Fbuoyancy_en.html&simQueryParameters=brand%3Dphet%26ea%26fuzz%26fuzzPointers%3D2%26supportsPanAndZoom%3Dtrue&testInfo=%7B%22test%22%3A%5B%22buoyancy%22%2C%22pan-and-zoom-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1684762546721%22%2C%22timestamp%22%3A1684767650400%7D
[NAVIGATED] about:blank
[NAVIGATED] http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/buoyancy/buoyancy_en.html?continuousTest=%7B%22test%22%3A%5B%22buoyancy%22%2C%22pan-and-zoom-fuzz%22%2C%22unbuilt%22%5D%2C%22snapshotName%22%3A%22snapshot-1684762546721%22%2C%22timestamp%22%3A1684767650400%7D&brand=phet&ea&fuzz&fuzzPointers=2&supportsPanAndZoom=true
[CONSOLE] enabling assert
[CONSOLE] continuous-test-load
[CONSOLE] Assertion failed: tried to removeListener on something that wasn't a listener
[PAGE ERROR] Error: Error: Assertion failed: tried to removeListener on something that wasn't a listener
at window.assertions.assertFunction (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/assert/js/assert.js:28:13)
at TinyProperty.removeListener (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/TinyEmitter.js:143:7)
at TinyProperty.unlink (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/TinyProperty.js:133:10)
at LocalizedStringProperty.unlink (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/ReadOnlyProperty.js:344:23)
at DynamicProperty.onPropertyChange (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/DynamicProperty.js:194:37)
at TinyProperty.emit (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/TinyEmitter.js:95:9)
at Property._notifyListeners (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/ReadOnlyProperty.js:237:23)
at Property.unguardedSet (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/ReadOnlyProperty.js:189:14)
at Property.set (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/ReadOnlyProperty.js:174:12)
at set value [as value] (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/Property.js:48:11)
[CONSOLE] continuous-test-error
[CONSOLE] Assertion failed: tried to removeListener on something that wasn't a listener
[PAGE ERROR] Error: Error: Assertion failed: tried to removeListener on something that wasn't a listener
at window.assertions.assertFunction (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/assert/js/assert.js:28:13)
at TinyProperty.removeListener (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/TinyEmitter.js:143:7)
at TinyProperty.unlink (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/TinyProperty.js:133:10)
at LocalizedStringProperty.unlink (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/ReadOnlyProperty.js:344:23)
at DynamicProperty.onPropertyChange (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/DynamicProperty.js:194:37)
at TinyProperty.emit (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/TinyEmitter.js:95:9)
at Property._notifyListeners (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/ReadOnlyProperty.js:237:23)
at Property.unguardedSet (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/ReadOnlyProperty.js:189:14)
at Property.set (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/ReadOnlyProperty.js:174:12)
at set value [as value] (http://127.0.0.1/continuous-testing/ct-snapshots/1684762546721/chipper/dist/js/axon/js/Property.js:48:11)
[CONSOLE] continuous-test-error

id: "Sparky Node Puppeteer"
Snapshot from 5/22/2023, 7:35:46 AM

@samreid
Copy link
Member

samreid commented Sep 26, 2023

@jonathanolson @zepumph and I reproduced this problem by:

Buoyancy => Application screen => Cement => set volume and mass to 0 => select "custom"

@samreid
Copy link
Member

samreid commented Sep 26, 2023

@jonathanolson and @zepumph and I looked into this, and it seems the problem is:

  1. The material changes to "custom" via the user selection
  2. This changes the enabled range property on one of the sliders
  3. This changes the material again to a new "custom" value (different than step 1)

The Material derives a name Property in this code:
https://github.com/phetsims/density-buoyancy-common/blob/d0336128a3743915cee7e99a4e57471f5b475f9e/js/buoyancy/view/DensityReadoutListNode.ts#L29-L38

@jonathanolson hypothesizes that when a re-entrant Property changes from A=>B then B=>C we do not have the correct "oldValue" or miss it somehow.

@jonathanolson was reminded of a scenery case where there was something similar in rendering SVG gradients, where we had to track the oldValue ourselves because it was showing incorrectly in the axon notifications.

@jonathanolson and @zepumph agree we need a unit test to exercise this case and see if there is an incorrect or missing oldValue in this re-entrant case.

@samreid
Copy link
Member

samreid commented Sep 26, 2023

Patch from discoveries with @jbphet and @marlitas :

Subject: [PATCH] Restore density testing, see https://github.com/phetsims/phet-io-wrappers/issues/551
---
Index: axon/js/DynamicProperty.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/DynamicProperty.ts b/axon/js/DynamicProperty.ts
--- a/axon/js/DynamicProperty.ts	(revision 2c5d217ca16faaa5be669b3a1b804027ce0bafc6)
+++ b/axon/js/DynamicProperty.ts	(date 1695749935550)
@@ -241,13 +241,17 @@
    *                                              undefined.
    */
   private onPropertyChange( newPropertyValue: OuterValueType | null, oldPropertyValue: OuterValueType | null | undefined ): void {
+    // console.log( 'new = ', newPropertyValue, 'old', oldPropertyValue );
     if ( oldPropertyValue ) {
+      console.log( this.id, 'unlinking from ', oldPropertyValue );
       this.derive( oldPropertyValue ).unlink( this.propertyPropertyListener );
     }
     if ( newPropertyValue ) {
+      console.log( this.id, 'linking to ', newPropertyValue );
       this.derive( newPropertyValue ).link( this.propertyPropertyListener );
     }
     else {
+      console.log( 'null???' );
       // Switch to null when our Property's value is null.
       this.onPropertyPropertyChange( this.defaultValue, null, null );
     }
Index: axon/js/PropertyTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/PropertyTests.ts b/axon/js/PropertyTests.ts
--- a/axon/js/PropertyTests.ts	(revision 2c5d217ca16faaa5be669b3a1b804027ce0bafc6)
+++ b/axon/js/PropertyTests.ts	(date 1695751222840)
@@ -311,4 +311,30 @@
 
     thirdProperty.dispose();
   } );
-}
\ No newline at end of file
+}
+
+QUnit.test( 'TEST HELLO', assert => {
+  const myProperty = new Property( 'a',{
+    reentrant: true
+  } );
+  myProperty.link( value => {
+    if ( value === 'b' ) {
+      myProperty.value = 'c';
+    }
+  } );
+
+  myProperty.link( ( value, oldValue ) => console.log( `link result: ${oldValue} => ${value}` ) );
+  myProperty.value = 'b';
+
+  assert.ok( true, 'first test' );
+
+  // We hope:
+  // null => a
+  // a => b
+  // b => c
+
+  // We think since it is buggy we will instead get:
+  // null=>a
+  // b=>c
+  // a=>b
+} );
\ No newline at end of file
Index: axon/js/TinyEmitter.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/TinyEmitter.ts b/axon/js/TinyEmitter.ts
--- a/axon/js/TinyEmitter.ts	(revision 2c5d217ca16faaa5be669b3a1b804027ce0bafc6)
+++ b/axon/js/TinyEmitter.ts	(date 1695750540343)
@@ -171,7 +171,10 @@
     // Throw an error when removing a non-listener (except when the Emitter has already been disposed, see
     // https://github.com/phetsims/sun/issues/394#issuecomment-419998231
     if ( assert && !this.isDisposed ) {
-      assert( this.listeners.has( listener ), 'tried to removeListener on something that wasn\'t a listener' );
+      if ( !this.listeners.has( listener ) ) {
+        console.log( this.listeners.has( listener ), 'tried to removeListener on something that wasn\'t a listener' );
+        return;
+      }
     }
     this.guardListeners();
     this.listeners.delete( listener );
Index: density-buoyancy-common/js/buoyancy/view/BoatView.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/density-buoyancy-common/js/buoyancy/view/BoatView.ts b/density-buoyancy-common/js/buoyancy/view/BoatView.ts
--- a/density-buoyancy-common/js/buoyancy/view/BoatView.ts	(revision e3e1c677c4b60789c7a497f772567a3de905ca67)
+++ b/density-buoyancy-common/js/buoyancy/view/BoatView.ts	(date 1695746335194)
@@ -142,6 +142,17 @@
       boat.displacementVolumeProperty,
       boat.basin.liquidVolumeProperty
     ], ( boatLiquidY, boatDisplacement, boatLiquidVolume ) => {
+
+      setTimeout( () => updateItAll(), 0 );
+
+    } );
+
+    const updateItAll = () => {
+
+      const boatLiquidY = boat.basin.liquidYInterpolatedProperty.value;
+      const boatDisplacement = boat.displacementVolumeProperty.value;
+      const boatLiquidVolume = boat.basin.liquidVolumeProperty.value;
+
       const poolLiquidY = liquidYInterpolatedProperty.value;
       const liters = boatDisplacement / 0.001;
 
@@ -169,7 +180,7 @@
       }
       bottomPoolClipPlane.constant = poolLiquidY;
       topPoolClipPlane.constant = -poolLiquidY;
-    } );
+    };
 
     Material.linkLiquidColor( boat.liquidMaterialProperty, topLiquidMaterial );
     Material.linkLiquidColor( boat.liquidMaterialProperty, backMiddleMaterial );
Index: density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts b/density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts
--- a/density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts	(revision e3e1c677c4b60789c7a497f772567a3de905ca67)
+++ b/density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts	(date 1695750122003)
@@ -26,8 +26,11 @@
       align: 'center'
     } );
 
-    this.children = materialProperties.map( materialProperty => {
+    this.children = [ materialProperties[ 0 ] ].map( materialProperty => {
       // Exists for the lifetime of a sim, so disposal patterns not needed.
+      const dynamicProperty = new DynamicProperty<string, string, Material>( materialProperty, { derive: material => material.nameProperty } );
+
+      console.log( 'making dynamic property material=>nameProperty', dynamicProperty.id );
       return new RichText( new PatternStringProperty( new DerivedProperty( [
         DensityBuoyancyCommonPreferences.volumeUnitsProperty,
         DensityBuoyancyCommonStrings.densityReadoutPatternStringProperty,
@@ -35,7 +38,7 @@
       ], ( units, litersString, decimetersCubedString ) => {
         return units === 'liters' ? litersString : decimetersCubedString;
       } ), {
-        material: new DynamicProperty<string, string, Material>( materialProperty, { derive: material => material.nameProperty } ),
+        material: dynamicProperty,
         density: new DerivedProperty( [ materialProperty ], material => material.density / 1000 )
       }, {
         tandem: Tandem.OPT_OUT,

Indeed we saw the incorrect order in a listener test:

QUnit.test( 'TEST HELLO', assert => {
  const myProperty = new Property( 'a',{
    reentrant: true
  } );
  myProperty.link( value => {
    if ( value === 'b' ) {
      myProperty.value = 'c';
    }
  } );

  myProperty.link( ( value, oldValue ) => console.log( `link result: ${oldValue} => ${value}` ) );
  myProperty.value = 'b';

  assert.ok( true, 'first test' );

  // We hope:
  // null => a
  // a => b
  // b => c

  // We think since it is buggy we will instead get:
  // null=>a
  // b=>c
  // a=>b
} );

@samreid
Copy link
Member

samreid commented Sep 26, 2023

We proposed a fix for that problem above that has the correct behavior and doesn't use setTimeout. That is working well. However, when we tried to generalize to N levels of re-entrancy, it had very broken behavior:

Subject: [PATCH] Restore density testing, see https://github.com/phetsims/phet-io-wrappers/issues/551
---
Index: axon/js/DynamicProperty.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/DynamicProperty.ts b/axon/js/DynamicProperty.ts
--- a/axon/js/DynamicProperty.ts	(revision 2c5d217ca16faaa5be669b3a1b804027ce0bafc6)
+++ b/axon/js/DynamicProperty.ts	(date 1695749935550)
@@ -241,13 +241,17 @@
    *                                              undefined.
    */
   private onPropertyChange( newPropertyValue: OuterValueType | null, oldPropertyValue: OuterValueType | null | undefined ): void {
+    // console.log( 'new = ', newPropertyValue, 'old', oldPropertyValue );
     if ( oldPropertyValue ) {
+      console.log( this.id, 'unlinking from ', oldPropertyValue );
       this.derive( oldPropertyValue ).unlink( this.propertyPropertyListener );
     }
     if ( newPropertyValue ) {
+      console.log( this.id, 'linking to ', newPropertyValue );
       this.derive( newPropertyValue ).link( this.propertyPropertyListener );
     }
     else {
+      console.log( 'null???' );
       // Switch to null when our Property's value is null.
       this.onPropertyPropertyChange( this.defaultValue, null, null );
     }
Index: axon/js/PropertyTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/PropertyTests.ts b/axon/js/PropertyTests.ts
--- a/axon/js/PropertyTests.ts	(revision 2c5d217ca16faaa5be669b3a1b804027ce0bafc6)
+++ b/axon/js/PropertyTests.ts	(date 1695752519473)
@@ -311,4 +311,62 @@
 
     thirdProperty.dispose();
   } );
-}
\ No newline at end of file
+}
+
+QUnit.test( 'TEST HELLO', assert => {
+  const myProperty = new Property( 'a', {
+    reentrant: true
+  } );
+  myProperty.link( value => {
+    if ( value === 'b' ) {
+      myProperty.value = 'c';
+    }
+  } );
+
+  myProperty.link( ( value, oldValue ) => console.log( `link result: ${oldValue} => ${value}` ) );
+  myProperty.value = 'b';
+
+  assert.ok( true, 'first test' );
+
+  // We hope:
+  // null => a
+  // a => b
+  // b => c
+
+  // We think since it is buggy we will instead get:
+  // null=>a
+  // b=>c
+  // a=>b
+} );
+
+QUnit.test( 'TEST Property reentrant multi levels ', assert => {
+  const myProperty = new Property( 'a', {
+    reentrant: true
+  } );
+  myProperty.link( value => {
+    if ( value === 'b' ) {
+      myProperty.value = 'c';
+    }
+    else if ( value === 'c' ) {
+      myProperty.value = 'd';
+    }
+    else if ( value === 'd' ) {
+      myProperty.value = 'e';
+    }
+  } );
+
+  myProperty.link( ( value, oldValue ) => console.log( `link result: ${oldValue} => ${value}` ) );
+  myProperty.value = 'b';
+
+  assert.ok( true, 'first test' );
+
+  // We hope:
+  // null => a
+  // a => b
+  // b => c
+
+  // We think since it is buggy we will instead get:
+  // null=>a
+  // b=>c
+  // a=>b
+} );
\ No newline at end of file
Index: density-buoyancy-common/js/buoyancy/view/BoatView.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/density-buoyancy-common/js/buoyancy/view/BoatView.ts b/density-buoyancy-common/js/buoyancy/view/BoatView.ts
--- a/density-buoyancy-common/js/buoyancy/view/BoatView.ts	(revision e3e1c677c4b60789c7a497f772567a3de905ca67)
+++ b/density-buoyancy-common/js/buoyancy/view/BoatView.ts	(date 1695746335194)
@@ -142,6 +142,17 @@
       boat.displacementVolumeProperty,
       boat.basin.liquidVolumeProperty
     ], ( boatLiquidY, boatDisplacement, boatLiquidVolume ) => {
+
+      setTimeout( () => updateItAll(), 0 );
+
+    } );
+
+    const updateItAll = () => {
+
+      const boatLiquidY = boat.basin.liquidYInterpolatedProperty.value;
+      const boatDisplacement = boat.displacementVolumeProperty.value;
+      const boatLiquidVolume = boat.basin.liquidVolumeProperty.value;
+
       const poolLiquidY = liquidYInterpolatedProperty.value;
       const liters = boatDisplacement / 0.001;
 
@@ -169,7 +180,7 @@
       }
       bottomPoolClipPlane.constant = poolLiquidY;
       topPoolClipPlane.constant = -poolLiquidY;
-    } );
+    };
 
     Material.linkLiquidColor( boat.liquidMaterialProperty, topLiquidMaterial );
     Material.linkLiquidColor( boat.liquidMaterialProperty, backMiddleMaterial );
Index: axon/js/ReadOnlyProperty.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/ReadOnlyProperty.ts b/axon/js/ReadOnlyProperty.ts
--- a/axon/js/ReadOnlyProperty.ts	(revision 2c5d217ca16faaa5be669b3a1b804027ce0bafc6)
+++ b/axon/js/ReadOnlyProperty.ts	(date 1695753423326)
@@ -94,7 +94,7 @@
   private tinyProperty: TinyProperty<T>;
 
   // whether we are in the process of notifying listeners; changed in some Property test files with @ts-expect-error
-  private notifying: boolean;
+  private notifying: number;
 
   // whether to allow reentry of calls to set
   private readonly reentrant: boolean;
@@ -113,6 +113,8 @@
   protected readonly valueValidator: Validator<T>;
   public static readonly TANDEM_NAME_SUFFIX: string = 'Property';
 
+  private thingsToDoLater: Array<() => void> = [];
+
   /**
    * This is protected to indicate to clients that subclasses should be used instead.
    * @param value - the initial value of the property
@@ -176,7 +178,7 @@
     // Since we are already in the heavyweight Property, we always assign TinyProperty.useDeepEquality for clarity.
     // @ts-expect-error
     this.tinyProperty.useDeepEquality = options.valueComparisonStrategy && options.valueComparisonStrategy === 'equalsFunction';
-    this.notifying = false;
+    this.notifying = 0;
     this.reentrant = options.reentrant;
     this.isDeferred = false;
     this.deferredValue = null;
@@ -259,7 +261,7 @@
       else if ( !this.equalsValue( value ) ) {
         const oldValue = this.get();
         this.setPropertyValue( value );
-        this._notifyListeners( oldValue );
+        this._notifyListeners( oldValue, value );
       }
     }
   }
@@ -289,8 +291,8 @@
   /**
    * NOTE: a few sims are calling this even though they shouldn't
    */
-  private _notifyListeners( oldValue: T | null ): void {
-    const newValue = this.get();
+  private _notifyListeners( oldValue: T | null, newValue: T ): void {
+    // const newValue = this.get();
 
     // validate the before notifying listeners
     assert && validate( newValue, this.valueValidator, VALIDATE_OPTIONS_FALSE );
@@ -308,12 +310,37 @@
     } );
 
     // notify listeners, optionally detect loops where this Property is set again before this completes.
-    assert && assert( !this.notifying || this.reentrant,
+    assert && assert( this.notifying === 0 || this.reentrant,
       `reentry detected, value=${newValue}, oldValue=${oldValue}` );
-    this.notifying = true;
+
+    const later = this.notifying > 0;
 
-    this.tinyProperty.emit( newValue, oldValue, this ); // cannot use tinyProperty.notifyListeners because it uses the wrong this
-    this.notifying = false;
+    console.log( 'incrementing notifying = ' + this.notifying, 'oldvalue = ' + oldValue, 'newValue = ', newValue );
+    if ( oldValue === 'e' ) {
+      debugger;
+    }
+
+    this.notifying++;
+
+    if ( later ) {
+
+      console.log( 'pushing one for later that has oldValue = ' + oldValue, 'newValue', newValue );
+
+      debugger;
+      this.thingsToDoLater.unshift( () => {
+        this.tinyProperty.emit( newValue, oldValue, this ); // cannot use tinyProperty.notifyListeners because it uses the wrong this
+      } );
+    }
+    else {
+      this.tinyProperty.emit( newValue, oldValue, this ); // cannot use tinyProperty.notifyListeners because it uses the wrong this
+    }
+
+    this.notifying--;
+
+    if ( this.notifying === 0 ) {
+      this.thingsToDoLater.forEach( action => action() );
+      this.thingsToDoLater.length = 0;
+    }
 
     Tandem.PHET_IO_ENABLED && this.isPhetioInstrumented() && this.phetioEndEvent();
   }
Index: density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts b/density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts
--- a/density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts	(revision e3e1c677c4b60789c7a497f772567a3de905ca67)
+++ b/density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts	(date 1695750122003)
@@ -26,8 +26,11 @@
       align: 'center'
     } );
 
-    this.children = materialProperties.map( materialProperty => {
+    this.children = [ materialProperties[ 0 ] ].map( materialProperty => {
       // Exists for the lifetime of a sim, so disposal patterns not needed.
+      const dynamicProperty = new DynamicProperty<string, string, Material>( materialProperty, { derive: material => material.nameProperty } );
+
+      console.log( 'making dynamic property material=>nameProperty', dynamicProperty.id );
       return new RichText( new PatternStringProperty( new DerivedProperty( [
         DensityBuoyancyCommonPreferences.volumeUnitsProperty,
         DensityBuoyancyCommonStrings.densityReadoutPatternStringProperty,
@@ -35,7 +38,7 @@
       ], ( units, litersString, decimetersCubedString ) => {
         return units === 'liters' ? litersString : decimetersCubedString;
       } ), {
-        material: new DynamicProperty<string, string, Material>( materialProperty, { derive: material => material.nameProperty } ),
+        material: dynamicProperty,
         density: new DerivedProperty( [ materialProperty ], material => material.density / 1000 )
       }, {
         tandem: Tandem.OPT_OUT,

We feel it overlaps with the intermediate states listed in phetsims/axon#303

We saw that queueMicrotask for all callbacks did have the correct listener order. But that is a scary change.

Here is the one that uses queueMicrotask at every level:

Subject: [PATCH] Restore density testing, see https://github.com/phetsims/phet-io-wrappers/issues/551
---
Index: axon/js/DynamicProperty.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/DynamicProperty.ts b/axon/js/DynamicProperty.ts
--- a/axon/js/DynamicProperty.ts	(revision 2c5d217ca16faaa5be669b3a1b804027ce0bafc6)
+++ b/axon/js/DynamicProperty.ts	(date 1695749935550)
@@ -241,13 +241,17 @@
    *                                              undefined.
    */
   private onPropertyChange( newPropertyValue: OuterValueType | null, oldPropertyValue: OuterValueType | null | undefined ): void {
+    // console.log( 'new = ', newPropertyValue, 'old', oldPropertyValue );
     if ( oldPropertyValue ) {
+      console.log( this.id, 'unlinking from ', oldPropertyValue );
       this.derive( oldPropertyValue ).unlink( this.propertyPropertyListener );
     }
     if ( newPropertyValue ) {
+      console.log( this.id, 'linking to ', newPropertyValue );
       this.derive( newPropertyValue ).link( this.propertyPropertyListener );
     }
     else {
+      console.log( 'null???' );
       // Switch to null when our Property's value is null.
       this.onPropertyPropertyChange( this.defaultValue, null, null );
     }
Index: axon/js/PropertyTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/PropertyTests.ts b/axon/js/PropertyTests.ts
--- a/axon/js/PropertyTests.ts	(revision 2c5d217ca16faaa5be669b3a1b804027ce0bafc6)
+++ b/axon/js/PropertyTests.ts	(date 1695752519473)
@@ -311,4 +311,62 @@
 
     thirdProperty.dispose();
   } );
-}
\ No newline at end of file
+}
+
+QUnit.test( 'TEST HELLO', assert => {
+  const myProperty = new Property( 'a', {
+    reentrant: true
+  } );
+  myProperty.link( value => {
+    if ( value === 'b' ) {
+      myProperty.value = 'c';
+    }
+  } );
+
+  myProperty.link( ( value, oldValue ) => console.log( `link result: ${oldValue} => ${value}` ) );
+  myProperty.value = 'b';
+
+  assert.ok( true, 'first test' );
+
+  // We hope:
+  // null => a
+  // a => b
+  // b => c
+
+  // We think since it is buggy we will instead get:
+  // null=>a
+  // b=>c
+  // a=>b
+} );
+
+QUnit.test( 'TEST Property reentrant multi levels ', assert => {
+  const myProperty = new Property( 'a', {
+    reentrant: true
+  } );
+  myProperty.link( value => {
+    if ( value === 'b' ) {
+      myProperty.value = 'c';
+    }
+    else if ( value === 'c' ) {
+      myProperty.value = 'd';
+    }
+    else if ( value === 'd' ) {
+      myProperty.value = 'e';
+    }
+  } );
+
+  myProperty.link( ( value, oldValue ) => console.log( `link result: ${oldValue} => ${value}` ) );
+  myProperty.value = 'b';
+
+  assert.ok( true, 'first test' );
+
+  // We hope:
+  // null => a
+  // a => b
+  // b => c
+
+  // We think since it is buggy we will instead get:
+  // null=>a
+  // b=>c
+  // a=>b
+} );
\ No newline at end of file
Index: density-buoyancy-common/js/buoyancy/view/BoatView.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/density-buoyancy-common/js/buoyancy/view/BoatView.ts b/density-buoyancy-common/js/buoyancy/view/BoatView.ts
--- a/density-buoyancy-common/js/buoyancy/view/BoatView.ts	(revision e3e1c677c4b60789c7a497f772567a3de905ca67)
+++ b/density-buoyancy-common/js/buoyancy/view/BoatView.ts	(date 1695746335194)
@@ -142,6 +142,17 @@
       boat.displacementVolumeProperty,
       boat.basin.liquidVolumeProperty
     ], ( boatLiquidY, boatDisplacement, boatLiquidVolume ) => {
+
+      setTimeout( () => updateItAll(), 0 );
+
+    } );
+
+    const updateItAll = () => {
+
+      const boatLiquidY = boat.basin.liquidYInterpolatedProperty.value;
+      const boatDisplacement = boat.displacementVolumeProperty.value;
+      const boatLiquidVolume = boat.basin.liquidVolumeProperty.value;
+
       const poolLiquidY = liquidYInterpolatedProperty.value;
       const liters = boatDisplacement / 0.001;
 
@@ -169,7 +180,7 @@
       }
       bottomPoolClipPlane.constant = poolLiquidY;
       topPoolClipPlane.constant = -poolLiquidY;
-    } );
+    };
 
     Material.linkLiquidColor( boat.liquidMaterialProperty, topLiquidMaterial );
     Material.linkLiquidColor( boat.liquidMaterialProperty, backMiddleMaterial );
Index: axon/js/ReadOnlyProperty.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/ReadOnlyProperty.ts b/axon/js/ReadOnlyProperty.ts
--- a/axon/js/ReadOnlyProperty.ts	(revision 2c5d217ca16faaa5be669b3a1b804027ce0bafc6)
+++ b/axon/js/ReadOnlyProperty.ts	(date 1695753747505)
@@ -94,7 +94,7 @@
   private tinyProperty: TinyProperty<T>;
 
   // whether we are in the process of notifying listeners; changed in some Property test files with @ts-expect-error
-  private notifying: boolean;
+  private notifying: number;
 
   // whether to allow reentry of calls to set
   private readonly reentrant: boolean;
@@ -113,6 +113,8 @@
   protected readonly valueValidator: Validator<T>;
   public static readonly TANDEM_NAME_SUFFIX: string = 'Property';
 
+  // private thingsToDoLater: Array<() => void> = [];
+
   /**
    * This is protected to indicate to clients that subclasses should be used instead.
    * @param value - the initial value of the property
@@ -176,7 +178,7 @@
     // Since we are already in the heavyweight Property, we always assign TinyProperty.useDeepEquality for clarity.
     // @ts-expect-error
     this.tinyProperty.useDeepEquality = options.valueComparisonStrategy && options.valueComparisonStrategy === 'equalsFunction';
-    this.notifying = false;
+    this.notifying = 0;
     this.reentrant = options.reentrant;
     this.isDeferred = false;
     this.deferredValue = null;
@@ -259,7 +261,7 @@
       else if ( !this.equalsValue( value ) ) {
         const oldValue = this.get();
         this.setPropertyValue( value );
-        this._notifyListeners( oldValue );
+        this._notifyListeners( oldValue, value );
       }
     }
   }
@@ -289,7 +291,7 @@
   /**
    * NOTE: a few sims are calling this even though they shouldn't
    */
-  private _notifyListeners( oldValue: T | null ): void {
+  private _notifyListeners( oldValue: T | null): void {
     const newValue = this.get();
 
     // validate the before notifying listeners
@@ -308,12 +310,37 @@
     } );
 
     // notify listeners, optionally detect loops where this Property is set again before this completes.
-    assert && assert( !this.notifying || this.reentrant,
+    assert && assert( this.notifying === 0 || this.reentrant,
       `reentry detected, value=${newValue}, oldValue=${oldValue}` );
-    this.notifying = true;
+
+    const later = this.notifying > 0;
 
-    this.tinyProperty.emit( newValue, oldValue, this ); // cannot use tinyProperty.notifyListeners because it uses the wrong this
-    this.notifying = false;
+    console.log( 'incrementing notifying = ' + this.notifying, 'oldvalue = ' + oldValue, 'newValue = ', newValue );
+    // if ( oldValue === 'e' ) {
+    //   debugger;
+    // }
+
+    this.notifying++;
+
+    // if ( later ) {
+
+      console.log( 'pushing one for later that has oldValue = ' + oldValue, 'newValue', newValue );
+
+      // debugger;
+      window.queueMicrotask( () => {
+        this.tinyProperty.emit( newValue, oldValue, this ); // cannot use tinyProperty.notifyListeners because it uses the wrong this
+      } );
+    // }
+    // else {
+    //   this.tinyProperty.emit( newValue, oldValue, this ); // cannot use tinyProperty.notifyListeners because it uses the wrong this
+    // }
+
+    this.notifying--;
+
+    // if ( this.notifying === 0 ) {
+    //   this.thingsToDoLater.forEach( action => action() );
+    //   this.thingsToDoLater.length = 0;
+    // }
 
     Tandem.PHET_IO_ENABLED && this.isPhetioInstrumented() && this.phetioEndEvent();
   }
Index: density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts b/density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts
--- a/density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts	(revision e3e1c677c4b60789c7a497f772567a3de905ca67)
+++ b/density-buoyancy-common/js/buoyancy/view/DensityReadoutListNode.ts	(date 1695750122003)
@@ -26,8 +26,11 @@
       align: 'center'
     } );
 
-    this.children = materialProperties.map( materialProperty => {
+    this.children = [ materialProperties[ 0 ] ].map( materialProperty => {
       // Exists for the lifetime of a sim, so disposal patterns not needed.
+      const dynamicProperty = new DynamicProperty<string, string, Material>( materialProperty, { derive: material => material.nameProperty } );
+
+      console.log( 'making dynamic property material=>nameProperty', dynamicProperty.id );
       return new RichText( new PatternStringProperty( new DerivedProperty( [
         DensityBuoyancyCommonPreferences.volumeUnitsProperty,
         DensityBuoyancyCommonStrings.densityReadoutPatternStringProperty,
@@ -35,7 +38,7 @@
       ], ( units, litersString, decimetersCubedString ) => {
         return units === 'liters' ? litersString : decimetersCubedString;
       } ), {
-        material: new DynamicProperty<string, string, Material>( materialProperty, { derive: material => material.nameProperty } ),
+        material: dynamicProperty,
         density: new DerivedProperty( [ materialProperty ], material => material.density / 1000 )
       }, {
         tandem: Tandem.OPT_OUT,

@samreid
Copy link
Member

samreid commented Sep 26, 2023

@zepumph and I worked out a pattern in TinyEmitter so that re-entrant emits are scheduled in order. It is working really well and resolves this problem. We are wondering if it should be the default behavior and if we can implement it in a way that has low risk for performance or memory problems in the non-re-entrant cases.

@zepumph
Copy link
Member

zepumph commented Sep 26, 2023

I'd like to add that ?stringTest=dynamic causes an infinite loop in the patch. I'm nervous about what it would take to go "all in" on this, and would probably prefer to just have an option for cases we need it to (even a mutable option for a DynamicProperty to set/assert onto its provided parameter propertyProperty. Still more to think about. I think bringing @jonathanolson into the loop would be helpful from here.

I also added unit tests to Property to show how that fixed things up.

   Subject: [PATCH] update dependencies for chains test, https://github.com/phetsims/perennial/issues/334
---
Index: js/TinyEmitterTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/TinyEmitterTests.ts b/js/TinyEmitterTests.ts
--- a/js/TinyEmitterTests.ts	(revision 2c5d217ca16faaa5be669b3a1b804027ce0bafc6)
+++ b/js/TinyEmitterTests.ts	(date 1695760327497)
@@ -204,4 +204,63 @@
 
   // Check these values when running with ?listenerOrder=reverse or ?listenerOrder=random or ?listenerOrder=random(123)
   console.log( values.join( '' ) );
+} );
+
+
+class FakeEmitter {
+  listeners = [];
+  notifyingCount = 0;
+  notifyingCountListeners = [];
+  emitContexts = [];
+
+  constructor() {}
+
+  addListener( listener ) {
+    this.listeners.push( listener );
+  }
+
+  emit( arg: number ) {
+    this.emitContexts.push( {
+      listeners: this.listeners,
+      arg: arg
+    } );
+    if ( this.emitContexts.length === 1 ) {
+      while ( this.emitContexts.length > 0 ) {
+        const emitContext = this.emitContexts[ 0 ];
+        for ( let i = 0; i < emitContext.listeners.length; i++ ) {
+          const listener = emitContext.listeners[ i ];
+          listener( emitContext.arg );
+        }
+        this.emitContexts.shift();      }
+    }
+  }
+
+  handleSelf() {
+
+  }
+}
+
+
+QUnit.test( 'TinyEmitter listener order bugz', assert => {
+  assert.ok( true );
+  const emitter = new TinyEmitter();
+  emitter.addListener( number => {
+    if ( number < 10 ) {
+      emitter.emit( number + 1 );
+    }
+  } );
+  emitter.addListener( number => console.log( number ) );
+  emitter.emit( 1 );
+
+  /*
+  expect:
+  2
+  1
+  3
+
+  desire:
+  1
+  2
+  3
+   */
 } );
\ No newline at end of file
Index: js/TinyEmitter.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/TinyEmitter.ts b/js/TinyEmitter.ts
--- a/js/TinyEmitter.ts	(revision 2c5d217ca16faaa5be669b3a1b804027ce0bafc6)
+++ b/js/TinyEmitter.ts	(date 1695762396344)
@@ -31,6 +31,7 @@
 type EmitContext<T extends IntentionalAny[]> = {
   index: number;
   listenerArray?: TEmitterListener<T>[];
+  args: T;
 };
 
 // Store the number of listeners from the single TinyEmitter instance that has the most listeners in the whole runtime.
@@ -114,30 +115,37 @@
     if ( this.listeners.size > 0 ) {
 
       const emitContext: EmitContext<T> = {
-        index: 0
+        index: 0,
+        args: args
         // listenerArray: [] // {Array.<function>|undefined} assigned if a mutation is made during emit
       };
       this.emitContexts.push( emitContext );
+      // assert && assert( this.emitContexts.length <= 1000, 'reentrant depth of 1000 seems like a bug to me!' );
 
-      for ( const listener of this.listeners ) {
-        listener( ...args );
-        emitContext.index++;
-
-        // If a listener was added or removed, we cannot continue processing the mutated Set, we must switch to
-        // iterate over the guarded array
-        if ( emitContext.listenerArray ) {
-          break;
-        }
-      }
+      if ( this.emitContexts.length === 1 ) {
+        while ( this.emitContexts.length > 0 ) {
+          const emitContext = this.emitContexts[ 0 ];
+          const listeners = emitContext.listenerArray || Array.from( this.listeners );
+          const hadListenerArray = !!emitContext.listenerArray;
+          for ( let i = 0; i < listeners.length; i++ ) {
+            const listener = listeners[ i ];
+            listener( ...emitContext.args );
+            emitContext.index++;
+          }
 
-      // If the listeners were guarded during emit, we bailed out on the for..of and continue iterating over the original
-      // listeners in order from where we left off.
-      if ( emitContext.listenerArray ) {
-        for ( let i = emitContext.index; i < emitContext.listenerArray.length; i++ ) {
-          emitContext.listenerArray[ i ]( ...args );
+          // If the listeners were guarded during emit, we bailed out on the for..of and continue iterating over the original
+          // listeners in order from where we left off.
+          if ( emitContext.listenerArray ) {
+            for ( let i = emitContext.index; i < emitContext.listenerArray.length; i++ ) {
+              emitContext.listenerArray[ i ]( ...args );
+            }
+          }
+          this.emitContexts.shift();
         }
       }
-      this.emitContexts.pop();
+      else {
+        console.log( this.emitContexts.length );
+      }
     }
   }
 


@zepumph zepumph removed their assignment Sep 26, 2023
@zepumph
Copy link
Member

zepumph commented Sep 27, 2023

We met with @jonathanolson today and things seemed really promising. We found an infinite loop in Buoyancy with these changes when switching the "Volume units" simulation preference, this patch comments out the maxWidth setter in NumberDisplay to show that that is the bug. @jonathanolson wanted to take a look at that. There is also still that infinite loop to investigate with dynamic stringTest.

Subject: [PATCH] so hard, so bad
---
Index: axon/js/TinyEmitterTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/TinyEmitterTests.ts b/axon/js/TinyEmitterTests.ts
--- a/axon/js/TinyEmitterTests.ts	(revision 2c5d217ca16faaa5be669b3a1b804027ce0bafc6)
+++ b/axon/js/TinyEmitterTests.ts	(date 1695850084702)
@@ -204,4 +204,19 @@
 
   // Check these values when running with ?listenerOrder=reverse or ?listenerOrder=random or ?listenerOrder=random(123)
   console.log( values.join( '' ) );
+} );
+
+QUnit.test( 'TinyEmitter listener order bugz', assert => {
+  const emitter = new TinyEmitter<[ number ]>();
+  let count = 1;
+  emitter.addListener( number => {
+    if ( number < 10 ) {
+      emitter.emit( number + 1 );
+    }
+  } );
+  emitter.addListener( number => {
+    console.log( number );
+    assert.ok( number === count++, `should go in order of emitting: ${number}` );
+  } );
+  emitter.emit( count );
 } );
\ No newline at end of file
Index: axon/js/TinyPropertyTests.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/TinyPropertyTests.ts b/axon/js/TinyPropertyTests.ts
--- a/axon/js/TinyPropertyTests.ts	(revision 2c5d217ca16faaa5be669b3a1b804027ce0bafc6)
+++ b/axon/js/TinyPropertyTests.ts	(date 1695849980490)
@@ -21,6 +21,35 @@
   assert.ok( true, 'one test' );
 } );
 
+QUnit.test( 'TinyProperty notify in value-change order', assert => {
+  let count = 2; // starts as a value of 1, so 2 is the first value we change to.
+
+  const myProperty = new TinyProperty<number>( 1 );
+
+  myProperty.lazyLink( value => {
+    if ( value < 10 ) {
+      myProperty.value = value + 1;
+    }
+  } );
+
+  myProperty.lazyLink( ( value, oldValue ) => {
+    console.log( `asserts ${oldValue} => ${value}` );
+    assert.ok( value === oldValue + 1, `increment each time: ${oldValue} -> ${value}` );
+    assert.ok( value === count++, `increment in order expected: ${count - 2}->${count - 1}, received: ${oldValue} -> ${value}` );
+  } );
+  myProperty.value = count;
+
+  // We hope:
+  // null => a
+  // a => b
+  // b => c
+
+  // We think since it is buggy we will instead get:
+  // null=>a
+  // b=>c
+  // a=>b
+} );
+
 QUnit.test( 'TinyProperty onBeforeNotify', assert => {
 
   class MyObservedObject {
Index: axon/js/TinyEmitter.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/TinyEmitter.ts b/axon/js/TinyEmitter.ts
--- a/axon/js/TinyEmitter.ts	(revision 2c5d217ca16faaa5be669b3a1b804027ce0bafc6)
+++ b/axon/js/TinyEmitter.ts	(date 1695850084680)
@@ -31,6 +31,7 @@
 type EmitContext<T extends IntentionalAny[]> = {
   index: number;
   listenerArray?: TEmitterListener<T>[];
+  args: T;
 };
 
 // Store the number of listeners from the single TinyEmitter instance that has the most listeners in the whole runtime.
@@ -114,30 +115,36 @@
     if ( this.listeners.size > 0 ) {
 
       const emitContext: EmitContext<T> = {
-        index: 0
+        index: 0,
+        args: args //.slice() as T // TODO: do we need to slice? https://github.com/phetsims/buoyancy/issues/67
         // listenerArray: [] // {Array.<function>|undefined} assigned if a mutation is made during emit
       };
       this.emitContexts.push( emitContext );
+      // assert && assert( this.emitContexts.length <= 1000, 'reentrant depth of 1000 seems like a bug to me!' );
 
-      for ( const listener of this.listeners ) {
-        listener( ...args );
-        emitContext.index++;
-
-        // If a listener was added or removed, we cannot continue processing the mutated Set, we must switch to
-        // iterate over the guarded array
-        if ( emitContext.listenerArray ) {
-          break;
-        }
-      }
+      if ( this.emitContexts.length === 1 ) {
+        while ( this.emitContexts.length > 0 ) {
+          const emitContext = this.emitContexts[ 0 ];
+          const listeners = emitContext.listenerArray || Array.from( this.listeners );
+          for ( let i = 0; i < listeners.length; i++ ) {
+            listeners[ i ]( ...emitContext.args );
+            emitContext.index++;
+          }
 
-      // If the listeners were guarded during emit, we bailed out on the for..of and continue iterating over the original
-      // listeners in order from where we left off.
-      if ( emitContext.listenerArray ) {
-        for ( let i = emitContext.index; i < emitContext.listenerArray.length; i++ ) {
-          emitContext.listenerArray[ i ]( ...args );
+          // If the listeners were guarded during emit, we bailed out on the for..of and continue iterating over the original
+          // listeners in order from where we left off.
+          if ( emitContext.listenerArray ) {
+            for ( let i = emitContext.index; i < emitContext.listenerArray.length; i++ ) {
+              emitContext.listenerArray[ i ]( ...args );
+            }
+          }
+          this.emitContexts.shift();
         }
       }
-      this.emitContexts.pop();
+      else {
+        // TODO: delete or assert out? "too many levels deep, it must be an infinite loop" 1000 worked well in testing, https://github.com/phetsims/buoyancy/issues/67
+        console.log( 'this.emitContexts.length', this.emitContexts.length );
+      }
     }
   }
 
Index: scenery/js/nodes/Node.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/scenery/js/nodes/Node.ts b/scenery/js/nodes/Node.ts
--- a/scenery/js/nodes/Node.ts	(revision 93246e46f9a0072ae8a8e05c04ddfbc110af7b5d)
+++ b/scenery/js/nodes/Node.ts	(date 1695850084698)
@@ -3072,6 +3072,9 @@
   public setCenterY( y: number ): this {
     const currentCenterY = this.getCenterY();
     if ( isFinite( currentCenterY ) ) {
+      if ( this.constructor.name === 'RichText' ) {
+        console.log( 'y', y, currentCenterY );
+      }
       this.translate( 0, y - currentCenterY, true );
     }
 
Index: scenery-phet/js/NumberDisplay.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/scenery-phet/js/NumberDisplay.ts b/scenery-phet/js/NumberDisplay.ts
--- a/scenery-phet/js/NumberDisplay.ts	(revision 9d90b2325a590021ba74b585d1c110288a38292d)
+++ b/scenery-phet/js/NumberDisplay.ts	(date 1695850084707)
@@ -244,8 +244,9 @@
     longestStringProperty.link( longestString => {
       const demoText = new Constructor( longestString, _.omit( valueTextOptions, 'tandem' ) );
 
-      valueText.maxWidth = ( options.textOptions.maxWidth !== null ) ? options.textOptions.maxWidth! :
-                           ( demoText.width !== 0 ) ? demoText.width : null;
+      // TODO: This maxWidth causes a bug with "breadth first" listener order https://github.com/phetsims/buoyancy/issues/67
+      // valueText.maxWidth = ( options.textOptions.maxWidth !== null ) ? options.textOptions.maxWidth! :
+      //                      ( demoText.width !== 0 ) ? demoText.width : null;
       demoText.maxWidth = valueText.maxWidth;
 
       backgroundNode.rectWidth = Math.max( options.minBackgroundWidth, demoText.width + 2 * options.xMargin );
@@ -260,6 +261,11 @@
     this.valueText = valueText;
     this.backgroundNode = backgroundNode;
 
+    this.valueText.transformEmitter.listeners = new Set( [ () => {
+      debugger;
+      console.log( new Error().stack );
+    }, ...Array.from( this.valueText.transformEmitter.listeners ) ] );
+
     // Align the value in the background.
     ManualConstraint.create( this, [ valueText, backgroundNode ], ( valueTextProxy, backgroundNodeProxy ) => {
 

@zepumph
Copy link
Member

zepumph commented Feb 6, 2024

Since fixing #73, this issue is breaking CT just about every snapshot.

@zepumph
Copy link
Member

zepumph commented Feb 6, 2024

Just catching up on this issue right now, it seems like there are two problems, and both feel like they are worth @jonathanolson's time in investigating. Co-assigning.

@zepumph
Copy link
Member

zepumph commented Feb 7, 2024

Lots of great investigation here today. @jonathanolson and @AgustinVallejo and I were able to reproduce with steps in #67 (comment). This is far from the first time that reentrancy has meant that we can't trust the "oldValue" of the Property. For example, it was worked around in phetsims/scenery@3f1cd688. So above @jonathanolson added another workaround that doesn't solve the underlying problem, but does solve the assertion we were getting by keeping track of the actually last set Property (old value) instead of trusting the value that comes from the Property listener callback parameter.

@jonathanolson and I were still worried about how this is quite buggy for DynamicProperty still in cases where the reentrant cases need to take precedent as the listened-to Property, since it will be swapped out for a previous propertyProperty. Basically this won't be solved until we have a general solution in phetsims/axon#447.

BUT, it doesn't effect this case in buoyancy. This is because all the reentrant cases just HAPPEN to be mapping to the same value (all new instance of a custom Material have the same value for the nameProperty and the density as the combobox settles on the new value). This is totally happen stance, but at least we can close this issue. CT is clear, and we know how to proceed generally. Closing

@zepumph zepumph removed their assignment Jul 26, 2024
@samreid
Copy link
Member

samreid commented Jul 29, 2024

@AgustinVallejo AgustinVallejo removed their assignment Jul 29, 2024
@samreid
Copy link
Member

samreid commented Aug 5, 2024

Patch with refactoring and debug information, still broken:

Subject: [PATCH] Make methods static, see https://github.com/phetsims/density-buoyancy-common/issues/314
---
Index: axon/js/DynamicProperty.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/DynamicProperty.ts b/axon/js/DynamicProperty.ts
--- a/axon/js/DynamicProperty.ts	(revision 8188a120188df2107121e308e109420718c67e20)
+++ b/axon/js/DynamicProperty.ts	(date 1722896731122)
@@ -243,13 +243,26 @@
    *                                              undefined.
    */
   private onPropertyChange( newPropertyValue: OuterValueType | null, oldPropertyValue: OuterValueType | null | undefined ): void {
+    if ( this.hello ) {
+      console.log( phet.preloads.phetio.queryParameters.frameTitle, 'onPropertyChange' );
+      console.log( phet.preloads.phetio.queryParameters.frameTitle, 'thisdisposed', this.isDisposed );
+    }
     if ( oldPropertyValue ) {
+      if ( this.hello ) {
+        console.log( phet.preloads.phetio.queryParameters.frameTitle, 'remove old one' )
+      }
       this.derive( oldPropertyValue ).unlink( this.propertyPropertyListener );
     }
     if ( newPropertyValue ) {
+      if ( this.hello ) {
+        console.log( phet.preloads.phetio.queryParameters.frameTitle, 'add new one' )
+      }
       this.derive( newPropertyValue ).link( this.propertyPropertyListener );
     }
     else {
+      if ( this.hello ) {
+        console.log( phet.preloads.phetio.queryParameters.frameTitle, 'its the else' )
+      }
       // Switch to null when our Property's value is null.
       this.onPropertyPropertyChange( this.defaultValue, null, null );
     }
@@ -280,6 +293,9 @@
    * Disposes this Property
    */
   public override dispose(): void {
+    if ( this.hello ) {
+      console.log( phet.preloads.phetio.queryParameters.frameTitle, 'disposing' )
+    }
     this.valuePropertyProperty.unlink( this.propertyListener );
 
     if ( this.valuePropertyProperty.value !== null ) {
Index: density-buoyancy-common/js/buoyancy/view/ReadoutListAccordionBox.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/density-buoyancy-common/js/buoyancy/view/ReadoutListAccordionBox.ts b/density-buoyancy-common/js/buoyancy/view/ReadoutListAccordionBox.ts
--- a/density-buoyancy-common/js/buoyancy/view/ReadoutListAccordionBox.ts	(revision c176d2fcf605eb025443a6747dfe797af3767744)
+++ b/density-buoyancy-common/js/buoyancy/view/ReadoutListAccordionBox.ts	(date 1722896896216)
@@ -16,11 +16,14 @@
 import DensityBuoyancyCommonConstants from '../../common/DensityBuoyancyCommonConstants.js';
 import { combineOptions, optionize4 } from '../../../../phet-core/js/optionize.js';
 import AccordionBox, { AccordionBoxOptions } from '../../../../sun/js/AccordionBox.js';
-import DensityBuoyancyCommonStrings from '../../DensityBuoyancyCommonStrings.js';
-import PatternStringProperty from '../../../../axon/js/PatternStringProperty.js';
 import TinyProperty from '../../../../axon/js/TinyProperty.js';
 import WithRequired from '../../../../phet-core/js/types/WithRequired.js';
+import PatternStringProperty from '../../../../axon/js/PatternStringProperty.js';
+import DensityBuoyancyCommonStrings from '../../DensityBuoyancyCommonStrings.js';
 import DerivedStringProperty from '../../../../axon/js/DerivedStringProperty.js';
+import MaterialProperty from '../../common/model/MaterialProperty.js';
+import Mass from '../../common/model/Mass.js';
+import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
 
 const DEFAULT_FONT = new PhetFont( 14 );
 const HBOX_SPACING = 5;
@@ -30,15 +33,18 @@
   font: DEFAULT_FONT
 };
 
-type SelfOptions<ReadoutType> = {
+type SelfOptions<ReadoutType = MaterialProperty | Mass> = {
 
   // Provide the ideal max content width for the accordion box content. This is used to apply maxWidths to the Texts of the readout.
   contentWidthMax?: number | TReadOnlyProperty<number>;
 
   readoutItems?: ReadoutItemOptions<ReadoutType>[];
+
+  generateNameItem: ( readoutItem: ReadoutType ) => TReadOnlyProperty<string>;
+  generateValueItem: ( readoutItem: ReadoutType ) => TReadOnlyProperty<string>;
 };
 
-export type ReadoutItemOptions<ReadoutType> = {
+export type ReadoutItemOptions<ReadoutType = MaterialProperty | Mass> = {
   readoutItem: ReadoutType; // Provided for use by generateReadoutData() to create the name/value Properties
 
   // By default, the implementation of generateReadoutData() will create a default nameProperty, but you can supply your
@@ -47,20 +53,18 @@
   readoutFormat?: RichTextOptions; // Any extra formatting options to be passed ONLY to the value text.
 };
 
-export type ReadoutData = {
-  nameProperty: TReadOnlyProperty<string>;
-  valueProperty: TReadOnlyProperty<string>;
-};
+export type ReadoutListAccordionBoxOptions<ReadoutType = MaterialProperty | Mass> = SelfOptions<ReadoutType> & WithRequired<AccordionBoxOptions, 'tandem'>;
 
-export type ReadoutListAccordionBoxOptions<ReadoutType> = SelfOptions<ReadoutType> & WithRequired<AccordionBoxOptions, 'tandem'>;
-
-export default abstract class ReadoutListAccordionBox<ReadoutType> extends AccordionBox {
+export default abstract class ReadoutListAccordionBox<ReadoutType = MaterialProperty | Mass> extends AccordionBox {
 
   protected readonly cleanupEmitter = new TinyEmitter();
 
   private readonly readoutBox: VBox;
   private readonly contentWidthMaxProperty: TReadOnlyProperty<number>;
 
+  private readonly generateValueItem: ( readoutItem: ReadoutType ) => TReadOnlyProperty<string>;
+  private readonly generateNameItem: ( readoutItem: ReadoutType ) => TReadOnlyProperty<string>;
+
   protected constructor(
     titleStringProperty: TReadOnlyProperty<string>,
     providedOptions?: ReadoutListAccordionBoxOptions<ReadoutType>
@@ -96,71 +100,80 @@
     this.contentWidthMaxProperty.link( maxWidthListener );
     this.disposeEmitter.addListener( () => this.contentWidthMaxProperty.unlink( maxWidthListener ) );
 
+    this.generateValueItem = options.generateValueItem;
+    this.generateNameItem = options.generateNameItem;
+
     options.readoutItems && this.setReadoutItems( options.readoutItems );
   }
 
   public setReadoutItems( readoutItems: ReadoutItemOptions<ReadoutType>[] ): void {
+    this.readoutBox.children.forEach( child => child.dispose() );
+    this.readoutBox.children = readoutItems.map( readoutItem => new ReadoutBoxLine(
+      readoutItem,
+      this.contentWidthMaxProperty,
+      item => this.generateValueItem( item ),
+      item => this.generateNameItem( item )
+    ) );
+  }
 
-    // Clear the previous materials that may have been created.
-    this.cleanupEmitter.emit();
-    this.cleanupEmitter.removeAllListeners();
+  public override dispose(): void {
+    assert && assert( false, 'Not disposable' );
+    this.readoutBox.children.forEach( child => child.dispose() );
+    super.dispose();
+  }
+}
 
-    this.readoutBox.children = readoutItems.map( readoutItem => {
+class ReadoutBoxLine<T> extends HBox {
+  public constructor( readoutItem: ReadoutItemOptions<T>, contentWidthMaxProperty: TReadOnlyProperty<number>, generateValueProperty: ( t: T ) => TReadOnlyProperty<string>, generateNameProperty: ( t: T ) => TReadOnlyProperty<string> ) {
 
-      // TODO: Create a new type and file for this data structure, see https://github.com/phetsims/density-buoyancy-common/issues/123
-      // TODO: So that the map will read readoutItems.map( item => new ReadoutItemNode(item)), and dispose has a method, see https://github.com/phetsims/density-buoyancy-common/issues/123
-      const readoutData = this.generateReadoutData( readoutItem.readoutItem );
-      const nameProperty = readoutItem.readoutNameProperty || readoutData.nameProperty;
-      const nameColonProperty = new PatternStringProperty(
-        DensityBuoyancyCommonStrings.nameColonPatternStringProperty, {
-          name: nameProperty
-        } );
+    const valueProperty = generateValueProperty( readoutItem.readoutItem );
+    const nameProperty = readoutItem.readoutNameProperty ? new DerivedProperty( [ readoutItem.readoutNameProperty ], name => name ) : generateNameProperty( readoutItem.readoutItem );
+    const nameColonProperty = new PatternStringProperty(
+      DensityBuoyancyCommonStrings.nameColonPatternStringProperty, {
+        name: nameProperty
+      } );
 
-      const labelText = new RichText( nameColonProperty, TEXT_OPTIONS );
-      const readoutFormat = readoutItem.readoutFormat ? readoutItem.readoutFormat : {};
-      const valueText = new RichText( readoutData.valueProperty,
-        combineOptions<RichTextOptions>( {
+    const labelText = new RichText( nameColonProperty, TEXT_OPTIONS );
+    const readoutFormat = readoutItem.readoutFormat ? readoutItem.readoutFormat : {};
+    const derivedStringProperty = new DerivedStringProperty( [ nameColonProperty, valueProperty ], ( name, value ) => {
+      return `${name} ${value}`;
+    } );
+    const valueText = new RichText( valueProperty,
+      combineOptions<RichTextOptions>( {
 
-          // A11y content for the PDOM
-          tagName: 'p',
-          innerContent: new DerivedStringProperty( [ nameColonProperty, readoutData.valueProperty ], ( name, value ) => {
-            return `${name} ${value}`;
-          } )
-        }, TEXT_OPTIONS, readoutFormat ) );
+        // A11y content for the PDOM
+        tagName: 'p',
+        innerContent: derivedStringProperty
+      }, TEXT_OPTIONS, readoutFormat ) );
 
-      const maxWidthListener = ( contentWidthMax: number ) => {
-        const maxWidth = ( contentWidthMax - HBOX_SPACING ) / 2;
-        labelText.maxWidth = maxWidth;
-        valueText.maxWidth = maxWidth;
-      };
-      this.contentWidthMaxProperty.link( maxWidthListener );
-
-      this.cleanupEmitter.addListener( () => {
-        this.contentWidthMaxProperty.unlink( maxWidthListener );
-        valueText.dispose();
-        labelText.dispose();
-        nameColonProperty.dispose();
-      } );
+    const maxWidthListener = ( contentWidthMax: number ) => {
+      const maxWidth = ( contentWidthMax - HBOX_SPACING ) / 2;
+      labelText.maxWidth = maxWidth;
+      valueText.maxWidth = maxWidth;
+    };
+    contentWidthMaxProperty.link( maxWidthListener );
 
-      const alignGroup = new AlignGroup();
-      return new HBox( {
-        children: [
-          alignGroup.createBox( labelText, { xAlign: 'right' } ),
-          alignGroup.createBox( valueText, { xAlign: 'left' } )
-        ],
-        align: 'origin',
-        justify: 'center',
-        spacing: DensityBuoyancyCommonConstants.SPACING_SMALL
-      } );
+    const alignGroup = new AlignGroup();
+    super( {
+      children: [
+        alignGroup.createBox( labelText, { xAlign: 'right' } ),
+        alignGroup.createBox( valueText, { xAlign: 'left' } )
+      ],
+      align: 'origin',
+      justify: 'center',
+      spacing: DensityBuoyancyCommonConstants.SPACING_SMALL
     } );
-  }
 
-  protected abstract generateReadoutData( readoutType: ReadoutType ): ReadoutData;
-
-  public override dispose(): void {
-    assert && assert( false, 'Not disposable' );
-    this.cleanupEmitter.emit();
-    super.dispose();
+    this.disposeEmitter.addListener( () => {
+      contentWidthMaxProperty.unlink( maxWidthListener );
+      valueText.dispose();
+      labelText.dispose();
+      nameColonProperty.dispose();
+      valueProperty.dispose();
+      // nameProperty.dispose();
+      alignGroup.dispose();
+      derivedStringProperty.dispose();
+    } );
   }
 }
 
Index: density-buoyancy-common/js/buoyancy/view/DensityAccordionBox.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/density-buoyancy-common/js/buoyancy/view/DensityAccordionBox.ts b/density-buoyancy-common/js/buoyancy/view/DensityAccordionBox.ts
--- a/density-buoyancy-common/js/buoyancy/view/DensityAccordionBox.ts	(revision c176d2fcf605eb025443a6747dfe797af3767744)
+++ b/density-buoyancy-common/js/buoyancy/view/DensityAccordionBox.ts	(date 1722896868767)
@@ -14,14 +14,16 @@
 import DensityBuoyancyCommonConstants from '../../common/DensityBuoyancyCommonConstants.js';
 import StringUtils from '../../../../phetcommon/js/util/StringUtils.js';
 import Utils from '../../../../dot/js/Utils.js';
-import ReadoutListAccordionBox, { ReadoutData, ReadoutListAccordionBoxOptions } from './ReadoutListAccordionBox.js';
+import ReadoutListAccordionBox, { ReadoutListAccordionBoxOptions } from './ReadoutListAccordionBox.js';
 import DerivedStringProperty from '../../../../axon/js/DerivedStringProperty.js';
 import optionize, { EmptySelfOptions } from '../../../../phet-core/js/optionize.js';
 import MaterialProperty from '../../common/model/MaterialProperty.js';
+import StrictOmit from '../../../../phet-core/js/types/StrictOmit.js';
+import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
 
 type ParentOptions = ReadoutListAccordionBoxOptions<MaterialProperty>;
 type SelfOptions = EmptySelfOptions;
-type DensityAccordionBoxOptions = SelfOptions & ParentOptions;
+type DensityAccordionBoxOptions = SelfOptions & StrictOmit<ParentOptions, 'generateNameItem' | 'generateValueItem'>;
 
 export default class DensityAccordionBox extends ReadoutListAccordionBox<MaterialProperty> {
 
@@ -29,28 +31,21 @@
 
     const options = optionize<DensityAccordionBoxOptions, SelfOptions, ParentOptions>()( {
       expandedDefaultValue: false,
-      accessibleName: DensityBuoyancyCommonStrings.densityStringProperty
-    }, providedOptions );
-
-    super( titleStringProperty, options );
-  }
+      accessibleName: DensityBuoyancyCommonStrings.densityStringProperty,
+      generateNameItem: materialProperty => {
 
-  protected override generateReadoutData( materialProperty: MaterialProperty ): ReadoutData {
-
-    // Use DynamicProperty so that this name is updated based on the material AND material's name changing.
-    const nameProperty = new DynamicProperty<string, string, Material>( materialProperty, {
-      derive: material => material.nameProperty
-    } );
-
-    // Returns the filled in string for the material readout or '?' if the material is hidden
-    const valueProperty = new DerivedStringProperty(
-      [
+        const dynaprop = new DynamicProperty<string, string, Material>( materialProperty, {
+          derive: material => new DerivedProperty( [ material.nameProperty ], name => name )
+        } );
+        dynaprop.hello = true;
+        return dynaprop;
+      },
+      generateValueItem: materialProperty => new DerivedStringProperty( [
         materialProperty,
         materialProperty.densityProperty,
         DensityBuoyancyCommonConstants.KILOGRAMS_PER_VOLUME_PATTERN_STRING_PROPERTY,
         DensityBuoyancyCommonStrings.questionMarkStringProperty
-      ],
-      ( material, density, patternStringProperty, questionMarkString ) => {
+      ], ( material, density, patternStringProperty, questionMarkString ) => {
         return material.hidden ?
                questionMarkString :
                StringUtils.fillIn( patternStringProperty, {
@@ -61,17 +56,10 @@
                  value: Utils.toFixed( density / DensityBuoyancyCommonConstants.LITERS_IN_CUBIC_METER, 2 ),
                  decimalPlaces: 2
                } );
-      } );
+      } )
+    }, providedOptions );
 
-    this.cleanupEmitter.addListener( () => {
-      nameProperty.dispose();
-      valueProperty.dispose();
-    } );
-
-    return {
-      nameProperty: nameProperty,
-      valueProperty: valueProperty
-    };
+    super( titleStringProperty, options );
   }
 }
 
Index: density-buoyancy-common/js/buoyancy/view/SubmergedAccordionBox.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/density-buoyancy-common/js/buoyancy/view/SubmergedAccordionBox.ts b/density-buoyancy-common/js/buoyancy/view/SubmergedAccordionBox.ts
--- a/density-buoyancy-common/js/buoyancy/view/SubmergedAccordionBox.ts	(revision c176d2fcf605eb025443a6747dfe797af3767744)
+++ b/density-buoyancy-common/js/buoyancy/view/SubmergedAccordionBox.ts	(date 1722895931915)
@@ -8,45 +8,38 @@
 
 import densityBuoyancyCommon from '../../densityBuoyancyCommon.js';
 import { combineOptions } from '../../../../phet-core/js/optionize.js';
-import Utils from '../../../../dot/js/Utils.js';
-import ReadoutListAccordionBox, { ReadoutData, ReadoutListAccordionBoxOptions } from './ReadoutListAccordionBox.js';
+import ReadoutListAccordionBox, { ReadoutListAccordionBoxOptions } from './ReadoutListAccordionBox.js';
 import DensityBuoyancyCommonStrings from '../../DensityBuoyancyCommonStrings.js';
 import DensityBuoyancyCommonPreferences from '../../common/model/DensityBuoyancyCommonPreferences.js';
 import Mass from '../../common/model/Mass.js';
 import PatternStringProperty from '../../../../axon/js/PatternStringProperty.js';
+import Utils from '../../../../dot/js/Utils.js';
+import DerivedProperty from '../../../../axon/js/DerivedProperty.js';
+import StrictOmit from '../../../../phet-core/js/types/StrictOmit.js';
 
 export default class SubmergedAccordionBox extends ReadoutListAccordionBox<Mass> {
 
-  public constructor( providedOptions?: ReadoutListAccordionBoxOptions<Mass> ) {
+  public constructor( providedOptions?: StrictOmit<ReadoutListAccordionBoxOptions<Mass>, 'generateNameItem' | 'generateValueItem'> ) {
 
     const options = combineOptions<ReadoutListAccordionBoxOptions<Mass>>( {
       visibleProperty: DensityBuoyancyCommonPreferences.percentSubmergedVisibleProperty,
       readoutItems: [],
       expandedDefaultValue: false,
 
-      accessibleName: DensityBuoyancyCommonStrings.percentSubmergedStringProperty
-    }, providedOptions );
-
-    super( DensityBuoyancyCommonStrings.percentSubmergedStringProperty, options );
-  }
+      accessibleName: DensityBuoyancyCommonStrings.percentSubmergedStringProperty,
 
-  protected override generateReadoutData( mass: Mass ): ReadoutData {
-
-    const patternStringProperty = new PatternStringProperty( DensityBuoyancyCommonStrings.valuePercentStringProperty, {
-      value: mass.percentSubmergedProperty
-    }, {
-      maps: {
-        value: percentSubmerged => Utils.toFixed( percentSubmerged, 1 )
-      }
-    } );
-    this.cleanupEmitter.addListener( () => {
-      patternStringProperty.dispose();
-    } );
+      // Thin wrapper that is safe to dispose
+      generateNameItem: mass => new DerivedProperty( [ mass.nameProperty ], name => name ),
+      generateValueItem: mass => new PatternStringProperty( DensityBuoyancyCommonStrings.valuePercentStringProperty, {
+        value: mass.percentSubmergedProperty
+      }, {
+        maps: {
+          value: percentSubmerged => Utils.toFixed( percentSubmerged, 1 )
+        }
+      } )
+    }, providedOptions );
 
-    return {
-      nameProperty: mass.nameProperty,
-      valueProperty: patternStringProperty
-    };
+    super( DensityBuoyancyCommonStrings.percentSubmergedStringProperty, options );
   }
 }
 

@samreid
Copy link
Member

samreid commented Aug 5, 2024

Steps to reproduce the problem:

  1. Buoyancy in the state wrapper
  2. Set state rate => 0
  3. Applications screen
  4. Change material inside to Sand
  5. Change bottle to boat
  6. Set state now

@samreid
Copy link
Member

samreid commented Aug 13, 2024

Even removing all of the cleanupEmitter work in the accordion boxes:

Subject: [PATCH] Eliminate availableMasses.remove, see https://github.com/phetsims/density-buoyancy-common/issues/331
---
Index: js/buoyancy/view/ReadoutListAccordionBox.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/buoyancy/view/ReadoutListAccordionBox.ts b/js/buoyancy/view/ReadoutListAccordionBox.ts
--- a/js/buoyancy/view/ReadoutListAccordionBox.ts	(revision 9038a37285a27b952d9ccee43a93c60d70890788)
+++ b/js/buoyancy/view/ReadoutListAccordionBox.ts	(date 1723581970099)
@@ -12,7 +12,6 @@
 import PhetFont from '../../../../scenery-phet/js/PhetFont.js';
 import { AlignGroup, HBox, RichText, RichTextOptions, Text, VBox } from '../../../../scenery/js/imports.js';
 import densityBuoyancyCommon from '../../densityBuoyancyCommon.js';
-import TinyEmitter from '../../../../axon/js/TinyEmitter.js';
 import DensityBuoyancyCommonConstants from '../../common/DensityBuoyancyCommonConstants.js';
 import { combineOptions, optionize4 } from '../../../../phet-core/js/optionize.js';
 import AccordionBox, { AccordionBoxOptions } from '../../../../sun/js/AccordionBox.js';
@@ -57,7 +56,7 @@
 
 export default abstract class ReadoutListAccordionBox<ReadoutType> extends AccordionBox {
 
-  protected readonly cleanupEmitter = new TinyEmitter();
+  // protected readonly cleanupEmitter = new TinyEmitter();
 
   private readonly readoutBox: VBox;
   private readonly contentWidthMaxProperty: TReadOnlyProperty<number>;
@@ -103,8 +102,8 @@
   public setReadoutItems( readoutItems: ReadoutItemOptions<ReadoutType>[] ): void {
 
     // Clear the previous materials that may have been created.
-    this.cleanupEmitter.emit();
-    this.cleanupEmitter.removeAllListeners();
+    // this.cleanupEmitter.emit();
+    // this.cleanupEmitter.removeAllListeners();
 
     this.readoutBox.children = readoutItems.map( readoutItem => {
 
@@ -136,12 +135,12 @@
       };
       this.contentWidthMaxProperty.link( maxWidthListener );
 
-      this.cleanupEmitter.addListener( () => {
-        this.contentWidthMaxProperty.unlink( maxWidthListener );
-        valueText.dispose();
-        labelText.dispose();
-        nameColonProperty.dispose();
-      } );
+      // this.cleanupEmitter.addListener( () => {
+      //   this.contentWidthMaxProperty.unlink( maxWidthListener );
+      //   valueText.dispose();
+      //   labelText.dispose();
+      //   nameColonProperty.dispose();
+      // } );
 
       const alignGroup = new AlignGroup();
       return new HBox( {
Index: js/buoyancy/view/DensityAccordionBox.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/buoyancy/view/DensityAccordionBox.ts b/js/buoyancy/view/DensityAccordionBox.ts
--- a/js/buoyancy/view/DensityAccordionBox.ts	(revision 9038a37285a27b952d9ccee43a93c60d70890788)
+++ b/js/buoyancy/view/DensityAccordionBox.ts	(date 1723582005437)
@@ -63,10 +63,10 @@
                } );
       } );
 
-    this.cleanupEmitter.addListener( () => {
-      nameProperty.dispose();
-      valueProperty.dispose();
-    } );
+    // this.cleanupEmitter.addListener( () => {
+    //   nameProperty.dispose();
+    //   valueProperty.dispose();
+    // } );
 
     return {
       nameProperty: nameProperty,
Index: js/buoyancy/view/SubmergedAccordionBox.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/buoyancy/view/SubmergedAccordionBox.ts b/js/buoyancy/view/SubmergedAccordionBox.ts
--- a/js/buoyancy/view/SubmergedAccordionBox.ts	(revision 9038a37285a27b952d9ccee43a93c60d70890788)
+++ b/js/buoyancy/view/SubmergedAccordionBox.ts	(date 1723581988414)
@@ -42,9 +42,9 @@
         value: percentSubmerged => Utils.toFixed( percentSubmerged, 1 )
       }
     } );
-    this.cleanupEmitter.addListener( () => {
-      patternStringProperty.dispose();
-    } );
+    // this.cleanupEmitter.addListener( () => {
+    //   patternStringProperty.dispose();
+    // } );
 
     return {
       nameProperty: mass.nameProperty,

There is still a tried to removeListener on something that wasn't a listener on the bottle material inside dynamic property.

@samreid
Copy link
Member

samreid commented Aug 14, 2024

This patch demonstrates that we attempt to dispose the listener before it is linked:

Subject: [PATCH] Eliminate availableMasses.remove, see https://github.com/phetsims/density-buoyancy-common/issues/331
---
Index: axon/js/DynamicProperty.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/DynamicProperty.ts b/axon/js/DynamicProperty.ts
--- a/axon/js/DynamicProperty.ts	(revision 7fc653baeca10f122b62e2ad8dcf803cc226decd)
+++ b/axon/js/DynamicProperty.ts	(date 1723595144158)
@@ -205,6 +205,12 @@
       // No unlink needed, since our own disposal will remove this listener.
       this.lazyLink( this.onSelfChange.bind( this ) );
     }
+
+    setInterval( () => {
+      if ( this.id === 4888 ) {
+        console.log( phet.preloads.phetio.queryParameters.frameTitle + ': 4888.isdisposed = ', this.isDisposed )
+      }
+    }, 1000 );
   }
 
   /**
@@ -244,10 +250,18 @@
    */
   private onPropertyChange( newPropertyValue: OuterValueType | null, oldPropertyValue: OuterValueType | null | undefined ): void {
     if ( oldPropertyValue ) {
-      this.derive( oldPropertyValue ).unlink( this.propertyPropertyListener );
+      const derive = this.derive( oldPropertyValue );
+      if (derive.id===508){
+        console.log( phet.preloads.phetio.queryParameters.frameTitle + ': 508.unlink old');
+      }
+      derive.unlink( this.propertyPropertyListener );
     }
     if ( newPropertyValue ) {
-      this.derive( newPropertyValue ).link( this.propertyPropertyListener );
+      const derive1 = this.derive( newPropertyValue );
+      if (derive1.id===508){
+        console.log( phet.preloads.phetio.queryParameters.frameTitle + ': 508.link new');
+      }
+      derive1.link( this.propertyPropertyListener );
     }
     else {
       // Switch to null when our Property's value is null.
@@ -280,10 +294,25 @@
    * Disposes this Property
    */
   public override dispose(): void {
+
+    assert && assert( !this.isDisposed, 'Disposable can only be disposed once' );
+
+    console.log( phet.preloads.phetio.queryParameters.frameTitle, 'id', this.id, 'dispose, ', this.valuePropertyProperty.phetioID );
+    if ( this.valuePropertyProperty.phetioID === 'buoyancy.applicationsScreen.model.objects.bottle.materialInside.materialProperty' ) {
+      debugger;
+    }
     this.valuePropertyProperty.unlink( this.propertyListener );
 
     if ( this.valuePropertyProperty.value !== null ) {
-      this.derive( this.valuePropertyProperty.value ).unlink( this.propertyPropertyListener );
+
+
+      const derive = this.derive( this.valuePropertyProperty.value );
+
+      if (derive.id===508){
+        console.log( phet.preloads.phetio.queryParameters.frameTitle + ': 508.dispose');
+      }
+
+      derive.hasListener(this.propertyPropertyListener) && derive.unlink( this.propertyPropertyListener );
     }
 
     super.dispose();
Index: density-buoyancy-common/js/buoyancy/view/ReadoutListAccordionBox.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/density-buoyancy-common/js/buoyancy/view/ReadoutListAccordionBox.ts b/density-buoyancy-common/js/buoyancy/view/ReadoutListAccordionBox.ts
--- a/density-buoyancy-common/js/buoyancy/view/ReadoutListAccordionBox.ts	(revision 9038a37285a27b952d9ccee43a93c60d70890788)
+++ b/density-buoyancy-common/js/buoyancy/view/ReadoutListAccordionBox.ts	(date 1723593170022)
@@ -140,7 +140,7 @@
         this.contentWidthMaxProperty.unlink( maxWidthListener );
         valueText.dispose();
         labelText.dispose();
-        nameColonProperty.dispose();
+        // nameColonProperty.dispose();
       } );
 
       const alignGroup = new AlignGroup();
Index: density-buoyancy-common/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/density-buoyancy-common/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts b/density-buoyancy-common/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts
--- a/density-buoyancy-common/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts	(revision 9038a37285a27b952d9ccee43a93c60d70890788)
+++ b/density-buoyancy-common/js/buoyancy/view/applications/BuoyancyApplicationsScreenView.ts	(date 1723595144113)
@@ -38,6 +38,8 @@
 import getBottleIcon from './getBottleIcon.js';
 import BoatPanel from './BoatPanel.js';
 import BottlePanel from './BottlePanel.js';
+import propertyStateHandlerSingleton from '../../../../../axon/js/propertyStateHandlerSingleton.js';
+import PropertyStatePhase from '../../../../../axon/js/PropertyStatePhase.js';
 
 type BuoyancyApplicationsScreenViewOptions = StrictOmit<BuoyancyScreenViewOptions, 'supportsDepthLines' | 'forcesInitiallyDisplayed' | 'massValuesInitiallyDisplayed' | 'initialForceScale'>;
 
@@ -145,6 +147,10 @@
     const displayedMysteryMaterials = [ Material.FLUID_E, Material.FLUID_F ];
     const invisibleMaterials = [ ...Material.BUOYANCY_FLUID_MYSTERY_MATERIALS ].filter( material => !displayedMysteryMaterials.includes( material ) );
 
+    // propertyStateHandlerSingleton.registerPhetioOrderDependency( model.applicationModeProperty, PropertyStatePhase.UNDEFER, model.bottle.materialInsideProperty, PropertyStatePhase.UNDEFER );
+    // propertyStateHandlerSingleton.registerPhetioOrderDependency( model.applicationModeProperty, PropertyStatePhase.UNDEFER, DensityBuoyancyCommonStrings.material.oilStringProperty, PropertyStatePhase.UNDEFER );
+    // propertyStateHandlerSingleton.registerPhetioOrderDependency( model.applicationModeProperty, PropertyStatePhase.UNDEFER, DensityBuoyancyCommonColors.materialOilColorProperty, PropertyStatePhase.UNDEFER );
+
     model.applicationModeProperty.link( scene => {
       const materials = scene === 'bottle' ? [ model.bottle.materialInsideProperty, model.bottle.materialProperty ] :
                         scene === 'boat' ? [ model.block.materialProperty, model.boat.materialProperty ] : [];
Index: axon/js/Multilink.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/axon/js/Multilink.ts b/axon/js/Multilink.ts
--- a/axon/js/Multilink.ts	(revision 7fc653baeca10f122b62e2ad8dcf803cc226decd)
+++ b/axon/js/Multilink.ts	(date 1723593479702)
@@ -127,8 +127,6 @@
       const values = dependencies.map( dependency => dependency.get() ) as [ T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 ];
       callback( ...values );
     }
-
-    this.isDisposed = false;
   }
 
   /**
Index: bamboo/js/CanvasPainter.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/bamboo/js/CanvasPainter.ts b/bamboo/js/CanvasPainter.ts
--- a/bamboo/js/CanvasPainter.ts	(revision 2c5740011601cf178bd7a262af2e377eaaf32d32)
+++ b/bamboo/js/CanvasPainter.ts	(date 1723593479772)
@@ -30,8 +30,7 @@
     }, providedOptions );
 
     this.visible = options.visible;
-    this.isDisposed = false;
-  }
+  };
 
   // override to paint or change the canvas context state.  Only called if this.visible is true
   public abstract paintCanvas( context: CanvasRenderingContext2D ): void;
image

samreid added a commit to phetsims/density-buoyancy-common that referenced this issue Aug 14, 2024
samreid added a commit to phetsims/density-buoyancy-common that referenced this issue Aug 14, 2024
samreid added a commit to phetsims/density-buoyancy-common that referenced this issue Aug 14, 2024
@samreid samreid added status:ready-for-review and removed dev:help-wanted Extra attention is needed labels Aug 14, 2024
zepumph added a commit to phetsims/utterance-queue that referenced this issue Aug 14, 2024
zepumph added a commit to phetsims/density-buoyancy-common that referenced this issue Aug 14, 2024
zepumph added a commit to phetsims/axon that referenced this issue Aug 14, 2024
zepumph added a commit to phetsims/axon that referenced this issue Aug 14, 2024
zepumph added a commit to phetsims/axon that referenced this issue Aug 14, 2024
@samreid
Copy link
Member

samreid commented Aug 15, 2024

@zepumph and I fixed this and it has been clear for 10+ columns on CT. Thanks everyone, closing.

@samreid samreid closed this as completed Aug 15, 2024
@phet-dev phet-dev reopened this Aug 16, 2024
@phet-dev
Copy link
Contributor

Reopening because there is a TODO marked for this issue.

samreid added a commit to phetsims/density-buoyancy-common that referenced this issue Aug 16, 2024
@samreid samreid closed this as completed Aug 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants