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