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