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