• 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*/
16
17/**
18 *
19 * This file includes only framework internal classes and functions
20 * non are part of SDK. Do not access from app.
21 *
22 * PUV2ViewBase is the common base class of ViewPU and ViewV2
23 *
24 */
25
26/// <reference path="../../../../ark_theme/export/ark_theme_scope_manager.d.ts" />
27
28type ExtraInfo = { page: string, line: number, col: number };
29type ProfileRecursionCounter = { total: number };
30
31// NativeView
32// implemented in C++  for release
33abstract class PUV2ViewBase extends NativeViewPartialUpdate {
34
35  // List of inactive components used for Dfx
36  protected static readonly inactiveComponents_: Set<string> = new Set<string>();
37
38  // Array.sort() converts array items to string to compare them!
39  static readonly compareNumber = (a: number, b: number): number => {
40    return (a < b) ? -1 : (a > b) ? 1 : 0;
41  };
42
43  // indicates the currently rendered or rendered UINode's elmtIds
44  // or UINodeRegisterProxy.notRecordingDependencies if none is currently rendering
45  // isRenderInProgress == true always when currentlyRenderedElmtIdStack_ length >= 0
46  protected currentlyRenderedElmtIdStack_: Array<number> = new Array<number>();
47
48  // Set of elmtIds that need re-render
49  protected dirtDescendantElementIds_: Set<number> = new Set<number>();
50
51  // Map elmtId -> Repeat instance in this ViewPU
52  protected elmtId2Repeat_: Map<number, RepeatAPI<any>> = new Map<number, RepeatAPI<any>>();
53
54  private id_: number;
55
56  protected parent_: IView | undefined = undefined;
57  protected childrenWeakrefMap_ = new Map<number, WeakRef<IView>>();
58
59  // static flag for paused rendering
60  // when paused, getCurrentlyRenderedElmtId() will return UINodeRegisterProxy.notRecordingDependencies
61  public static renderingPaused: boolean = false;
62
63  // flag if active of inActive
64  // inActive means updates are delayed
65  protected isActive_: boolean = true;
66
67  // flag if {aboutToBeDeletedInternal} is called and the instance of ViewPU/V2 has not been GC.
68  protected isDeleting_: boolean = false;
69
70  protected isCompFreezeAllowed_: boolean = false;
71
72  // registry of update functions
73  // the key is the elementId of the Component/Element that's the result of this function
74  protected updateFuncByElmtId = new UpdateFuncsByElmtId();
75
76  protected extraInfo_: ExtraInfo = undefined;
77
78  // Set of elements for delayed update
79  private elmtIdsDelayedUpdate_: Set<number> = new Set();
80
81  protected static arkThemeScopeManager: ArkThemeScopeManager | undefined = undefined
82
83  constructor(parent: IView, elmtId: number = UINodeRegisterProxy.notRecordingDependencies, extraInfo: ExtraInfo = undefined) {
84    super();
85    // if set use the elmtId also as the ViewPU/V2 object's subscribable id.
86    // these matching is requirement for updateChildViewById(elmtId) being able to
87    // find the child ViewPU/V2 object by given elmtId
88    this.id_ = elmtId === UINodeRegisterProxy.notRecordingDependencies ? SubscriberManager.MakeId() : elmtId;
89
90    stateMgmtConsole.debug(`PUV2ViewBase constructor: Creating @Component '${this.constructor.name}' from parent '${parent?.constructor.name}'`);
91
92    if (extraInfo) {
93      this.extraInfo_ = extraInfo;
94    }
95
96    if (parent) {
97      // this View is not a top-level View
98      this.setCardId(parent.getCardId());
99      // Call below will set this parent_ to parent as well
100      parent.addChild(this as unknown as IView); // FIXME
101    }
102
103    this.isCompFreezeAllowed_ = this.isCompFreezeAllowed_ || (this.parent_ && this.parent_.isCompFreezeAllowed());
104
105    stateMgmtConsole.debug(`${this.debugInfo__()}: constructor: done`);
106  }
107
108
109  // globally unique id, this is different from compilerAssignedUniqueChildId!
110  id__(): number {
111    return this.id_;
112  }
113
114  updateId(elmtId: number): void {
115    this.id_ = elmtId;
116  }
117
118  /* Adds the elmtId to elmtIdsDelayedUpdate for delayed update
119      once the view gets active
120  */
121  public scheduleDelayedUpdate(elmtId: number) : void {
122    this.elmtIdsDelayedUpdate.add(elmtId);
123  }
124
125  public get elmtIdsDelayedUpdate(): Set<number> {
126    return this.elmtIdsDelayedUpdate_;
127  }
128
129  public setParent(parent: IView): void {
130    if (this.parent_ && parent) {
131      stateMgmtConsole.warn(`${this.debugInfo__()}: setChild: changing parent to '${parent?.debugInfo__()} (unsafe operation)`);
132    }
133    this.parent_ = parent;
134  }
135
136  public getParent(): IView | undefined {
137    return this.parent_;
138  }
139
140  /**
141  * add given child and set 'this' as its parent
142  * @param child child to add
143  * @returns returns false if child with given child's id already exists
144  *
145  * framework internal function
146  * Note: Use of WeakRef ensures child and parent do not generate a cycle dependency.
147  * The add. Set<ids> is required to reliably tell what children still exist.
148  */
149  public addChild(child: IView): boolean {
150    if (this.childrenWeakrefMap_.has(child.id__())) {
151      stateMgmtConsole.warn(`${this.debugInfo__()}: addChild '${child?.debugInfo__()}' elmtId already exists ${child.id__()}. Internal error!`);
152      return false;
153    }
154    this.childrenWeakrefMap_.set(child.id__(), new WeakRef(child));
155    child.setParent(this as unknown as IView); // FIXME
156    return true;
157  }
158
159  /**
160   * remove given child and remove 'this' as its parent
161   * @param child child to add
162   * @returns returns false if child with given child's id does not exist
163   */
164  public removeChild(child: IView): boolean {
165    const hasBeenDeleted = this.childrenWeakrefMap_.delete(child.id__());
166    if (!hasBeenDeleted) {
167      stateMgmtConsole.warn(`${this.debugInfo__()}: removeChild '${child?.debugInfo__()}', child id ${child.id__()} not known. Internal error!`);
168    } else {
169      child.setParent(undefined);
170    }
171    return hasBeenDeleted;
172  }
173
174  /**
175   * Retrieve child by given id
176   * @param id
177   * @returns child if in map and weak ref resolves to IView object
178   */
179  public getChildById(id: number): IView | undefined {
180    const childWeakRef = this.childrenWeakrefMap_.get(id);
181    return childWeakRef ? childWeakRef.deref() : undefined;
182  }
183
184  // inform the subscribed property
185  // that the View and thereby all properties
186  // are about to be deleted
187  abstract aboutToBeDeleted(): void;
188
189  aboutToReuse(_: Object): void { }
190  aboutToRecycle(): void { }
191
192  public isDeleting(): boolean {
193    return this.isDeleting_;
194  }
195  public setDeleting(): void {
196    stateMgmtConsole.debug(`${this.debugInfo__()}: set as deleting (self)`);
197    this.isDeleting_ = true;
198  }
199
200  public setDeleteStatusRecursively(): void {
201    if (!this.childrenWeakrefMap_.size) {
202      return;
203    }
204    stateMgmtConsole.debug(`${this.debugInfo__()}: set as deleting (${this.childrenWeakrefMap_.size} children)`);
205    this.childrenWeakrefMap_.forEach((value: WeakRef<IView>) => {
206      let child: IView = value.deref();
207      if (child) {
208        child.setDeleting();
209        child.setDeleteStatusRecursively();
210      }
211    });
212  }
213
214  public isCompFreezeAllowed(): boolean {
215    return this.isCompFreezeAllowed_;
216  }
217
218  public getChildViewV2ForElmtId(elmtId: number): ViewV2 | undefined {
219    const optComp = this.childrenWeakrefMap_.get(elmtId);
220    return optComp?.deref() && (optComp.deref() instanceof ViewV2) ? optComp?.deref() as ViewV2 : undefined;
221  }
222
223  protected purgeVariableDependenciesOnElmtIdOwnFunc(elmtId: number): void {
224    // ViewPU overrides to unregister ViewPU from variables,
225    // not in use in ViewV2
226  }
227
228  // overwritten by sub classes
229  public debugInfo__(): string {
230    return `@Component '${this.constructor.name}'[${this.id__()}]`;
231  }
232
233  public debugInfoRegisteredElmtIds(): string {
234    return this.updateFuncByElmtId.debugInfoRegisteredElmtIds();
235  }
236
237  // for given elmtIds look up their component name/type and format a string out of this info
238  // use function only for debug output and DFX.
239  public debugInfoElmtIds(elmtIds: Array<number>): string {
240    let result: string = '';
241    let sepa: string = '';
242    elmtIds.forEach((elmtId: number) => {
243      result += `${sepa}${this.debugInfoElmtId(elmtId)}`;
244      sepa = ', ';
245    });
246    return result;
247  }
248
249  public debugInfoElmtId(elmtId: number, isProfiler: boolean = false): string | ElementType {
250
251    return isProfiler ? {
252      elementId: elmtId,
253      elementTag: this.updateFuncByElmtId.get(elmtId).getComponentName(),
254      isCustomNode: this.childrenWeakrefMap_.has(elmtId)
255    } : this.updateFuncByElmtId.debugInfoElmtId(elmtId);
256  }
257
258  public dumpStateVars(): void {
259    stateMgmtConsole.debug(`${this.debugInfo__()}:  State variables:\n ${this.debugInfoStateVars()}`);
260  }
261
262  protected abstract debugInfoStateVars(): string;
263
264  public isViewActive(): boolean {
265    return this.isActive_;
266  }
267
268  // abstract functions to be implemented by application defined class / transpiled code
269  protected abstract purgeVariableDependenciesOnElmtId(removedElmtId: number);
270  protected abstract initialRender(): void;
271  protected abstract rerender(): void;
272
273  public abstract updateRecycleElmtId(oldElmtId: number, newElmtId: number): void;
274  public abstract updateStateVars(params: Object);
275  public abstract UpdateElement(elmtId: number): void;
276
277  public dumpReport(): void {
278    stateMgmtConsole.warn(`Printing profiler information`);
279    stateMgmtProfiler.report();
280  }
281
282
283  public updateStateVarsOfChildByElmtId(elmtId, params: Object): void {
284    stateMgmtProfiler.begin('ViewPU/V2.updateStateVarsOfChildByElmtId');
285    stateMgmtConsole.debug(`${this.debugInfo__()}: updateChildViewById(${elmtId}) - start`);
286
287    if (elmtId < 0) {
288      stateMgmtConsole.warn(`${this.debugInfo__()}: updateChildViewById(${elmtId}) - invalid elmtId - internal error!`);
289      stateMgmtProfiler.end();
290      return;
291    }
292    let iChild: IView = this.getChildById(elmtId);
293    if (!iChild || !((iChild instanceof ViewPU) || (iChild instanceof ViewV2))) {
294      stateMgmtConsole.warn(`${this.debugInfo__()}: updateChildViewById(${elmtId}) - no child with this elmtId - internal error!`);
295      stateMgmtProfiler.end();
296      return;
297    }
298    const child = iChild as ViewPU | ViewV2;
299    if ('updateStateVars' in child) {
300      child.updateStateVars(params);
301    }
302    stateMgmtConsole.debug(`${this.debugInfo__()}: updateChildViewById(${elmtId}) - end`);
303    stateMgmtProfiler.end();
304  }
305
306  // request list of all (global) elmtIds of deleted UINodes and unregister from the all ViewPUs/ViewV2
307  // this function equals purgeDeletedElmtIdsRecursively because it does un-registration for all ViewPU/V2's
308  protected purgeDeletedElmtIds(): void {
309    stateMgmtConsole.debug(`purgeDeletedElmtIds @Component '${this.constructor.name}' (id: ${this.id__()}) start ...`);
310    // request list of all (global) elmtIds of deleted UINodes that need to be unregistered
311    UINodeRegisterProxy.obtainDeletedElmtIds();
312    // unregister the removed elmtIds requested from the cpp side for all ViewPUs/ViewV2, it will make the first ViewPUs/ViewV2 slower
313    // than before, but the rest ViewPUs/ViewV2 will be faster
314    UINodeRegisterProxy.unregisterElmtIdsFromIViews();
315    stateMgmtConsole.debug(`purgeDeletedElmtIds @Component '${this.constructor.name}' (id: ${this.id__()}) end... `);
316  }
317
318  /**
319  * force a complete rerender / update by executing all update functions
320  * exec a regular rerender first
321  *
322  * @param deep recurse all children as well
323  *
324  * framework internal functions, apps must not call
325  */
326  public forceCompleteRerender(deep: boolean = false): void {
327    stateMgmtProfiler.begin('ViewPU/V2.forceCompleteRerender');
328    stateMgmtConsole.debug(`${this.debugInfo__()}: forceCompleteRerender - start.`);
329
330    // see which elmtIds are managed by this View
331    // and clean up all book keeping for them
332    this.purgeDeletedElmtIds();
333
334    Array.from(this.updateFuncByElmtId.keys()).sort(ViewPU.compareNumber).forEach(elmtId => this.UpdateElement(elmtId));
335
336    if (!deep) {
337      stateMgmtConsole.debug(`${this.debugInfo__()}: forceCompleteRerender - end`);
338      stateMgmtProfiler.end();
339      return;
340    }
341
342    for (const child of this.childrenWeakrefMap_.values()) {
343      const childView: IView | undefined = child.deref();
344
345      if (!childView) {
346        continue;
347      }
348
349      if (child instanceof ViewPU) {
350        if (!child.isRecycled()) {
351          child.forceCompleteRerender(true);
352        } else {
353          child.delayCompleteRerender(deep);
354        }
355      } else {
356        childView.forceCompleteRerender(true);
357      }
358    }
359    stateMgmtConsole.debug(`${this.debugInfo__()}: forceCompleteRerender - end`);
360    stateMgmtProfiler.end();
361  }
362
363  /**
364  * force a complete rerender / update on specific node by executing update function.
365  *
366  * @param elmtId which node needs to update.
367  *
368  * framework internal functions, apps must not call
369  */
370  public forceRerenderNode(elmtId: number): void {
371    stateMgmtProfiler.begin('ViewPU/V2.forceRerenderNode');
372    // see which elmtIds are managed by this View
373    // and clean up all book keeping for them
374    this.purgeDeletedElmtIds();
375    this.UpdateElement(elmtId);
376
377    // remove elemtId from dirtDescendantElementIds.
378    this.dirtDescendantElementIds_.delete(elmtId);
379    stateMgmtProfiler.end();
380  }
381
382  /**
383   * for C++ to judge whether a CustomNode has updateFunc with specified nodeId.
384   * use same judgement with UpdateElement, to make sure it can rerender if return true.
385   *
386   * @param elmtId query ID
387   *
388   * framework internal function
389   */
390  public hasNodeUpdateFunc(elmtId: number): boolean {
391    const entry: UpdateFuncRecord | undefined = this.updateFuncByElmtId.get(elmtId);
392    const updateFunc = entry ? entry.getUpdateFunc() : undefined;
393    // if this component does not have updateFunc for elmtId, return false.
394    return typeof updateFunc === 'function';
395  }
396
397  public static pauseRendering(): void {
398    PUV2ViewBase.renderingPaused = true;
399  }
400
401  public static restoreRendering(): void {
402    PUV2ViewBase.renderingPaused = false;
403  }
404
405  // performs the update on a branch within if() { branch } else if (..) { branch } else { branch }
406  public ifElseBranchUpdateFunction(branchId: number, branchfunc: () => void): void {
407    const oldBranchid: number = If.getBranchId();
408
409    if (branchId === oldBranchid) {
410      stateMgmtConsole.debug(`${this.debugInfo__()}: ifElseBranchUpdateFunction: IfElse branch unchanged, no work to do.`);
411      return;
412    }
413    PUV2ViewBase.arkThemeScopeManager?.onIfElseBranchUpdateEnter()
414    // branchid identifies uniquely the if .. <1> .. else if .<2>. else .<3>.branch
415    // ifElseNode stores the most recent branch, so we can compare
416    // removedChildElmtIds will be filled with the elmtIds of all children and their children will be deleted in response to if .. else change
417    let removedChildElmtIds = new Array<number>();
418    If.branchId(branchId, removedChildElmtIds);
419
420    //un-registers the removed child elementIDs using proxy
421    UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
422
423    // purging these elmtIds from state mgmt will make sure no more update function on any deleted child wi;ll be executed
424    stateMgmtConsole.debug(`${this.debugInfo__()}: ifElseBranchUpdateFunction: elmtIds need unregister after if/else branch switch: ${JSON.stringify(removedChildElmtIds)}`);
425    this.purgeDeletedElmtIds();
426
427    branchfunc();
428    PUV2ViewBase.arkThemeScopeManager?.onIfElseBranchUpdateExit(removedChildElmtIds)
429  }
430
431  /**
432   Partial updates for ForEach.
433   * @param elmtId ID of element.
434   * @param itemArray Array of items for use of itemGenFunc.
435   * @param itemGenFunc Item generation function to generate new elements. If index parameter is
436   *                    given set itemGenFuncUsesIndex to true.
437   * @param idGenFunc   ID generation function to generate unique ID for each element. If index parameter is
438   *                    given set idGenFuncUsesIndex to true.
439   * @param itemGenFuncUsesIndex itemGenFunc optional index parameter is given or not.
440   * @param idGenFuncUsesIndex idGenFunc optional index parameter is given or not.
441   */
442  public forEachUpdateFunction(
443    elmtId: number,
444    itemArray: Array<any>,
445    itemGenFunc: (item: any, index?: number) => void,
446    idGenFunc?: (item: any, index?: number) => string,
447    itemGenFuncUsesIndex: boolean = false,
448    idGenFuncUsesIndex: boolean = false
449  ): void {
450
451    stateMgmtProfiler.begin('ViewPU/V2.forEachUpdateFunction');
452    stateMgmtConsole.debug(`${this.debugInfo__()}: forEachUpdateFunction (ForEach re-render) start ...`);
453
454    if (itemArray === null || itemArray === undefined) {
455      stateMgmtConsole.applicationError(`${this.debugInfo__()}: forEachUpdateFunction (ForEach re-render): input array is null or undefined error. Application error!`);
456      stateMgmtProfiler.end();
457      return;
458    }
459
460    if (typeof itemGenFunc !== 'function') {
461      stateMgmtConsole.applicationError(`${this.debugInfo__()}: forEachUpdateFunction (ForEach re-render): Item generation function missing. Application error!`);
462      stateMgmtProfiler.end();
463      return;
464    }
465
466    if (idGenFunc !== undefined && typeof idGenFunc !== 'function') {
467      stateMgmtConsole.applicationError(`${this.debugInfo__()}: forEachUpdateFunction (ForEach re-render): id generator is not a function. Application error!`);
468      stateMgmtProfiler.end();
469      return;
470    }
471
472    if (idGenFunc === undefined) {
473      stateMgmtConsole.debug(`${this.debugInfo__()}: forEachUpdateFunction: providing default id gen function `);
474      idGenFuncUsesIndex = true;
475      // catch possible error caused by Stringify and re-throw an Error with a meaningful (!) error message
476      idGenFunc = (item: any, index: number): string => {
477        try {
478          return `${index}__${JSON.stringify(item)}`;
479        } catch (e) {
480          throw new Error(`${this.debugInfo__()}: ForEach id ${elmtId}: use of default id generator function not possible on provided data structure. Need to specify id generator function (ForEach 3rd parameter). Application Error!`);
481        }
482      };
483    }
484
485    let diffIndexArray = []; // New indexes compared to old one.
486    let newIdArray = [];
487    let idDuplicates = [];
488    const arr = itemArray; // just to trigger a 'get' onto the array
489
490    // ID gen is with index.
491    if (idGenFuncUsesIndex || idGenFunc.length > 1) {
492      // Create array of new ids.
493      arr.forEach((item, indx) => {
494        newIdArray.push(idGenFunc(item, indx));
495      });
496    }
497    else {
498      // Create array of new ids.
499      arr.forEach((item, index) => {
500        newIdArray.push(`${itemGenFuncUsesIndex ? index + '_' : ''}` + idGenFunc(item));
501      });
502    }
503
504    // removedChildElmtIds will be filled with the elmtIds of all children and their children will be deleted in response to foreach change
505    let removedChildElmtIds = [];
506    // Set new array on C++ side.
507    // C++ returns array of indexes of newly added array items.
508    // these are indexes in new child list.
509    ForEach.setIdArray(elmtId, newIdArray, diffIndexArray, idDuplicates, removedChildElmtIds);
510
511    // Its error if there are duplicate IDs.
512    if (idDuplicates.length > 0) {
513      idDuplicates.forEach((indx) => {
514        stateMgmtConsole.error(`Error: ${newIdArray[indx]} generated for ${indx}${indx < 4 ? indx === 2 ? 'nd' : 'rd' : 'th'} array item ${arr[indx]}.`);
515      });
516      stateMgmtConsole.applicationError(`${this.debugInfo__()}: Ids generated by the ForEach id gen function must be unique. Application error!`);
517    }
518
519    stateMgmtConsole.debug(`${this.debugInfo__()}: forEachUpdateFunction: diff indexes ${JSON.stringify(diffIndexArray)} . `);
520
521    // Item gen is with index.
522    stateMgmtConsole.debug(`   ... item Gen ${itemGenFuncUsesIndex ? 'with' : 'without'} index`);
523    // Create new elements if any.
524    stateMgmtProfiler.begin('ViewPU/V2.forEachUpdateFunction (native)');
525    diffIndexArray.forEach((indx) => {
526      ForEach.createNewChildStart(newIdArray[indx], this);
527      if (itemGenFuncUsesIndex) {
528        itemGenFunc(arr[indx], indx);
529      } else {
530        itemGenFunc(arr[indx]);
531      }
532      ForEach.createNewChildFinish(newIdArray[indx], this);
533    });
534
535    // un-registers the removed child elementIDs using proxy
536    UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
537
538    // purging these elmtIds from state mgmt will make sure no more update function on any deleted child will be executed
539    stateMgmtConsole.debug(`${this.debugInfo__()}: forEachUpdateFunction: elmtIds need unregister after foreach key change: ${JSON.stringify(removedChildElmtIds)}`);
540    this.purgeDeletedElmtIds();
541
542    stateMgmtConsole.debug(`${this.debugInfo__()}: forEachUpdateFunction (ForEach re-render) - DONE.`);
543    stateMgmtProfiler.end();
544    stateMgmtProfiler.end();
545  }
546
547  public createOrGetNode(elmtId: number, builder: () => ArkComponent): object {
548    const entry = this.updateFuncByElmtId.get(elmtId);
549    if (entry === undefined) {
550      throw new Error(`${this.debugInfo__()} fail to create node, elmtId is illegal`);
551    }
552    let nodeInfo = entry.getNode();
553    if (nodeInfo === undefined) {
554      nodeInfo = builder();
555      entry.setNode(nodeInfo);
556    }
557    return nodeInfo;
558  }
559
560  /**
561   * getNodeById is used to get ArkComponent stored updateFuncByElmtId
562   * @param elmtId -  the id of the component
563   * @returns ArkComponent | undefined
564   */
565  public getNodeById(elmtId: number): ArkComponent | undefined {
566    const entry = this.updateFuncByElmtId.get(elmtId);
567    return entry ? entry.getNode() : undefined;
568  }
569
570  /**
571   * return its elmtId if currently rendering or re-rendering an UINode
572   * otherwise return UINodeRegisterProxy.notRecordingDependencies
573   * set in observeComponentCreation(2)
574   */
575  public getCurrentlyRenderedElmtId() {
576    return PUV2ViewBase.renderingPaused || this.currentlyRenderedElmtIdStack_.length === 0
577      ? UINodeRegisterProxy.notRecordingDependencies
578      : this.currentlyRenderedElmtIdStack_[this.currentlyRenderedElmtIdStack_.length - 1];
579  }
580
581  protected debugInfoViewHierarchy(recursive: boolean = false): string {
582    return this.debugInfoViewHierarchyInternal(0, recursive);
583  }
584
585  public debugInfoViewHierarchyInternal(depth: number = 0, recursive: boolean = false): string {
586    let retVaL: string = `\n${'  '.repeat(depth)}|--${this.constructor.name}[${this.id__()}]`;
587    if (this.isCompFreezeAllowed()) {
588      retVaL += ` {freezeWhenInactive : ${this.isCompFreezeAllowed()}}`;
589    }
590
591    if (depth < 1 || recursive) {
592      this.childrenWeakrefMap_.forEach((weakChild: WeakRef<IView>) => {
593        retVaL += weakChild.deref()?.debugInfoViewHierarchyInternal(depth + 1, recursive);
594      });
595    }
596    return retVaL;
597  }
598
599  protected debugInfoUpdateFuncByElmtId(recursive: boolean = false): string {
600    return this.debugInfoUpdateFuncByElmtIdInternal({ total: 0 }, 0, recursive);
601  }
602
603  public debugInfoUpdateFuncByElmtIdInternal(counter: ProfileRecursionCounter, depth: number = 0, recursive: boolean = false): string {
604    let retVaL: string = `\n${'  '.repeat(depth)}|--${this.constructor.name}[${this.id__()}]: {`;
605    this.updateFuncByElmtId.forEach((value, key, map) => {
606      retVaL += `\n${'  '.repeat(depth + 2)}${value.getComponentName()}[${key}]`;
607    });
608    counter.total += this.updateFuncByElmtId.size;
609    retVaL += `\n${'  '.repeat(depth + 1)}}[${this.updateFuncByElmtId.size}]`;
610    if (recursive) {
611      this.childrenWeakrefMap_.forEach((value, key, map) => {
612        retVaL += value.deref()?.debugInfoUpdateFuncByElmtIdInternal(counter, depth + 1, recursive);
613      });
614    }
615    if (recursive && depth === 0) {
616      retVaL += `\nTotal: ${counter.total}`;
617    }
618    return retVaL;
619  }
620
621  protected debugInfoInactiveComponents(): string {
622    return Array.from(PUV2ViewBase.inactiveComponents_)
623      .map((component) => `- ${component}`).join('\n');
624  }
625
626  /**
627   * on first render create a new Instance of Repeat
628   * on re-render connect to existing instance
629   * @param arr
630   * @returns
631   */
632  abstract __mkRepeatAPI<I>(arr: Array<I>): RepeatAPI<I>;
633
634  onGlobalThemeChanged(): void {
635  }
636
637  public static setArkThemeScopeManager(mgr: ArkThemeScopeManager): void {
638    PUV2ViewBase.arkThemeScopeManager = mgr
639  }
640} // class PUV2ViewBase
641