• 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(): TargetInfo {
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    let res = ((typeof value === 'object' && typeof value !== 'function' &&
361      !ObserveV2.IsObservedObjectV2(value) &&
362      !ObserveV2.IsMakeObserved(value)) ||
363      typeof value === 'number' ||
364      typeof value === 'string' ||
365      typeof value === 'boolean' ||
366      value === undefined ||
367      value === null);
368
369    if (!res) {
370      errorReport.varValueCheckFailed({
371        customComponent: this.debugInfoOwningView(),
372        variableDeco: this.debugInfoDecorator(),
373        variableName: this.info(),
374        expectedType: `undefined, null, number, boolean, string, or Object but not function, not V2 @ObservedV2 / @Trace class, and makeObserved return value either`,
375        value: value
376      });
377    }
378    return res;
379  }
380
381  /*
382    type checking for allowed Object type value
383    see 1st parameter for explanation what is allowed
384
385      FIXME this expects the Map, Set patch to go in
386   */
387  protected checkIsObject(value: T): boolean {
388    let res = ((typeof value === 'object' && typeof value !== 'function' && !ObserveV2.IsObservedObjectV2(value)) ||
389    value === undefined || value === null);
390    if (!res) {
391      errorReport.varValueCheckFailed({
392          customComponent: this.debugInfoOwningView(),
393          variableDeco: this.debugInfoDecorator(),
394          variableName: this.info(),
395          expectedType: `undefined, null, Object including Array and instance of SubscribableAbstract, excluding function and V2 @Observed/@Trace object`,
396          value: value
397        });
398    }
399    return res;
400  }
401
402  /*
403    type checking for allowed simple types value
404    see 1st parameter for explanation what is allowed
405   */
406  protected checkIsSimple(value: T): boolean {
407    let res = (value === undefined || typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean');
408    if (!res) {
409      errorReport.varValueCheckFailed({
410        customComponent: this.debugInfoOwningView(),
411        variableDeco: this.debugInfoDecorator(),
412        variableName: this.info(),
413        expectedType: `undefined, number, boolean, string`,
414        value: value
415      });
416    }
417    return res;
418  }
419
420  protected checkNewValue(isAllowedComment : string, newValue: T, validator: (value: T) => boolean) : boolean {
421    if (validator(newValue)) {
422      return true;
423    }
424
425    // report error
426    // current implementation throws an Exception
427    errorReport.varValueCheckFailed({
428      customComponent: this.debugInfoOwningView(),
429      variableDeco: this.debugInfoDecorator(),
430      variableName: this.info(),
431      expectedType: isAllowedComment,
432      value: newValue
433    });
434
435    // never gets here if errorReport.varValueCheckFailed throws an exception
436    // but should not depend on its implementation
437    return false;
438  }
439
440
441  /**
442   * factory function for concrete 'object' or 'simple' ObservedProperty object
443   * depending if value is Class object
444   * or simple type (boolean | number | string)
445   * @param value
446   * @param owningView
447   * @param thisPropertyName
448   * @returns either
449   */
450  static CreateObservedObject<C>(value: C, owningView: IPropertySubscriber, thisPropertyName: PropertyInfo)
451    : ObservedPropertyAbstract<C> {
452    return (typeof value === 'object') ?
453      new ObservedPropertyObject(value, owningView, thisPropertyName)
454      : new ObservedPropertySimple(value, owningView, thisPropertyName);
455  }
456
457
458  /**
459   * If owning viewPU is currently rendering or re-rendering a UINode, return its elmtId
460   * return notRecordingDependencies (-1) otherwise
461   * ViewPU caches the info, it does not request the info from C++ side (by calling
462   * ViewStackProcessor.GetElmtIdToAccountFor(); as done in earlier implementation
463   */
464  protected getRenderingElmtId() : number {
465    return (this.owningView_) ? this.owningView_.getCurrentlyRenderedElmtId() : UINodeRegisterProxy.notRecordingDependencies;
466  }
467
468
469  /**
470   * during 'get' access recording take note of the created component and its elmtId
471   * and add this component to the list of components who are dependent on this property
472   */
473  protected recordPropertyDependentUpdate() : void {
474    const elmtId = this.getRenderingElmtId();
475    if (elmtId === UINodeRegisterProxy.notRecordingDependencies) {
476      // not access recording
477      return;
478    }
479    if (elmtId === UINodeRegisterProxy.monitorIllegalV1V2StateAccess) {
480      const error = `${this.debugInfo()}: recordPropertyDependentUpdate trying to use V1 state to init/update child V2 @Component. Application error`;
481      stateMgmtConsole.applicationError(error);
482      throw new TypeError(error);
483    }
484
485    stateMgmtConsole.debug(`${this.debugInfo()}: recordPropertyDependentUpdate: add (state) variable dependency for elmtId ${elmtId}.`);
486    this.dependentElmtIdsByProperty_.addPropertyDependency(elmtId);
487  }
488
489  /** record dependency ObservedObject + propertyName -> elmtId
490   * caller ensures renderingElmtId >= 0
491   */
492  protected recordTrackObjectPropertyDependencyForElmtId(renderingElmtId : number, readTrackedPropertyName : string) : void {
493    stateMgmtConsole.debug(`${this.debugInfo()}: recordTrackObjectPropertyDependency on elmtId ${renderingElmtId}.`);
494    this.dependentElmtIdsByProperty_.addTrackedObjectPropertyDependency(readTrackedPropertyName, renderingElmtId);
495  }
496
497  public purgeDependencyOnElmtId(rmElmtId: number): void {
498    this.dependentElmtIdsByProperty_?.purgeDependenciesForElmtId(rmElmtId);
499  }
500
501  public SetPropertyUnchanged(): void {
502    // function to be removed
503    // keep it here until transpiler is updated.
504  }
505
506  // unified Appstorage, what classes to use, and the API
507  public createLink(subscribeOwner?: IPropertySubscriber,
508    linkPropName?: PropertyInfo): ObservedPropertyAbstractPU<T> {
509      throw new Error(`${this.debugInfo()}: createLink: Can not create a AppStorage 'Link' from this property.`);
510  }
511
512  public createProp(subscribeOwner?: IPropertySubscriber,
513    linkPropName?: PropertyInfo): ObservedPropertyAbstractPU<T> {
514      throw new Error(`${this.debugInfo()}: createProp: Can not create a AppStorage 'Prop' from a @State property. `);
515  }
516
517  /*
518    Below empty functions required to keep as long as this class derives from FU version
519    ObservedPropertyAbstract. Need to overwrite these functions to do nothing for PU
520   */
521    protected notifyHasChanged(_: T) {
522      stateMgmtConsole.error(`${this.debugInfo()}: notifyHasChanged, DO NOT USE with PU. Use syncPeerHasChanged() \
523                                            or onTrackedObjectProperty(CompatMode)HasChangedPU()`);
524    }
525
526
527  /**
528  * event emitted by wrapped ObservedObject, when one of its property values changes
529  * for class objects when in compatibility mode
530  * for Array, Date instances always
531  * @param souceObject
532  * @param changedPropertyName
533  */
534  public onTrackedObjectPropertyHasChangedPU(sourceObject: ObservedObject<T>, changedPropertyName: string) {
535    stateMgmtConsole.debug(`${this.debugInfo()}: onTrackedObjectPropertyHasChangedPU: property '${changedPropertyName}' of \
536      object value has changed.`);
537
538    this.notifyTrackedObjectPropertyHasChanged(changedPropertyName);
539  }
540
541  /**
542  * event emitted by wrapped ObservedObject, when one of its property values changes
543  * for class objects when in compatibility mode
544  * for Array, Date instances always
545  * @param souceObject
546  * @param changedPropertyName
547  */
548  public onTrackedObjectPropertyCompatModeHasChangedPU(sourceObject: ObservedObject<T>, changedPropertyName: string) {
549    stateMgmtConsole.debug(`${this.debugInfo()}: onTrackedObjectPropertyCompatModeHasChangedPU: property '${changedPropertyName}' of \
550      object value has changed.`);
551
552    this.notifyPropertyHasChangedPU();
553  }
554
555
556  hasChanged(_: T): void {
557    // unused for PU
558    // need to overwrite impl of base class with empty function.
559  }
560
561  propertyHasChanged(_?: PropertyInfo): void {
562    // unused for PU
563    // need to overwrite impl of base class with empty function.
564  }
565
566  propertyRead(_?: PropertyInfo): void {
567    // unused for PU
568    // need to overwrite impl of base class with empty function.
569  }
570}
571
572class PropertyDependencies {
573
574  // dependencies for property -> elmtId
575  // variable read during render adds elmtId
576  // variable assignment causes elmtId to need re-render.
577  // UINode with elmtId deletion needs elmtId to be removed from all records, see purgeDependenciesForElmtId
578  private propertyDependencies_: Set<number> = new Set<number>();
579
580  public getAllPropertyDependencies(): Set<number> {
581    stateMgmtConsole.debug(`  ... variable value assignment: returning affected elmtIds ${JSON.stringify(Array.from(this.propertyDependencies_))}`);
582    return this.propertyDependencies_;
583  }
584
585  public addPropertyDependency(elmtId: number): void {
586    this.propertyDependencies_.add(elmtId);
587    stateMgmtConsole.debug(`   ... variable value read: add dependent elmtId ${elmtId} - updated list of dependent elmtIds: ${JSON.stringify(Array.from(this.propertyDependencies_))}`);
588  }
589
590  public purgeDependenciesForElmtId(rmElmtId: number): void {
591    stateMgmtConsole.debug(`   ...purge all dependencies for elmtId ${rmElmtId} `);
592    this.propertyDependencies_.delete(rmElmtId);
593    stateMgmtConsole.debug(`      ... updated list of elmtIds dependent on variable assignment: ${JSON.stringify(Array.from(this.propertyDependencies_))}`);
594    this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => {
595      propertyElmtId.delete(rmElmtId);
596      stateMgmtConsole.debug(`      ... updated dependencies on objectProperty '${propertyName}' changes: ${JSON.stringify(Array.from(propertyElmtId))}`);
597    });
598  }
599
600  // dependencies on individual object properties
601  private trackedObjectPropertyDependencies_: Map<string, Set<number>> = new Map<string, Set<number>>();
602
603  public addTrackedObjectPropertyDependency(readProperty: string, elmtId: number): void {
604    let dependentElmtIds = this.trackedObjectPropertyDependencies_.get(readProperty);
605    if (!dependentElmtIds) {
606      dependentElmtIds = new Set<number>();
607      this.trackedObjectPropertyDependencies_.set(readProperty, dependentElmtIds);
608    }
609    dependentElmtIds.add(elmtId);
610    stateMgmtConsole.debug(`   ... object property '${readProperty}' read: add dependent elmtId ${elmtId} - updated list of dependent elmtIds: ${JSON.stringify(Array.from(dependentElmtIds))}`);
611  }
612
613  public getTrackedObjectPropertyDependencies(changedObjectProperty: string, debugInfo: string): Set<number> {
614    const dependentElmtIds = this.trackedObjectPropertyDependencies_.get(changedObjectProperty) || new Set<number>();
615    stateMgmtConsole.debug(`  ... property '@Track ${changedObjectProperty}': returning affected elmtIds ${JSON.stringify(Array.from(dependentElmtIds))}`);
616    return dependentElmtIds;
617  }
618
619  public dumpInfoDependencies(owningView: ViewPU | undefined = undefined, dumpDependantElements): string {
620    const formatElmtId = owningView ? (elmtId => owningView.debugInfoElmtId(elmtId)) : (elmtId => elmtId);
621    let result: string = '';
622    const arr = Array.from(this.propertyDependencies_).map(formatElmtId);
623    if (dumpDependantElements) {
624      return (arr.length > 1 ? arr.join(', ') : arr[0]);
625    }
626    if (!this.trackedObjectPropertyDependencies_.size) {
627      result += `dependencies: variable assignment affects elmtIds: ${Array.from(this.propertyDependencies_).map(formatElmtId).join(', ')}`;
628      return result;
629    }
630    this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => {
631      result += `dependencies: property '@Track ${propertyName}' change affects elmtIds: ${Array.from(propertyElmtId).map(formatElmtId).join(', ')}`;
632    });
633    return result;
634  }
635
636
637  public dumpInfoDependenciesObj(owningView: ViewPU | undefined = undefined, isTrackedMode: boolean, isProfiler: boolean): PropertyDependenciesInfo {
638
639    const formatElmtId = owningView ? (elmtId => owningView.debugInfoElmtId(elmtId, isProfiler)) : (elmtId => elmtId);
640
641    let trackedObjectPropertyDependenciesDumpInfo: Map<string, Array<ElementType | number | string>> = new Map<string, Array<ElementType | number | string>>();
642
643    this.trackedObjectPropertyDependencies_.forEach((propertyElmtId, propertyName) => {
644      trackedObjectPropertyDependenciesDumpInfo.set(propertyName, Array.from(propertyElmtId).map(formatElmtId));
645    });
646
647    let PropertyDependenciesInfo: PropertyDependenciesInfo = {
648      mode: isTrackedMode ? 'Track Mode' : 'Compatible Mode',
649      trackPropertiesDependencies: MapInfo.toObject(trackedObjectPropertyDependenciesDumpInfo).keyToValue,
650      propertyDependencies: Array.from(this.propertyDependencies_).map(formatElmtId),
651    }
652    return PropertyDependenciesInfo;
653  }
654
655  public hasDependencies() : boolean {
656    return this.propertyDependencies_.size > 0 || this.trackedObjectPropertyDependencies_.size > 0;
657  }
658}
659