Closed pixelzoom closed 1 year ago
Ugh, this code is still in JavaScript. This would be so much easier if it was in TypeScript, because it's a type change fromstring
to string | TReadOnlyProperty<string>
type change.
I made good progress for Geometric Optics, see screenshot below. Everything that's sim-specific is now supported by common code. I haven't seen any layout problems yet.
What I addressed in common code:
KeyboardHelpSectionRow
.SliderControlsKeyboardHelpSection
.TODO:
SliderControlsKeyboardHelpSection
- see ComboBoxKeyboardHelpSection
for an example, this blocks Geometric OpticsKeyboardHelpSection.getGrabReleaseHelpSection
Over to @jessegreenberg to complete this work.
This is a big one for me over in https://github.com/phetsims/ratio-and-proportion/issues/499, co-assigning.
Reviewing scenery-phet issues for Q4 planning...
There has been no progress on the TODO list in https://github.com/phetsims/scenery-phet/issues/769#issuecomment-1232096259. Assigning to @kathy-phet to prioritize.
I removed the two TODOs in KeyboardHelpSection, because we are supporting (from an API perspective) passing in a TReadOnlyProperty
If we need to support disposal of KeyboardHelpSections, then maybe we should start with this patch:
UPDATE: I will likely apply the above disposal patch after proceeding/finishing with https://github.com/phetsims/joist/issues/874
Also note much of this was covered by https://github.com/phetsims/scenery-phet/issues/780
Not blocking for PhET brand.
@zepumph indicated that he and @pixelzoom are taking the lead on this, still I wanted to check the status.
I tested Gravity and Orbits fuzzing in the state wrapper and saw large memory leaks. 100MB over a minute. There is no keyboard help dialog. But I saw the downstream sim creating more and more PreferencesDialogs. Perhaps the state engine clears the capsules before re-triggering creation? The places where this may show up in use cases:
I confirmed that PhET brand gravity and orbits is not leaking.
I'm seeing huge memory increases for both phet and phet-io brands, and I doubt that it's all related to keyboard help.
For geometric-optics (master, unbuilt):
For brand=phet, I see a large increase in heap size. The first 3 snapshots were formerly 42MB, 50MB, 54MB (see https://github.com/phetsims/geometric-optics/issues/372). They are now 59MB, 88MB, 85MB.
For brand=phet-io, I see a MASSIVE increase in heap size. The first 3 snapshots were formerly 44MB, 54MB, 56MB (see https://github.com/phetsims/geometric-optics/issues/373). They are now 92MB, 300MB, 128MB. The 92MB is at startup, before keyboard help has even been opened.
These tests were run with unbuilt versions, while the original tests were run with built versions. So I expected to see some difference, but nothing this extreme.
What is responsible for this huge increase in memory?
(At least part of the) memory issue is due to https://github.com/phetsims/chipper/issues/1323
I measured that it takes about 70ms and 1MB to create a PreferencesDialog in Gravity and Orbits PhET brand.
With @samreid today I got to a good stopping point.
Here were some pretty nice memory leak testing tips that I will note here.
for ( let i = 0; i < 10; i++ ) {
keyboardHelpDialogCapsule.getElement();
keyboardHelpDialogCapsule.disposeElement();
}
This is definitely a lot better, but we should still take a look to ensure that all is good when creating a lot in the state wrapper.
I think I helped this issue a bit over in https://github.com/phetsims/joist/issues/878 because of the Dialog work. No matter, after creating 100 keyboard help dialogs and disposing them on startup. The memory only increased from 66->68MB. I feel like our dispose work is done here for Ratio and Proportion, Friction, and the common code that it uses. I tried to test the other common code content as I went, but I am not as confident in it.
@pixelzoom, it may be good to touch base here with you. Can you do a review and let me know if you think I'm on the right track. The main files to investigate are KeyboardHelpSection and KeyboardHelpSectionRow. Perhaps the best way forward would be for you to do some memory testing in GO. Steps to do a test I was happy with.
Index: js/KeyboardHelpButton.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/KeyboardHelpButton.ts b/js/KeyboardHelpButton.ts
--- a/js/KeyboardHelpButton.ts (revision 0d162fab6359e197a55a634316e0a02e3008ffb0)
+++ b/js/KeyboardHelpButton.ts (date 1667430198865)
@@ -72,7 +72,7 @@
super( icon, backgroundColorProperty, options );
keyboardHelpDialogCapsule = new PhetioCapsule<KeyboardHelpDialog>( tandem => {
-
+ console.log( 'new keyboardDialog' );
// Wrap in a node to prevent DAG problems if archetypes are also created
return new KeyboardHelpDialog( screens, screenProperty, {
tandem: tandem,
@@ -87,6 +87,11 @@
backgroundColorProperty.link( backgroundColor => {
icon.image = backgroundColor.equals( Color.BLACK ) ? keyboardIcon_png : keyboardIconOnWhite_png;
} );
+
+ for ( let i = 0; i < 10; i++ ) {
+ keyboardHelpDialogCapsule.getElement();
+ keyboardHelpDialogCapsule.disposeElement();
+ }
}
}
This memory leak is because GO-specific content isn't disposing its rows/sections/icons. I think that the best review would be to take things for a test drive. Let me know if you want to discuss anything.
Two questions here:
I spoke with @jessegreenberg and we definitely agree that letter keys should be translatable since word keys are. Unassigning him and making a new issue to tackle that.
Also @pixelzoom please note that I am working on https://github.com/phetsims/scenery-phet/issues/787 so potentially we won't need to dispose many of those icons.
Moving to a hopeful commit point here in the next hour or so. Things are looking good! I have been able to remove a lot of the boilerplate from above commits.
@zepumph since you're still actively working on this issue, I'm going to add you to assignees.
At this time I would recommend @pixelzoom hold off on further review. @jonathanolson and I brainstormed some new patterns for disposal and are playing with them over in https://github.com/phetsims/scenery/issues/1494. They have potentially to greatly reduce the boilerplate needed for disposing keyboard help content. Unassigning @pixelzoom for now.
The 3 issues linked immediately above are for sims that will fail in the State wrapper because they do not support dispose
for keyboard-help content. When common-code support propers dispose
of keyboard-help content, those sim-specific issues should be addressed.
@zepumph @jonathanolson FYI, in https://github.com/phetsims/ph-scale/issues/249#issuecomment-1319265006, we needed to unblock ph-scale for publication. So we decided to disable alternative input and comment out createKeyboardHelpNode
in all screens, to workaround the memory leak issue.
KeyboardHelpSection.generateReadingBlockNameResponse
should use a DerivedProperty to formulate these stringsA bit more investigation into current memory leaks and also some refactoring into the new disposer
pattern today. Some success, but things are going a bit slowly. I'll keep cranking next week.
As of this morning, creating and disposing 50 keyboard help dialogs only created 4MB of heap (from 61 -> 65). I think the memory leak has been fixed.
Same with Friction from 37.5->41
There are just a couple more items here before I'm ready to give it back to @pixelzoom:
It sounds like @zepumph would like me to review. So self-assigning and labeling for review.
I'm not familiar with "the new disposer pattern". I don't see it described in this issue, or in PhET's software design patterns doc. @zepumph can you please catch me up?
I still have a few loose ends before i toss it back to you. The disposer pattern is still getting worked out, but see Disposable.ts and the disposer option for a sneak peak. Ill let you know about your next steps.
From https://github.com/phetsims/scenery-phet/issues/769#issuecomment-1232096259:
PhET-iO instrumentation? There's no PhET-iO instrumentation anywhere in this code. So making StringProperties discoverable in Studio by instrumenting their associated Text/RichText nodes is not possible. I don't know if that's s requirement for https://github.com/orgs/phetsims/projects/44 - you should check with @kathy-phet.
We do not want to add instrumentation to the content of the help dialog, but just like with other non-important text, it is possible to customize within the model strings section of studio.
I believe all else is done.
@pixelzoom I think I'm ready for a review here. Things are quite scattered, but I believe the best way to proceed is to follow steps in https://github.com/phetsims/scenery-phet/issues/769#issuecomment-1301473507 to show the keyboard memory leak when disposing, and to help convey progress as you implement dispose. Please feel free to use the new disposer
pattern demonstrated in many spots like GrabReleaseKeyboardHelpSection
and ComboBoxKeyboardHelpSection
. Let me know if you want to pair or have any questions.
I figured out how this works, but...
The original problem was that we needed to delete all children of a Node in some cases, specifically in the case of keyboard help where we had a memory leak. Instead we got a framework for "dispose strategies" that is overly-clever, challenging to understand/maintain, and invasive (integrated into PhET's most basic classes, like PhetioObject).
This is an excellent example of the framework trap antipattern. From Learning Agile, Chapter 7:
When developers overplan for edge cases or add too many hooks, they're being too clever. Instead of thinking about building code that's simple and easy to change, they're thinking about building code that's too complex, too abstract, or solves tomorrow's problems and not just today's.
This brings us to what we call the framework trap, an antipattern that stems from extreme developer cleverness. The framework trap is what we call it when a developer has to solve a single problem or perform a task, but instead of just writing code to do that one thing, he writes a larger framework that can be used in the future to solve similar problems or perform similar tasks.”
Specific review comments about Disposable
and its use:
Use sites are obfuscated. When children use their parent's disposeEmitter
as their disposer
, they will be disposed when dispose
is called on that parent. That's even complicated to write out as a sentence. And it is not at all obvious when looking at use sites like GrabReleaseKeyboardHelpSection.
{ disposer: options.disposeEmitter }
is heavily duplicated at use sites.
It's easy to forget to pass disposer
to some child, thereby not disposing that child, and leaking memory. So much for the framework solving the original problem.
Disposable.ts header documentation is:
* Type to implement disposal strategies, with a disposer option and a disposeEmitter.
This 1-liner is a woefully inadequate. It tells me almost nothing ("Type to implement disposal strategies"), it's somewhat incorrect (it's not a "type", it's a class and associated types), and it includes details that don't belong in an overview ("with a disposer option and a disposeEmitter"). As a framework, it needs overview, responsibilities, motivation, examples,...
this.disposeMyClass
pattern is used everywhere. Why do we need to "be careful"? Is this a problem in general, or in specific situations where that pattern is (mis)used? Examples? // Called after all code that is directly in `dispose()` methods, be careful with mixing this pattern and the
// `this.disposeMyClass()` pattern.
public readonly _disposeEmitter: TEmitter = new TinyEmitter();
// type case because instanceof check isn't flexible enough to check for all that implement TDisposable
return ( this._disposer instanceof Disposable ? this._disposer._disposeEmitter : this._disposer ) as TEmitter;
So... I'm so not a fan of this solution, in so many ways. It's overly-clever. It's complexity on top of complexity. It's invasive. It's a framework that no one needs. And I predict that it will come back to haunt PhET. I recommend deleting it and replacing with a simpler solution that addresses the original problem.
A few simpler solutions that would have solved the original problem:
public override dispose() {
this.children.forEach( child => child.dispose() );
super.dispose();
}
public disposeDeep() {
this.children.forEach( child => child.dispose() );
super.dispose();
}
public dispose( deep = false ): void
, and use dispose( true )
when you want to dispose all children.This issue reminded me of https://github.com/phetsims/axon/issues/93#issuecomment-215510393 where we discussed a similar pattern. I wanted to take this proposal for a test drive, so I updated a CCK file to use this pattern, and it turned out OK:
To me, it feels more declarative and less imperative. But perhaps not a big advantage in this simple case. @zepumph and @jonathanolson can you please recommend a few usage sites where I can see this pattern helps even more?
I swear I wasn't ignoring https://github.com/phetsims/scenery-phet/issues/769#issuecomment-1346823550. There is a lot there, and I have been thinking deeply about how best to proceed there. I would like to pick up general disposal strategies over in https://github.com/phetsims/scenery/issues/1494 and come back here to implement whatever patterns we deem best there. Marking on hold.
Over in https://github.com/phetsims/scenery/issues/1494 I have become confident enough in using disposeEmitter
to make sure we take care of memory issues. I found a few others above, and I think there is one more Text to do. I'll come back soon.
Ok. After the above commits, I'm feeling good about the memory management of the keyboard help dialog. I just tested Ratio and Proportion, and that creating/disposing 50 and then 100 keyboard help dialogs only increases the memory by ~1MB. Please note that disposer
is now deprecated, and we can work towards getting rid of it once we come up with a better solution over in https://github.com/phetsims/scenery/issues/1494
From here, I think we are ready for @pixelzoom to take a look at this same comment from a while ago. This is a review/test drive, where by working in one of your sims, you can also note about how things are working generally.
@zepumph said:
From here, I think we are ready for @pixelzoom to take a look at this same comment from a while ago.
I have no idea what you're asking me to do here. Please clarify.
Sorry, I meant to re-link to it. Instructions in https://github.com/phetsims/scenery-phet/issues/769#issuecomment-1301473507 talk about how to make sure that a keyboard help dialog for your sim doesn't have a memory leak in PhET-iO. Common code should be set up to handle this at this point, so it is most likely just disposing your sim-specific components. Please see RAPKeyboardHelpContent as an example. I do not believe that this is the cleanest code, but our disposal pattern has never been, instead we have preferred verbosity and consistency. Let me know if you'd like to discuss.
@zepumph said:
Instructions in https://github.com/phetsims/scenery-phet/issues/769#issuecomment-1301473507 talk about how to make sure that a keyboard help dialog for your sim doesn't have a memory leak in PhET-iO.
I still don't understand. Am I still supposed to apply the patch, as the comment instructs? Why don't I see the patch code in KeyboardHelpButton.ts? What exactly am I supposed to be reviewing?
@zepumph Let's schedule a time to review this together on Zoom, rather than tossing the issue back and forth.
Disposable.ts has a memory leak. The line added in this diff is missing, so disposeEmitter
listeners are never freed.
public dispose(): void {
assert && assert( !this.isDisposed, 'Disposable can only be disposed once' );
this._disposeEmitter.emit();
+ this._disposeEmitter.dispose();
this.isDisposed = true;
}
@zepumph and I reviewed on Zoom. Summary:
There's a memory leak in Disposable.ts, see https://github.com/phetsims/scenery-phet/issues/769#issuecomment-1495060141
Let's not make KeyboardHelpSection dispose its content. It's confusing having one special case that's unlike anything else. Address TODO comments in GOKeyboardHelpContent.ts when this has been done.
Investigate having a method like disposeDeep
that calls dispose for a branch of the tree rooted as some Node. Nodes in that branch would still need to have proper dispose
methods. But disposeDeep
would be a way to say (for example) "yes, I want my keyboard-help content and everything it involves to be disposed".
Assigning to @zepumph to address those things.
Leaving this assigned to me also. I'm going to commit changes to GOSim.ts and GOKeyboardHelpContent.ts. My next step is to test using the patch in https://github.com/phetsims/scenery-phet/issues/769#issuecomment-1301473507, which @zepumph clarified is a way of testing keyboard-help disposal without having to use the State Wrapper.
Using the procedure and patch in https://github.com/phetsims/scenery-phet/issues/769#issuecomment-1301473507, here are heap sizes for geometric-optics:
In https://github.com/phetsims/scenery-phet/issues/769#issuecomment-1301473507, @zepumph tells me what to expect when there's a leak, but doesn't tell me what to expect when there's not a leak. I'd expect the heap sizes to be the same, which they are not. I took another look through GOKeyboardHelpContent.ts, and I don't see anything that I'm failing to dispose.
So... I'm guessing that there's still a small leak somewhere in joist or scenery-phet.
@zepumph I'm going to unassign myself. Let me know if you need anything else.
See https://github.com/phetsims/axon/issues/433 for the issues discussed above with Disposable.
Investigate having a method like disposeDeep that calls dispose for a branch of the tree rooted as some Node. Nodes in that branch would still need to have proper dispose methods. But disposeDeep would be a way to say (for example) "yes, I want my keyboard-help content and everything it involves to be disposed".
Should be done in https://github.com/phetsims/scenery/issues/1523.
As for the addition of 4MB or so when creating 100 dialogs, I do not believe that this is an issue. It is very possible that this is mostly/some in the browser/view logic weeds, and I have spent enough time diving through memory testing tools to know that it is likely not worth the effort to find them at this time.
I have also gotten rid of the automatic disposal of KeyboardHelpSectionRows in KeyboardHelpSection.
@pixelzoom will you please review this issue. I am not sure what else is left that won't be handled by side issues.
It looks like we've covered everything here. Good work, and closing.
I'm going to do https://github.com/phetsims/ph-scale/issues/253 and https://github.com/phetsims/fourier-making-waves/issues/227 (which were linked above) while this is fresh in my mind.
Keyboard Help (scenery-phet/js/keyboard) currently does not support dynamic strings, and @jessegreenberg reports that dynamic layout is untested.
In https://github.com/phetsims/geometric-optics/issues/441, I'm adding support for dynamic layout to Geometric Optics, and it has many translated strings that appear in Keyboard Help. I'm going to convert what is needed for Geometric Optics, then pass along to @jessegreenberg to finish the rest.
This is blocking for https://github.com/orgs/phetsims/projects/44.