phetsims / tandem

Simulation-side code for PhET-iO
MIT License
0 stars 5 forks source link

Remove Tandem.OPTIONAL in typescript code #289

Closed zepumph closed 1 year ago

zepumph commented 1 year ago

From https://github.com/phetsims/studio/issues/286#issuecomment-1378580398, @samreid and I are likely in agreement about how Tandem.OPTIONAL has run its course. We likely can remove it from everywhere. It was incredibly useful in javascript.

The main concern is where we don't want to break code. Currently, there is always a tandem instance default to call createTandem on, but if we remove the defaults, you would have to provide all tandems until you could run the sim. Likely this is a deal breaker unless we can think of a new pattern for this.

@samreid can you speak to some ideas about this?

samreid commented 1 year ago

I think a workable long-term solution would be to:

Here is a very broken attempt patch. It seems like this would be a lot of work and we should discuss.

I would also recommend that larger issues like this be integrated into our monthly sprint planning. If we decide it is worthwhile and important to the project, it should become a subteam priority rather than something you and/or I look into when we have time.

```diff Subject: [PATCH] Add REVIEW comments and minor fixes, see https://github.com/phetsims/circuit-construction-kit-common/issues/872 --- Index: main/circuit-construction-kit-common/js/view/CCKCProbeNode.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/circuit-construction-kit-common/js/view/CCKCProbeNode.ts b/main/circuit-construction-kit-common/js/view/CCKCProbeNode.ts --- a/main/circuit-construction-kit-common/js/view/CCKCProbeNode.ts (revision 4b7f41c4fb1506b8ea5ed7d5b68a1affee1ea4b7) +++ b/main/circuit-construction-kit-common/js/view/CCKCProbeNode.ts (date 1673589853216) @@ -35,7 +35,7 @@ sensorTypeFunction: ProbeNode.crosshairs( { stroke: 'white' } ), scale: 0.4, drag: _.noop, - tandem: Tandem.OPTIONAL + tandem: undefined }, providedOptions ); super( options ); Index: main/tandem/js/PhetioObject.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/tandem/js/PhetioObject.ts b/main/tandem/js/PhetioObject.ts --- a/main/tandem/js/PhetioObject.ts (revision f17453596348df1a53e70c6c4a76f0840eb0b5d9) +++ b/main/tandem/js/PhetioObject.ts (date 1673619522724) @@ -46,7 +46,7 @@ }; const OBJECT_VALIDATOR = { valueType: [ Object, null ] }; -const objectToPhetioID = ( phetioObject: PhetioObject ) => phetioObject.tandem.phetioID; +const objectToPhetioID = ( phetioObject: PhetioObject ) => phetioObject.tandem?.phetioID; type StartEventOptions = { data?: Record | null; @@ -56,10 +56,7 @@ // When an event is suppressed from the data stream, we keep track of it with this token. const SKIPPING_MESSAGE = -1; -const DEFAULTS: OptionizeDefaults> = { - - // Subtypes can use `Tandem.tandemRequired` to require a named tandem passed in - tandem: Tandem.OPTIONAL, +const DEFAULTS: OptionizeDefaults> = { // Defines API methods, events and serialization phetioType: IOType.ObjectIO, @@ -121,7 +118,7 @@ // Options for creating a PhetioObject type SelfOptions = StrictOmit, 'phetioTypeName' | 'phetioArchetypePhetioID' | 'phetioIsArchetype' | 'phetioEventType'> & { - tandem?: Tandem; + tandem?: Tandem | undefined; phetioType?: IOType; phetioEventType?: EventType; phetioEventMetadata?: EventMetadata | null; @@ -144,7 +141,7 @@ class PhetioObject extends Disposable { // assigned in initializePhetioObject - see docs at DEFAULTS declaration - public tandem: Tandem; + public tandem: Tandem | undefined = undefined; // track whether the object has been initialized. This is necessary because initialization can happen in the // constructor or in a subsequent call to initializePhetioObject (to support scenery Node) @@ -170,14 +167,13 @@ private linkedElements!: LinkedElement[] | null; public phetioNotifiedObjectCreated!: boolean; private phetioMessageStack!: number[]; + public phetioID!: string; + public static readonly DEFAULT_OPTIONS = DEFAULTS; - public phetioID: string; public constructor( options?: PhetioObjectOptions ) { super( options ); - this.tandem = DEFAULTS.tandem; - this.phetioID = this.tandem.phetioID; this.phetioObjectInitialized = false; if ( options ) { @@ -192,17 +188,9 @@ protected initializePhetioObject( baseOptions: Partial, providedOptions: PhetioObjectOptions ): void { assert && assert( providedOptions, 'initializePhetioObject must be called with providedOptions' ); - // call before we exit early to support logging unsupplied Tandems. - providedOptions.tandem && Tandem.onMissingTandem( providedOptions.tandem ); - - // Make sure that required tandems are supplied - if ( Tandem.VALIDATION && providedOptions.tandem && providedOptions.tandem.required ) { - assert && assert( providedOptions.tandem.supplied, 'required tandems must be supplied' ); - } - // The presence of `tandem` indicates if this PhetioObject can be initialized. If not yet initialized, perhaps // it will be initialized later on, as in Node.mutate(). - if ( !( PHET_IO_ENABLED && providedOptions.tandem && providedOptions.tandem.supplied ) ) { + if ( !( PHET_IO_ENABLED && providedOptions.tandem ) ) { assert && !providedOptions.tandem && assert( !specifiesNonTandemPhetioObjectKey( providedOptions ), 'only specify metadata when providing a Tandem' ); // In this case, the PhetioObject is not initialized, but still set tandem to maintain a consistent API for @@ -336,8 +324,8 @@ const suffixArray = Array.isArray( options.tandemNameSuffix ) ? options.tandemNameSuffix : [ options.tandemNameSuffix ]; const matches = suffixArray.filter( suffix => { - return this.tandem.name.endsWith( suffix ) || - this.tandem.name.endsWith( PhetioObject.swapCaseOfFirstCharacter( suffix ) ); + return this.tandem?.name.endsWith( suffix ) || + this.tandem?.name.endsWith( PhetioObject.swapCaseOfFirstCharacter( suffix ) ); } ); assert && assert( matches.length > 0, 'Incorrect Tandem suffix, expected = ' + suffixArray.join( ', ' ) + '. actual = ' + this.tandem.phetioID ); } @@ -468,7 +456,7 @@ const data = options.getData ? options.getData() : options.data; this.phetioMessageStack.push( - phet.phetio.dataStream.start( this.phetioEventType, this.tandem.phetioID, this.phetioType, event, data, this.phetioEventMetadata, this.phetioHighFrequency ) + phet.phetio.dataStream.start( this.phetioEventType, this.tandem?.phetioID, this.phetioType, event, data, this.phetioEventMetadata, this.phetioHighFrequency ) ); // To support PhET-iO playback, any potential playback events downstream of this playback event must be marked as @@ -508,7 +496,7 @@ // in the same order as bufferedPhetioObjects const unlaunchedPhetioIDs = !Tandem.launched ? Tandem.bufferedPhetioObjects.map( objectToPhetioID ) : []; - this.tandem.iterateDescendants( tandem => { + this.tandem?.iterateDescendants( tandem => { const phetioID = tandem.phetioID; if ( phetioEngine.hasPhetioObject( phetioID ) || ( !Tandem.launched && unlaunchedPhetioIDs.includes( phetioID ) ) ) { @@ -542,7 +530,7 @@ // For dynamic elements, indicate the corresponding archetype element so that clients like Studio can leverage // the archetype metadata. Static elements don't have archetypes. - this.phetioArchetypePhetioID = phetioDynamicElement ? this.tandem.getArchetypalPhetioID() : null; + this.phetioArchetypePhetioID = ( phetioDynamicElement && this.tandem ) ? this.tandem?.getArchetypalPhetioID() : null; // Keep the baseline metadata in sync. if ( this.phetioBaselineMetadata ) { @@ -572,7 +560,7 @@ * for more info. */ public isPhetioInstrumented(): boolean { - return this.tandem && this.tandem.supplied; + return !!this.tandem; } /** @@ -633,7 +621,7 @@ */ public override dispose(): void { const descendants: PhetioObject[] = []; - if ( Tandem.PHET_IO_ENABLED && this.tandem.supplied ) { + if ( Tandem.PHET_IO_ENABLED && this.tandem ) { const phetioEngine = phet.phetio.phetioEngine; this.tandem.iterateDescendants( tandem => { if ( phetioEngine.hasPhetioObject( tandem.phetioID ) ) { @@ -655,12 +643,12 @@ 'phetioMessageStack should be clear' ); descendants.forEach( descendant => { - assert && assert( descendant.isDisposed, `All descendants must be disposed by the next frame: ${descendant.tandem.phetioID}` ); + assert && assert( descendant.isDisposed, `All descendants must be disposed by the next frame: ${descendant.tandem?.phetioID}` ); } ); } ); if ( this.phetioObjectInitialized ) { - this.tandem.removePhetioObject( this ); + this.tandem?.removePhetioObject( this ); } // Dispose LinkedElements Index: main/tandem/js/Tandem.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/tandem/js/Tandem.ts b/main/tandem/js/Tandem.ts --- a/main/tandem/js/Tandem.ts (revision f17453596348df1a53e70c6c4a76f0840eb0b5d9) +++ b/main/tandem/js/Tandem.ts (date 1673619470873) @@ -9,7 +9,6 @@ */ import arrayRemove from '../../phet-core/js/arrayRemove.js'; -import merge from '../../phet-core/js/merge.js'; import optionize from '../../phet-core/js/optionize.js'; import PhetioObject from './PhetioObject.js'; import tandemNamespace from './tandemNamespace.js'; @@ -19,7 +18,6 @@ const packageJSON = _.hasIn( window, 'phet.chipper.packageObject' ) ? phet.chipper.packageObject : { name: 'placeholder' }; const PHET_IO_ENABLED = _.hasIn( window, 'phet.preloads.phetio' ); -const PRINT_MISSING_TANDEMS = PHET_IO_ENABLED && phet.preloads.phetio.queryParameters.phetioPrintMissingTandems; // Validation defaults to true, but can be overridden to be false in package.json. const IS_VALIDATION_DEFAULT = _.hasIn( packageJSON, 'phet.phet-io.validation' ) ? !!packageJSON.phet[ 'phet-io' ].validation : true; @@ -29,25 +27,14 @@ const IS_VALIDATION_SPECIFIED = ( PHET_IO_ENABLED && IS_VALIDATION_QUERY_PARAMETER_SPECIFIED ) ? !!phet.preloads.phetio.queryParameters.phetioValidation : ( PHET_IO_ENABLED && IS_VALIDATION_DEFAULT ); -const VALIDATION = PHET_IO_ENABLED && IS_VALIDATION_SPECIFIED && !PRINT_MISSING_TANDEMS; +const VALIDATION = PHET_IO_ENABLED && IS_VALIDATION_SPECIFIED; const UNALLOWED_TANDEM_NAMES = [ 'pickableProperty' ]; -const REQUIRED_TANDEM_NAME = 'requiredTandem'; -const OPTIONAL_TANDEM_NAME = 'optionalTandem'; const TEST_TANDEM_NAME = 'test'; const INTER_TERM_SEPARATOR = phetio.PhetioIDUtils.INTER_TERM_SEPARATOR; export const DYNAMIC_ARCHETYPE_NAME = 'archetype'; -// used to keep track of missing tandems -const missingTandems: { - required: Array<{ phetioID: string; stack: string }>; - optional: Array<{ phetioID: string; stack: string }>; -} = { - required: [], - optional: [] -}; - type PhetioObjectListener = { addPhetioObject: ( phetioObject: PhetioObject ) => void; removePhetioObject: ( phetioObject: PhetioObject ) => void; @@ -60,8 +47,6 @@ const launchListeners: Array<() => void> = []; export type TandemOptions = { - required?: boolean; - supplied?: boolean; isValidTandemName?: ( name: string ) => boolean; }; @@ -75,8 +60,6 @@ public readonly name: string; public readonly phetioID: string; private readonly children: Record = {}; - public readonly required: boolean; - public readonly supplied: boolean; private isDisposed = false; // Disabling lint rule because GroupTandem is a subtype @@ -117,13 +100,6 @@ // Note: Make sure that added options here are also added to options for inheritance and/or for composition // (createTandem/parentTandem/getExtendedOptions) as appropriate. const options = optionize()( { - - // required === false means it is an optional tandem - required: true, - - // if the tandem is required but not supplied, an error will be thrown. - supplied: true, - isValidTandemName: ( name: string ) => /^[a-zA-Z0-9[\],]+$/.test( name ) }, providedOptions ); @@ -135,9 +111,6 @@ assert && assert( !this.parentTandem.hasChild( name ), `parent should not have child: ${name}` ); this.parentTandem.addChild( name, this ); } - - this.required = options.required; - this.supplied = options.supplied; } /** @@ -148,54 +121,17 @@ // return /^[a-zA-Z0-9[\],-]+$/; // } - /** - * If the provided tandem is not supplied, support the ?printMissingTandems query parameter for extra logging during - * initial instrumentation. - */ - public static onMissingTandem( tandem: Tandem ): void { - - // When the query parameter phetioPrintMissingTandems is true, report tandems that are required but not supplied - if ( PRINT_MISSING_TANDEMS && !tandem.supplied ) { - const stackTrace = new Error().stack!; - if ( tandem.required ) { - missingTandems.required.push( { phetioID: tandem.phetioID, stack: stackTrace } ); - } - else { - - // When the query parameter phetioPrintMissingTandems is true, report tandems that are optional but not - // supplied, but not for Fonts because they are too numerous. - if ( !stackTrace.includes( 'Font' ) ) { - missingTandems.optional.push( { phetioID: tandem.phetioID, stack: stackTrace } ); - } - } - } - } - /** * Adds a PhetioObject. For example, it could be an axon Property, SCENERY/Node or SUN/RoundPushButton. * phetioEngine listens for when PhetioObjects are added and removed to keep track of them for PhET-iO. */ public addPhetioObject( phetioObject: PhetioObject ): void { - - if ( PHET_IO_ENABLED ) { - - // Throw an error if the tandem is required but not supplied - assert && Tandem.VALIDATION && assert( !( this.required && !this.supplied ), 'Tandem was required but not supplied' ); - - // If tandem is optional and not supplied, then ignore it. - if ( !this.required && !this.supplied ) { - - // Optionally instrumented types without tandems are not added. - return; - } - - if ( !Tandem.launched ) { - Tandem.bufferedPhetioObjects.push( phetioObject ); - } - else { - for ( let i = 0; i < phetioObjectListeners.length; i++ ) { - phetioObjectListeners[ i ].addPhetioObject( phetioObject ); - } + if ( PHET_IO_ENABLED && !Tandem.launched ) { + Tandem.bufferedPhetioObjects.push( phetioObject ); + } + else { + for ( let i = 0; i < phetioObjectListeners.length; i++ ) { + phetioObjectListeners[ i ].addPhetioObject( phetioObject ); } } } @@ -212,10 +148,6 @@ */ public removePhetioObject( phetioObject: PhetioObject ): void { - if ( !this.required && !this.supplied ) { - return; - } - // Only active when running as phet-io if ( PHET_IO_ENABLED ) { if ( !Tandem.launched ) { @@ -229,20 +161,7 @@ } } - phetioObject.tandem.dispose(); - } - - /** - * Used for creating new tandems, extends this Tandem's options with the passed-in options. - */ - public getExtendedOptions( options?: TandemOptions ): TandemOptions { - - // Any child of something should be passed all inherited options. Make sure that this extend call includes all - // that make sense from the constructor's extend call. - return merge( { - supplied: this.supplied, - required: this.required - }, options ); + phetioObject.tandem?.dispose(); } /** @@ -251,14 +170,9 @@ public createTandem( name: string, options?: TandemOptions ): Tandem { assert && Tandem.VALIDATION && assert( !UNALLOWED_TANDEM_NAMES.includes( name ), 'tandem name is not allowed: ' + name ); - options = this.getExtendedOptions( options ); - // re-use the child if it already exists, but make sure it behaves the same. if ( this.hasChild( name ) ) { - const currentChild = this.children[ name ]; - assert && assert( currentChild.required === options.required ); - assert && assert( currentChild.supplied === options.supplied ); - return currentChild; + return this.children[ name ]; } else { return new Tandem( this, name, options ); @@ -380,12 +294,6 @@ launchListeners.push( listener ); } - /** - * Expose collected missing tandems only populated from specific query parameter, see phetioPrintMissingTandems - * (phet-io internal) - */ - public static readonly missingTandems = missingTandems; - /** * If PhET-iO is enabled in this runtime. */ @@ -429,7 +337,7 @@ Tandem.addLaunchListener( () => { while ( Tandem.bufferedPhetioObjects.length > 0 ) { const phetioObject = Tandem.bufferedPhetioObjects.shift(); - phetioObject!.tandem.addPhetioObject( phetioObject! ); + phetioObject!.tandem?.addPhetioObject( phetioObject! ); } assert && assert( Tandem.bufferedPhetioObjects.length === 0, 'bufferedPhetioObjects should be empty' ); } ); @@ -442,8 +350,6 @@ public override createTandem( name: string, options?: TandemOptions ): Tandem { if ( Tandem.VALIDATION ) { const allowedOnRoot = name === window.phetio.PhetioIDUtils.GLOBAL_COMPONENT_NAME || - name === REQUIRED_TANDEM_NAME || - name === OPTIONAL_TANDEM_NAME || name === TEST_TANDEM_NAME || name === window.phetio.PhetioIDUtils.GENERAL_COMPONENT_NAME || _.endsWith( name, Tandem.SCREEN_TANDEM_NAME_SUFFIX ); @@ -516,30 +422,6 @@ */ Tandem.COLORS = Tandem.GLOBAL_VIEW.createTandem( window.phetio.PhetioIDUtils.COLORS_COMPONENT_NAME ); -/** - * Used to indicate a common code component that supports tandem, but doesn't not require it. If a tandem is not - * passed in, then it will not be instrumented. - */ -Tandem.OPTIONAL = Tandem.ROOT.createTandem( OPTIONAL_TANDEM_NAME, { - required: false, - supplied: false -} ); - -/** - * To be used exclusively to opt out of situations where a tandem is required, see https://github.com/phetsims/tandem/issues/97. - */ -Tandem.OPT_OUT = Tandem.OPTIONAL; - -/** - * Some common code (such as Checkbox or RadioButton) must always be instrumented. - */ -Tandem.REQUIRED = Tandem.ROOT.createTandem( REQUIRED_TANDEM_NAME, { - - // let phetioPrintMissingTandems bypass this - required: VALIDATION || PRINT_MISSING_TANDEMS, - supplied: false -} ); - /** * Use this as the parent tandem for Properties that are related to sim-specific preferences. */ Index: main/axon/js/createObservableArray.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/axon/js/createObservableArray.ts b/main/axon/js/createObservableArray.ts --- a/main/axon/js/createObservableArray.ts (revision 9c93acc44d319393b8b468744098875b789392ec) +++ b/main/axon/js/createObservableArray.ts (date 1673622245632) @@ -259,7 +259,7 @@ /****************************************** * PhET-iO support *******************************************/ - if ( options.tandem.supplied ) { + if ( options.tandem ) { assert && assert( options.phetioType ); observableArray.phetioElementType = options.phetioType!.parameterTypes![ 0 ]; Index: main/natural-selection/js/common/model/Organism.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/natural-selection/js/common/model/Organism.ts b/main/natural-selection/js/common/model/Organism.ts --- a/main/natural-selection/js/common/model/Organism.ts (revision 74b2ef54c6ec056f0107b42a87414f5a25075557) +++ b/main/natural-selection/js/common/model/Organism.ts (date 1673589568447) @@ -12,7 +12,6 @@ import optionize from '../../../../phet-core/js/optionize.js'; import PickOptional from '../../../../phet-core/js/types/PickOptional.js'; import PhetioObject, { PhetioObjectOptions } from '../../../../tandem/js/PhetioObject.js'; -import Tandem from '../../../../tandem/js/Tandem.js'; import naturalSelection from '../../naturalSelection.js'; import EnvironmentModelViewTransform from './EnvironmentModelViewTransform.js'; import XDirection from './XDirection.js'; @@ -44,15 +43,16 @@ xDirection: XDirection.RIGHT, // PhetioObjectOptions - tandem: Tandem.OPTIONAL + tandem: undefined }, providedOptions ); super( options ); + options.tandem; this.modelViewTransform = modelViewTransform; this.positionProperty = new Property( options.position, { - tandem: options.tandem.createTandem( 'positionProperty' ), + tandem: options.tandem?.createTandem( 'positionProperty' ), phetioValueType: Vector3.Vector3IO, phetioHighFrequency: true, phetioReadOnly: true, Index: main/sun/js/Slider.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/sun/js/Slider.ts b/main/sun/js/Slider.ts --- a/main/sun/js/Slider.ts (revision bd5a093e76c581dd7f8097b85a26f069cfefca12) +++ b/main/sun/js/Slider.ts (date 1673622245616) @@ -394,7 +394,7 @@ const thumbDragListener = new DragListener( { // Deviate from the variable name because we will nest this tandem under the thumb directly - tandem: thumb.tandem.createTandem( 'dragListener' ), + tandem: thumb.tandem?.createTandem( 'dragListener' ), start: ( event, listener ) => { if ( this.enabledProperty.get() ) { Index: main/circuit-construction-kit-common/js/model/Vertex.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/main/circuit-construction-kit-common/js/model/Vertex.ts b/main/circuit-construction-kit-common/js/model/Vertex.ts --- a/main/circuit-construction-kit-common/js/model/Vertex.ts (revision 4b7f41c4fb1506b8ea5ed7d5b68a1affee1ea4b7) +++ b/main/circuit-construction-kit-common/js/model/Vertex.ts (date 1673619090623) @@ -101,9 +101,7 @@ interactive: true, // Black box interface vertices can be interactive (tap to select) without being draggable blackBoxInterface: false, // Black box interface vertices cannot be dragged or deleted, but can be connected to insideTrueBlackBox: false, // Behavior differs in explore vs test mode - tandem: Tandem.OPTIONAL, // Temporary vertices (for icons) should not be instrumented since they phetioDynamicElement: true - // are more of an implementation detail rather than a feature }, providedOptions ); super( options ); @@ -115,7 +113,7 @@ this.vertexTandem = options.tandem; this.positionProperty = new Vector2Property( position, { - tandem: options.tandem && options.tandem.createTandem( 'positionProperty' ), + tandem: options.tandem?.createTandem( 'positionProperty' ), useDeepEquality: true, isValidValue: ( position: Vector2 ) => position.isFinite(), phetioReadOnly: true, @@ -127,7 +125,7 @@ } ); this.voltageProperty = new NumberProperty( 0, { - tandem: options.tandem && options.tandem.createTandem( 'voltageProperty' ), + tandem: options.tandem?.createTandem( 'voltageProperty' ), units: 'V', phetioReadOnly: true, phetioHighFrequency: true ```
samreid commented 1 year ago

Tagging for https://github.com/phetsims/phet-io/issues/1914 and self-unassigning.

zepumph commented 1 year ago

I actually think that we should close this issue. @samreid we will still want Tandem.OPTIONAL in typescript code so that we can support PhET brand that uses common code, as well as phet-io ports where we want the sim runnable mid-way through the instrumentation process.

Do you want to do more work here?

samreid commented 1 year ago

I feel the above proposal like using tandem?: Tandem instead of tandem: Tandem + Tandem.OPTIONAL would work well for the optional parts (using optional chaining for usage sites). But I don't see how it could support Tandem.REQUIRED in a way that supports PhET-iO and non-PhET-iO clients. i.e. we only want to require a tandem if the sim is enabled for PhET-iO. The most refined solution would be if there were different modalities for type checking, like tandem: BRAND_PHET_IO ? Tandem : Tandem | undefined. But as far as I know there is no way to put conditional types or different modes into TypeScript, and adding that ourselves in another layer sounds horrible. @zepumph any other remarks? Still want to close? Feel free to do so at your discretion.

zepumph commented 1 year ago

I'm understanding more now. I think it is worth some investigation about just getting rid of Tandem.OPTIONAL. I tried in a patch like this, and ran into a couple problems:

```diff Subject: [PATCH] Remove redundant usages of Tandem.OPTIONAL (typescript has us covered), --- Index: phet-io/doc/phet-io-instrumentation-technical-guide.md IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/phet-io/doc/phet-io-instrumentation-technical-guide.md b/phet-io/doc/phet-io-instrumentation-technical-guide.md --- a/phet-io/doc/phet-io-instrumentation-technical-guide.md (revision 3bb17e8bc1e3c7d5e7cf5ca1df2b2884a6bff114) +++ b/phet-io/doc/phet-io-instrumentation-technical-guide.md (date 1687792350436) @@ -223,6 +223,8 @@ Tandem's `GLOBAL`. Tandems in the `general` section are the same for all simulations, like `activeProperty`. - Add `tandem: Tandem.REQUIRED`, `tandem: Tandem.OPTIONAL` or `tandem: Tandem.OPT_OUT` to the options accordingly. Here are some conventions to guide this decision: + * TypeScript removes the need for Tandem.OPTIONAL in many cases. Omit unless you need to pass through to + subcomponents. * Most UI components are `Tandem.REQUIRED` * Even if a common-code component that your subtype extends is a certain option, it is safest to set this tandem constraint at the subclass level in sim-specific code too. 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 83bc8abc096e82c4ff52d7d7343e0b036bd34839) +++ b/axon/js/ReadOnlyProperty.ts (date 1687792557308) @@ -125,7 +125,6 @@ hasListenerOrderDependencies: false, // phet-io - tandem: Tandem.OPTIONAL, phetioOuterType: ReadOnlyProperty.PropertyIO, phetioValueType: IOType.ObjectIO }, providedOptions ); ```
  1. Tandem.OPTIONAL is necessary if you are passing options.tandem.createTandem() to subcomponents.
  2. When I removed the above default in ReadOnlyProperty, we hit this assertion, trying to keep tandem provided with the rest of the other metadata. https://github.com/phetsims/tandem/blob/036b4067b32c9c71757392288e176bf61e0c31fb/js/PhetioObject.ts#L210

I am not really sure how much more effort to put into this. I kinda thought it would be simpler. I am not too interested in options.tandem && options.tandem.createTandem( 'subcomponent' ) in common code. May be we should close it. Please though look at my commit to VarianceNumberProperty, that is nice cleanup, no?

samreid commented 1 year ago

Yes, that is a good change to VarianceNumberProperty. I agree options.tandem && options.tandem.createTandem( 'subcomponent' ) is not good. I feel options.tandem?.createTandem( 'subcomponent' ) is slightly better.

zepumph commented 1 year ago

@samreid and I discussed this. We noted that we will not be able to get Tandem.REQUIRED to go away. This is to support PhET brand code that we don't want to require tandem options for to pass the type checker. We also never want to get rid of Tandem.OPT_OUT. For Tandem.OPTIONAL, there are 100 usages, and pretty much every one could just be removed if PhetioObject could handle it gracefully.

Though this isn't a high priority or high value item, it does seem obvious to let TypeScript handle this for you in client code.

We can proceed like this:


Subject: [PATCH] move all allowed Tandem characters into the base term character class, https://github.com/phetsims/tandem/issues/270
---
Index: js/createObservableArray.ts
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/js/createObservableArray.ts b/js/createObservableArray.ts
--- a/js/createObservableArray.ts   (revision 83bc8abc096e82c4ff52d7d7343e0b036bd34839)
+++ b/js/createObservableArray.ts   (date 1687805583971)
@@ -16,7 +16,6 @@
 import merge from '../../phet-core/js/merge.js';
 import optionize, { combineOptions } from '../../phet-core/js/optionize.js';
 import PhetioObject, { PhetioObjectOptions } from '../../tandem/js/PhetioObject.js';
-import Tandem from '../../tandem/js/Tandem.js';
 import ArrayIO from '../../tandem/js/types/ArrayIO.js';
 import IOType from '../../tandem/js/types/IOType.js';
 import axon from './axon.js';
@@ -95,7 +94,6 @@

     length: 0,
     elements: [],
-    tandem: Tandem.OPTIONAL,
     elementAddedEmitterOptions: {},
     elementRemovedEmitterOptions: {},
     lengthPropertyOptions: {}
@@ -118,7 +116,7 @@

   // notifies when an element has been added
   const elementAddedEmitter = new Emitter<[ T ]>( combineOptions<EmitterOptions>( {
-    tandem: options.tandem.createTandem( 'elementAddedEmitter' ),
+    tandem: options.tandem?.createTandem( 'elementAddedEmitter' ),
     parameters: [ emitterParameterOptions ],
     phetioReadOnly: true,
     hasListenerOrderDependencies: options.hasListenerOrderDependencies
@@ -126,7 +124,7 @@

   // notifies when an element has been removed
   const elementRemovedEmitter = new Emitter<[ T ]>( combineOptions<EmitterOptions>( {
-    tandem: options.tandem.createTandem( 'elementRemovedEmitter' ),
+    tandem: options.tandem?.createTandem( 'elementRemovedEmitter' ),
     parameters: [ emitterParameterOptions ],
     phetioReadOnly: true,
     hasListenerOrderDependencies: options.hasListenerOrderDependencies
@@ -135,7 +133,7 @@
   // observe this, but don't set it. Updated when Array modifiers are called (except array.length=...)
   const lengthProperty = new NumberProperty( 0, combineOptions<NumberPropertyOptions>( {
     numberType: 'Integer',
-    tandem: options.tandem.createTandem( 'lengthProperty' ),
+    tandem: options.tandem?.createTandem( 'lengthProperty' ),
     phetioReadOnly: true
   }, options.lengthPropertyOptions ) );

@@ -264,7 +262,7 @@
   /******************************************
    * PhET-iO support
    *******************************************/
-  if ( options.tandem.supplied ) {
+  if ( options.tandem?.supplied ) {
     assert && assert( options.phetioType );

     observableArray.phetioElementType = options.phetioType.parameterTypes![ 0 ];

``
zepumph commented 1 year ago

There are quite a few spots where it is ending up being easier to keep Tandem.OPTIONAL around. For example Carousel, it is just easier to have all variables be of type Tandem. Something else I was finding though is that the vast majority of usages were actually wanting to use Tandem.OPT_OUT. Now there are only 68 usages left. I would like to at least go through to find more cases where they should be OPT_OUT.

zepumph commented 1 year ago

Ok. There are now 26 usages of Tandem.OPTIONAL. That is nice! I deprecated Tandem.OPTIONAL and I'm feeling really good about proceeding without Tandem.OPTIONAL in new code. @samreid can you please review and let me know if you like this path?

samreid commented 1 year ago

I skimmed the commits, reviewed the changed assertion, and reviewed remaining occurrences of Tandem.OPTIONAL. I feel everything is is good shape, thanks! OK to close if you are.

zepumph commented 1 year ago

Excellent. I did one more in Input, putting to the test if it is ever worth factoring out Tandem.OPTIONAL for 24 usages of options.tandem?. Feel free to revert. Closing

zepumph commented 1 year ago

I wonder if this is causing a CT error for the LightSpectrumDialog's close button in studio.

zepumph commented 1 year ago

Looks like it was actually from https://github.com/phetsims/phet-io/issues/1810. Closing

zepumph commented 11 months ago

We will always want Tandem.OPTIONAL for common code instrumentation grace. I'm removing the deprecated markings, as I ran into this in https://github.com/phetsims/scenery-phet/issues/540.