• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 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
16
17/**
18 * A decorator function that sets the static `isReusable_` property to `true`
19 * on the provided class. This decorator is automatically invoked when the generated component
20 * class in the transpiler has the `@ReusableV2` decorator prefix, as below:
21 *
22 * @ReusableV2
23 * class MyComponent { }
24 */
25function ReusableV2<T extends Constructor>(BaseClass: T): T {
26    stateMgmtConsole.debug(`@ReusableV2 ${BaseClass.name}: Redefining isReusable_ as true.`);
27    Reflect.defineProperty(BaseClass.prototype, 'isReusable_', {
28        get: () => {
29          return true;
30        }
31      });
32    return BaseClass;
33}
34
35/**
36 *
37 * This file includes only framework internal classes and functions
38 * non are part of SDK. Do not access from app.
39 *
40 * Implementation of @ComponentV2 is ViewV2
41 * When transpiling @ComponentV2, the transpiler generates a class that extends from ViewV2.
42 *
43 */
44
45abstract class ViewV2 extends PUV2ViewBase implements IView {
46
47    // Set of elmtIds that need re-render
48    protected dirtDescendantElementIds_: Set<number> = new Set<number>();
49
50    private monitorIdsDelayedUpdate: Set<number> = new Set();
51    private monitorIdsDelayedUpdateForAddMonitor_: Set<number> = new Set();
52    private computedIdsDelayedUpdate: Set<number> = new Set();
53
54    private recyclePoolV2_: RecyclePoolV2 | undefined = undefined;
55
56    public hasBeenRecycled_: boolean = false;
57
58    public paramsGenerator_?: () => Object;
59
60    constructor(parent: IView, elmtId: number = UINodeRegisterProxy.notRecordingDependencies, extraInfo: ExtraInfo = undefined) {
61        super(parent, elmtId, extraInfo);
62        this.setIsV2(true);
63        ViewBuildNodeBase.arkThemeScopeManager?.onViewPUCreate(this);
64        if (parent instanceof ViewPU) {
65            stateMgmtConsole.debug(`Both V1 and V2 components are involved. Disabling Parent-Child optimization`)
66            ObserveV2.getObserve().isParentChildOptimizable_ = false;
67        }
68        stateMgmtConsole.debug(`ViewV2 constructor: Creating @Component '${this.constructor.name}' from parent '${parent?.constructor.name}'`);
69    }
70
71    /**
72     * The `freezeState` parameter determines whether this @ComponentV2 is allowed to freeze, when inactive
73     * Its called with value of the `freezeWhenInactive` parameter from the @ComponentV2 decorator,
74     * or it may be called with `undefined` depending on how the UI compiler works.
75     *
76     * @param freezeState Only the value `true` will be used to set the freeze state,
77     * otherwise it inherits from its parent instance if its freezeState is true
78     */
79    protected finalizeConstruction(freezeState?: boolean | undefined): void {
80
81        ObserveV2.getObserve().constructComputed(this, this.constructor.name);
82        ObserveV2.getObserve().constructMonitor(this, this.constructor.name);
83
84        // Always use ID_REFS in ViewV2
85        this[ObserveV2.ID_REFS] = {};
86
87        // set to true if freeze parameter set for this @ComponentV2 to true
88        // otherwise inherit from its parentComponent (if it exists).
89        this.isCompFreezeAllowed_ = freezeState || this.isCompFreezeAllowed_;
90        stateMgmtConsole.debug(`${this.debugInfo__()}: @ComponentV2 freezeWhenInactive state is set to ${this.isCompFreezeAllowed()}`);
91
92    }
93
94    public debugInfo__(): string {
95        return `@ComponentV2 '${this.constructor.name}'[${this.id__()}]`;
96    }
97
98    /**
99     * @function recycleSelf
100     * @description
101     * This callback function is triggered from the native side when the native-side recycle dummy UI node,
102     * acting as the parent for the recycled component, is deleted in the ~RecycleDummyNode() destructor.
103     * It attempts to add the current JS object to the RecyclePool to ensure proper recycling.
104     *
105     * If the parent is invalid or being deleted, the component is reset by invoking the native
106     * `resetRecycleCustomNode` function, which restores the custom node associated with the JSView object:
107     * - If the JSView object has been garbage collected by the engine, the CustomNode is deleted.
108     * - If the JSView object is managed by the RecycleManager, the CustomNode is reused.
109     *
110     * @param {string} reuseId - The ID used for recycling the component.
111     */
112    public recycleSelf(reuseId: string): void {
113        stateMgmtConsole.debug(`${this.debugInfo__()}:  reuseId: ${reuseId}`);
114
115        if (this.getParent() && this.getParent() instanceof ViewV2 && !(this.getParent() as ViewV2).isDeleting_) {
116            const parentV2: ViewV2 = this.getParent() as ViewV2;
117            parentV2.getOrCreateRecyclePool().pushRecycleV2Component(reuseId, this);
118            this.hasBeenRecycled_ = true;
119        } else {
120            // Native function call to restore the custom node for the JSView object
121            // Deletes or reuses the custom node based on GC or RecycleManager
122            this.resetRecycleCustomNode();
123        }
124    }
125
126    // The resetStateVarsOnReuse function defined in the transpiler will be called.
127    // If it's not defined, it indicates that an older version of the toolchain is being used,
128    // and an error is thrown to notify about the outdated toolchain.
129    public resetStateVarsOnReuse(params: Object): void {
130        throw new Error('Old toolchain detected. Please upgrade to the latest.');
131    }
132
133    // The aboutToReuse function defined in the application will be called if it exists.
134    // If not, this empty function will be called, which does nothing.
135    aboutToReuse(): void {
136        // Empty function
137    }
138
139    /**
140     * @function aboutToReuseInternal
141     * @description This function is triggered from the function reuseOrCreateNewComponent when the component is
142     * about to be reused from the recycle Pool.
143     * It invokes the `resetStateVarsOnReuse` method (defined in the transpiler) to reinitialize the component's
144     * decorated variables either from its parent or local initialization
145     * It also invokes the `aboutToReuse` function if defined in the application.
146     * Additionally, it recursively traverses  all its subcomponents, calling `resetStateVarsOnReuse`
147     * and `aboutToReuse` on each subcomponent to prepare them for reuse.
148     * @param {?Object} initialParams - optional, the first reused component use this params to reset value, or it will not record
149     * dependency of params.
150     */
151    aboutToReuseInternal(initialParams?: Object): void {
152        stateMgmtConsole.debug(`${this.debugInfo__()}: aboutToReuseInternal`);
153        stateMgmtTrace.scopedTrace(() => {
154            if (this.paramsGenerator_ && typeof this.paramsGenerator_ === 'function') {
155                const params = initialParams ? initialParams : this.paramsGenerator_();
156                stateMgmtConsole.debug(`${this.debugInfo__()}: resetStateVarsOnReuse params: ${JSON.stringify(params)}`);
157                ObserveV2.getObserve().setCurrentReuseId(this.id__());
158                // resets the variables to its initial state
159                this.resetStateVarsOnReuse(params);
160                // unfreeze the component on reuse
161                this.unfreezeReusedComponent();
162                this.aboutToReuse();
163            }
164        }, 'aboutToReuseInternal', this.constructor.name);
165        ObserveV2.getObserve().updateDirty2(true, true);
166        ObserveV2.getObserve().setCurrentReuseId(ObserveV2.NO_REUSE);
167        this.traverseChildDoRecycleOrReuse(PUV2ViewBase.doReuse);
168    }
169
170    /**
171     * @function aboutToRecycleInternal
172     * @description Callback function invoked from the native side function 'CustomNodeBase::SetRecycleFunction'
173     * when the component is about to be recycled.
174     * It first calls the `aboutToRecycle` function in the application, and performs the necessary actions
175     * defined in the application before recycling.
176     * Then, it freezes the component to avoid performing UI updates when its in recycle pool
177     * Finally recursively traverses all subcomponents, calling `aboutToRecycleInternal` on each subcomponent
178     * that is about to be recycled, preparing them for recycling as well.
179     */
180    aboutToRecycleInternal(): void {
181
182        stateMgmtConsole.debug(`ViewV2 ${this.debugInfo__()} aboutToRecycleInternal`);
183
184        // Calls the application's aboutToRecycle() method if defined
185        this.aboutToRecycle();
186
187        // Freeze the component when its in recycle pool
188        this.freezeRecycledComponent();
189
190        this.traverseChildDoRecycleOrReuse(PUV2ViewBase.doRecycle);
191    }
192
193    // Freezes the component when it is moved to the recycle pool to prevent elementId updates
194    private freezeRecycledComponent(): void {
195        this.activeCount_--;
196        ViewV2.inactiveComponents_.add(`${this.constructor.name}[${this.id__()}]`);
197    }
198
199    /**
200     * @function unfreezeReusedComponent
201     * @description Unfreezes the component when it is removed from the recycle pool for active rendering
202     * Only delayed element update is performed here, as monitors and computed
203     * are reset by resetStateVarsOnReuse() prior to calling this function
204     *
205     * @returns void
206     */
207    private unfreezeReusedComponent(): void {
208        this.activeCount_++;
209        if (this.elmtIdsDelayedUpdate.size) {
210            this.elmtIdsDelayedUpdate.forEach((element) => {
211                ObserveV2.getObserve().elmtIdsChanged_.add(element);
212            });
213        }
214        this.elmtIdsDelayedUpdate.clear();
215        ViewV2.inactiveComponents_.delete(`${this.constructor.name}[${this.id__()}]`);
216    }
217
218    /**
219     * @function getOrCreateRecyclePool
220     * @description Retrieves the existing `RecyclePoolV2` instance or creates a new one
221     * if it does not exist.
222     *
223     * @returns {RecyclePoolV2} - The `RecyclePoolV2` instance for managing recycling.
224     */
225    getOrCreateRecyclePool(): RecyclePoolV2 {
226        if (!this.recyclePoolV2_) {
227          this.recyclePoolV2_ = new RecyclePoolV2();
228        }
229        return this.recyclePoolV2_;
230      }
231
232    /**
233     * @function getRecyclePool
234     * @description Retrieves the `RecyclePoolV2` instance if it exists.
235     * @returns {RecyclePoolV2} - The existing `RecyclePoolV2` instance for managing recycling.
236     */
237    getRecyclePool(): RecyclePoolV2 {
238        return this.recyclePoolV2_;
239    }
240
241    /**
242     * @function hasRecyclePool
243     * @description Checks if a `RecyclePoolV2` instance exists.
244     * The RecyclePoolV2 instance is created when the native side triggers the recycleSelf callback
245     * during the recycling of a component.
246     * @returns {boolean} - `true` if the `RecyclePoolV2` exists, otherwise `false`.
247     */
248    hasRecyclePool(): boolean {
249        return !(this.recyclePoolV2_ === undefined);
250    }
251
252    /**
253     * @function cleanupRecycledElmtId
254     * @description purges the recycled Element ID in ViewV2
255     *
256     * @returns void
257     */
258    private cleanupRecycledElmtId(elmtId: number): void {
259        this.updateFuncByElmtId.delete(elmtId);
260        UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(elmtId);
261        ObserveV2.getObserve().clearBinding(elmtId);
262    }
263
264    protected get isViewV2(): boolean {
265        return true;
266    }
267
268    /**
269     * Virtual function implemented in ViewPU and ViewV2
270     * Unregisters and purges all child elements associated with the specified Element ID in ViewV2.
271     *
272     * @param rmElmtId - The Element ID to be purged and deleted
273     * @returns {boolean} - Returns `true` if the Element ID was successfully deleted, `false` otherwise.
274     */
275    public purgeDeleteElmtId(rmElmtId: number): boolean {
276        stateMgmtConsole.debug(`${this.debugInfo__()} purgeDeleteElmtId (V2) is purging the rmElmtId:${rmElmtId}`);
277        const result = this.updateFuncByElmtId.delete(rmElmtId);
278        if (result) {
279            const childOpt = this.getChildViewV2ForElmtId(rmElmtId);
280            if (childOpt) {
281                childOpt.setDeleting();
282                childOpt.setDeleteStatusRecursively();
283            }
284
285            // it means rmElmtId has finished all the unregistration from the js side, ElementIdToOwningViewPU_  does not need to keep it
286            UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(rmElmtId);
287        }
288
289        // Needed only for V2
290        ObserveV2.getObserve().clearBinding(rmElmtId);
291        return result;
292    }
293
294
295    // super class will call this function from
296    // its aboutToBeDeleted implementation
297    protected aboutToBeDeletedInternal(): void {
298        stateMgmtConsole.debug(`${this.debugInfo__()}: aboutToBeDeletedInternal`);
299        // if this isDeleting_ is true already, it may be set delete status recursively by its parent, so it is not necessary
300        // to set and resursively set its children any more
301        if (!this.isDeleting_) {
302            this.isDeleting_ = true;
303            this.setDeleteStatusRecursively();
304        }
305        // tell UINodeRegisterProxy that all elmtIds under
306        // this ViewV2 should be treated as already unregistered
307
308        stateMgmtConsole.debug(`${this.constructor.name}: aboutToBeDeletedInternal `);
309
310        // purge the elmtIds owned by this ViewV2 from the updateFuncByElmtId and also the state variable dependent elmtIds
311        this.updateFuncByElmtId.forEach((_updateFun: UpdateFuncRecord, elmtId: number) => {
312            UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(elmtId);
313            ObserveV2.getObserve().clearBinding(elmtId);
314            delete ObserveV2.getObserve().id2cmp_[elmtId];
315        });
316
317        delete ObserveV2.getObserve().id2cmp_[this.id_];
318
319        // unregistration of ElementIDs
320        stateMgmtConsole.debug(`${this.debugInfo__()}: onUnRegElementID`);
321
322        // Clears all cached components from the Recycle pool and resets the customNode on the native side
323        if (this.hasRecyclePool()) {
324            this.getRecyclePool().purgeAllCachedRecycleElmtIds();
325        }
326
327        // it will unregister removed elementids from all the ViewV2, equals purgeDeletedElmtIdsRecursively
328        this.purgeDeletedElmtIds();
329
330        // unregisters its own id once its children are unregistered above
331        UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs([this.id__()]);
332
333        stateMgmtConsole.debug(`${this.debugInfo__()}: onUnRegElementID  - DONE`);
334
335        PUV2ViewBase.inactiveComponents_.delete(`${this.constructor.name}[${this.id__()}]`);
336
337        MonitorV2.clearWatchesFromTarget(this);
338        ComputedV2.clearComputedFromTarget(this);
339
340        this.updateFuncByElmtId.clear();
341        if (this.parent_) {
342            this.parent_.removeChild(this);
343        }
344        ViewBuildNodeBase.arkThemeScopeManager?.onViewPUDelete(this);
345        // if memory watch register the callback func, then report such information to memory watch
346        // when custom node destroyed
347        if (ArkUIObjectFinalizationRegisterProxy.callbackFunc_) {
348            ArkUIObjectFinalizationRegisterProxy.call(new WeakRef(this),
349                `${this.debugInfo__()} is in the process of destruction`);
350        }
351    }
352
353    public initialRenderView(): void {
354        stateMgmtProfiler.begin(`ViewV2: initialRenderView`);
355        if (this.isReusable_ === true) {
356            const isReusableAllowed = this.allowReusableV2Descendant();
357            if (!isReusableAllowed) {
358                const error = `Using @ReusableV2 component inside Repeat.template or other invalid parent component is not allowed!`;
359                stateMgmtConsole.applicationError(error);
360                throw new Error(error);
361            }
362        }
363        this.onWillApplyThemeInternally();
364        this.initialRender();
365        stateMgmtProfiler.end();
366    }
367
368    /**
369     * @function resetMonitorsOnReuse
370     * @description
371     * Called from the transpiler's `resetStateVarsOnReuse` method when the component is about to be reused.
372     * Ensures that @Monitor functions are reset and reinitialized during the reuse cycle
373     */
374    public resetMonitorsOnReuse(): void {
375        // Clear the monitorIds set for delayed updates, if any
376        this.monitorIdsDelayedUpdate.clear();
377        this.monitorIdsDelayedUpdateForAddMonitor_.clear()
378        ObserveV2.getObserve().resetMonitorValues();
379    }
380
381    // Resets the computed value when the reused component variables are reinitialized
382    // through the resetStateVarsOnReuse process
383    public resetComputed(name: string): void {
384        // Clear the computedIds set for delayed updates, if any
385        this.computedIdsDelayedUpdate.clear();
386
387        const refs = this[ObserveV2.COMPUTED_REFS];
388        refs[name].resetComputed(name);
389     }
390
391    // Resets the consumer value when the component is reinitialized on reuse
392     public resetConsumer<T>(varName: string, consumerVal: T): void {
393        let providerInfo = ProviderConsumerUtilV2.findProvider(this, varName);
394        if (!providerInfo) {
395          ProviderConsumerUtilV2.defineConsumerWithoutProvider(this, varName, consumerVal);
396          ObserveV2.getObserve().fireChange(this, varName);
397        }
398        stateMgmtConsole.debug(`resetConsumer value: ${consumerVal} for ${varName}`);
399     }
400
401    public observeComponentCreation2(compilerAssignedUpdateFunc: UpdateFunc, classObject: { prototype: Object, pop?: () => void }): void {
402        if (PUV2ViewBase.isNeedBuildPrebuildCmd() && PUV2ViewBase.prebuildFuncQueues.has(PUV2ViewBase.prebuildingElmtId_)) {
403            const prebuildFunc: PrebuildFunc = () => {
404              this.observeComponentCreation2(compilerAssignedUpdateFunc, classObject);
405            };
406            PUV2ViewBase.prebuildFuncQueues.get(PUV2ViewBase.prebuildingElmtId_)?.push(prebuildFunc);
407            ViewStackProcessor.PushPrebuildCompCmd();
408            return;
409        }
410        if (this.isDeleting_) {
411            stateMgmtConsole.error(`@ComponentV2 ${this.constructor.name} elmtId ${this.id__()} is already in process of destruction, will not execute observeComponentCreation2 `);
412            return;
413        }
414        const _componentName: string = (classObject && ('name' in classObject)) ? Reflect.get(classObject, 'name') as string : 'unspecified UINode';
415        const _popFunc: () => void = (classObject && 'pop' in classObject) ? classObject.pop! : (): void => { };
416        const updateFunc = (elmtId: number, isFirstRender: boolean): void => {
417            this.syncInstanceId();
418            stateMgmtConsole.debug(`@ComponentV2 ${this.debugInfo__()}: ${isFirstRender ? `First render` : `Re-render/update`} ${_componentName}[${elmtId}] - start ....`);
419            ViewBuildNodeBase.arkThemeScopeManager?.onComponentCreateEnter(_componentName, elmtId, isFirstRender, this);
420            ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
421            ObserveV2.getObserve().startRecordDependencies(this, elmtId);
422
423            compilerAssignedUpdateFunc(elmtId, isFirstRender);
424
425            // After first render, new bindings (pending) need to be recorded
426            // immediately, as they may fire changes before the next idle time,
427            // e.g. in the onAreaChange handler
428            if (isFirstRender) {
429                ObserveV2.getObserve().runIdleTasks();
430            }
431
432            if (!isFirstRender) {
433                _popFunc();
434            }
435
436            let node = this.getNodeById(elmtId);
437            if (node !== undefined) {
438                (node as ArkComponent).cleanStageValue();
439            }
440
441            ObserveV2.getObserve().stopRecordDependencies();
442            ViewStackProcessor.StopGetAccessRecording();
443            ViewBuildNodeBase.arkThemeScopeManager?.onComponentCreateExit(elmtId);
444            stateMgmtConsole.debug(`${this.debugInfo__()}: ${isFirstRender ? `First render` : `Re-render/update`}  ${_componentName}[${elmtId}] - DONE ....`);
445            this.restoreInstanceId();
446        };
447
448        const elmtId = ViewStackProcessor.AllocateNewElmetIdForNextComponent();
449        // needs to move set before updateFunc.
450        // make sure the key and object value exist since it will add node in attributeModifier during updateFunc.
451        this.updateFuncByElmtId.set(elmtId, { updateFunc: updateFunc, classObject: classObject });
452        // add element id -> owning ViewV2
453        UINodeRegisterProxy.ElementIdToOwningViewPU_.set(elmtId, new WeakRef(this));
454        try {
455            updateFunc(elmtId, /* is first render */ true);
456        } catch (error) {
457            // avoid the incompatible change that move set function before updateFunc.
458            this.updateFuncByElmtId.delete(elmtId);
459            UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(elmtId);
460            stateMgmtConsole.applicationError(`${this.debugInfo__()} has error in update func: ${(error as Error).message}`);
461            throw error;
462        }
463        stateMgmtConsole.debug(`${this.debugInfo__()} is initial rendering elmtId ${elmtId}, tag: ${_componentName}, and updateFuncByElmtId size :${this.updateFuncByElmtId.size}`);
464    }
465
466    /**
467     *
468     * @param paramVariableName
469     * @Param @Once paramVariableName
470     * @Param is read only, therefore, init from parent needs to be done without
471     *        causing property setter() to be called
472     * @param newValue
473     */
474    protected initParam<Z>(paramVariableName: string, newValue: Z): void {
475        VariableUtilV2.initParam<Z>(this, paramVariableName, newValue);
476    }
477    /**
478     *
479     * @param paramVariableName
480     * @Param @Once paramVariableName
481     * @Param is read only, therefore, update from parent needs to be done without
482     *        causing property setter() to be called
483     * @Param @Once reject any update
484     * @param newValue
485     */
486    protected updateParam<Z>(paramVariableName: string, newValue: Z): void {
487        VariableUtilV2.updateParam<Z>(this, paramVariableName, newValue);
488    }
489
490    protected resetParam<Z>(paramVariableName: string, newValue: Z): void {
491        VariableUtilV2.resetParam<Z>(this, paramVariableName, newValue);
492    }
493
494    /**
495     *  inform that UINode with given elmtId needs rerender
496     *  does NOT exec @Watch function.
497     *  only used on V2 code path from ObserveV2.fireChange.
498     *
499     * FIXME will still use in the future?
500     */
501    public uiNodeNeedUpdateV2(elmtId: number): void {
502        if (this.isPrebuilding_) {
503            const propertyChangedFunc: PrebuildFunc = () => {
504                this.uiNodeNeedUpdateV2(elmtId);
505            };
506            if (!PUV2ViewBase.propertyChangedFuncQueues.has(this.id__())) {
507                PUV2ViewBase.propertyChangedFuncQueues.set(this.id__(), new Array<PrebuildFunc>());
508            }
509            PUV2ViewBase.propertyChangedFuncQueues.get(this.id__())?.push(propertyChangedFunc);
510            return;
511        }
512        if (this.isFirstRender()) {
513            return;
514        }
515
516        stateMgmtProfiler.begin(`ViewV2.uiNodeNeedUpdate ${this.debugInfoElmtId(elmtId)}`);
517
518        if (!this.isViewActive()) {
519            this.scheduleDelayedUpdate(elmtId);
520            return;
521        }
522
523        if (!this.dirtDescendantElementIds_.size) { //  && !this runReuse_) {
524            // mark ComposedElement dirty when first elmtIds are added
525            // do not need to do this every time
526            this.syncInstanceId();
527            this.markNeedUpdate();
528            this.restoreInstanceId();
529        }
530        this.dirtDescendantElementIds_.add(elmtId);
531        stateMgmtConsole.debug(`${this.debugInfo__()}: uiNodeNeedUpdate: updated full list of elmtIds that need re-render [${this.debugInfoElmtIds(Array.from(this.dirtDescendantElementIds_))}].`);
532        stateMgmtProfiler.end();
533    }
534
535
536    /**
537     * For each recorded dirty Element in this custom component
538     * run its update function
539     *
540     */
541    public updateDirtyElements(): void {
542        stateMgmtProfiler.begin('ViewV2.updateDirtyElements');
543        do {
544            stateMgmtConsole.debug(`${this.debugInfo__()}: updateDirtyElements (re-render): sorted dirty elmtIds: ${Array.from(this.dirtDescendantElementIds_).sort(ViewV2.compareNumber)}, starting ....`);
545
546            // see which elmtIds are managed by this View
547            // and clean up all book keeping for them
548            this.purgeDeletedElmtIds();
549
550            // process all elmtIds marked as needing update in ascending order.
551            // ascending order ensures parent nodes will be updated before their children
552            // prior cleanup ensure no already deleted Elements have their update func executed
553            const dirtElmtIdsFromRootNode = Array.from(this.dirtDescendantElementIds_).sort(ViewV2.compareNumber);
554            // if state changed during exec update lambda inside UpdateElement, then the dirty elmtIds will be added
555            // to newly created this.dirtDescendantElementIds_ Set
556            dirtElmtIdsFromRootNode.forEach(elmtId => {
557                this.UpdateElement(elmtId);
558                this.dirtDescendantElementIds_.delete(elmtId);
559            });
560
561            if (this.dirtDescendantElementIds_.size) {
562                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!`);
563            }
564
565            for (const dirtRetakenElementId of this.dirtRetakenElementIds_) {
566                this.dirtDescendantElementIds_.add(dirtRetakenElementId);
567            }
568            this.dirtRetakenElementIds_.clear();
569        } while (this.dirtDescendantElementIds_.size);
570        stateMgmtConsole.debug(`${this.debugInfo__()}: updateDirtyElements (re-render) - DONE`);
571        stateMgmtProfiler.end();
572    }
573
574
575    public UpdateElement(elmtId: number): void {
576
577        if(this.isDeleting_) {
578            stateMgmtConsole.debug(`${this.debugInfo__()}: UpdateElement(${elmtId}) (V2) returns with NO UPDATE, this @ComponentV2 is under deletion!`);
579            return;
580        }
581
582        stateMgmtProfiler.begin('ViewV2.UpdateElement');
583        if (elmtId === this.id__()) {
584            // do not attempt to update itself
585            stateMgmtProfiler.end();
586            return;
587        }
588        // do not process an Element that has been marked to be deleted
589        const entry: UpdateFuncRecord | undefined = this.updateFuncByElmtId.get(elmtId);
590        if (!entry) {
591            stateMgmtProfiler.end();
592            return;
593        }
594        let updateFunc: UpdateFunc;
595        // if the element is pending, its updateFunc will not be executed during this function call, instead mark its UpdateFuncRecord as changed
596        // when the pending element is retaken and its UpdateFuncRecord is marked changed, then it will be inserted into dirtRetakenElementIds_
597        if (entry.isPending()) {
598            entry.setIsChanged(true);
599        } else {
600            updateFunc = entry.getUpdateFunc();
601        }
602
603        if (typeof updateFunc !== 'function') {
604            stateMgmtConsole.debug(`${this.debugInfo__()}: UpdateElement: update function of elmtId ${elmtId} not found, internal error!`);
605        } else {
606            const componentName = entry.getComponentName();
607            stateMgmtConsole.debug(`${this.debugInfo__()}: UpdateElement: re-render of ${componentName} elmtId ${elmtId} start ...`);
608            stateMgmtProfiler.begin('ViewV2.updateFunc');
609            try {
610                updateFunc(elmtId, /* isFirstRender */ false);
611            } catch (e) {
612                stateMgmtConsole.applicationError(`Exception caught in update function of ${componentName} for elmtId ${elmtId}`, e.toString());
613                throw e;
614            } finally {
615                stateMgmtProfiler.end();
616            }
617            stateMgmtProfiler.begin('ViewV2.finishUpdateFunc (native)');
618            this.finishUpdateFunc(elmtId);
619            stateMgmtProfiler.end();
620            stateMgmtConsole.debug(`${this.debugInfo__()}: UpdateElement: re-render of ${componentName} elmtId ${elmtId} - DONE`);
621        }
622        stateMgmtProfiler.end();
623    }
624
625    /**
626     * Retrieve child by given id
627     * @param id
628     * @returns child if child with this id exists and it is instance of ViewV2
629     */
630    public getViewV2ChildById(id: number): ViewV2 | undefined {
631        const childWeakRef = this.childrenWeakrefMap_.get(id);
632        const child = childWeakRef ? childWeakRef.deref() : undefined;
633        return (child && child instanceof ViewV2) ? child : undefined;
634    }
635
636    // WatchIds that needs to be fired later gets added to monitorIdsDelayedUpdate
637    // monitor fireChange will be triggered for all these watchIds once this view gets active
638    public addDelayedMonitorIds(watchId: number): void  {
639        stateMgmtConsole.debug(`${this.debugInfo__()} addDelayedMonitorIds called for watchId: ${watchId}`);
640        this.monitorIdsDelayedUpdate.add(watchId);
641    }
642
643    public addDelayedMonitorIdsForAddMonitor(watchId: number): void  {
644        stateMgmtConsole.debug(`${this.debugInfo__()} addDelayedMonitorIdsForAddMonitor called for watchId: ${watchId}`);
645        this.monitorIdsDelayedUpdateForAddMonitor_.add(watchId);
646    }
647
648    public addDelayedComputedIds(watchId: number): void {
649        stateMgmtConsole.debug(`${this.debugInfo__()} addDelayedComputedIds called for watchId: ${watchId}`);
650        this.computedIdsDelayedUpdate.add(watchId);
651    }
652    // If the component has `hasComponentFreezeEnabled` set to true and is marked as @ReusableV2,
653    // skip the delayed update, as freeze and delayed updates are handled in `aboutToRecycleInternal`
654    // and `aboutToReuseInternal` for @ReusableV2 components.
655    public setActiveInternal(active: boolean, isReuse: boolean = false): void {
656        stateMgmtProfiler.begin('ViewV2.setActive');
657        stateMgmtConsole.debug(`${this.debugInfo__()}: isCompFreezeAllowed : ${this.isCompFreezeAllowed()}`);
658        if (this.isCompFreezeAllowed() && !isReuse) {
659            stateMgmtConsole.debug(`${this.debugInfo__()}: ViewV2.setActive ${active ? ' inActive -> active' : 'active -> inActive'}`);
660            this.setActiveCount(active);
661            if (this.isViewActive()) {
662                this.performDelayedUpdate();
663                ViewV2.inactiveComponents_.delete(`${this.constructor.name}[${this.id__()}]`);
664            } else {
665                ViewV2.inactiveComponents_.add(`${this.constructor.name}[${this.id__()}]`);
666            }
667        }
668        // Propagate state to all child View
669        this.propagateToChildren(this.childrenWeakrefMap_, active, isReuse);
670        // Propagate state to all child BuilderNode
671        this.propagateToChildren(this.builderNodeWeakrefMap_, active, isReuse);
672        stateMgmtProfiler.end();
673    }
674
675    private performDelayedUpdate(): void {
676        stateMgmtProfiler.begin('ViewV2: performDelayedUpdate');
677        if(this.computedIdsDelayedUpdate.size) {
678            // exec computed functions
679            ObserveV2.getObserve().updateDirtyComputedProps([...this.computedIdsDelayedUpdate]);
680        }
681        if(this.monitorIdsDelayedUpdate.size) {
682          // exec monitor functions
683          ObserveV2.getObserve().updateDirtyMonitors(this.monitorIdsDelayedUpdate);
684        }
685        if (this.monitorIdsDelayedUpdateForAddMonitor_.size) {
686            ObserveV2.getObserve().updateDirtyMonitorPath(this.monitorIdsDelayedUpdateForAddMonitor_);
687        }
688        if (ObserveV2.getObserve().monitorFuncsToRun_.size) {
689            const monitorFuncs = ObserveV2.getObserve().monitorFuncsToRun_;
690            ObserveV2.getObserve().monitorFuncsToRun_ = new Set<number>();
691            ObserveV2.getObserve().runMonitorFunctionsForAddMonitor(monitorFuncs)
692        }
693        if(this.elmtIdsDelayedUpdate.size) {
694          // update re-render of updated element ids once the view gets active
695          if(this.dirtDescendantElementIds_.size === 0) {
696            this.dirtDescendantElementIds_ = new Set(this.elmtIdsDelayedUpdate);
697          }
698          else {
699            this.elmtIdsDelayedUpdate.forEach((element) => {
700              this.dirtDescendantElementIds_.add(element);
701            });
702          }
703        }
704        this.markNeedUpdate();
705        this.elmtIdsDelayedUpdate.clear();
706        this.monitorIdsDelayedUpdate.clear();
707        this.monitorIdsDelayedUpdateForAddMonitor_.clear();
708        this.computedIdsDelayedUpdate.clear();
709        stateMgmtProfiler.end();
710    }
711
712    /*
713      findProvidePU__ finds @Provided property recursively by traversing ViewPU's towards that of the UI tree root @Component:
714      if 'this' ViewPU has a @Provide('providedPropName') return it, otherwise ask from its parent ViewPU.
715      function needed for mixed @Component and @ComponentV2 parent child hierarchies.
716    */
717    public findProvidePU__(providedPropName: string): ObservedPropertyAbstractPU<any> | undefined {
718        return this.getParent()?.findProvidePU__(providedPropName) ||
719          (this.__parentViewBuildNode__ && this.__parentViewBuildNode__.findProvidePU__(providedPropName));
720    }
721
722    get localStorage_(): LocalStorage {
723        // FIXME check this also works for root @ComponentV2
724        return (this.getParent()) ? this.getParent().localStorage_ : new LocalStorage({ /* empty */ });
725    }
726
727    /**
728     * Handles the creation or reuse of a ReusableV2 component
729     *
730     * This function is invoked from the transpiler for components declared as ReusableV2.
731     * It manages the lifecycle of components by either creating a new component or reusing
732     * an existing recycle node.
733     *
734     * During the initial render:
735     * - If a recycle node is available, it is reused; otherwise, a new component is created.
736     * - A `ViewV2.createRecycle` call is made to the native side to manage recycling.
737     * - The callback `aboutToReuseInternal` is triggered when a recycled node is used, indicating
738     *   the node was fetched and reused instead of being newly created.
739     *
740     * On subsequent renders, state variables are updated for the reused component.
741     *
742     * @param componentClass - The class of the component to be created or reused.
743     * @param getParams - A function returning the parameters for the component.
744     * @param getReuseId - A function providing a unique reuse ID (default: component class name).
745     * @param extraInfo - Additional information required for component creation.
746     */
747    public reuseOrCreateNewComponent(params: {
748        componentClass: any, getParams: () => Object,
749        getReuseId?: () => string, extraInfo?: ExtraInfo
750    }): void {
751        const { componentClass, getParams, getReuseId = (): string => '', extraInfo } = params;
752        let reuseId = getReuseId();
753        // If reuseId is null or empty (not set by the application), default to the component's name
754        if (!reuseId) {
755            reuseId = componentClass.name;
756        }
757        this.observeComponentCreation2((elmtId, isInitialRender) => {
758            if (isInitialRender) {
759                const params = getParams(); // should call here to record dependency
760                const recycledNode = this.hasRecyclePool() ? this.getRecyclePool().popRecycleV2Component(reuseId) : null;
761                const componentRef = recycledNode ? recycledNode :
762                    new componentClass(/* Parent */this, params, /*localStorage */undefined, elmtId, /*paramsLambda */() => { }, extraInfo);
763                if (recycledNode) {
764                    // If a recycled node is found, update the recycled element ID mapping in the recycle pool
765                    const lastId = this.recyclePoolV2_.getRecycleIdMapping(recycledNode.id__());
766                    this.recyclePoolV2_.updateRecycleIdMapping(recycledNode.id__(), elmtId);
767                    recycledNode.hasBeenRecycled_ = false;
768
769                    // Removes the recycled elementId after the recycleId mapping is updated.
770                    this.cleanupRecycledElmtId(lastId); // clean useless dependency
771                }
772
773                // Native call to fetch the cached recycle node or create a new one if it doesn't exist
774                ViewV2.createRecycle(componentRef, recycledNode != null, reuseId, () => {
775                        // Callback from the native side when the component is reused.
776                        recycledNode?.aboutToReuseInternal(params);
777                    });
778
779                // Set the component's parameters generator function for later retrieval during reuse
780                componentRef.paramsGenerator_ = getParams;
781                stateMgmtConsole.debug(`${this.debugInfo__()}: paramsGenerator_:${JSON.stringify(componentRef.paramsGenerator_())}`);
782             }
783            else {
784                // Retrieve the mapped recycled element ID to update the state variables
785                const recycledElmtId = this.getOrCreateRecyclePool().getRecycleIdMapping(elmtId);
786                this.updateStateVarsOfChildByElmtId(recycledElmtId, getParams());
787            }
788        }, componentClass);
789    }
790
791    public debugInfoDirtDescendantElementIdsInternal(depth: number = 0, recursive: boolean = false, counter: ProfileRecursionCounter): string {
792        let retVaL: string = `\n${'  '.repeat(depth)}|--${this.constructor.name}[${this.id__()}]: {`;
793        retVaL += `ViewV2 keeps no info about dirty elmtIds}`;
794        if (recursive) {
795            this.childrenWeakrefMap_.forEach((value, key, map) => {
796                retVaL += value.deref()?.debugInfoDirtDescendantElementIdsInternal(depth + 1, recursive, counter);
797            });
798        }
799
800        if (recursive && depth === 0) {
801            retVaL += `\nTotal: ${counter.total}`;
802        }
803        return retVaL;
804    }
805
806    public __getDecoratorPropertyName__V2View__Internal(): [string, any][] {
807        const meta = this[ObserveV2.V2_DECO_META];
808        const metaMethod = this[ObserveV2.V2_DECO_METHOD_META];
809        let propertyVariableNames: [string, any][] = [];
810        if (!meta && !metaMethod) {
811            return propertyVariableNames;
812        }
813        if (meta) {
814            propertyVariableNames = Object.entries(meta);
815        }
816        if (metaMethod) {
817            propertyVariableNames = [...propertyVariableNames, ...Object.entries(metaMethod)]
818        }
819        return propertyVariableNames;
820    }
821
822    public debugInfoStateVars(): string {
823        let retVal: string = `|--${this.constructor.name}[${this.id__()}]\n`;
824        const propertyVariableNames: [string, any][] = this.__getDecoratorPropertyName__V2View__Internal();
825
826        if (propertyVariableNames.length === 0) {
827            retVal += ' No State Variables';
828            return retVal;
829        }
830
831        propertyVariableNames
832            .filter((entry) => !entry[0].startsWith(ProviderConsumerUtilV2.ALIAS_PREFIX))
833            .forEach((entry) => {
834                const prop: any = entry[1];
835                const varName: string = entry[0];
836                retVal += ObserveV2.getObserve().parseDecorator(prop);
837                retVal += ` varName: ${varName}`;
838
839                let dependentElmtIds = this[ObserveV2.SYMBOL_REFS]?.[varName];
840                if (dependentElmtIds) {
841                    retVal += `\n  |--DependentElements:`;
842                    dependentElmtIds.forEach((elmtId) => {
843                        if (elmtId < ComputedV2.MIN_COMPUTED_ID) {
844                            retVal += ` ` + ObserveV2.getObserve().getElementInfoById(elmtId);
845                        } else if (elmtId < MonitorV2.MIN_WATCH_ID) {
846                            retVal += ` @Computed[${elmtId}]`;
847                        } else if (elmtId < PersistenceV2Impl.MIN_PERSISTENCE_ID) {
848                            retVal += ` @Monitor[${elmtId}]`;
849                        } else {
850                            retVal += ` PersistenceV2[${elmtId}]`;
851                        }
852                    });
853                }
854                retVal += '\n';
855
856            });
857        return retVal;
858    }
859
860    /**
861   * on first render create a new Instance of Repeat
862   * on re-render connect to existing instance
863   * @param arr
864   * @returns
865   */
866    public __mkRepeatAPI: <I>(arr: Array<I>) => RepeatAPI<I> = <I>(arr: Array<I>): RepeatAPI<I> => {
867        // factory is for future extensions, currently always return the same
868        const elmtId = ObserveV2.getCurrentRecordedId();
869        let repeat = this.elmtId2Repeat_.get(elmtId) as __Repeat<I>;
870        if (!repeat) {
871            repeat = new __Repeat<I>(this, arr);
872            this.elmtId2Repeat_.set(elmtId, repeat);
873        } else {
874            repeat.updateArr(arr);
875        }
876        return repeat;
877    };
878
879    public debugInfoView(recursive: boolean = false): string {
880        return this.debugInfoViewInternal(recursive);
881    }
882
883    private debugInfoViewInternal(recursive: boolean = false): string {
884        let retVal: string = `@ComponentV2\n${this.constructor.name}[${this.id__()}]`;
885        retVal += `\n\nView Hierarchy:\n${this.debugInfoViewHierarchy(recursive)}`;
886        retVal += `\n\nState variables:\n${this.debugInfoStateVars()}`;
887        retVal += `\n\nRegistered Element IDs:\n${this.debugInfoUpdateFuncByElmtId(recursive)}`;
888        retVal += `\n\nDirty Registered Element IDs:\n${this.debugInfoDirtDescendantElementIds(recursive)}`;
889        return retVal;
890    }
891
892    public debugInfoDirtDescendantElementIds(recursive: boolean = false): string {
893        return this.debugInfoDirtDescendantElementIdsInternal(0, recursive, { total: 0 });
894    }
895
896    public observeRecycleComponentCreation(name: string, recycleUpdateFunc: RecycleUpdateFunc): void {
897        // cannot use ReusableV1 in V2, but for compatibility, do not throw error..
898        // transpiler will try to give a warning to hint that it will downgrade to normal V1
899        stateMgmtConsole.error(`${this.debugInfo__()}: Recycle not supported for ComponentV2 instance`);
900    }
901}
902