• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2023-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/// <reference path="../../state_mgmt/src/lib/common/ifelse_native.d.ts" />
16/// <reference path="../../state_mgmt/src/lib/puv2_common/puv2_viewstack_processor.d.ts" />
17
18class BuilderNode {
19  private _JSBuilderNode: JSBuilderNode;
20  // the name of "nodePtr_" is used in ace_engine/interfaces/native/node/native_node_napi.cpp.
21  private nodePtr_: NodePtr;
22  constructor(uiContext: UIContext, options: RenderOptions) {
23    let jsBuilderNode = new JSBuilderNode(uiContext, options);
24    this._JSBuilderNode = jsBuilderNode;
25    let id = Symbol('BuilderRootFrameNode');
26    BuilderNodeFinalizationRegisterProxy.ElementIdToOwningBuilderNode_.set(id, jsBuilderNode);
27    BuilderNodeFinalizationRegisterProxy.register(this, { name: 'BuilderRootFrameNode', idOfNode: id });
28  }
29  public update(params: Object) {
30    this._JSBuilderNode.update(params);
31  }
32  public build(builder: WrappedBuilder<Object[]>, params?: Object, options?: BuildOptions,): void {
33    this._JSBuilderNode.build(builder, params, options);
34    this.nodePtr_ = this._JSBuilderNode.getNodePtr();
35  }
36  public getNodePtr(): NodePtr {
37    return this._JSBuilderNode.getValidNodePtr();
38  }
39  public getFrameNode(): FrameNode {
40    return this._JSBuilderNode.getFrameNode();
41  }
42  public getFrameNodeWithoutCheck(): FrameNode | null {
43    return this._JSBuilderNode.getFrameNodeWithoutCheck();
44  }
45  public postTouchEvent(touchEvent: TouchEvent): boolean {
46    __JSScopeUtil__.syncInstanceId(this._JSBuilderNode.getInstanceId());
47    let ret = this._JSBuilderNode.postTouchEvent(touchEvent);
48    __JSScopeUtil__.restoreInstanceId();
49    return ret;
50  }
51  public dispose(): void {
52    this._JSBuilderNode.dispose();
53  }
54  public reuse(param?: Object): void {
55    this._JSBuilderNode.reuse(param);
56  }
57  public recycle(): void {
58    this._JSBuilderNode.recycle();
59  }
60  public updateConfiguration(): void {
61    this._JSBuilderNode.updateConfiguration();
62  }
63  public onReuseWithBindObject(param?: Object): void {
64    this._JSBuilderNode.onReuseWithBindObject(param);
65  }
66  public onRecycleWithBindObject(): void {
67    this._JSBuilderNode.onRecycleWithBindObject();
68  }
69}
70
71class JSBuilderNode extends BaseNode {
72  private params_: Object;
73  private uiContext_: UIContext;
74  private frameNode_: FrameNode;
75  private _nativeRef: NativeStrongRef;
76  private _supportNestingBuilder: boolean;
77  private _proxyObjectParam: Object;
78  private bindedViewOfBuilderNode:ViewPU;
79
80  constructor(uiContext: UIContext, options?: RenderOptions) {
81    super(uiContext, options);
82    this.uiContext_ = uiContext;
83    this.updateFuncByElmtId = new UpdateFuncsByElmtId();
84    this._supportNestingBuilder = false;
85  }
86  public reuse(param: Object): void {
87    this.updateStart();
88    this.childrenWeakrefMap_.forEach((weakRefChild) => {
89      const child = weakRefChild.deref();
90      if (child) {
91        if (child instanceof ViewPU) {
92          child.aboutToReuseInternal(param);
93        }
94        else {
95          // FIXME fix for mixed V2 - V3 Hierarchies
96          throw new Error('aboutToReuseInternal: Recycle not implemented for ViewV2, yet');
97        }
98      } // if child
99    });
100    this.updateEnd();
101  }
102  public recycle(): void {
103    this.childrenWeakrefMap_.forEach((weakRefChild) => {
104      const child = weakRefChild.deref();
105      if (child) {
106        if (child instanceof ViewPU) {
107          child.aboutToRecycleInternal();
108        }
109        else {
110          // FIXME fix for mixed V2 - V3 Hierarchies
111          throw new Error('aboutToRecycleInternal: Recycle not yet implemented for ViewV2');
112        }
113      } // if child
114    });
115  }
116  public onReuseWithBindObject(param?: Object): void {
117    __JSScopeUtil__.syncInstanceId(this.instanceId_);
118    super.onReuseWithBindObject(param);
119    __JSScopeUtil__.restoreInstanceId();
120  }
121  public onRecycleWithBindObject(): void {
122    __JSScopeUtil__.syncInstanceId(this.instanceId_);
123    super.onRecycleWithBindObject();
124    __JSScopeUtil__.restoreInstanceId();
125  }
126  public getCardId(): number {
127    return -1;
128  }
129  private isObject(param: Object): boolean {
130    const typeName = Object.prototype.toString.call(param);
131    const objectName = `[object Object]`;
132    if (typeName === objectName) {
133      return true;
134    } else {
135      return false;
136    }
137  }
138  private buildWithNestingBuilder(builder: WrappedBuilder<Object[]>, supportLazyBuild: boolean): void {
139    if (this._supportNestingBuilder && this.isObject(this.params_)) {
140      this._proxyObjectParam = new Proxy(this.params_, {
141        set(target, property, val): boolean {
142          throw Error(`@Builder : Invalid attempt to set(write to) parameter '${property.toString()}' error!`);
143        },
144        get: (target, property, receiver): Object => { return this.params_?.[property] }
145      });
146      this.nodePtr_ = super.create(builder.builder?.bind(this.bindedViewOfBuilderNode ? this.bindedViewOfBuilderNode : this),
147        this._proxyObjectParam, this.updateNodeFromNative, this.updateConfiguration, supportLazyBuild);
148    } else {
149      this.nodePtr_ = super.create(builder.builder?.bind(this.bindedViewOfBuilderNode ? this.bindedViewOfBuilderNode : this),
150        this.params_, this.updateNodeFromNative, this.updateConfiguration, supportLazyBuild);
151    }
152  }
153  public build(builder: WrappedBuilder<Object[]>, params?: Object, options?: BuildOptions): void {
154    __JSScopeUtil__.syncInstanceId(this.instanceId_);
155    this._supportNestingBuilder = options?.nestingBuilderSupported ? options.nestingBuilderSupported : false;
156    const supportLazyBuild = options?.lazyBuildSupported ? options.lazyBuildSupported : false;
157    this.bindedViewOfBuilderNode = options?.bindedViewOfBuilderNode;
158    this.params_ = params;
159    this.updateFuncByElmtId.clear();
160    if(this.bindedViewOfBuilderNode){
161      globalThis.__viewPuStack__?.push(this.bindedViewOfBuilderNode);
162    }
163    this.buildWithNestingBuilder(builder, supportLazyBuild);
164    if(this.bindedViewOfBuilderNode){
165      globalThis.__viewPuStack__?.pop();
166    }
167    this._nativeRef = getUINativeModule().nativeUtils.createNativeStrongRef(this.nodePtr_);
168    if (this.frameNode_ === undefined || this.frameNode_ === null) {
169      this.frameNode_ = new BuilderRootFrameNode(this.uiContext_);
170    }
171    this.frameNode_.setNodePtr(this._nativeRef, this.nodePtr_);
172    this.frameNode_.setRenderNode(this._nativeRef);
173    this.frameNode_.setBaseNode(this);
174    __JSScopeUtil__.restoreInstanceId();
175  }
176  public update(param: Object) {
177    __JSScopeUtil__.syncInstanceId(this.instanceId_);
178    this.updateStart();
179    this.purgeDeletedElmtIds();
180    this.params_ = param;
181    Array.from(this.updateFuncByElmtId.keys()).sort((a: number, b: number): number => {
182      return (a < b) ? -1 : (a > b) ? 1 : 0;
183    }).forEach(elmtId => this.UpdateElement(elmtId));
184    this.updateEnd();
185    __JSScopeUtil__.restoreInstanceId();
186  }
187  public updateConfiguration(): void {
188    __JSScopeUtil__.syncInstanceId(this.instanceId_);
189    this.updateStart();
190    this.purgeDeletedElmtIds();
191    Array.from(this.updateFuncByElmtId.keys()).sort((a: number, b: number): number => {
192      return (a < b) ? -1 : (a > b) ? 1 : 0;
193    }).forEach(elmtId => this.UpdateElement(elmtId));
194    for (const child of this.childrenWeakrefMap_.values()) {
195      const childView = child.deref();
196      if (childView) {
197        childView.forceCompleteRerender(true);
198      }
199    }
200    this.updateEnd();
201    __JSScopeUtil__.restoreInstanceId();
202  }
203  private UpdateElement(elmtId: number): void {
204    // do not process an Element that has been marked to be deleted
205    const obj: UpdateFunc | UpdateFuncRecord | undefined = this.updateFuncByElmtId.get(elmtId);
206    const updateFunc = (typeof obj === 'object') ? obj.getUpdateFunc() : null;
207    if (typeof updateFunc === 'function') {
208      updateFunc(elmtId, /* isFirstRender */ false);
209      this.finishUpdateFunc();
210    }
211  }
212
213  public purgeDeleteElmtId(rmElmtId: number): boolean {
214    const result = this.updateFuncByElmtId.delete(rmElmtId);
215    if (result) {
216      UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(rmElmtId);
217    }
218    return result;
219  }
220
221  public getFrameNode(): FrameNode | null {
222    if (
223      this.frameNode_ !== undefined &&
224      this.frameNode_ !== null &&
225      this.frameNode_.getNodePtr() !== null
226    ) {
227      return this.frameNode_;
228    }
229    return null;
230  }
231
232  public getFrameNodeWithoutCheck(): FrameNode | null | undefined {
233    return this.frameNode_;
234  }
235
236  public observeComponentCreation(func: (arg0: number, arg1: boolean) => void) {
237    let elmId: number = ViewStackProcessor.AllocateNewElmetIdForNextComponent();
238    UINodeRegisterProxy.ElementIdToOwningViewPU_.set(elmId, new WeakRef(this));
239    try {
240      func(elmId, true);
241    } catch (error) {
242      // avoid the incompatible change that move set function before updateFunc.
243      UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(elmId);
244      throw error;
245    }
246  }
247
248  public observeComponentCreation2(compilerAssignedUpdateFunc: UpdateFunc, classObject: { prototype: Object; pop?: () => void }): void {
249    const _componentName: string = classObject && 'name' in classObject ? (Reflect.get(classObject, 'name') as string) : 'unspecified UINode';
250    const _popFunc: () => void =
251      classObject && 'pop' in classObject ? classObject.pop! : () => { };
252    const updateFunc = (elmtId: number, isFirstRender: boolean): void => {
253      __JSScopeUtil__.syncInstanceId(this.instanceId_);
254      ViewBuildNodeBase.arkThemeScopeManager?.onComponentCreateEnter(_componentName, elmtId, isFirstRender, this);
255      ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
256      // if V2 @Observed/@Track used anywhere in the app (there is no more fine grained criteria),
257      // enable V2 object deep observation
258      // FIXME: A @Component should only use PU or V2 state, but ReactNative dynamic viewer uses both.
259      if (ConfigureStateMgmt.instance.needsV2Observe()) {
260        // FIXME: like in V2 setting bindId_ in ObserveV2 does not work with 'stacked'
261        // update + initial render calls, like in if and ForEach case, convert to stack as well
262        ObserveV2.getObserve().startRecordDependencies(this, elmtId, true);
263      }
264      if (this._supportNestingBuilder) {
265        compilerAssignedUpdateFunc(elmtId, isFirstRender);
266      } else {
267        compilerAssignedUpdateFunc(elmtId, isFirstRender, this.params_);
268      }
269      if (!isFirstRender) {
270        _popFunc();
271      }
272      if (ConfigureStateMgmt.instance.needsV2Observe()) {
273        ObserveV2.getObserve().stopRecordDependencies();
274      }
275      ViewStackProcessor.StopGetAccessRecording();
276      ViewBuildNodeBase.arkThemeScopeManager?.onComponentCreateExit(elmtId);
277      __JSScopeUtil__.restoreInstanceId();
278    };
279
280    const elmtId = ViewStackProcessor.AllocateNewElmetIdForNextComponent();
281    // needs to move set before updateFunc.
282    // make sure the key and object value exist since it will add node in attributeModifier during updateFunc.
283    this.updateFuncByElmtId.set(elmtId, { updateFunc: updateFunc, classObject: classObject });
284    UINodeRegisterProxy.ElementIdToOwningViewPU_.set(elmtId, new WeakRef(this));
285    try {
286      updateFunc(elmtId, /* is first render */ true);
287    } catch (error) {
288      // avoid the incompatible change that move set function before updateFunc.
289      this.updateFuncByElmtId.delete(elmtId);
290      UINodeRegisterProxy.ElementIdToOwningViewPU_.delete(elmtId);
291      throw error;
292    }
293  }
294
295  /**
296   Partial updates for ForEach.
297   * @param elmtId ID of element.
298   * @param itemArray Array of items for use of itemGenFunc.
299   * @param itemGenFunc Item generation function to generate new elements. If index parameter is
300   *                    given set itemGenFuncUsesIndex to true.
301   * @param idGenFunc   ID generation function to generate unique ID for each element. If index parameter is
302   *                    given set idGenFuncUsesIndex to true.
303   * @param itemGenFuncUsesIndex itemGenFunc optional index parameter is given or not.
304   * @param idGenFuncUsesIndex idGenFunc optional index parameter is given or not.
305   */
306  public forEachUpdateFunction(
307    elmtId: number,
308    itemArray: Array<any>,
309    itemGenFunc: (item: any, index?: number) => void,
310    idGenFunc?: (item: any, index?: number) => string,
311    itemGenFuncUsesIndex: boolean = false,
312    idGenFuncUsesIndex: boolean = false
313  ): void {
314    if (itemArray === null || itemArray === undefined) {
315      return;
316    }
317
318    if (itemGenFunc === null || itemGenFunc === undefined) {
319      return;
320    }
321
322    if (idGenFunc === undefined) {
323      idGenFuncUsesIndex = true;
324      // catch possible error caused by Stringify and re-throw an Error with a meaningful (!) error message
325      idGenFunc = (item: any, index: number): string => {
326        try {
327          return `${index}__${JSON.stringify(item)}`;
328        } catch (e) {
329          throw new Error(
330            ` 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!`
331          );
332        }
333      };
334    }
335
336    let diffIndexArray = []; // New indexes compared to old one.
337    let newIdArray = [];
338    let idDuplicates = [];
339    const arr = itemArray; // just to trigger a 'get' onto the array
340
341    // ID gen is with index.
342    if (idGenFuncUsesIndex) {
343      // Create array of new ids.
344      arr.forEach((item, indx) => {
345        newIdArray.push(idGenFunc(item, indx));
346      });
347    } else {
348      // Create array of new ids.
349      arr.forEach((item, index) => {
350        newIdArray.push(
351          `${itemGenFuncUsesIndex ? index + '_' : ''}` + idGenFunc(item)
352        );
353      });
354    }
355    // removedChildElmtIds will be filled with the elmtIds of all children and their children will be deleted in response to foreach change
356    let removedChildElmtIds = [];
357    // Set new array on C++ side.
358    // C++ returns array of indexes of newly added array items.
359    // these are indexes in new child list.
360    ForEach.setIdArray(elmtId, newIdArray, diffIndexArray, idDuplicates, removedChildElmtIds);
361    // Item gen is with index.
362    diffIndexArray.forEach((indx) => {
363      ForEach.createNewChildStart(newIdArray[indx], this);
364      if (itemGenFuncUsesIndex) {
365        itemGenFunc(arr[indx], indx);
366      } else {
367        itemGenFunc(arr[indx]);
368      }
369      ForEach.createNewChildFinish(newIdArray[indx], this);
370    });
371    // un-registers the removed child elementIDs using proxy
372    UINodeRegisterProxy.unregisterRemovedElmtsFromViewPUs(removedChildElmtIds);
373    // purging these elmtIds from state mgmt will make sure no more update function on any deleted child will be executed
374    this.purgeDeletedElmtIds();
375  }
376  public getNodePtr(): NodePtr {
377    return this.nodePtr_;
378  }
379  public getValidNodePtr(): NodePtr {
380    return this._nativeRef?.getNativeHandle();
381  }
382  public dispose(): void {
383    this.frameNode_?.dispose();
384  }
385  public disposeNode(): void {
386    super.disposeNode();
387    this.nodePtr_ = null;
388    this._nativeRef = null;
389    this.frameNode_?.resetNodePtr();
390  }
391  updateInstance(uiContext: UIContext): void {
392      this.uiContext_ = uiContext;
393      this.instanceId_ = uiContext.instanceId_;
394      if (this.frameNode_ !== undefined && this.frameNode_ !== null) {
395          this.frameNode_.updateInstance(uiContext);
396      }
397  }
398
399  private updateNodePtr(nodePtr: NodePtr)
400  {
401    if (nodePtr != this.nodePtr_) {
402      this.dispose();
403      this.nodePtr_ = nodePtr;
404      this._nativeRef = getUINativeModule().nativeUtils.createNativeStrongRef(this.nodePtr_);
405      this.frameNode_.setNodePtr(this._nativeRef, this.nodePtr_);
406    }
407  }
408
409  private updateInstanceId(instanceId: number)
410  {
411    this.instanceId_ = instanceId;
412  }
413
414  protected updateNodeFromNative(instanceId: number, nodePtr: NodePtr)
415  {
416    this.updateNodePtr(nodePtr);
417    this.updateInstanceId(instanceId);
418  }
419
420  public observeRecycleComponentCreation(name: string, recycleUpdateFunc: RecycleUpdateFunc): void {
421    throw new Error('custom component in @Builder used by BuilderNode does not support @Reusable');
422  }
423  public ifElseBranchUpdateFunctionDirtyRetaken(): void {}
424  public forceCompleteRerender(deep: boolean): void {}
425  public forceRerenderNode(elmtId: number): void {}
426}
427