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