1/* 2 * Copyright (c) 2023 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/// <reference path="../../state_mgmt/src/lib/common/ifelse_native.d.ts" /> 16/// <reference path="../../state_mgmt/src/lib/partial_update/pu_viewstack_processor.d.ts" /> 17 18class BuilderNode { 19 private _JSBuilderNode: JSBuilderNode; 20 private nodePtr_: number | null; 21 constructor(uiContext: UIContext, options: RenderOptions) { 22 let jsBuilderNode = new JSBuilderNode(uiContext, options); 23 this._JSBuilderNode = jsBuilderNode; 24 let id = Symbol('BuilderNode'); 25 BuilderNodeFinalizationRegisterProxy.ElementIdToOwningBuilderNode_.set(id, jsBuilderNode); 26 BuilderNodeFinalizationRegisterProxy.register(this, { name: 'BuilderNode', idOfNode: id }); 27 } 28 public update(params: Object) { 29 this._JSBuilderNode.update(params); 30 } 31 public build(builder: WrappedBuilder<Object[]>, params: Object) { 32 this._JSBuilderNode.build(builder, params); 33 this.nodePtr_ = this._JSBuilderNode.getNodePtr(); 34 } 35 public getFrameNode(): FrameNode { 36 return this._JSBuilderNode.getFrameNode(); 37 } 38 public postTouchEvent(touchEvent: TouchEvent): boolean { 39 return this._JSBuilderNode.postTouchEvent(touchEvent); 40 } 41 public dispose(): void { 42 this._JSBuilderNode.dispose(); 43 } 44} 45 46class JSBuilderNode extends BaseNode { 47 private updateFuncByElmtId?: Map<number, UpdateFunc | UpdateFuncRecord>; 48 private params_: Object; 49 private uiContext_: UIContext; 50 private frameNode_: FrameNode; 51 private childrenWeakrefMap_ = new Map<number, WeakRef<ViewPU>>(); 52 53 constructor(uiContext: UIContext, options?: RenderOptions) { 54 super(uiContext, options); 55 this.uiContext_ = uiContext; 56 this.updateFuncByElmtId = new Map(); 57 } 58 59 public getCardId(): number { 60 return -1; 61 } 62 63 public addChild(child: ViewPU): boolean { 64 if (this.childrenWeakrefMap_.has(child.id__())) { 65 return false; 66 } 67 this.childrenWeakrefMap_.set(child.id__(), new WeakRef(child)); 68 return true; 69 } 70 public getChildById(id: number) { 71 const childWeakRef = this.childrenWeakrefMap_.get(id); 72 return childWeakRef ? childWeakRef.deref() : undefined; 73 } 74 public updateStateVarsOfChildByElmtId(elmtId, params: Object): void { 75 if (elmtId < 0) { 76 return; 77 } 78 let child: ViewPU = this.getChildById(elmtId); 79 if (!child) { 80 return; 81 } 82 child.updateStateVars(params); 83 } 84 public createOrGetNode(elmtId: number, builder: () => object): object { 85 const entry = this.updateFuncByElmtId.get(elmtId); 86 if (entry === undefined) { 87 throw new Error(`fail to create node, elmtId is illegal`); 88 } 89 let updateFuncRecord : UpdateFuncRecord = (typeof entry === 'object') ? entry : undefined; 90 if(updateFuncRecord === undefined) 91 { 92 throw new Error(`fail to create node, the api level of app does not supported`); 93 } 94 let nodeInfo = updateFuncRecord.node; 95 if (nodeInfo === undefined) { 96 nodeInfo = builder(); 97 updateFuncRecord.node = nodeInfo; 98 } 99 return nodeInfo; 100 } 101 public build(builder: WrappedBuilder<Object[]>, params: Object) { 102 __JSScopeUtil__.syncInstanceId(this.instanceId_); 103 this.params_ = params; 104 this.updateFuncByElmtId.clear(); 105 this.nodePtr_ = super.create(builder.builder, this.params_); 106 if (this.frameNode_ === undefined || this.frameNode_ === null) { 107 this.frameNode_ = new FrameNode(this.uiContext_, 'BuilderNode'); 108 } 109 this.frameNode_.setNodePtr(this.nodePtr_); 110 this.frameNode_.setBaseNode(this); 111 __JSScopeUtil__.restoreInstanceId(); 112 } 113 public update(param: Object) { 114 __JSScopeUtil__.syncInstanceId(this.instanceId_); 115 this.purgeDeletedElmtIds(); 116 this.params_ = param; 117 Array.from(this.updateFuncByElmtId.keys()).sort((a: number, b: number): number => { 118 return (a < b) ? -1 : (a > b) ? 1 : 0; 119 }).forEach(elmtId => this.UpdateElement(elmtId)); 120 __JSScopeUtil__.restoreInstanceId(); 121 } 122 private UpdateElement(elmtId: number): void { 123 // do not process an Element that has been marked to be deleted 124 const obj: UpdateFunc | UpdateFuncRecord | undefined = this.updateFuncByElmtId.get(elmtId); 125 const updateFunc = (typeof obj === 'object') ? obj.updateFunc : null; 126 if (typeof updateFunc === 'function') { 127 updateFunc(elmtId, /* isFirstRender */ false); 128 this.finishUpdateFunc(); 129 } 130 } 131 132 protected purgeDeletedElmtIds(): void { 133 UINodeRegisterProxy.obtainDeletedElmtIds(); 134 UINodeRegisterProxy.unregisterElmtIdsFromViewPUs(); 135 } 136 public purgeDeleteElmtId(rmElmtId: number): boolean { 137 const result = this.updateFuncByElmtId.delete(rmElmtId); 138 if (result) { 139 UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(rmElmtId); 140 } 141 return result; 142 } 143 144 public getFrameNode(): FrameNode | null { 145 if ( 146 this.frameNode_ !== undefined && 147 this.frameNode_ !== null && 148 this.frameNode_.getNodePtr() !== null 149 ) { 150 return this.frameNode_; 151 } 152 return null; 153 } 154 155 public observeComponentCreation(func: (arg0: number, arg1: boolean) => void) { 156 let elmId: number = ViewStackProcessor.AllocateNewElmetIdForNextComponent(); 157 UINodeRegisterProxy.ElementIdToOwningViewPU_.set(elmId, new WeakRef(this)); 158 try { 159 func(elmId, true); 160 } catch (error) { 161 // avoid the incompatible change that move set function before updateFunc. 162 UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(elmId); 163 throw error; 164 } 165 } 166 167 public observeComponentCreation2(compilerAssignedUpdateFunc: UpdateFunc, classObject: { prototype: Object; pop?: () => void }): void { 168 const _componentName: string = classObject && 'name' in classObject ? (Reflect.get(classObject, 'name') as string) : 'unspecified UINode'; 169 const _popFunc: () => void = 170 classObject && "pop" in classObject ? classObject.pop! : () => { }; 171 const updateFunc = (elmtId: number, isFirstRender: boolean) => { 172 __JSScopeUtil__.syncInstanceId(this.instanceId_); 173 ViewStackProcessor.StartGetAccessRecordingFor(elmtId); 174 compilerAssignedUpdateFunc(elmtId, isFirstRender, this.params_); 175 if (!isFirstRender) { 176 _popFunc(); 177 } 178 ViewStackProcessor.StopGetAccessRecording(); 179 __JSScopeUtil__.restoreInstanceId(); 180 }; 181 182 const elmtId = ViewStackProcessor.AllocateNewElmetIdForNextComponent(); 183 // needs to move set before updateFunc. 184 // make sure the key and object value exist since it will add node in attributeModifier during updateFunc. 185 this.updateFuncByElmtId.set(elmtId, { 186 updateFunc: updateFunc, 187 componentName: _componentName, 188 }); 189 UINodeRegisterProxy.ElementIdToOwningViewPU_.set(elmtId, new WeakRef(this)); 190 try { 191 updateFunc(elmtId, /* is first render */ true); 192 } catch (error) { 193 // avoid the incompatible change that move set function before updateFunc. 194 this.updateFuncByElmtId.delete(elmtId); 195 UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(elmtId); 196 throw error; 197 } 198 } 199 200 /** 201 Partial updates for ForEach. 202 * @param elmtId ID of element. 203 * @param itemArray Array of items for use of itemGenFunc. 204 * @param itemGenFunc Item generation function to generate new elements. If index parameter is 205 * given set itemGenFuncUsesIndex to true. 206 * @param idGenFunc ID generation function to generate unique ID for each element. If index parameter is 207 * given set idGenFuncUsesIndex to true. 208 * @param itemGenFuncUsesIndex itemGenFunc optional index parameter is given or not. 209 * @param idGenFuncUsesIndex idGenFunc optional index parameter is given or not. 210 */ 211 public forEachUpdateFunction( 212 elmtId: number, 213 itemArray: Array<any>, 214 itemGenFunc: (item: any, index?: number) => void, 215 idGenFunc?: (item: any, index?: number) => string, 216 itemGenFuncUsesIndex: boolean = false, 217 idGenFuncUsesIndex: boolean = false 218 ): void { 219 if (itemArray === null || itemArray === undefined) { 220 return; 221 } 222 223 if (itemGenFunc === null || itemGenFunc === undefined) { 224 return; 225 } 226 227 if (idGenFunc === undefined) { 228 idGenFuncUsesIndex = true; 229 // catch possible error caused by Stringify and re-throw an Error with a meaningful (!) error message 230 idGenFunc = (item: any, index: number) => { 231 try { 232 return `${index}__${JSON.stringify(item)}`; 233 } catch (e) { 234 throw new Error( 235 ` 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!` 236 ); 237 } 238 }; 239 } 240 241 let diffIndexArray = []; // New indexes compared to old one. 242 let newIdArray = []; 243 let idDuplicates = []; 244 const arr = itemArray; // just to trigger a 'get' onto the array 245 246 // ID gen is with index. 247 if (idGenFuncUsesIndex) { 248 // Create array of new ids. 249 arr.forEach((item, indx) => { 250 newIdArray.push(idGenFunc(item, indx)); 251 }); 252 } else { 253 // Create array of new ids. 254 arr.forEach((item, index) => { 255 newIdArray.push( 256 `${itemGenFuncUsesIndex ? index + "_" : ""}` + idGenFunc(item) 257 ); 258 }); 259 } 260 261 // Set new array on C++ side. 262 // C++ returns array of indexes of newly added array items. 263 // these are indexes in new child list. 264 ForEach.setIdArray(elmtId, newIdArray, diffIndexArray, idDuplicates); 265 // Item gen is with index. 266 diffIndexArray.forEach((indx) => { 267 ForEach.createNewChildStart(newIdArray[indx], this); 268 if (itemGenFuncUsesIndex) { 269 itemGenFunc(arr[indx], indx); 270 } else { 271 itemGenFunc(arr[indx]); 272 } 273 ForEach.createNewChildFinish(newIdArray[indx], this); 274 }); 275 } 276 277 public ifElseBranchUpdateFunction(branchId: number, branchfunc: () => void) { 278 const oldBranchid = If.getBranchId(); 279 if (branchId === oldBranchid) { 280 return; 281 } 282 // branchId identifies uniquely the if .. <1> .. else if .<2>. else .<3>.branch 283 // ifElseNode stores the most recent branch, so we can compare 284 // removedChildElmtIds will be filled with the elmtIds of all children and their children will be deleted in response to if .. else change 285 let removedChildElmtIds = new Array(); 286 If.branchId(branchId, removedChildElmtIds); 287 this.purgeDeletedElmtIds(); 288 289 branchfunc(); 290 } 291 public getNodePtr(): number | null { 292 return this.nodePtr_; 293 } 294 public dispose() { 295 this.nodePtr_ = null; 296 super.dispose(); 297 if (this.frameNode_ !== undefined && this.frameNode_ !== null) { 298 this.frameNode_.setNodePtr(null); 299 } 300 } 301} 302