• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022-2024 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 *  * ViewPU - View for Partial Update
16 *
17* all definitions in this file are framework internal
18*/
19
20type DFXCommand = { what: string, viewId: number, isRecursive: boolean };
21type RecycleUpdateFunc = (elmtId: number, isFirstRender: boolean, recycleNode: ViewPU) => void;
22
23abstract class ViewPU extends PUV2ViewBase
24  implements IViewPropertiesChangeSubscriber, IView {
25
26  // flag for initial rendering or re-render on-going.
27  private isRenderInProgress: boolean = false;
28
29  // flag for initial rendering being done
30  private isInitialRenderDone: boolean = false;
31
32  private runReuse_: boolean = false;
33
34  private paramsGenerator_: () => Object;
35
36  private watchedProps: Map<string, (propName: string) => void> = new Map<string, (propName: string) => void>();
37
38  private recycleManager_: RecycleManager = undefined;
39
40  private hasBeenRecycled_: boolean = false;
41
42  private preventRecursiveRecycle_: boolean = false;
43
44  private delayRecycleNodeRerender: boolean = false;
45
46  private delayRecycleNodeRerenderDeep: boolean = false;
47
48  // @Provide'd variables by this class and its ancestors
49  protected providedVars_: Map<string, ObservedPropertyAbstractPU<any>> = new Map<string, ObservedPropertyAbstractPU<any>>();
50
51  // my LocalStorage instance, shared with ancestor Views.
52  // create a default instance on demand if none is initialized
53  protected localStoragebackStore_: LocalStorage = undefined;
54
55  private ownObservedPropertiesStore__?: Set<ObservedPropertyAbstractPU<any>>;
56
57  private get ownObservedPropertiesStore_() {
58    if (!this.ownObservedPropertiesStore__) {
59      // lazy init
60      this.ownObservedPropertiesStore__ = new Set<ObservedPropertyAbstractPU<any>>();
61      this.obtainOwnObservedProperties();
62    }
63    return this.ownObservedPropertiesStore__;
64  }
65
66  protected obtainOwnObservedProperties(): void {
67    let usesStateMgmtVersion = 0;
68    Object.getOwnPropertyNames(this)
69      .filter((propName) => {
70        // do not include backing store, and ObserveV2/MonitorV2/ComputedV2 meta data objects
71        return (propName.startsWith('__') &&
72          !propName.startsWith(ObserveV2.OB_PREFIX) &&
73          !propName.startsWith(MonitorV2.WATCH_PREFIX) &&
74          !propName.startsWith(ComputedV2.COMPUTED_PREFIX));
75      })
76      .forEach((propName) => {
77        const stateVar = Reflect.get(this, propName) as Object;
78        if (stateVar && typeof stateVar === 'object' && 'notifyPropertyHasChangedPU' in stateVar) {
79          stateMgmtConsole.debug(`... add state variable ${propName} to ${stateVar}`);
80          this.ownObservedPropertiesStore_.add(stateVar as unknown as ObservedPropertyAbstractPU<any>);
81          usesStateMgmtVersion = 2;
82        } else {
83          stateMgmtConsole.debug(`${this.debugInfo__()} ${propName} application may use an unregular naming style, or stateVar may be Non-Object.`);
84        }
85      });
86
87    if (this.isViewV3 === true) {
88      if (usesStateMgmtVersion === 2) {
89        const error = `${this.debugInfo__()}: mixed use of stateMgmt V2 and V3 variable decorators. Application error!`;
90        stateMgmtConsole.applicationError(error);
91        throw new Error(error);
92      }
93    }
94    stateMgmtConsole.debug(`${this.debugInfo__()}: uses stateMgmt version ${this.isViewV3 === true ? 3 : 2}`);
95  }
96
97  public get localStorage_(): LocalStorage {
98    if (!this.localStoragebackStore_ && this.getParent()) {
99      stateMgmtConsole.debug(`${this.debugInfo__()}: constructor: get localStorage_ : Using LocalStorage instance of the parent View.`);
100      this.localStoragebackStore_ = this.getParent().localStorage_;
101    }
102
103    if (!this.localStoragebackStore_) {
104      stateMgmtConsole.info(`${this.debugInfo__()}: constructor: is accessing LocalStorage without being provided an instance. Creating a default instance.`);
105      this.localStoragebackStore_ = new LocalStorage({ /* empty */ });
106    }
107    return this.localStoragebackStore_;
108  }
109
110  public set localStorage_(instance: LocalStorage) {
111    if (!instance) {
112      // setting to undefined not allowed
113      return;
114    }
115    if (this.localStoragebackStore_) {
116      stateMgmtConsole.applicationError(`${this.debugInfo__()}: constructor: is setting LocalStorage instance twice. Application error.`);
117    }
118    this.localStoragebackStore_ = instance;
119  }
120
121  // FIXME
122  // indicate if this is  V2 or a V3 component
123  // V2 by default, changed to V3 by the first V3 decorated variable
124  // when splitting ViewPU and ViewV3
125  // use instanceOf. Until then, this is a workaround.
126  // @state, @track, etc V3 decorator functions modify isViewV3 to return true
127  // (decorator can modify functions in prototype)
128  // FIXME
129  private get isViewV3(): boolean {
130    return false;
131  }
132
133  /**
134   * Create a View
135   *
136   * 1. option: top level View, specify
137   *    - compilerAssignedUniqueChildId must specify
138   *    - parent=undefined
139   *    - localStorage  must provide if @LocalSTorageLink/Prop variables are used
140   *      in this View or descendant Views.
141   *
142   * 2. option: not a top level View
143   *    - compilerAssignedUniqueChildId must specify
144   *    - parent must specify
145   *    - localStorage do not specify, will inherit from parent View.
146   *
147  */
148  constructor(parent: IView, localStorage: LocalStorage, elmtId: number = UINodeRegisterProxy.notRecordingDependencies, extraInfo: ExtraInfo = undefined) {
149    super(parent, elmtId, extraInfo);
150    // if set use the elmtId also as the ViewPU object's subscribable id.
151    // these matching is requirement for updateChildViewById(elmtId) being able to
152    // find the child ViewPU object by given elmtId
153    //this.id_ = elmtId == UINodeRegisterProxy.notRecordingDependencies ? SubscriberManager.MakeId() : elmtId;
154
155    this.localStoragebackStore_ = undefined;
156    stateMgmtConsole.debug(`ViewPU constructor: Creating @Component '${this.constructor.name}' from parent '${parent?.constructor.name}'`);
157
158    PUV2ViewBase.arkThemeScopeManager?.onViewPUCreate(this)
159
160    if (localStorage) {
161      this.localStorage_ = localStorage;
162      stateMgmtConsole.debug(`${this.debugInfo__()}: constructor: Using LocalStorage instance provided via @Entry or view instance creation.`);
163    }
164
165    SubscriberManager.Add(this);
166    stateMgmtConsole.debug(`${this.debugInfo__()}: constructor: done`);
167  }
168
169  onGlobalThemeChanged(): void {
170    this.onWillApplyThemeInternally();
171    this.forceCompleteRerender(false)
172    this.childrenWeakrefMap_.forEach((weakRefChild) => {
173      const child = weakRefChild.deref();
174      if (child) {
175        child.onGlobalThemeChanged();
176      }
177    });
178  }
179
180  // inform the subscribed property
181  // that the View and thereby all properties
182  // are about to be deleted
183  abstract aboutToBeDeleted(): void;
184
185  aboutToReuse(params: Object): void { }
186
187  aboutToRecycle(): void { }
188
189  private onWillApplyThemeInternally(): void {
190    const theme = PUV2ViewBase.arkThemeScopeManager?.getFinalTheme(this.id__())
191    if (theme) {
192        this.onWillApplyTheme(theme)
193    }
194  }
195
196  onWillApplyTheme(theme: Theme): void {}
197  // super class will call this function from
198  // its aboutToBeDeleted implementation
199  protected aboutToBeDeletedInternal(): void {
200    stateMgmtConsole.debug(`${this.debugInfo__()}: aboutToBeDeletedInternal`);
201    // if this isDeleting_ is true already, it may be set delete status recursively by its parent, so it is not necessary
202    // to set and recursively set its children any more
203    if (!this.isDeleting_) {
204      this.isDeleting_ = true;
205      this.setDeleteStatusRecursively();
206    }
207    // tell UINodeRegisterProxy that all elmtIds under
208    // this ViewPU should be treated as already unregistered
209
210    stateMgmtConsole.debug(`${this.constructor.name}: aboutToBeDeletedInternal `);
211
212    // purge the elmtIds owned by this viewPU from the updateFuncByElmtId and also the state variable dependent elmtIds
213    Array.from(this.updateFuncByElmtId.keys()).forEach((elmtId: number) => {
214      this.purgeDeleteElmtId(elmtId);
215    })
216
217    if (this.hasRecycleManager()) {
218      this.getRecycleManager().purgeAllCachedRecycleNode();
219    }
220
221    // un-registration of ElementIDs
222    stateMgmtConsole.debug(`${this.debugInfo__()}: onUnRegElementID`);
223
224    // it will unregister removed elmtIds from all ViewPu, equals purgeDeletedElmtIdsRecursively
225    this.purgeDeletedElmtIds();
226
227    // un-registers its own id once its children are unregistered above
228    //FIXME: Uncomment once photos app avoids rerendering of removed elementIds
229    //UINodeRegisterProxy unregisterRemovedElmtsFromViewPUs([this id__()]);
230
231    stateMgmtConsole.debug(`${this.debugInfo__()}: onUnRegElementID  - DONE`);
232
233    // in case this ViewPU is currently frozen
234    PUV2ViewBase.inactiveComponents_.delete(`${this.constructor.name}[${this.id__()}]`);
235
236    // FIXME needed ?
237    MonitorV2.clearWatchesFromTarget(this);
238
239    this.updateFuncByElmtId.clear();
240    this.watchedProps.clear();
241    this.providedVars_.clear();
242    if (this.ownObservedPropertiesStore__) {
243      this.ownObservedPropertiesStore__.clear();
244    }
245    if (this.getParent()) {
246      this.getParent().removeChild(this);
247    }
248    PUV2ViewBase.arkThemeScopeManager?.onViewPUDelete(this);
249    this.localStoragebackStore_ = undefined;
250  }
251
252  public purgeDeleteElmtId(rmElmtId: number): boolean {
253    stateMgmtConsole.debug(`${this.debugInfo__()} purgeDeleteElmtId (PU) is purging the rmElmtId:${rmElmtId}`);
254    const result = this.updateFuncByElmtId.delete(rmElmtId);
255    if (result) {
256      this.purgeVariableDependenciesOnElmtIdOwnFunc(rmElmtId);
257      // it means rmElmtId has finished all the unregistration from the js side, ElementIdToOwningViewPU_  does not need to keep it
258      UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(rmElmtId);
259    }
260    return result;
261  }
262
263  protected purgeVariableDependenciesOnElmtIdOwnFunc(elmtId: number): void {
264    this.ownObservedPropertiesStore_.forEach((stateVar: ObservedPropertyAbstractPU<any>) => {
265      stateVar.purgeDependencyOnElmtId(elmtId);
266    });
267  }
268  protected debugInfoStateVars(): string {
269    let result: string = `|--${this.constructor.name}[${this.id__()}]`;
270    Object.getOwnPropertyNames(this)
271      .filter((varName: string) => varName.startsWith('__') && !varName.startsWith(ObserveV2.OB_PREFIX))
272      .forEach((varName) => {
273        const prop: any = Reflect.get(this, varName);
274        if ('debugInfoDecorator' in prop) {
275          const observedProp = prop as ObservedPropertyAbstractPU<any>;
276          result += `\n  ${observedProp.debugInfoDecorator()} '${observedProp.info()}'[${observedProp.id__()}]`;
277          result += `\n  ${observedProp.debugInfoSubscribers()}`;
278          result += `\n  ${observedProp.debugInfoSyncPeers()}`;
279          result += `\n  ${observedProp.debugInfoDependentElmtIds()}`;
280          result += `\n  ${observedProp.debugInfoDependentComponents()}`;
281        }
282      });
283    return result;
284  }
285
286   /**
287   * Indicate if this @Component is allowed to freeze by calling with freezeState=true
288   * Called with value of the @Component decorator 'freezeWhenInactive' parameter
289   * or depending how UI compiler works also with 'undefined'
290   * @param freezeState only value 'true' will be used, otherwise inherits from parent
291   * if not parent, set to false.
292   */
293   protected initAllowComponentFreeze(freezeState: boolean | undefined): void {
294    // set to true if freeze parameter set for this @Component to true
295    // otherwise inherit from parent @Component (if it exists).
296    this.isCompFreezeAllowed_ = freezeState || this.isCompFreezeAllowed_;
297    stateMgmtConsole.debug(`${this.debugInfo__()}: @Component freezeWhenInactive state is set to ${this.isCompFreezeAllowed()}`);
298  }
299
300  /**
301 * ArkUI engine will call this function when the corresponding CustomNode's active status change.
302 * ArkUI engine will not recurse children nodes to inform the stateMgmt for the performance reason.
303 * So the stateMgmt needs to recurse the children although the isCompFreezeAllowed is false because the children nodes
304 * may enable the freezeWhenInActive.
305 * @param active true for active, false for inactive
306 */
307  public setActiveInternal(active: boolean): void {
308    stateMgmtProfiler.begin('ViewPU.setActive');
309    if (this.isCompFreezeAllowed()) {
310      this.isActive_ = active;
311      if (this.isActive_) {
312        this.onActiveInternal();
313      } else {
314        this.onInactiveInternal();
315      }
316    }
317    for (const child of this.childrenWeakrefMap_.values()) {
318      const childView: IView | undefined = child.deref();
319      if (childView) {
320        childView.setActiveInternal(active);
321      }
322    }
323    stateMgmtProfiler.end();
324  }
325
326  private onActiveInternal(): void {
327    if (!this.isActive_) {
328      return;
329    }
330
331    stateMgmtConsole.debug(`${this.debugInfo__()}: onActiveInternal`);
332    this.performDelayedUpdate();
333    // Remove the active component from the Map for Dfx
334    ViewPU.inactiveComponents_.delete(`${this.constructor.name}[${this.id__()}]`);
335  }
336
337
338  private onInactiveInternal(): void {
339    if (this.isActive_) {
340      return;
341    }
342
343    stateMgmtConsole.debug(`${this.debugInfo__()}: onInactiveInternal`);
344    for (const stateLinkProp of this.ownObservedPropertiesStore_) {
345      stateLinkProp.enableDelayedNotification();
346    }
347    // Add the inactive Components to Map for Dfx listing
348    ViewPU.inactiveComponents_.add(`${this.constructor.name}[${this.id__()}]`);
349  }
350
351
352  // abstract functions to be implemented by application defined class / transpiled code
353  protected abstract purgeVariableDependenciesOnElmtId(removedElmtId: number);
354  protected abstract initialRender(): void;
355  protected abstract rerender(): void;
356
357  public abstract updateRecycleElmtId(oldElmtId: number, newElmtId: number): void;
358  public abstract updateStateVars(params: Object);
359
360  public initialRenderView(): void {
361    stateMgmtProfiler.begin('ViewPU.initialRenderView');
362    this.onWillApplyThemeInternally();
363    this.obtainOwnObservedProperties();
364    this.isRenderInProgress = true;
365    this.initialRender();
366    this.isRenderInProgress = false;
367    this.isInitialRenderDone = true;
368    stateMgmtProfiler.end();
369  }
370
371  public UpdateElement(elmtId: number): void {
372    stateMgmtProfiler.begin('ViewPU.UpdateElement');
373    if (elmtId === this.id__()) {
374      // do not attempt to update itself.
375      // a @Prop can add a dependency of the ViewPU onto itself. Ignore it.
376      stateMgmtProfiler.end();
377      return;
378    }
379
380    // do not process an Element that has been marked to be deleted
381    const entry: UpdateFuncRecord | undefined = this.updateFuncByElmtId.get(elmtId);
382    const updateFunc = entry ? entry.getUpdateFunc() : undefined;
383
384    if (typeof updateFunc !== 'function') {
385      stateMgmtConsole.debug(`${this.debugInfo__()}: UpdateElement: update function of elmtId ${elmtId} not found, internal error!`);
386    } else {
387      stateMgmtConsole.debug(`${this.debugInfo__()}: UpdateElement: re-render of ${entry.getComponentName()} elmtId ${elmtId} start ...`);
388      this.isRenderInProgress = true;
389      stateMgmtProfiler.begin('ViewPU.updateFunc');
390      updateFunc(elmtId, /* isFirstRender */ false);
391      stateMgmtProfiler.end();
392      stateMgmtProfiler.begin('ViewPU.finishUpdateFunc (native)');
393      this.finishUpdateFunc(elmtId);
394      stateMgmtProfiler.end();
395      this.isRenderInProgress = false;
396      stateMgmtConsole.debug(`${this.debugInfo__()}: UpdateElement: re-render of ${entry.getComponentName()} elmtId ${elmtId} - DONE`);
397    }
398    stateMgmtProfiler.end();
399  }
400
401  public delayCompleteRerender(deep: boolean = false): void {
402    this.delayRecycleNodeRerender = true;
403    this.delayRecycleNodeRerenderDeep = deep;
404  }
405
406  public flushDelayCompleteRerender(): void {
407    this.forceCompleteRerender(this.delayRecycleNodeRerenderDeep);
408    this.delayRecycleNodeRerender = false;
409  }
410
411  /**
412   * force a complete rerender / update on specific node by executing update function.
413   *
414   * @param elmtId which node needs to update.
415   *
416   * framework internal functions, apps must not call
417   */
418  public forceRerenderNode(elmtId: number): void {
419    stateMgmtProfiler.begin('ViewPU.forceRerenderNode');
420    // see which elmtIds are managed by this View
421    // and clean up all book keeping for them
422    this.purgeDeletedElmtIds();
423    this.UpdateElement(elmtId);
424
425    // remove elemtId from dirtDescendantElementIds.
426    this.dirtDescendantElementIds_.delete(elmtId);
427    stateMgmtProfiler.end();
428  }
429
430  // implements IMultiPropertiesChangeSubscriber
431  viewPropertyHasChanged(varName: PropertyInfo, dependentElmtIds: Set<number>): void {
432    stateMgmtProfiler.begin('ViewPU.viewPropertyHasChanged');
433    aceTrace.begin('ViewPU.viewPropertyHasChanged', this.constructor.name, varName, dependentElmtIds.size);
434    if (this.isRenderInProgress) {
435      stateMgmtConsole.applicationError(`${this.debugInfo__()}: State variable '${varName}' has changed during render! It's illegal to change @Component state while build (initial render or re-render) is on-going. Application error!`);
436    }
437
438    this.syncInstanceId();
439
440    if (dependentElmtIds.size && !this.isFirstRender()) {
441      if (!this.dirtDescendantElementIds_.size && !this.runReuse_) {
442        // mark ComposedElement dirty when first elmtIds are added
443        // do not need to do this every time
444        this.markNeedUpdate();
445      }
446      stateMgmtConsole.debug(`${this.debugInfo__()}: viewPropertyHasChanged property: elmtIds that need re-render due to state variable change: ${this.debugInfoElmtIds(Array.from(dependentElmtIds))} .`);
447      for (const elmtId of dependentElmtIds) {
448        if (this.hasRecycleManager()) {
449          this.dirtDescendantElementIds_.add(this.recycleManager_.proxyNodeId(elmtId));
450        } else {
451          this.dirtDescendantElementIds_.add(elmtId);
452        }
453      }
454      stateMgmtConsole.debug(`   ... updated full list of elmtIds that need re-render [${this.debugInfoElmtIds(Array.from(this.dirtDescendantElementIds_))}].`);
455    } else {
456      stateMgmtConsole.debug(`${this.debugInfo__()}: viewPropertyHasChanged: state variable change adds no elmtIds for re-render`);
457      stateMgmtConsole.debug(`   ... unchanged full list of elmtIds that need re-render [${this.debugInfoElmtIds(Array.from(this.dirtDescendantElementIds_))}].`);
458    }
459
460    let cb = this.watchedProps.get(varName);
461    if (cb && typeof cb === 'function') {
462      stateMgmtConsole.debug(`   ... calling @Watch function`);
463      cb.call(this, varName);
464    }
465
466    this.restoreInstanceId();
467    aceTrace.end();
468    stateMgmtProfiler.end();
469  }
470
471
472  /**
473 *  inform that UINode with given elmtId needs rerender
474 *  does NOT exec @Watch function.
475 *  only used on V3 code path from ObserveV2.fireChange.
476 *
477 * FIXME will still use in the future?
478 */
479  public uiNodeNeedUpdateV3(elmtId: number): void {
480    if (this.isFirstRender()) {
481      return;
482    }
483
484    stateMgmtProfiler.begin(`ViewPU.uiNodeNeedUpdate ${this.debugInfoElmtId(elmtId)}`);
485
486    if (!this.dirtDescendantElementIds_.size) { //  && !this runReuse_) {
487      // mark ComposedElement dirty when first elmtIds are added
488      // do not need to do this every time
489      this.syncInstanceId();
490      this.markNeedUpdate();
491      this.restoreInstanceId();
492    }
493    if (this.hasRecycleManager()) {
494      this.dirtDescendantElementIds_.add(this.recycleManager_.proxyNodeId(elmtId));
495    } else {
496      this.dirtDescendantElementIds_.add(elmtId);
497    }
498    stateMgmtConsole.debug(`${this.debugInfo__()}: uiNodeNeedUpdate: updated full list of elmtIds that need re-render [${this.debugInfoElmtIds(Array.from(this.dirtDescendantElementIds_))}].`);
499
500    stateMgmtProfiler.end();
501  }
502
503  private performDelayedUpdate(): void {
504    if (!this.ownObservedPropertiesStore_.size && !this.elmtIdsDelayedUpdate.size) {
505      return;
506    }
507    stateMgmtProfiler.begin('ViewPU.performDelayedUpdate');
508    aceTrace.begin('ViewPU.performDelayedUpdate', this.constructor.name);
509    stateMgmtConsole.debug(`${this.debugInfo__()}: performDelayedUpdate start ...`);
510    this.syncInstanceId();
511
512    for (const stateLinkPropVar of this.ownObservedPropertiesStore_) {
513      const changedElmtIds = stateLinkPropVar.moveElmtIdsForDelayedUpdate();
514      if (changedElmtIds) {
515        const varName = stateLinkPropVar.info();
516        if (changedElmtIds.size && !this.isFirstRender()) {
517          for (const elmtId of changedElmtIds) {
518            this.dirtDescendantElementIds_.add(elmtId);
519          }
520        }
521
522        stateMgmtConsole.debug(`${this.debugInfo__()}: performDelayedUpdate: all elmtIds that need re-render [${Array.from(this.dirtDescendantElementIds_).toString()}].`);
523
524        const cb = this.watchedProps.get(varName);
525        if (cb) {
526          stateMgmtConsole.debug(`   ... calling @Watch function`);
527          cb.call(this, varName);
528        }
529      }
530    } // for all ownStateLinkProps_
531
532    for (let elementId of this.elmtIdsDelayedUpdate) {
533      this.dirtDescendantElementIds_.add(elementId);
534    }
535    this.elmtIdsDelayedUpdate.clear();
536
537    this.restoreInstanceId();
538
539    if (this.dirtDescendantElementIds_.size) {
540      this.markNeedUpdate();
541    }
542    aceTrace.end();
543    stateMgmtProfiler.end();
544  }
545
546  /**
547   * Function to be called from the constructor of the sub component
548   * to register a @Watch variable
549   * @param propStr name of the variable. Note from @Provide and @Consume this is
550   *      the variable name and not the alias!
551   * @param callback application defined member function of sub-class
552   */
553  protected declareWatch(propStr: string, callback: (propName: string) => void): void {
554    this.watchedProps.set(propStr, callback);
555  }
556
557  /**
558   * This View @Provide's a variable under given name
559   * Call this function from the constructor of the sub class
560   * @param providedPropName either the variable name or the alias defined as
561   *        decorator param
562   * @param store the backing store object for this variable (not the get/set variable!)
563   */
564  protected addProvidedVar<T>(providedPropName: string, store: ObservedPropertyAbstractPU<T>, allowOverride: boolean = false) {
565    if (!allowOverride && this.findProvidePU(providedPropName)) {
566      throw new ReferenceError(`${this.constructor.name}: duplicate @Provide property with name ${providedPropName}. Property with this name is provided by one of the ancestor Views already. @Provide override not allowed.`);
567    }
568    store.setDecoratorInfo('@Provide');
569    this.providedVars_.set(providedPropName, store);
570  }
571
572  /*
573    findProvidePU finds @Provided property recursively by traversing ViewPU's towards that of the UI tree root @Component:
574    if 'this' ViewPU has a @Provide('providedPropName') return it, otherwise ask from its parent ViewPU.
575  */
576  public findProvidePU(providedPropName: string): ObservedPropertyAbstractPU<any> | undefined {
577    return this.providedVars_.get(providedPropName) || (this.parent_ && this.parent_.findProvidePU(providedPropName));
578  }
579
580  /**
581   * Method for the sub-class to call from its constructor for resolving
582   *       a @Consume variable and initializing its backing store
583   *       with the SyncedPropertyTwoWay<T> object created from the
584   *       @Provide variable's backing store.
585   * @param providedPropName the name of the @Provide'd variable.
586   *     This is either the @Consume decorator parameter, or variable name.
587   * @param consumeVarName the @Consume variable name (not the
588   *            @Consume decorator parameter)
589   * @returns initializing value of the @Consume backing store
590   */
591  protected initializeConsume<T>(providedPropName: string,
592    consumeVarName: string): ObservedPropertyAbstractPU<T> {
593    let providedVarStore: ObservedPropertyAbstractPU<any> = this.findProvidePU(providedPropName);
594    if (providedVarStore === undefined) {
595      throw new ReferenceError(`${this.debugInfo__()} missing @Provide property with name ${providedPropName}.
596          Fail to resolve @Consume(${providedPropName}).`);
597    }
598
599    const factory = <T>(source: ObservedPropertyAbstract<T>) => {
600      const result: ObservedPropertyAbstractPU<T> = new SynchedPropertyTwoWayPU<T>(source, this, consumeVarName);
601      result.setDecoratorInfo('@Consume');
602      stateMgmtConsole.debug(`The @Consume is instance of ${result.constructor.name}`);
603      return result;
604    };
605    return providedVarStore.createSync(factory) as ObservedPropertyAbstractPU<T>;
606  }
607
608
609  /**
610   * given the elmtId of a child or child of child within this custom component
611   * remember this component needs a partial update
612   * @param elmtId
613   */
614  public markElemenDirtyById(elmtId: number): void {
615    // TODO ace-ets2bundle, framework, compiled apps need to update together
616    // this function will be removed after a short transition period
617    stateMgmtConsole.applicationError(`${this.debugInfo__()}: markElemenDirtyById no longer supported.
618        Please update your ace-ets2bundle and recompile your application. Application error!`);
619  }
620
621  /**
622   * For each recorded dirty Element in this custom component
623   * run its update function
624   *
625   */
626  public updateDirtyElements(): void {
627    stateMgmtProfiler.begin('ViewPU.updateDirtyElements');
628    do {
629      stateMgmtConsole.debug(`${this.debugInfo__()}: updateDirtyElements (re-render): sorted dirty elmtIds: ${Array.from(this.dirtDescendantElementIds_).sort(ViewPU.compareNumber)}, starting ....`);
630
631      // see which elmtIds are managed by this View
632      // and clean up all book keeping for them
633      this.purgeDeletedElmtIds();
634
635      // process all elmtIds marked as needing update in ascending order.
636      // ascending order ensures parent nodes will be updated before their children
637      // prior cleanup ensure no already deleted Elements have their update func executed
638      const dirtElmtIdsFromRootNode = Array.from(this.dirtDescendantElementIds_).sort(ViewPU.compareNumber);
639      // if state changed during exec update lambda inside UpdateElement, then the dirty elmtIds will be added
640      // to newly created this.dirtDescendantElementIds_ Set
641      dirtElmtIdsFromRootNode.forEach(elmtId => {
642        if (this.hasRecycleManager()) {
643          this.UpdateElement(this.recycleManager_.proxyNodeId(elmtId));
644        } else {
645          this.UpdateElement(elmtId);
646        }
647        this.dirtDescendantElementIds_.delete(elmtId);
648      });
649
650      if (this.dirtDescendantElementIds_.size) {
651        stateMgmtConsole.applicationError(`${this.debugInfo__()}: New UINode objects added to update queue while re-render! - Likely caused by @Component state change during build phase, not allowed. Application error!`);
652      }
653    } while (this.dirtDescendantElementIds_.size);
654    stateMgmtConsole.debug(`${this.debugInfo__()}: updateDirtyElements (re-render) - DONE, dump of ViewPU in next lines`);
655    stateMgmtProfiler.end();
656  }
657
658  // executed on first render only
659  // kept for backward compatibility with old ace-ets2bundle
660  public observeComponentCreation(compilerAssignedUpdateFunc: UpdateFunc): void {
661    if (this.isDeleting_) {
662      stateMgmtConsole.error(`View ${this.constructor.name} elmtId ${this.id__()} is already in process of destruction, will not execute observeComponentCreation `);
663      return;
664    }
665    const updateFunc = (elmtId: number, isFirstRender: boolean): void => {
666      stateMgmtConsole.debug(`${this.debugInfo__()}: ${isFirstRender ? `First render` : `Re-render/update`} start ....`);
667      this.currentlyRenderedElmtIdStack_.push(elmtId);
668      compilerAssignedUpdateFunc(elmtId, isFirstRender);
669      this.currentlyRenderedElmtIdStack_.pop();
670      stateMgmtConsole.debug(`${this.debugInfo__()}: ${isFirstRender ? `First render` : `Re-render/update`} - DONE ....`);
671    }
672
673    const elmtId = ViewStackProcessor.AllocateNewElmetIdForNextComponent();
674    // in observeComponentCreation function we do not get info about the component name, in
675    // observeComponentCreation2 we do.
676    this.updateFuncByElmtId.set(elmtId, { updateFunc: updateFunc });
677    // add element id -> owning ViewPU
678    UINodeRegisterProxy.ElementIdToOwningViewPU_.set(elmtId, new WeakRef(this));
679    try {
680      updateFunc(elmtId, /* is first render */ true);
681    } catch (error) {
682      // avoid the incompatible change that move set function before updateFunc.
683      this.updateFuncByElmtId.delete(elmtId);
684      UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(elmtId);
685      stateMgmtConsole.applicationError(`${this.debugInfo__()} has error in update func: ${(error as Error).message}`);
686      throw error;
687    }
688  }
689
690  public observeComponentCreation2(compilerAssignedUpdateFunc: UpdateFunc, classObject: UIClassObject): void {
691    if (this.isDeleting_) {
692      stateMgmtConsole.error(`View ${this.constructor.name} elmtId ${this.id__()} is already in process of destruction, will not execute observeComponentCreation2 `);
693      return;
694    }
695    const _componentName: string = (classObject && ('name' in classObject)) ? Reflect.get(classObject, 'name') as string : 'unspecified UINode';
696    if (_componentName === '__Recycle__') {
697      return;
698    }
699    const _popFunc: () => void = (classObject && 'pop' in classObject) ? classObject.pop! : (): void => { };
700    const updateFunc = (elmtId: number, isFirstRender: boolean): void => {
701      this.syncInstanceId();
702      stateMgmtConsole.debug(`${this.debugInfo__()}: ${isFirstRender ? `First render` : `Re-render/update`} ${_componentName}[${elmtId}] ${!this.isViewV3 ? '(enable PU state observe) ' : ''} ${ConfigureStateMgmt.instance.needsV2Observe() ? '(enabled V2 state observe) ' : ''} - start ....`);
703
704      PUV2ViewBase.arkThemeScopeManager?.onComponentCreateEnter(_componentName, elmtId, isFirstRender, this)
705
706      ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
707
708      if (!this.isViewV3) {
709        // Enable PU state tracking only in PU @Components
710        this.currentlyRenderedElmtIdStack_.push(elmtId);
711        stateMgmtDFX.inRenderingElementId.push(elmtId);
712      }
713
714      // if V2 @Observed/@Track used anywhere in the app (there is no more fine grained criteria),
715      // enable V2 object deep observation
716      // FIXME: A @Component should only use PU or V2 state, but ReactNative dynamic viewer uses both.
717      if (this.isViewV3 || ConfigureStateMgmt.instance.needsV2Observe()) {
718        // FIXME: like in V2 setting bindId_ in ObserveV2 does not work with 'stacked'
719        // update + initial render calls, like in if and ForEach case, convert to stack as well
720        ObserveV2.getObserve().startRecordDependencies(this, elmtId);
721      }
722
723      compilerAssignedUpdateFunc(elmtId, isFirstRender);
724      if (!isFirstRender) {
725        _popFunc();
726      }
727
728      let node = this.getNodeById(elmtId);
729      if (node !== undefined) {
730        (node as ArkComponent).cleanStageValue();
731      }
732
733      if (this.isViewV3 || ConfigureStateMgmt.instance.needsV2Observe()) {
734        ObserveV2.getObserve().stopRecordDependencies();
735      }
736      if (!this.isViewV3) {
737        this.currentlyRenderedElmtIdStack_.pop();
738        stateMgmtDFX.inRenderingElementId.pop();
739      }
740      ViewStackProcessor.StopGetAccessRecording();
741
742      PUV2ViewBase.arkThemeScopeManager?.onComponentCreateExit(elmtId)
743
744      stateMgmtConsole.debug(`${this.debugInfo__()}: ${isFirstRender ? `First render` : `Re-render/update`}  ${_componentName}[${elmtId}] - DONE ....`);
745      this.restoreInstanceId();
746    };
747
748    const elmtId = ViewStackProcessor.AllocateNewElmetIdForNextComponent();
749    // needs to move set before updateFunc.
750    // make sure the key and object value exist since it will add node in attributeModifier during updateFunc.
751    this.updateFuncByElmtId.set(elmtId, { updateFunc: updateFunc, classObject: classObject });
752    // add element id -> owning ViewPU
753    UINodeRegisterProxy.ElementIdToOwningViewPU_.set(elmtId, new WeakRef(this));
754    try {
755      updateFunc(elmtId, /* is first render */ true);
756    } catch (error) {
757      // avoid the incompatible change that move set function before updateFunc.
758      this.updateFuncByElmtId.delete(elmtId);
759      UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(elmtId);
760      stateMgmtConsole.applicationError(`${this.debugInfo__()} has error in update func: ${(error as Error).message}`);
761      throw error;
762    }
763    stateMgmtConsole.debug(`${this.debugInfo__()} is initial rendering elmtId ${elmtId}, tag: ${_componentName}, and updateFuncByElmtId size :${this.updateFuncByElmtId.size}`);
764  }
765
766
767  getOrCreateRecycleManager(): RecycleManager {
768    if (!this.recycleManager_) {
769      this.recycleManager_ = new RecycleManager;
770    }
771    return this.recycleManager_;
772  }
773
774  getRecycleManager(): RecycleManager {
775    return this.recycleManager_;
776  }
777
778  hasRecycleManager(): boolean {
779    return !(this.recycleManager_ === undefined);
780  }
781
782  initRecycleManager(): void {
783    if (this.recycleManager_) {
784      stateMgmtConsole.error(`${this.debugInfo__()}: init recycleManager multiple times. Internal error.`);
785      return;
786    }
787    this.recycleManager_ = new RecycleManager;
788  }
789  rebuildUpdateFunc(elmtId, compilerAssignedUpdateFunc): void {
790    const updateFunc = (elmtId, isFirstRender): void => {
791      this.currentlyRenderedElmtIdStack_.push(elmtId);
792      compilerAssignedUpdateFunc(elmtId, isFirstRender);
793      this.currentlyRenderedElmtIdStack_.pop();
794    };
795    if (this.updateFuncByElmtId.has(elmtId)) {
796      this.updateFuncByElmtId.set(elmtId, { updateFunc: updateFunc });
797    }
798  }
799
800  /**
801   * @function observeRecycleComponentCreation
802   * @description custom node recycle creation
803   * @param name custom node name
804   * @param recycleUpdateFunc custom node recycle update which can be converted to a normal update function
805   * @return void
806   */
807  public observeRecycleComponentCreation(name: string, recycleUpdateFunc: RecycleUpdateFunc): void {
808    // convert recycle update func to update func
809    const compilerAssignedUpdateFunc: UpdateFunc = (element, isFirstRender) => {
810      recycleUpdateFunc(element, isFirstRender, undefined);
811    };
812    let node: ViewPU;
813    // if there is no suitable recycle node, run a normal creation function.
814    if (!this.hasRecycleManager() || !(node = this.getRecycleManager().popRecycleNode(name))) {
815      stateMgmtConsole.debug(`${this.constructor.name}[${this.id__()}]: cannot init node by recycle, crate new node`);
816      this.observeComponentCreation(compilerAssignedUpdateFunc);
817      return;
818    }
819
820    // if there is a suitable recycle node, run a recycle update function.
821    const newElmtId: number = ViewStackProcessor.AllocateNewElmetIdForNextComponent();
822    const oldElmtId: number = node.id__();
823    this.recycleManager_.updateNodeId(oldElmtId, newElmtId);
824    node.hasBeenRecycled_ = false;
825    this.rebuildUpdateFunc(oldElmtId, compilerAssignedUpdateFunc);
826    recycleUpdateFunc(oldElmtId, /* is first render */ true, node);
827  }
828
829  // param is used by BuilderNode
830  aboutToReuseInternal(param?: Object) {
831    this.runReuse_ = true;
832    stateMgmtTrace.scopedTrace(() => {
833      if (this.paramsGenerator_ && typeof this.paramsGenerator_ === 'function') {
834        const params = param ? param : this.paramsGenerator_();
835        this.updateStateVars(params);
836        this.aboutToReuse(params);
837      }
838    }, 'aboutToReuse', this.constructor.name);
839
840    for (const stateLinkPropVar of this.ownObservedPropertiesStore_) {
841      const changedElmtIds = stateLinkPropVar.moveElmtIdsForDelayedUpdate(true);
842      if (changedElmtIds) {
843        if (changedElmtIds.size && !this.isFirstRender()) {
844          for (const elmtId of changedElmtIds) {
845            this.dirtDescendantElementIds_.add(elmtId);
846          }
847        }
848      }
849    }
850    if (!this.delayRecycleNodeRerender) {
851      this.updateDirtyElements();
852    } else {
853      this.flushDelayCompleteRerender();
854    }
855    this.childrenWeakrefMap_.forEach((weakRefChild) => {
856      const child = weakRefChild.deref();
857      if (child) {
858        if (child instanceof ViewPU) {
859          if (!child.hasBeenRecycled_) {
860            child.aboutToReuseInternal();
861          }
862        } else {
863          // FIXME fix for mixed V2 - V3 Hierarchies
864          throw new Error('aboutToReuseInternal: Recycle not implemented for ViewV2, yet');
865        }
866      } // if child
867    });
868    this.runReuse_ = false;
869  }
870
871  stopRecursiveRecycle() {
872    this.preventRecursiveRecycle_ = true;
873  }
874
875  aboutToRecycleInternal() {
876    this.runReuse_ = true;
877    stateMgmtTrace.scopedTrace(() => {
878      this.aboutToRecycle();
879    }, 'aboutToRecycle', this.constructor.name);
880    if (this.preventRecursiveRecycle_) {
881      this.preventRecursiveRecycle_ = false;
882      return;
883    }
884    this.childrenWeakrefMap_.forEach((weakRefChild) => {
885      const child = weakRefChild.deref();
886      if (child) {
887        if (child instanceof ViewPU) {
888          if (!child.hasBeenRecycled_) {
889            child.aboutToRecycleInternal();
890          }
891        } else {
892          // FIXME fix for mixed V2 - V3 Hierarchies
893          throw new Error('aboutToRecycleInternal: Recycle not yet implemented for ViewV2');
894        }
895      } // if child
896    });
897    this.runReuse_ = false;
898  }
899
900  // add current JS object to it's parent recycle manager
901  public recycleSelf(name: string): void {
902
903    if (this.getParent() && this.getParent() instanceof ViewPU && !(this.getParent() as ViewPU).isDeleting_) {
904      const parentPU : ViewPU = this.getParent() as ViewPU;
905      parentPU.getOrCreateRecycleManager().pushRecycleNode(name, this);
906      this.hasBeenRecycled_ = true;
907    } else {
908      this.resetRecycleCustomNode();
909    }
910  }
911
912  public isRecycled() : boolean {
913    return this.hasBeenRecycled_;
914  }
915
916  public UpdateLazyForEachElements(elmtIds: Array<number>): void {
917    if (!Array.isArray(elmtIds)) {
918      return;
919    }
920    Array.from(elmtIds).sort(ViewPU.compareNumber).forEach((elmtId: number) => {
921      const entry: UpdateFuncRecord | undefined = this.updateFuncByElmtId.get(elmtId);
922      const updateFunc: UpdateFunc = entry ? entry.getUpdateFunc() : undefined;
923      if (typeof updateFunc !== 'function') {
924        stateMgmtConsole.debug(`${this.debugInfo__()}: update function of elmtId ${elmtId} not found, internal error!`);
925      } else {
926        this.isRenderInProgress = true;
927        updateFunc(elmtId, false);
928        this.finishUpdateFunc(elmtId);
929        this.isRenderInProgress = false;
930      }
931    })
932  }
933
934  /**
935     * CreateStorageLink and CreateStorageLinkPU are used by the implementation of @StorageLink and
936     * @LocalStotrageLink in full update and partial update solution respectively.
937     * These are not part of the public AppStorage API , apps should not use.
938     * @param storagePropName - key in LocalStorage
939     * @param defaultValue - value to use when creating a new prop in the LocalStotage
940     * @param owningView - the View/ViewPU owning the @StorageLink/@LocalStorageLink variable
941     * @param viewVariableName -  @StorageLink/@LocalStorageLink variable name
942     * @returns SynchedPropertySimple/ObjectTwoWay/PU
943     */
944  public createStorageLink<T>(storagePropName: string, defaultValue: T, viewVariableName: string): ObservedPropertyAbstractPU<T> {
945    const appStorageLink = AppStorage.__createSync<T>(storagePropName, defaultValue,
946      <T>(source: ObservedPropertyAbstract<T>) => (source === undefined)
947        ? undefined
948        : new SynchedPropertyTwoWayPU<T>(source, this, viewVariableName)
949    ) as ObservedPropertyAbstractPU<T>;
950    appStorageLink?.setDecoratorInfo('@StorageLink');
951    return appStorageLink;
952  }
953
954  public createStorageProp<T>(storagePropName: string, defaultValue: T, viewVariableName: string): ObservedPropertyAbstractPU<T> {
955    const appStorageProp = AppStorage.__createSync<T>(storagePropName, defaultValue,
956      <T>(source: ObservedPropertyAbstract<T>) => (source === undefined)
957        ? undefined
958        : new SynchedPropertyOneWayPU<T>(source, this, viewVariableName)
959    ) as ObservedPropertyAbstractPU<T>;
960    appStorageProp?.setDecoratorInfo('@StorageProp');
961    return appStorageProp;
962  }
963
964  public createLocalStorageLink<T>(storagePropName: string, defaultValue: T,
965    viewVariableName: string): ObservedPropertyAbstractPU<T> {
966    const localStorageLink = this.localStorage_.__createSync<T>(storagePropName, defaultValue,
967      <T>(source: ObservedPropertyAbstract<T>) => (source === undefined)
968        ? undefined
969        : new SynchedPropertyTwoWayPU<T>(source, this, viewVariableName)
970    ) as ObservedPropertyAbstractPU<T>;
971    localStorageLink?.setDecoratorInfo('@LocalStorageLink');
972    return localStorageLink;
973  }
974
975  public createLocalStorageProp<T>(storagePropName: string, defaultValue: T,
976    viewVariableName: string): ObservedPropertyAbstractPU<T> {
977    const localStorageProp = this.localStorage_.__createSync<T>(storagePropName, defaultValue,
978      <T>(source: ObservedPropertyAbstract<T>) => (source === undefined)
979        ? undefined
980        : new SynchedPropertyObjectOneWayPU<T>(source, this, viewVariableName)
981    ) as ObservedPropertyAbstractPU<T>;
982    localStorageProp?.setDecoratorInfo('@LocalStorageProp');
983    return localStorageProp;
984  }
985
986  /**
987   * onDumpInfo is used to process commands delivered by the hidumper process
988   * @param commands -  list of commands provided in the shell
989   * @returns void
990   */
991  protected onDumpInfo(commands: string[]): void {
992
993    let dfxCommands: DFXCommand[] = this.processOnDumpCommands(commands);
994
995    dfxCommands.forEach((command) => {
996      let view: ViewPU = undefined;
997      if (command.viewId) {
998        view = this.findViewPUInHierarchy(command.viewId);
999        if (!view) {
1000          DumpLog.print(0, `\nTarget view: ${command.viewId} not found for command: ${command.what}\n`);
1001          return;
1002        }
1003      } else {
1004        view = this;
1005        command.viewId = view.id__();
1006      }
1007      switch (command.what) {
1008        case '-dumpAll':
1009          view.printDFXHeader('ViewPU Info', command);
1010          DumpLog.print(0, view.debugInfoView(command.isRecursive));
1011          break;
1012        case '-viewHierarchy':
1013          view.printDFXHeader('ViewPU Hierarchy', command);
1014          DumpLog.print(0, view.debugInfoViewHierarchy(command.isRecursive));
1015          break;
1016        case '-stateVariables':
1017          view.printDFXHeader('ViewPU State Variables', command);
1018          DumpLog.print(0, view.debugInfoStateVars());
1019          break;
1020        case '-registeredElementIds':
1021          view.printDFXHeader('ViewPU Registered Element IDs', command);
1022          DumpLog.print(0, view.debugInfoUpdateFuncByElmtId(command.isRecursive));
1023          break;
1024        case '-dirtyElementIds':
1025          view.printDFXHeader('ViewPU Dirty Registered Element IDs', command);
1026          DumpLog.print(0, view.debugInfoDirtDescendantElementIds(command.isRecursive));
1027          break;
1028        case '-inactiveComponents':
1029          view.printDFXHeader('List of Inactive Components', command);
1030          DumpLog.print(0, view.debugInfoInactiveComponents());
1031          break;
1032        case '-profiler':
1033          view.printDFXHeader('Profiler Info', command);
1034          view.dumpReport();
1035          this.sendStateInfo('{}');
1036          break;
1037        default:
1038          DumpLog.print(0, `\nUnsupported JS DFX dump command: [${command.what}, viewId=${command.viewId}, isRecursive=${command.isRecursive}]\n`);
1039      }
1040    })
1041  }
1042
1043  private printDFXHeader(header: string, command: DFXCommand): void {
1044    let length: number = 50;
1045    let remainder: number = length - header.length < 0 ? 0 : length - header.length;
1046    DumpLog.print(0, `\n${'-'.repeat(remainder / 2)}${header}${'-'.repeat(remainder / 2)}`);
1047    DumpLog.print(0, `[${command.what}, viewId=${command.viewId}, isRecursive=${command.isRecursive}]\n`);
1048  }
1049
1050  private processOnDumpCommands(commands: string[]): DFXCommand[] {
1051    let isFlag: Function = (param: string): boolean => {
1052      return '-r'.match(param) != null || param.startsWith('-viewId=');
1053    }
1054
1055    let dfxCommands: DFXCommand[] = [];
1056
1057    for (var i: number = 0; i < commands.length; i++) {
1058      let command = commands[i];
1059      if (isFlag(command)) {
1060        if (command.startsWith('-viewId=')) {
1061          let dfxCommand: DFXCommand = dfxCommands[dfxCommands.length - 1];
1062          if (dfxCommand) {
1063            let input: string[] = command.split('=');
1064            if (input[1]) {
1065              let viewId: number = Number.parseInt(input[1]);
1066              dfxCommand.viewId = Number.isNaN(viewId) ? UINodeRegisterProxy.notRecordingDependencies : viewId;
1067            }
1068          }
1069        } else if (command.match('-r')) {
1070          let dfxCommand: DFXCommand = dfxCommands[dfxCommands.length - 1];
1071          if (dfxCommand) {
1072            dfxCommand.isRecursive = true;
1073          }
1074        }
1075      } else {
1076        dfxCommands.push({
1077          what: command,
1078          viewId: undefined,
1079          isRecursive: false,
1080        });
1081      }
1082    }
1083    return dfxCommands;
1084  }
1085
1086  public findViewPUInHierarchy(id: number): ViewPU {
1087    let weakChild = this.childrenWeakrefMap_.get(id);
1088    if (weakChild) {
1089      const child = weakChild.deref();
1090      // found child with id, is it a ViewPU?
1091      return (child instanceof ViewPU) ? child : undefined;
1092    }
1093
1094    // did not find, continue searching
1095    let retVal: ViewPU = undefined;
1096    for (const [key, value] of this.childrenWeakrefMap_.entries()) {
1097      retVal = value.deref().findViewPUInHierarchy(id);
1098      if (retVal) {
1099        break;
1100      }
1101    }
1102    return retVal;
1103  }
1104
1105  private debugInfoView(recursive: boolean = false): string {
1106    return this.debugInfoViewInternal(recursive);
1107  }
1108
1109  private debugInfoViewInternal(recursive: boolean = false): string {
1110    let retVal: string = `@Component\n${this.constructor.name}[${this.id__()}]`;
1111    retVal += `\n\nView Hierarchy:\n${this.debugInfoViewHierarchy(recursive)}`;
1112    retVal += `\n\nState variables:\n${this.debugInfoStateVars()}`;
1113    retVal += `\n\nRegistered Element IDs:\n${this.debugInfoUpdateFuncByElmtId(recursive)}`;
1114    retVal += `\n\nDirty Registered Element IDs:\n${this.debugInfoDirtDescendantElementIds(recursive)}`;
1115    return retVal;
1116  }
1117
1118  private debugInfoDirtDescendantElementIds(recursive: boolean = false): string {
1119    return this.debugInfoDirtDescendantElementIdsInternal(0, recursive, { total: 0 });
1120  }
1121
1122  public debugInfoDirtDescendantElementIdsInternal(depth: number = 0, recursive: boolean = false, counter: ProfileRecursionCounter): string {
1123    let retVaL: string = `\n${'  '.repeat(depth)}|--${this.constructor.name}[${this.id__()}]: {`;
1124    this.dirtDescendantElementIds_.forEach((value) => {
1125      retVaL += `${value}, `;
1126    });
1127    counter.total += this.dirtDescendantElementIds_.size;
1128    retVaL += `\n${'  '.repeat(depth + 1)}}[${this.dirtDescendantElementIds_.size}]`;
1129    if (recursive) {
1130      this.childrenWeakrefMap_.forEach((value, key, map) => {
1131        retVaL += value.deref()?.debugInfoDirtDescendantElementIdsInternal(depth + 1, recursive, counter);
1132      })
1133    }
1134
1135    if (recursive && depth == 0) {
1136      retVaL += `\nTotal: ${counter.total}`;
1137    }
1138    return retVaL;
1139  }
1140
1141  /**
1142    * onDumpInspector is invoked by native side to create Inspector tree including state variables
1143    * @returns dump info
1144    */
1145  protected onDumpInspector(): string {
1146    let res: DumpInfo = new DumpInfo();
1147    res.viewInfo = { componentName: this.constructor.name, id: this.id__() };
1148    Object.getOwnPropertyNames(this)
1149      .filter((varName: string) => varName.startsWith('__') && !varName.startsWith(ObserveV2.OB_PREFIX))
1150      .forEach((varName) => {
1151        const prop: any = Reflect.get(this, varName);
1152        if (typeof prop === 'object' && 'debugInfoDecorator' in prop) {
1153          const observedProp: ObservedPropertyAbstractPU<any> = prop as ObservedPropertyAbstractPU<any>;
1154          res.observedPropertiesInfo.push(stateMgmtDFX.getObservedPropertyInfo(observedProp, false));
1155        }
1156      });
1157    let resInfo: string = '';
1158    try {
1159      resInfo = JSON.stringify(res);
1160    } catch (error) {
1161      stateMgmtConsole.applicationError(`${this.debugInfo__()} has error in getInspector: ${(error as Error).message}`);
1162    }
1163    return resInfo;
1164  }
1165
1166
1167
1168  /**
1169   * on first render create a new Instance of Repeat
1170   * on re-render connect to existing instance
1171   * @param arr
1172   * @returns
1173   */
1174  public __mkRepeatAPI: <I>(arr: Array<I>) => RepeatAPI<I> = <I>(arr: Array<I>): RepeatAPI<I> => {
1175    // factory is for future extensions, currently always return the same
1176    const elmtId = this.getCurrentlyRenderedElmtId();
1177    let repeat = this.elmtId2Repeat_.get(elmtId) as __Repeat<I>;
1178    if (!repeat) {
1179        repeat = new __Repeat<I>(this, arr);
1180        this.elmtId2Repeat_.set(elmtId, repeat);
1181    } else {
1182        repeat.updateArr(arr);
1183    }
1184
1185    return repeat;
1186  }
1187} // class ViewPU
1188
1189