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 196 public setDeleting(): void { 197 stateMgmtConsole.debug(`${this.debugInfo__()}: set as deleting (self)`); 198 this.isDeleting_ = true; 199 } 200 201 public setDeleteStatusRecursively(): void { 202 if (!this.childrenWeakrefMap_.size) { 203 return; 204 } 205 stateMgmtConsole.debug(`${this.debugInfo__()}: set as deleting (${this.childrenWeakrefMap_.size} children)`); 206 this.childrenWeakrefMap_.forEach((value: WeakRef<IView>) => { 207 let child: IView = value.deref(); 208 if (child) { 209 child.setDeleting(); 210 child.setDeleteStatusRecursively(); 211 } 212 }); 213 } 214 215 public isCompFreezeAllowed(): boolean { 216 return this.isCompFreezeAllowed_; 217 } 218 219 public getChildViewV2ForElmtId(elmtId: number): ViewV2 | undefined { 220 const optComp = this.childrenWeakrefMap_.get(elmtId); 221 return optComp?.deref() && (optComp.deref() instanceof ViewV2) ? 222 optComp?.deref() as ViewV2 : undefined; 223 } 224 225 protected purgeVariableDependenciesOnElmtIdOwnFunc(elmtId: number): void { 226 // ViewPU overrides to unregister ViewPU from variables, 227 // not in use in ViewV2 228 } 229 230 // overwritten by sub classes 231 public debugInfo__(): string { 232 return `@Component '${this.constructor.name}'[${this.id__()}]`; 233 } 234 235 public debugInfoRegisteredElmtIds(): string { 236 return this.updateFuncByElmtId.debugInfoRegisteredElmtIds(); 237 } 238 239 // for given elmtIds look up their component name/type and format a string out of this info 240 // use function only for debug output and DFX. 241 public debugInfoElmtIds(elmtIds: Array<number>): string { 242 let result: string = ''; 243 let sepa: string = ''; 244 elmtIds.forEach((elmtId: number) => { 245 result += `${sepa}${this.debugInfoElmtId(elmtId)}`; 246 sepa = ', '; 247 }); 248 return result; 249 } 250 251 public debugInfoElmtId(elmtId: number, isProfiler: boolean = false): string | ElementType { 252 253 return isProfiler ? { 254 elementId: elmtId, 255 elementTag: this.updateFuncByElmtId.get(elmtId).getComponentName(), 256 isCustomNode: this.childrenWeakrefMap_.has(elmtId) 257 } : this.updateFuncByElmtId.debugInfoElmtId(elmtId); 258 } 259 260 public dumpStateVars(): void { 261 stateMgmtConsole.debug(`${this.debugInfo__()}: State variables:\n ${this.debugInfoStateVars()}`); 262 } 263 264 protected abstract debugInfoStateVars(): string; 265 266 public isViewActive(): boolean { 267 return this.isActive_; 268 } 269 270 // abstract functions to be implemented by application defined class / transpiled code 271 protected abstract purgeVariableDependenciesOnElmtId(removedElmtId: number); 272 protected abstract initialRender(): void; 273 protected abstract rerender(): void; 274 275 public abstract updateRecycleElmtId(oldElmtId: number, newElmtId: number): void; 276 public abstract updateStateVars(params: Object); 277 public abstract UpdateElement(elmtId: number): void; 278 279 public dumpReport(): void { 280 stateMgmtConsole.warn(`Printing profiler information`); 281 stateMgmtProfiler.report(); 282 } 283 284 public updateStateVarsOfChildByElmtId(elmtId, params: Object): void { 285 stateMgmtProfiler.begin('ViewPU/V2.updateStateVarsOfChildByElmtId'); 286 stateMgmtConsole.debug(`${this.debugInfo__()}: updateChildViewById(${elmtId}) - start`); 287 288 if (elmtId < 0) { 289 stateMgmtConsole.warn(`${this.debugInfo__()}: updateChildViewById(${elmtId}) - invalid elmtId - internal error!`); 290 stateMgmtProfiler.end(); 291 return; 292 } 293 let iChild: IView = this.getChildById(elmtId); 294 if (!iChild || !((iChild instanceof ViewPU) || (iChild instanceof ViewV2))) { 295 stateMgmtConsole.warn(`${this.debugInfo__()}: updateChildViewById(${elmtId}) - no child with this elmtId - internal error!`); 296 stateMgmtProfiler.end(); 297 return; 298 } 299 const child = iChild as ViewPU | ViewV2; 300 if ('updateStateVars' in child) { 301 child.updateStateVars(params); 302 } 303 stateMgmtConsole.debug(`${this.debugInfo__()}: updateChildViewById(${elmtId}) - end`); 304 stateMgmtProfiler.end(); 305 } 306 307 // request list of all (global) elmtIds of deleted UINodes and unregister from the all ViewPUs/ViewV2 308 // this function equals purgeDeletedElmtIdsRecursively because it does un-registration for all ViewPU/V2's 309 protected purgeDeletedElmtIds(): void { 310 stateMgmtConsole.debug(`purgeDeletedElmtIds @Component '${this.constructor.name}' (id: ${this.id__()}) start ...`); 311 // request list of all (global) elmtIds of deleted UINodes that need to be unregistered 312 UINodeRegisterProxy.obtainDeletedElmtIds(); 313 // unregister the removed elmtIds requested from the cpp side for all ViewPUs/ViewV2, it will make the first ViewPUs/ViewV2 slower 314 // than before, but the rest ViewPUs/ViewV2 will be faster 315 UINodeRegisterProxy.unregisterElmtIdsFromIViews(); 316 stateMgmtConsole.debug(`purgeDeletedElmtIds @Component '${this.constructor.name}' (id: ${this.id__()}) end... `); 317 } 318 319 /** 320 * force a complete rerender / update by executing all update functions 321 * exec a regular rerender first 322 * 323 * @param deep recurse all children as well 324 * 325 * framework internal functions, apps must not call 326 */ 327 public forceCompleteRerender(deep: boolean = false): void { 328 stateMgmtProfiler.begin('ViewPU/V2.forceCompleteRerender'); 329 stateMgmtConsole.debug(`${this.debugInfo__()}: forceCompleteRerender - start.`); 330 331 // see which elmtIds are managed by this View 332 // and clean up all book keeping for them 333 this.purgeDeletedElmtIds(); 334 335 Array.from(this.updateFuncByElmtId.keys()).sort(ViewPU.compareNumber).forEach(elmtId => this.UpdateElement(elmtId)); 336 337 if (!deep) { 338 stateMgmtConsole.debug(`${this.debugInfo__()}: forceCompleteRerender - end`); 339 stateMgmtProfiler.end(); 340 return; 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: ForEach id generated for ${indx}${indx < 4 ? indx === 2 ? 'nd' : 'rd' : 'th'} array item is duplicated.`); 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 stateMgmtConsole.warn(`${this.debugInfo__()} fail to create node, elmtId is illegal`); 551 return builder(); 552 } 553 let nodeInfo = entry.getNode(); 554 if (nodeInfo === undefined) { 555 nodeInfo = builder(); 556 entry.setNode(nodeInfo); 557 } 558 return nodeInfo; 559 } 560 561 /** 562 * getNodeById is used to get ArkComponent stored updateFuncByElmtId 563 * @param elmtId - the id of the component 564 * @returns ArkComponent | undefined 565 */ 566 public getNodeById(elmtId: number): ArkComponent | undefined { 567 const entry = this.updateFuncByElmtId.get(elmtId); 568 return entry ? entry.getNode() : undefined; 569 } 570 571 /** 572 * return its elmtId if currently rendering or re-rendering an UINode 573 * otherwise return UINodeRegisterProxy.notRecordingDependencies 574 * set in observeComponentCreation(2) 575 */ 576 public getCurrentlyRenderedElmtId() { 577 return PUV2ViewBase.renderingPaused || this.currentlyRenderedElmtIdStack_.length === 0 578 ? UINodeRegisterProxy.notRecordingDependencies 579 : this.currentlyRenderedElmtIdStack_[this.currentlyRenderedElmtIdStack_.length - 1]; 580 } 581 582 protected debugInfoViewHierarchy(recursive: boolean = false): string { 583 return this.debugInfoViewHierarchyInternal(0, recursive); 584 } 585 586 public debugInfoViewHierarchyInternal(depth: number = 0, recursive: boolean = false): string { 587 let retVaL: string = `\n${' '.repeat(depth)}|--${this.constructor.name}[${this.id__()}]`; 588 if (this.isCompFreezeAllowed()) { 589 retVaL += ` {freezeWhenInactive : ${this.isCompFreezeAllowed()}}`; 590 } 591 592 if (depth < 1 || recursive) { 593 this.childrenWeakrefMap_.forEach((weakChild: WeakRef<IView>) => { 594 retVaL += weakChild.deref()?.debugInfoViewHierarchyInternal(depth + 1, recursive); 595 }); 596 } 597 return retVaL; 598 } 599 600 protected debugInfoUpdateFuncByElmtId(recursive: boolean = false): string { 601 return this.debugInfoUpdateFuncByElmtIdInternal({ total: 0 }, 0, recursive); 602 } 603 604 public debugInfoUpdateFuncByElmtIdInternal(counter: ProfileRecursionCounter, depth: number = 0, recursive: boolean = false): string { 605 let retVaL: string = `\n${' '.repeat(depth)}|--${this.constructor.name}[${this.id__()}]: {`; 606 this.updateFuncByElmtId.forEach((value, key, map) => { 607 retVaL += `\n${' '.repeat(depth + 2)}${value.getComponentName()}[${key}]`; 608 }); 609 counter.total += this.updateFuncByElmtId.size; 610 retVaL += `\n${' '.repeat(depth + 1)}}[${this.updateFuncByElmtId.size}]`; 611 if (recursive) { 612 this.childrenWeakrefMap_.forEach((value, key, map) => { 613 retVaL += value.deref()?.debugInfoUpdateFuncByElmtIdInternal(counter, depth + 1, recursive); 614 }); 615 } 616 if (recursive && depth === 0) { 617 retVaL += `\nTotal: ${counter.total}`; 618 } 619 return retVaL; 620 } 621 622 protected debugInfoInactiveComponents(): string { 623 return Array.from(PUV2ViewBase.inactiveComponents_) 624 .map((component) => `- ${component}`).join('\n'); 625 } 626 627 /** 628 * on first render create a new Instance of Repeat 629 * on re-render connect to existing instance 630 * @param arr 631 * @returns 632 */ 633 abstract __mkRepeatAPI<I>(arr: Array<I>): RepeatAPI<I>; 634 635 onGlobalThemeChanged(): void { 636 } 637 638 public static setArkThemeScopeManager(mgr: ArkThemeScopeManager): void { 639 PUV2ViewBase.arkThemeScopeManager = mgr 640 } 641} // class PUV2ViewBase 642