• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16/**
17 * ObservedPropertyAbstractPU aka ObservedPropertyAbstract for partial update
18 *
19 * all definitions in this file are framework internal
20 */
21
22abstract class ObservedPropertyAbstractPU<T> extends ObservedPropertyAbstract<T>
23implements ISinglePropertyChangeSubscriber<T>, IMultiPropertiesChangeSubscriber, IMultiPropertiesReadSubscriber
24// these interfaces implementations are all empty functions, overwrite FU base class implementations.
25{
26  static readonly DelayedNotifyChangesEnum = class {
27    static readonly do_not_delay = 0;
28    static readonly delay_none_pending = 1;
29    static readonly delay_notification_pending = 2;
30  };
31
32  private owningView_ : ViewPU;
33  public changeNotificationIsOngoing_: boolean = false;
34
35  // PU code stores object references to dependencies directly as class variable
36  // SubscriberManager is not used for lookup in PU code path to speedup updates
37  protected subscriberRefs_: Set<IPropertySubscriber>;
38
39  // when owning ViewPU is inActive, delay notifying changes
40  private delayedNotification_: number = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay;
41
42  // install when current value is ObservedObject and the value type is not using compatibility mode
43  // note value may change for union type variables when switching an object from one class to another.
44  protected shouldInstallTrackedObjectReadCb : boolean = false;
45  private dependentElmtIdsByProperty_ = new PropertyDependencies();
46
47  constructor(subscriber: IPropertySubscriber, viewName: PropertyInfo) {
48    super(subscriber, viewName);
49    Object.defineProperty(this, 'owningView_', {writable: true, enumerable: false, value: undefined});
50    Object.defineProperty(this, 'subscriberRefs_',
51      {writable: true, enumerable: false, value: new Set<IPropertySubscriber>()});
52    if (subscriber) {
53      if (subscriber instanceof ViewPU) {
54        this.owningView_ = subscriber;
55      } else {
56        this.subscriberRefs_.add(subscriber);
57      }
58    }
59  }
60
61  aboutToBeDeleted() {
62    super.aboutToBeDeleted();
63    this.subscriberRefs_.clear();
64    this.owningView_ = undefined;
65  }
66
67  private decoratorInfo_?: string;
68
69  public setDecoratorInfo(decorate: string) {
70    this.decoratorInfo_ = decorate;
71  }
72
73  // dump info about variable decorator to string
74  // e.g. @State, @Link, etc.
75  public debugInfoDecorator() : string {
76    return this.decoratorInfo_;
77  }
78
79  // dump basic info about this variable to a string, non-recursive, no subscriber info
80  public debugInfo() : string {
81    const propSource : string | false = this.isPropSourceObservedPropertyFakeName();
82    return (propSource)
83    ? `internal source (ObservedPropertyPU) of @Prop ${propSource} [${this.id__()}]`
84    : `${this.debugInfoDecorator()} '${this.info()}'[${this.id__()}] <${this.debugInfoOwningView()}>`;
85  }
86
87  public debugInfoOwningView() : string {
88    return `${this.owningView_ ? this.owningView_.debugInfo__() : 'owning @Component UNKNOWN'}`;
89  }
90
91  // dump info about owning view and subscribers (PU ones only)
92  // use function only for debug output and DFX.
93  public debugInfoSubscribers(): string {
94    return (this.owningView_)
95      ? `|--Owned by ${this.debugInfoOwningView()} `
96      : `|--Owned by: owning view not known`;
97  }
98
99  public debugInfoSyncPeers(): string {
100    if (!this.subscriberRefs_.size) {
101      return '|--Sync peers: none';
102    }
103    let result: string = `|--Sync peers: {`;
104    let sepa: string = '';
105    this.subscriberRefs_.forEach((subscriber: IPropertySubscriber) => {
106      if ('debugInfo' in subscriber) {
107        result += `\n    ${sepa}${(subscriber as ObservedPropertyAbstractPU<any>).debugInfo()}`;
108        sepa = ', ';
109      }
110    });
111    result += '\n  }';
112    return result;
113  }
114
115  public debugInfoDependentElmtIds(dumpDependantElements: boolean = false): string {
116    return this.dependentElmtIdsByProperty_.dumpInfoDependencies(this.owningView_, dumpDependantElements);
117  }
118
119  public dumpDependentElmtIdsObj(isTrackedMode: boolean, isProfiler: boolean): PropertyDependenciesInfo {
120    return this.dependentElmtIdsByProperty_.dumpInfoDependenciesObj(this.owningView_, isTrackedMode, isProfiler);
121  }
122
123  public debugInfoElmtId(elmtId: number): string {
124    if (this.owningView_) {
125      return this.owningView_.debugInfoElmtId(elmtId) as string;
126    }
127    return '<unknown element id ' + elmtId + ', missing owning view>';
128  }
129
130  public debugInfoDependentComponents(): string | Object {
131    let result: string = `|--Dependent elements: `;
132    let sepa: string = '; ';
133    let sepaDiff: string = '';
134    const dumpDependantElements = true;
135
136    let queue: Array<ObservedPropertyAbstractPU<any>> = [this];
137    let seen = new Set<ObservedPropertyAbstractPU<any>>();
138
139    while (queue.length) {
140      let item = queue.shift();
141      seen.add(item);
142
143      if (item !== this) {
144        result += `${sepa}${item.debugInfoOwningView()}`;
145      }
146      result += `${sepaDiff}${item.debugInfoDependentElmtIds(dumpDependantElements)}`; // new dependent elements
147      sepaDiff = ', ';
148
149      item.subscriberRefs_.forEach((subscriber: IPropertySubscriber) => {
150        if ((subscriber instanceof ObservedPropertyAbstractPU)) {
151          if (!seen.has(subscriber)) {
152            queue.push(subscriber);
153          }
154        }
155      });
156    }
157    return result;
158  }
159
160  /**/
161  public hasDependencies(): boolean {
162    return this.dependentElmtIdsByProperty_.hasDependencies();
163  }
164
165  /* for @Prop value from source we need to generate a @State
166     that observes when this value changes. This ObservedPropertyPU
167     sits inside SynchedPropertyOneWayPU.
168     below methods invent a fake variable name for it
169  */
170  protected getPropSourceObservedPropertyFakeName(): string {
171    return `${this.info()}_prop_fake_state_source___`;
172  }
173
174  protected isPropSourceObservedPropertyFakeName(): string | false {
175    return this.info() && this.info().endsWith('_prop_fake_state_source___')
176      ? this.info().substring(0, this.info().length - '_prop_fake_state_source___'.length)
177      : false;
178  }
179
180  public getOwningView(): ViewPUInfo {
181    return { componentName: this.owningView_?.constructor.name, id: this.owningView_?.id__() };
182  }
183
184  public dumpSyncPeers(isProfiler: boolean, changedTrackPropertyName?: string): ObservedPropertyInfo<T>[] {
185    let res: ObservedPropertyInfo<T>[] = [];
186    this.subscriberRefs_.forEach((subscriber: IPropertySubscriber) => {
187      if ('debugInfo' in subscriber) {
188        const observedProp = subscriber as ObservedPropertyAbstractPU<any>;
189        res.push(stateMgmtDFX.getObservedPropertyInfo(observedProp, isProfiler, changedTrackPropertyName));
190      }
191    });
192    return res;
193  }
194
195  protected onDumpProfiler(changedTrackPropertyName?: string): void {
196    let res: DumpInfo = new DumpInfo();
197    res.viewInfo = { componentName: this.owningView_?.constructor.name, id: this.owningView_?.id__() };
198    res.observedPropertiesInfo.push(stateMgmtDFX.getObservedPropertyInfo(this, true, changedTrackPropertyName));
199    if (this.owningView_) {
200      try {
201        this.owningView_.sendStateInfo(JSON.stringify(res));
202      } catch (error) {
203        stateMgmtConsole.applicationError(`${this.debugInfo()} has error in sendStateInfo: ${(error as Error).message}`);
204      }
205    }
206  }
207
208  /*
209    Virtualized version of the subscription mechanism - add subscriber
210    Overrides implementation in ObservedPropertyAbstract<T>
211  */
212  public addSubscriber(subscriber: ISinglePropertyChangeSubscriber<T>):void {
213    if (subscriber) {
214      // ObservedPropertyAbstract will also add subscriber to
215      // SubscriberManager map and to its own Set of subscribers as well
216      // Something to improve in the future for PU path.
217      // subscribeMe should accept IPropertySubscriber interface
218      super.subscribeMe(subscriber as ISinglePropertyChangeSubscriber<T>);
219      this.subscriberRefs_.add(subscriber);
220    }
221  }
222
223  /*
224    Virtualized version of the subscription mechanism - remove subscriber
225    Overrides implementation in ObservedPropertyAbstract<T>
226  */
227  public removeSubscriber(subscriber: IPropertySubscriber, id?: number):void {
228    if (subscriber) {
229      this.subscriberRefs_.delete(subscriber);
230      if (!id) {
231        id = subscriber.id__();
232      }
233    }
234    super.unlinkSuscriber(id);
235  }
236
237  /**
238   * put the property to delayed notification mode
239   * feature is only used for @StorageLink/Prop, @LocalStorageLink/Prop
240   */
241  public enableDelayedNotification() : void {
242  if (this.delayedNotification_ !== ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending) {
243      stateMgmtConsole.debug(`${this.constructor.name}: enableDelayedNotification.`);
244      this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_none_pending;
245    }
246  }
247
248  /*
249     when moving from inActive to active state the owning ViewPU calls this function
250     This solution is faster than ViewPU polling each variable to send back a viewPropertyHasChanged event
251     with the elmtIds
252
253    returns undefined if variable has _not_ changed
254    returns dependentElementIds_ Set if changed. This Set is empty if variable is not used to construct the UI
255  */
256    public moveElmtIdsForDelayedUpdate(isReused: boolean = false): Set<number> | undefined {
257      const result = (this.delayedNotification_ === ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending) ?
258        this.dependentElmtIdsByProperty_.getAllPropertyDependencies() :
259        undefined;
260      stateMgmtConsole.debug(`${this.debugInfo()}: moveElmtIdsForDelayedUpdate: elmtIds that need delayed update \
261                        ${result ? Array.from(result).toString() : 'no delayed notifications'} .`);
262      if (isReused && !this.owningView_.isViewActive()) {
263        this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_none_pending;
264      } else {
265        this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay;
266      }
267      return result;
268    }
269
270  protected notifyPropertyRead() {
271    stateMgmtConsole.error(`${this.debugInfo()}: notifyPropertyRead, DO NOT USE with PU. Use notifyReadCb mechanism.`);
272
273  }
274
275  // notify owning ViewPU and peers of a variable assignment
276  // also property/item changes to  ObservedObjects of class object type, which use compat mode
277  // Date and Array are notified as if there had been an assignment.
278  protected notifyPropertyHasChangedPU() : void {
279    stateMgmtProfiler.begin('ObservedPropertyAbstractPU.notifyPropertyHasChangedPU');
280    stateMgmtConsole.debug(`${this.debugInfo()}: notifyPropertyHasChangedPU.`);
281    if (this.owningView_) {
282      if (this.delayedNotification_ === ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay) {
283        // send viewPropertyHasChanged right away
284        this.owningView_.viewPropertyHasChanged(this.info_, this.dependentElmtIdsByProperty_.getAllPropertyDependencies());
285
286        // send changed observed property to profiler
287        // only will be true when enable profiler
288        if (stateMgmtDFX.enableProfiler) {
289          stateMgmtConsole.debug(`notifyPropertyHasChangedPU in profiler mode`);
290          this.onDumpProfiler();
291        }
292      } else {
293        // mark this @StorageLink/Prop or @LocalStorageLink/Prop variable has having changed and notification of viewPropertyHasChanged delivery pending
294        this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending;
295      }
296    }
297    this.subscriberRefs_.forEach((subscriber) => {
298      if (subscriber && typeof subscriber === 'object' && 'syncPeerHasChanged' in subscriber) {
299        (subscriber as unknown as PeerChangeEventReceiverPU<T>).syncPeerHasChanged(this);
300      } else {
301        stateMgmtConsole.warn(`${this.debugInfo()}: notifyPropertyHasChangedPU: unknown subscriber ID 'subscribedId' error!`);
302      }
303    });
304    stateMgmtProfiler.end();
305  }
306
307
308  // notify owning ViewPU and peers of a ObservedObject @Track property's assignment
309  protected notifyTrackedObjectPropertyHasChanged(changedPropertyName : string) : void {
310    stateMgmtProfiler.begin('ObservedPropertyAbstract.notifyTrackedObjectPropertyHasChanged');
311    stateMgmtConsole.debug(`${this.debugInfo()}: notifyTrackedObjectPropertyHasChanged.`);
312    if (this.owningView_) {
313      if (this.delayedNotification_ == ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay) {
314        // send viewPropertyHasChanged right away
315        this.owningView_.viewPropertyHasChanged(this.info_, this.dependentElmtIdsByProperty_.getTrackedObjectPropertyDependencies(changedPropertyName, 'notifyTrackedObjectPropertyHasChanged'));
316        // send changed observed property to profiler
317        // only will be true when enable profiler
318        if (stateMgmtDFX.enableProfiler) {
319          stateMgmtConsole.debug(`notifyPropertyHasChangedPU in profiler mode`);
320          this.onDumpProfiler(changedPropertyName);
321        }
322      } else {
323        // mark this @StorageLink/Prop or @LocalStorageLink/Prop variable has having changed and notification of viewPropertyHasChanged delivery pending
324        this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending;
325      }
326    }
327    this.subscriberRefs_.forEach((subscriber) => {
328      if (subscriber) {
329        if ('syncPeerTrackedPropertyHasChanged' in subscriber) {
330          (subscriber as unknown as PeerChangeEventReceiverPU<T>).syncPeerTrackedPropertyHasChanged(this, changedPropertyName);
331        } else  {
332          stateMgmtConsole.warn(`${this.debugInfo()}: notifyTrackedObjectPropertyHasChanged: unknown subscriber ID 'subscribedId' error!`);
333        }
334      }
335    });
336    stateMgmtProfiler.end();
337  }
338
339  protected abstract onOptimisedObjectPropertyRead(readObservedObject : T, readPropertyName : string, isTracked : boolean) : void;
340
341  public markDependentElementsDirty(view: ViewPU) {
342    // TODO ace-ets2bundle, framework, complicated apps need to update together
343    // this function will be removed after a short transition period.
344    stateMgmtConsole.warn(`${this.debugInfo()}: markDependentElementsDirty no longer supported. App will work ok, but
345        please update your ace-ets2bundle and recompile your application!`);
346  }
347
348  public numberOfSubscrbers(): number {
349    return this.subscriberRefs_.size + (this.owningView_ ? 1 : 0);
350  }
351
352  /*
353   type checking for any supported type, as required for union type support
354    see 1st parameter for explanation what is allowed
355
356    FIXME this expects the Map, Set patch to go in
357   */
358
359  protected checkIsSupportedValue(value: T): boolean {
360    // FIXME enable the check when V1-V2 interoperability is forbidden
361    // && !ObserveV2.IsProxiedObservedV2(value))
362    let res = ((typeof value === 'object' && typeof value !== 'function' &&
363      !ObserveV2.IsObservedObjectV2(value) &&
364      !ObserveV2.IsMakeObserved(value)) ||
365      typeof value === 'number' ||
366      typeof value === 'string' ||
367      typeof value === 'boolean' ||
368      value === undefined ||
369      value === null);
370
371    if (!res) {
372      errorReport.varValueCheckFailed({
373        customComponent: this.debugInfoOwningView(),
374        variableDeco: this.debugInfoDecorator(),
375        variableName: this.info(),
376        expectedType: `undefined, null, number, boolean, string, or Object but not function, not V2 @ObservedV2 / @Trace class, and makeObserved return value either`,
377        value: value
378      });
379    }
380    return res;
381  }
382
383  /*
384    type checking for allowed Object type value
385    see 1st parameter for explanation what is allowed
386
387      FIXME this expects the Map, Set patch to go in
388   */
389  protected checkIsObject(value: T): boolean {
390    let res = ((typeof value === 'object' && typeof value !== 'function' && !ObserveV2.IsObservedObjectV2(value)) ||
391    value === undefined || value === null);
392    if (!res) {
393      errorReport.varValueCheckFailed({
394          customComponent: this.debugInfoOwningView(),
395          variableDeco: this.debugInfoDecorator(),
396          variableName: this.info(),
397          expectedType: `undefined, null, Object including Array and instance of SubscribableAbstract and excluding function and V3 @observed/@track object`,
398          value: value
399        });
400    }
401    return res;
402  }
403
404  /*
405    type checking for allowed simple types value
406    see 1st parameter for explanation what is allowed
407   */
408  protected checkIsSimple(value: T): boolean {
409    let res = (value === undefined || typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean');
410    if (!res) {
411      errorReport.varValueCheckFailed({
412        customComponent: this.debugInfoOwningView(),
413        variableDeco: this.debugInfoDecorator(),
414        variableName: this.info(),
415        expectedType: `undefined, number, boolean, string`,
416        value: value
417      });
418    }
419    return res;
420  }
421
422  protected checkNewValue(isAllowedComment : string, newValue: T, validator: (value: T) => boolean) : boolean {
423    if (validator(newValue)) {
424      return true;
425    }
426
427    // report error
428    // current implementation throws an Exception
429    errorReport.varValueCheckFailed({
430      customComponent: this.debugInfoOwningView(),
431      variableDeco: this.debugInfoDecorator(),
432      variableName: this.info(),
433      expectedType: isAllowedComment,
434      value: newValue
435    });
436
437    // never gets here if errorReport.varValueCheckFailed throws an exception
438    // but should not depend on its implementation
439    return false;
440  }
441
442
443  /**
444   * factory function for concrete 'object' or 'simple' ObservedProperty object
445   * depending if value is Class object
446   * or simple type (boolean | number | string)
447   * @param value
448   * @param owningView
449   * @param thisPropertyName
450   * @returns either
451   */
452  static CreateObservedObject<C>(value: C, owningView: IPropertySubscriber, thisPropertyName: PropertyInfo)
453    : ObservedPropertyAbstract<C> {
454    return (typeof value === 'object') ?
455      new ObservedPropertyObject(value, owningView, thisPropertyName)
456      : new ObservedPropertySimple(value, owningView, thisPropertyName);
457  }
458
459
460  /**
461   * If owning viewPU is currently rendering or re-rendering a UINode, return its elmtId
462   * return notRecordingDependencies (-1) otherwise
463   * ViewPU caches the info, it does not request the info from C++ side (by calling
464   * ViewStackProcessor.GetElmtIdToAccountFor(); as done in earlier implementation
465   */
466  protected getRenderingElmtId() : number {
467    return (this.owningView_) ? this.owningView_.getCurrentlyRenderedElmtId() : UINodeRegisterProxy.notRecordingDependencies;
468  }
469
470
471  /**
472   * during 'get' access recording take note of the created component and its elmtId
473   * and add this component to the list of components who are dependent on this property
474   */
475  protected recordPropertyDependentUpdate() : void {
476    const elmtId = this.getRenderingElmtId();
477    if (elmtId === UINodeRegisterProxy.notRecordingDependencies) {
478      // not access recording
479      return;
480    }
481    if (elmtId === UINodeRegisterProxy.monitorIllegalV2V3StateAccess) {
482      const error = `${this.debugInfo()}: recordPropertyDependentUpdate trying to use V2 state to init/update child V3 @Component. Application error`;
483      stateMgmtConsole.applicationError(error);
484      throw new TypeError(error);
485    }
486
487    stateMgmtConsole.debug(`${this.debugInfo()}: recordPropertyDependentUpdate: add (state) variable dependency for elmtId ${elmtId}.`);
488    this.dependentElmtIdsByProperty_.addPropertyDependency(elmtId);
489  }
490
491  /** record dependency ObservedObject + propertyName -> elmtId
492   * caller ensures renderingElmtId >= 0
493   */
494  protected recordTrackObjectPropertyDependencyForElmtId(renderingElmtId : number, readTrackedPropertyName : string) : void {
495    stateMgmtConsole.debug(`${this.debugInfo()}: recordTrackObjectPropertyDependency on elmtId ${renderingElmtId}.`);
496    this.dependentElmtIdsByProperty_.addTrackedObjectPropertyDependency(readTrackedPropertyName, renderingElmtId);
497  }
498
499  public purgeDependencyOnElmtId(rmElmtId: number): void {
500    this.dependentElmtIdsByProperty_?.purgeDependenciesForElmtId(rmElmtId);
501  }
502
503  public SetPropertyUnchanged(): void {
504    // function to be removed
505    // keep it here until transpiler is updated.
506  }
507
508  // unified Appstorage, what classes to use, and the API
509  public createLink(subscribeOwner?: IPropertySubscriber,
510    linkPropName?: PropertyInfo): ObservedPropertyAbstractPU<T> {
511      throw new Error(`${this.debugInfo()}: createLink: Can not create a AppStorage 'Link' from this property.`);
512  }
513
514  public createProp(subscribeOwner?: IPropertySubscriber,
515    linkPropName?: PropertyInfo): ObservedPropertyAbstractPU<T> {
516      throw new Error(`${this.debugInfo()}: createProp: Can not create a AppStorage 'Prop' from a @State property. `);
517  }
518
519  /*
520    Below empty functions required to keep as long as this class derives from FU version
521    ObservedPropertyAbstract. Need to overwrite these functions to do nothing for PU
522   */
523    protected notifyHasChanged(_: T) {
524      stateMgmtConsole.error(`${this.debugInfo()}: notifyHasChanged, DO NOT USE with PU. Use syncPeerHasChanged() \
525                                            or onTrackedObjectProperty(CompatMode)HasChangedPU()`);
526    }
527
528
529  /**
530  * event emitted by wrapped ObservedObject, when one of its property values changes
531  * for class objects when in compatibility mode
532  * for Array, Date instances always
533  * @param souceObject
534  * @param changedPropertyName
535  */
536  public onTrackedObjectPropertyHasChangedPU(sourceObject: ObservedObject<T>, changedPropertyName: string) {
537    stateMgmtConsole.debug(`${this.debugInfo()}: onTrackedObjectPropertyHasChangedPU: property '${changedPropertyName}' of \
538      object value has changed.`);
539
540    this.notifyTrackedObjectPropertyHasChanged(changedPropertyName);
541  }
542
543  /**
544  * event emitted by wrapped ObservedObject, when one of its property values changes
545  * for class objects when in compatibility mode
546  * for Array, Date instances always
547  * @param souceObject
548  * @param changedPropertyName
549  */
550  public onTrackedObjectPropertyCompatModeHasChangedPU(sourceObject: ObservedObject<T>, changedPropertyName: string) {
551    stateMgmtConsole.debug(`${this.debugInfo()}: onTrackedObjectPropertyCompatModeHasChangedPU: property '${changedPropertyName}' of \
552      object value has changed.`);
553
554    this.notifyPropertyHasChangedPU();
555  }
556
557
558  hasChanged(_: T): void {
559    // unused for PU
560    // need to overwrite impl of base class with empty function.
561  }
562
563  propertyHasChanged(_?: PropertyInfo): void {
564    // unused for PU
565    // need to overwrite impl of base class with empty function.
566  }
567
568  propertyRead(_?: PropertyInfo): void {
569    // unused for PU
570    // need to overwrite impl of base class with empty function.
571  }
572}
573
574class PropertyDependencies {
575
576  // dependencies for property -> elmtId
577  // variable read during render adds elmtId
578  // variable assignment causes elmtId to need re-render.
579  // UINode with elmtId deletion needs elmtId to be removed from all records, see purgeDependenciesForElmtId
580  private propertyDependencies_: Set<number> = new Set<number>();
581
582  public getAllPropertyDependencies(): Set<number> {
583    stateMgmtConsole.debug(`  ... variable value assignment: returning affected elmtIds ${JSON.stringify(Array.from(this.propertyDependencies_))}`);
584    return this.propertyDependencies_;
585  }
586
587  public addPropertyDependency(elmtId: number): void {
588    this.propertyDependencies_.add(elmtId);
589    stateMgmtConsole.debug(`   ... variable value read: add dependent elmtId ${elmtId} - updated list of dependent elmtIds: ${JSON.stringify(Array.from(this.propertyDependencies_))}`);
590  }
591
592  public purgeDependenciesForElmtId(rmElmtId: number): void {
593    stateMgmtConsole.debug(`   ...purge all dependencies for elmtId ${rmElmtId} `);
594    this.propertyDependencies_.delete(rmElmtId);
595    stateMgmtConsole.debug(`      ... updated list of elmtIds dependent on variable assignment: ${JSON.stringify(Array.from(this.propertyDependencies_))}`);
596    this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => {
597      propertyElmtId.delete(rmElmtId);
598      stateMgmtConsole.debug(`      ... updated dependencies on objectProperty '${propertyName}' changes: ${JSON.stringify(Array.from(propertyElmtId))}`);
599    });
600  }
601
602  // dependencies on individual object properties
603  private trackedObjectPropertyDependencies_: Map<string, Set<number>> = new Map<string, Set<number>>();
604
605  public addTrackedObjectPropertyDependency(readProperty: string, elmtId: number): void {
606    let dependentElmtIds = this.trackedObjectPropertyDependencies_.get(readProperty);
607    if (!dependentElmtIds) {
608      dependentElmtIds = new Set<number>();
609      this.trackedObjectPropertyDependencies_.set(readProperty, dependentElmtIds);
610    }
611    dependentElmtIds.add(elmtId);
612    stateMgmtConsole.debug(`   ... object property '${readProperty}' read: add dependent elmtId ${elmtId} - updated list of dependent elmtIds: ${JSON.stringify(Array.from(dependentElmtIds))}`);
613  }
614
615  public getTrackedObjectPropertyDependencies(changedObjectProperty: string, debugInfo: string): Set<number> {
616    const dependentElmtIds = this.trackedObjectPropertyDependencies_.get(changedObjectProperty) || new Set<number>();
617    stateMgmtConsole.debug(`  ... property '@Track ${changedObjectProperty}': returning affected elmtIds ${JSON.stringify(Array.from(dependentElmtIds))}`);
618    return dependentElmtIds;
619  }
620
621  public dumpInfoDependencies(owningView: ViewPU | undefined = undefined, dumpDependantElements): string {
622    const formatElmtId = owningView ? (elmtId => owningView.debugInfoElmtId(elmtId)) : (elmtId => elmtId);
623    let result: string = '';
624    const arr = Array.from(this.propertyDependencies_).map(formatElmtId);
625    if (dumpDependantElements) {
626      return (arr.length > 1 ? arr.join(', ') : arr[0]);
627    }
628    if (!this.trackedObjectPropertyDependencies_.size) {
629      result += `dependencies: variable assignment affects elmtIds: ${Array.from(this.propertyDependencies_).map(formatElmtId).join(', ')}`;
630      return result;
631    }
632    this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => {
633      result += `dependencies: property '@Track ${propertyName}' change affects elmtIds: ${Array.from(propertyElmtId).map(formatElmtId).join(', ')}`;
634    });
635    return result;
636  }
637
638
639  public dumpInfoDependenciesObj(owningView: ViewPU | undefined = undefined, isTrackedMode: boolean, isProfiler: boolean): PropertyDependenciesInfo {
640
641    const formatElmtId = owningView ? (elmtId => owningView.debugInfoElmtId(elmtId, isProfiler)) : (elmtId => elmtId);
642
643    let trackedObjectPropertyDependenciesDumpInfo: Map<string, Array<ElementType | number | string>> = new Map<string, Array<ElementType | number | string>>();
644
645    this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => {
646      trackedObjectPropertyDependenciesDumpInfo.set(propertyName, Array.from(propertyElmtId).map(formatElmtId));
647    });
648
649    let PropertyDependenciesInfo: PropertyDependenciesInfo = {
650      mode: isTrackedMode ? 'Track Mode' : 'Compatible Mode',
651      trackPropertiesDependencies: MapInfo.toObject(trackedObjectPropertyDependenciesDumpInfo).keyToValue,
652      propertyDependencies: Array.from(this.propertyDependencies_).map(formatElmtId),
653    }
654    return PropertyDependenciesInfo;
655  }
656
657  public hasDependencies() : boolean {
658    return this.propertyDependencies_.size > 0 || this.trackedObjectPropertyDependencies_.size > 0;
659  }
660}
661