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