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