• 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  public getDependencies(): Set<number> {
166    return this.dependentElmtIdsByProperty_.getAllPropertyDependencies();
167  }
168
169  /* for @Prop value from source we need to generate a @State
170     that observes when this value changes. This ObservedPropertyPU
171     sits inside SynchedPropertyOneWayPU.
172     below methods invent a fake variable name for it
173  */
174  protected getPropSourceObservedPropertyFakeName(): string {
175    return `${this.info()}_prop_fake_state_source___`;
176  }
177
178  protected isPropSourceObservedPropertyFakeName(): string | false {
179    return this.info() && this.info().endsWith('_prop_fake_state_source___')
180      ? this.info().substring(0, this.info().length - '_prop_fake_state_source___'.length)
181      : false;
182  }
183
184  public getOwningView(): TargetInfo {
185    return { componentName: this.owningView_?.constructor.name, id: this.owningView_?.id__() };
186  }
187
188  public dumpSyncPeers(isProfiler: boolean, changedTrackPropertyName?: string): ObservedPropertyInfo<T>[] {
189    let res: ObservedPropertyInfo<T>[] = [];
190    this.subscriberRefs_.forEach((subscriber: IPropertySubscriber) => {
191      if ('debugInfo' in subscriber) {
192        const observedProp = subscriber as ObservedPropertyAbstractPU<any>;
193        res.push(stateMgmtDFX.getObservedPropertyInfo(observedProp, isProfiler, changedTrackPropertyName));
194      }
195    });
196    return res;
197  }
198
199  protected onDumpProfiler(changedTrackPropertyName?: string): void {
200    let res: DumpInfo = new DumpInfo();
201    res.viewInfo = { componentName: this.owningView_?.constructor.name, id: this.owningView_?.id__() };
202    res.observedPropertiesInfo.push(stateMgmtDFX.getObservedPropertyInfo(this, true, changedTrackPropertyName));
203    if (this.owningView_) {
204      try {
205        this.owningView_.sendStateInfo(JSON.stringify(res));
206      } catch (error) {
207        stateMgmtConsole.applicationError(`${this.debugInfo()} has error in sendStateInfo: ${(error as Error).message}`);
208      }
209    }
210  }
211
212  /*
213    Virtualized version of the subscription mechanism - add subscriber
214    Overrides implementation in ObservedPropertyAbstract<T>
215  */
216  public addSubscriber(subscriber: ISinglePropertyChangeSubscriber<T>):void {
217    if (subscriber) {
218      // ObservedPropertyAbstract will also add subscriber to
219      // SubscriberManager map and to its own Set of subscribers as well
220      // Something to improve in the future for PU path.
221      // subscribeMe should accept IPropertySubscriber interface
222      super.subscribeMe(subscriber as ISinglePropertyChangeSubscriber<T>);
223      this.subscriberRefs_.add(subscriber);
224    }
225  }
226
227  /*
228    Virtualized version of the subscription mechanism - remove subscriber
229    Overrides implementation in ObservedPropertyAbstract<T>
230  */
231  public removeSubscriber(subscriber: IPropertySubscriber, id?: number):void {
232    if (subscriber) {
233      this.subscriberRefs_.delete(subscriber);
234      if (!id) {
235        id = subscriber.id__();
236      }
237    }
238    super.unlinkSuscriber(id);
239  }
240
241  /**
242   * put the property to delayed notification mode
243   * feature is only used for @StorageLink/Prop, @LocalStorageLink/Prop
244   */
245  public enableDelayedNotification() : void {
246  if (this.delayedNotification_ !== ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending) {
247      stateMgmtConsole.debug(`${this.constructor.name}: enableDelayedNotification.`);
248      this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_none_pending;
249    }
250  }
251
252  /*
253     when moving from inActive to active state the owning ViewPU calls this function
254     This solution is faster than ViewPU polling each variable to send back a viewPropertyHasChanged event
255     with the elmtIds
256
257    returns undefined if variable has _not_ changed
258    returns dependentElementIds_ Set if changed. This Set is empty if variable is not used to construct the UI
259  */
260    public moveElmtIdsForDelayedUpdate(isReused: boolean = false): Set<number> | undefined {
261      const result = (this.delayedNotification_ === ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending) ?
262        this.dependentElmtIdsByProperty_.getAllPropertyDependencies() :
263        undefined;
264      stateMgmtConsole.debug(`${this.debugInfo()}: moveElmtIdsForDelayedUpdate: elmtIds that need delayed update \
265                        ${result ? Array.from(result).toString() : 'no delayed notifications'} .`);
266      if (isReused && !this.owningView_.isViewActive()) {
267        this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_none_pending;
268      } else {
269        this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay;
270      }
271      return result;
272    }
273
274  protected notifyPropertyRead() {
275    stateMgmtConsole.error(`${this.debugInfo()}: notifyPropertyRead, DO NOT USE with PU. Use notifyReadCb mechanism.`);
276
277  }
278
279  // notify owning ViewPU and peers of a variable assignment
280  // also property/item changes to  ObservedObjects of class object type, which use compat mode
281  // Date and Array are notified as if there had been an assignment.
282  protected notifyPropertyHasChangedPU(isSync: boolean = false) : void {
283    stateMgmtProfiler.begin('ObservedPropertyAbstractPU.notifyPropertyHasChangedPU');
284    stateMgmtConsole.debug(`${this.debugInfo()}: notifyPropertyHasChangedPU.`);
285    if (this.owningView_) {
286      if (this.delayedNotification_ === ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay) {
287        if (!isSync) {
288          // send viewPropertyHasChanged right away
289          this.owningView_.viewPropertyHasChanged(this.info_, this.dependentElmtIdsByProperty_.getAllPropertyDependencies());
290        } else {
291          this.owningView_.collectElementsNeedToUpdateSynchronously(this.info_, this.dependentElmtIdsByProperty_.getAllPropertyDependencies(), true);
292        }
293        // send changed observed property to profiler
294        // only will be true when enable profiler
295        if (stateMgmtDFX.enableProfiler) {
296          stateMgmtConsole.debug(`notifyPropertyHasChangedPU in profiler mode`);
297          this.onDumpProfiler();
298        }
299      } else {
300        // mark this @StorageLink/Prop or @LocalStorageLink/Prop variable has having changed and notification of viewPropertyHasChanged delivery pending
301        this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending;
302      }
303    }
304    this.subscriberRefs_.forEach((subscriber) => {
305      if (subscriber && typeof subscriber === 'object' && 'syncPeerHasChanged' in subscriber) {
306        (subscriber as unknown as PeerChangeEventReceiverPU<T>).syncPeerHasChanged(this, isSync);
307      } else {
308        stateMgmtConsole.warn(`${this.debugInfo()}: notifyPropertyHasChangedPU: unknown subscriber ID 'subscribedId' error!`);
309      }
310    });
311    stateMgmtProfiler.end();
312  }
313
314
315  // notify owning ViewPU and peers of a ObservedObject @Track property's assignment
316  protected notifyTrackedObjectPropertyHasChanged(changedPropertyName : string, isSync: boolean = false) : void {
317    stateMgmtProfiler.begin('ObservedPropertyAbstract.notifyTrackedObjectPropertyHasChanged');
318    stateMgmtConsole.debug(`${this.debugInfo()}: notifyTrackedObjectPropertyHasChanged.`);
319    if (this.owningView_) {
320      if (this.delayedNotification_ == ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.do_not_delay) {
321        // send viewPropertyHasChanged right away
322        if (!isSync) {
323          this.owningView_.viewPropertyHasChanged(this.info_, this.dependentElmtIdsByProperty_.getTrackedObjectPropertyDependencies(changedPropertyName, 'notifyTrackedObjectPropertyHasChanged'));
324        } else {
325          this.owningView_.collectElementsNeedToUpdateSynchronously(this.info_,
326            this.dependentElmtIdsByProperty_.getTrackedObjectPropertyDependencies(changedPropertyName, 'notifyTrackedObjectPropertyHasChanged'), false);
327        }
328
329        // send changed observed property to profiler
330        // only will be true when enable profiler
331        if (stateMgmtDFX.enableProfiler) {
332          stateMgmtConsole.debug(`notifyPropertyHasChangedPU in profiler mode`);
333          this.onDumpProfiler(changedPropertyName);
334        }
335      } else {
336        // mark this @StorageLink/Prop or @LocalStorageLink/Prop variable has having changed and notification of viewPropertyHasChanged delivery pending
337        this.delayedNotification_ = ObservedPropertyAbstractPU.DelayedNotifyChangesEnum.delay_notification_pending;
338      }
339    }
340    this.subscriberRefs_.forEach((subscriber) => {
341      if (subscriber) {
342        if ('syncPeerTrackedPropertyHasChanged' in subscriber) {
343          (subscriber as unknown as PeerChangeEventReceiverPU<T>).syncPeerTrackedPropertyHasChanged(this, changedPropertyName, isSync);
344        } else  {
345          stateMgmtConsole.warn(`${this.debugInfo()}: notifyTrackedObjectPropertyHasChanged: unknown subscriber ID 'subscribedId' error!`);
346        }
347      }
348    });
349    stateMgmtProfiler.end();
350  }
351
352  protected abstract onOptimisedObjectPropertyRead(readObservedObject : T, readPropertyName : string, isTracked : boolean) : void;
353
354  public markDependentElementsDirty(view: ViewPU) {
355    // TODO ace-ets2bundle, framework, complicated apps need to update together
356    // this function will be removed after a short transition period.
357    stateMgmtConsole.warn(`${this.debugInfo()}: markDependentElementsDirty no longer supported. App will work ok, but
358        please update your ace-ets2bundle and recompile your application!`);
359  }
360
361  public numberOfSubscrbers(): number {
362    return this.subscriberRefs_.size + (this.owningView_ ? 1 : 0);
363  }
364
365  /*
366   type checking for any supported type, as required for union type support
367    see 1st parameter for explanation what is allowed
368
369    FIXME this expects the Map, Set patch to go in
370   */
371
372  protected checkIsSupportedValue(value: T): boolean {
373    // FIXME enable the check when V1-V2 interoperability is forbidden
374    // && !ObserveV2.IsProxiedObservedV2(value)
375    let res = ((typeof value === 'object' && typeof value !== 'function' &&
376      !ObserveV2.IsObservedObjectV2(value) &&
377      !ObserveV2.IsMakeObserved(value)) ||
378      typeof value === 'number' ||
379      typeof value === 'string' ||
380      typeof value === 'boolean' ||
381      value === undefined ||
382      value === null);
383
384    if (!res) {
385      errorReport.varValueCheckFailed({
386        customComponent: this.debugInfoOwningView(),
387        variableDeco: this.debugInfoDecorator(),
388        variableName: this.info(),
389        expectedType: `undefined, null, number, boolean, string, or Object but not function, not V2 @ObservedV2 / @Trace class, and makeObserved return value either`,
390        value: value
391      });
392    }
393    return res;
394  }
395
396  /*
397    type checking for allowed Object type value
398    see 1st parameter for explanation what is allowed
399
400      FIXME this expects the Map, Set patch to go in
401   */
402  protected checkIsObject(value: T): boolean {
403    let res = ((typeof value === 'object' && typeof value !== 'function' && !ObserveV2.IsObservedObjectV2(value)) ||
404    value === undefined || value === null);
405    if (!res) {
406      errorReport.varValueCheckFailed({
407          customComponent: this.debugInfoOwningView(),
408          variableDeco: this.debugInfoDecorator(),
409          variableName: this.info(),
410          expectedType: `undefined, null, Object including Array and instance of SubscribableAbstract, excluding function and V2 @Observed/@Trace object`,
411          value: value
412        });
413    }
414    return res;
415  }
416
417  /*
418    type checking for allowed simple types value
419    see 1st parameter for explanation what is allowed
420   */
421  protected checkIsSimple(value: T): boolean {
422    let res = (value === undefined || typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean');
423    if (!res) {
424      errorReport.varValueCheckFailed({
425        customComponent: this.debugInfoOwningView(),
426        variableDeco: this.debugInfoDecorator(),
427        variableName: this.info(),
428        expectedType: `undefined, number, boolean, string`,
429        value: value
430      });
431    }
432    return res;
433  }
434
435  protected checkNewValue(isAllowedComment : string, newValue: T, validator: (value: T) => boolean) : boolean {
436    if (validator(newValue)) {
437      return true;
438    }
439
440    // report error
441    // current implementation throws an Exception
442    errorReport.varValueCheckFailed({
443      customComponent: this.debugInfoOwningView(),
444      variableDeco: this.debugInfoDecorator(),
445      variableName: this.info(),
446      expectedType: isAllowedComment,
447      value: newValue
448    });
449
450    // never gets here if errorReport.varValueCheckFailed throws an exception
451    // but should not depend on its implementation
452    return false;
453  }
454
455
456  /**
457   * factory function for concrete 'object' or 'simple' ObservedProperty object
458   * depending if value is Class object
459   * or simple type (boolean | number | string)
460   * @param value
461   * @param owningView
462   * @param thisPropertyName
463   * @returns either
464   */
465  static CreateObservedObject<C>(value: C, owningView: IPropertySubscriber, thisPropertyName: PropertyInfo)
466    : ObservedPropertyAbstract<C> {
467    return (typeof value === 'object') ?
468      new ObservedPropertyObject(value, owningView, thisPropertyName)
469      : new ObservedPropertySimple(value, owningView, thisPropertyName);
470  }
471
472
473  /**
474   * If owning viewPU is currently rendering or re-rendering a UINode, return its elmtId
475   * return notRecordingDependencies (-1) otherwise
476   * ViewPU caches the info, it does not request the info from C++ side (by calling
477   * ViewStackProcessor.GetElmtIdToAccountFor(); as done in earlier implementation
478   */
479  protected getRenderingElmtId() : number {
480    return (this.owningView_) ? this.owningView_.getCurrentlyRenderedElmtId() : UINodeRegisterProxy.notRecordingDependencies;
481  }
482
483
484  /**
485   * during 'get' access recording take note of the created component and its elmtId
486   * and add this component to the list of components who are dependent on this property
487   */
488  protected recordPropertyDependentUpdate() : void {
489    const elmtId = this.getRenderingElmtId();
490    if (elmtId === UINodeRegisterProxy.notRecordingDependencies) {
491      // not access recording
492      return;
493    }
494    if (elmtId === UINodeRegisterProxy.monitorIllegalV1V2StateAccess) {
495      const error = `${this.debugInfo()}: recordPropertyDependentUpdate trying to use V1 state to init/update child V2 @Component. Application error`;
496      stateMgmtConsole.applicationError(error);
497      throw new TypeError(error);
498    }
499
500    stateMgmtConsole.debug(`${this.debugInfo()}: recordPropertyDependentUpdate: add (state) variable dependency for elmtId ${elmtId}.`);
501    this.dependentElmtIdsByProperty_.addPropertyDependency(elmtId);
502  }
503
504  /** record dependency ObservedObject + propertyName -> elmtId
505   * caller ensures renderingElmtId >= 0
506   */
507  protected recordTrackObjectPropertyDependencyForElmtId(renderingElmtId : number, readTrackedPropertyName : string) : void {
508    stateMgmtConsole.debug(`${this.debugInfo()}: recordTrackObjectPropertyDependency on elmtId ${renderingElmtId}.`);
509    this.dependentElmtIdsByProperty_.addTrackedObjectPropertyDependency(readTrackedPropertyName, renderingElmtId);
510  }
511
512  public purgeDependencyOnElmtId(rmElmtId: number): void {
513    this.dependentElmtIdsByProperty_?.purgeDependenciesForElmtId(rmElmtId);
514  }
515
516  public SetPropertyUnchanged(): void {
517    // function to be removed
518    // keep it here until transpiler is updated.
519  }
520
521  // unified Appstorage, what classes to use, and the API
522  public createLink(subscribeOwner?: IPropertySubscriber,
523    linkPropName?: PropertyInfo): ObservedPropertyAbstractPU<T> {
524      throw new Error(`${this.debugInfo()}: createLink: Can not create a AppStorage 'Link' from this property.`);
525  }
526
527  public createProp(subscribeOwner?: IPropertySubscriber,
528    linkPropName?: PropertyInfo): ObservedPropertyAbstractPU<T> {
529      throw new Error(`${this.debugInfo()}: createProp: Can not create a AppStorage 'Prop' from a @State property. `);
530  }
531
532  /*
533    Below empty functions required to keep as long as this class derives from FU version
534    ObservedPropertyAbstract. Need to overwrite these functions to do nothing for PU
535   */
536    protected notifyHasChanged(_: T) {
537      stateMgmtConsole.error(`${this.debugInfo()}: notifyHasChanged, DO NOT USE with PU. Use syncPeerHasChanged() \
538                                            or onTrackedObjectProperty(CompatMode)HasChangedPU()`);
539    }
540
541
542  /**
543  * event emitted by wrapped ObservedObject, when one of its property values changes
544  * for class objects when in compatibility mode
545  * for Array, Date instances always
546  * @param souceObject
547  * @param changedPropertyName
548  */
549  public onTrackedObjectPropertyHasChangedPU(sourceObject: ObservedObject<T>, changedPropertyName: string) {
550    stateMgmtConsole.debug(`${this.debugInfo()}: onTrackedObjectPropertyHasChangedPU: property '${changedPropertyName}' of \
551      object value has changed.`);
552
553    this.notifyTrackedObjectPropertyHasChanged(changedPropertyName);
554  }
555
556  /**
557  * event emitted by wrapped ObservedObject, when one of its property values changes
558  * for class objects when in compatibility mode
559  * for Array, Date instances always
560  * @param souceObject
561  * @param changedPropertyName
562  */
563  public onTrackedObjectPropertyCompatModeHasChangedPU(sourceObject: ObservedObject<T>, changedPropertyName: string) {
564    stateMgmtConsole.debug(`${this.debugInfo()}: onTrackedObjectPropertyCompatModeHasChangedPU: property '${changedPropertyName}' of \
565      object value has changed.`);
566
567    this.notifyPropertyHasChangedPU();
568  }
569
570
571  hasChanged(_: T): void {
572    // unused for PU
573    // need to overwrite impl of base class with empty function.
574  }
575
576  propertyHasChanged(_?: PropertyInfo): void {
577    // unused for PU
578    // need to overwrite impl of base class with empty function.
579  }
580
581  propertyRead(_?: PropertyInfo): void {
582    // unused for PU
583    // need to overwrite impl of base class with empty function.
584  }
585}
586
587class PropertyDependencies {
588
589  // dependencies for property -> elmtId
590  // variable read during render adds elmtId
591  // variable assignment causes elmtId to need re-render.
592  // UINode with elmtId deletion needs elmtId to be removed from all records, see purgeDependenciesForElmtId
593  private propertyDependencies_: Set<number> = new Set<number>();
594
595  public getAllPropertyDependencies(): Set<number> {
596    stateMgmtConsole.debug(`  ... variable value assignment: returning affected elmtIds ${JSON.stringify(Array.from(this.propertyDependencies_))}`);
597    return this.propertyDependencies_;
598  }
599
600  public addPropertyDependency(elmtId: number): void {
601    this.propertyDependencies_.add(elmtId);
602    stateMgmtConsole.debug(`   ... variable value read: add dependent elmtId ${elmtId} - updated list of dependent elmtIds: ${JSON.stringify(Array.from(this.propertyDependencies_))}`);
603  }
604
605  public purgeDependenciesForElmtId(rmElmtId: number): void {
606    stateMgmtConsole.debug(`   ...purge all dependencies for elmtId ${rmElmtId} `);
607    this.propertyDependencies_.delete(rmElmtId);
608    stateMgmtConsole.debug(`      ... updated list of elmtIds dependent on variable assignment: ${JSON.stringify(Array.from(this.propertyDependencies_))}`);
609    this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => {
610      propertyElmtId.delete(rmElmtId);
611      stateMgmtConsole.debug(`      ... updated dependencies on objectProperty '${propertyName}' changes: ${JSON.stringify(Array.from(propertyElmtId))}`);
612    });
613  }
614
615  // dependencies on individual object properties
616  private trackedObjectPropertyDependencies_: Map<string, Set<number>> = new Map<string, Set<number>>();
617
618  public addTrackedObjectPropertyDependency(readProperty: string, elmtId: number): void {
619    let dependentElmtIds = this.trackedObjectPropertyDependencies_.get(readProperty);
620    if (!dependentElmtIds) {
621      dependentElmtIds = new Set<number>();
622      this.trackedObjectPropertyDependencies_.set(readProperty, dependentElmtIds);
623    }
624    dependentElmtIds.add(elmtId);
625    stateMgmtConsole.debug(`   ... object property '${readProperty}' read: add dependent elmtId ${elmtId} - updated list of dependent elmtIds: ${JSON.stringify(Array.from(dependentElmtIds))}`);
626  }
627
628  public getTrackedObjectPropertyDependencies(changedObjectProperty: string, debugInfo: string): Set<number> {
629    const dependentElmtIds = this.trackedObjectPropertyDependencies_.get(changedObjectProperty) || new Set<number>();
630    stateMgmtConsole.debug(`  ... property '@Track ${changedObjectProperty}': returning affected elmtIds ${JSON.stringify(Array.from(dependentElmtIds))}`);
631    return dependentElmtIds;
632  }
633
634  public dumpInfoDependencies(owningView: ViewPU | undefined = undefined, dumpDependantElements): string {
635    const formatElmtId = owningView ? (elmtId => owningView.debugInfoElmtId(elmtId)) : (elmtId => elmtId);
636    let result: string = '';
637    const arr = Array.from(this.propertyDependencies_).map(formatElmtId);
638    if (dumpDependantElements) {
639      return (arr.length > 1 ? arr.join(', ') : arr[0]);
640    }
641    if (!this.trackedObjectPropertyDependencies_.size) {
642      result += `dependencies: variable assignment affects elmtIds: ${Array.from(this.propertyDependencies_).map(formatElmtId).join(', ')}`;
643      return result;
644    }
645    this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => {
646      result += `dependencies: property '@Track ${propertyName}' change affects elmtIds: ${Array.from(propertyElmtId).map(formatElmtId).join(', ')}`;
647    });
648    return result;
649  }
650
651
652  public dumpInfoDependenciesObj(owningView: ViewPU | undefined = undefined, isTrackedMode: boolean, isProfiler: boolean): PropertyDependenciesInfo {
653
654    const formatElmtId = owningView ? (elmtId => owningView.debugInfoElmtId(elmtId, isProfiler)) : (elmtId => elmtId);
655
656    let trackedObjectPropertyDependenciesDumpInfo: Map<string, Array<ElementType | number | string>> = new Map<string, Array<ElementType | number | string>>();
657
658    this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => {
659      trackedObjectPropertyDependenciesDumpInfo.set(propertyName, Array.from(propertyElmtId).map(formatElmtId));
660    });
661
662    let PropertyDependenciesInfo: PropertyDependenciesInfo = {
663      mode: isTrackedMode ? 'Track Mode' : 'Compatible Mode',
664      trackPropertiesDependencies: MapInfo.toObject(trackedObjectPropertyDependenciesDumpInfo).keyToValue,
665      propertyDependencies: Array.from(this.propertyDependencies_).map(formatElmtId),
666    }
667    return PropertyDependenciesInfo;
668  }
669
670  public hasDependencies() : boolean {
671    return this.propertyDependencies_.size > 0 || this.trackedObjectPropertyDependencies_.size > 0;
672  }
673}
674