• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2022-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 */
15import { float32, int32 } from "@koalaui/common"
16import { Access, ptrToBits, withFloat32Array, KFloat32ArrayPtr, Wrapper, pointer, bitsToPtr,
17    KPointer, KInt, KFloat, wrapCallback,
18    nullptr, Finalizable } from "@koalaui/interop"
19import { ArkUINativeModule } from "./generated/ArkUINativeModule"
20import { DataNode, IncrementalNode, contextNode } from "@koalaui/runtime"
21import {
22    AreaChangePeerEvent,
23    CallbackPeerEvent,
24    GesturePeerEvent,
25    ImageCompletePeerEvent, ImageErrorPeerEvent,
26    KeyPeerEvent,
27    MousePeerEvent,
28    PeerEvent,
29    SinglePointerPeerEvent,
30    XComponentLoadPeerEvent,
31    PeerEnterKeyType,
32    PeerSubmitEvent
33} from "./PeerEvents"
34import { NativePeerNode } from "./NativePeerNode"
35import { WebLoadInterceptDataType } from "./WebResourceRequest"
36import { ArkAlignment } from "./PeerLayout"
37
38export function IntToFloatArray(array: Int32Array, offset: number = 0, length?: number): Float32Array {
39    length = length ?? (array.length - offset)
40    const data = new Float32Array(length)
41    for (let i = 0; i < data.length; i++) {
42        data[i] = array[i + offset]
43    }
44    return data
45}
46
47// This class doesn't manage its pointer, so not Finalizable.
48export class PeerCanvas extends Wrapper {
49    constructor(ptr: pointer) {
50        super(ptr)
51    }
52    // TODO: commonize with Skoala!
53    drawRect(left: float32, top: float32, right: float32, botton: float32, paint: PeerPaint) {
54        console.log("drawRect: method not implemented")
55    }
56}
57
58
59export class PeerPaint extends Finalizable {
60    private constructor(ptr: pointer) {
61        super(ptr, 0)
62    }
63    // TODO: commonize with Skoala!
64    static make(): PeerPaint {
65        console.log("make: method not implemented")
66        return new PeerPaint(0)
67    }
68    setColor(color: int32) {
69        console.log("setColor: method not implemented")
70    }
71    getColor(): int32 {
72        console.log("getColor: method not implemented")
73        return 0;
74    }
75}
76
77
78export enum DirtyFlags {
79    Geometry = 1, // 1 << 0,
80    Visual = 2, // 1 << 1,
81}
82
83export interface CustomizableNode {
84    onMeasure(args: Float32Array): int32
85    onLayout(args: Float32Array): int32
86    onDraw(canvas: PeerCanvas, args: Float32Array): int32
87
88    firstChildNode(): CustomizableNode|undefined
89    previousSiblingNode(): CustomizableNode|undefined
90    nextSiblingNode(): CustomizableNode|undefined
91    parentNode(): CustomizableNode|undefined
92
93    markDirty(flags: int32): void
94    getDirty(): int32
95    clearDirty(flags: int32): void
96}
97
98export const PeerNodeType = 11
99export const PartialPropertiesType = 13
100export const GeneratedPartialPropertiesType = 14
101export const LegacyNodeType = 17
102
103function parentWithPeer(node?: IncrementalNode): PeerNode | undefined {
104    node = node?.parent
105    while (node) {
106        if (node.isKind(PeerNodeType)) return node as PeerNode
107        node = node.parent
108    }
109    return undefined
110}
111
112export interface Sized {
113    size(): number
114}
115
116export class LazyRangeStartMarker {
117    public onRangeUpdate: (startIndex: number, endIndex: number) => void
118    public currentStart: () => number
119    public currentEnd: () => number
120    private sizedRange: Sized
121
122    constructor(onRangeUpdate: (startIndex: number, endIndex: number) => void, sizedRange: Sized,
123                currentStart: () => number, currentEnd: () => number) {
124        this.sizedRange = sizedRange
125        this.currentEnd = currentEnd
126        this.currentStart = currentStart
127        this.onRangeUpdate = onRangeUpdate
128    }
129
130    rangeSize(): number {
131        return this.sizedRange.size()
132    }
133}
134
135export class LazyRangeEndMarker {
136    constructor() { }
137}
138
139export interface Properties {
140    onClick: (event: SinglePointerPeerEvent) => void
141    onSwiperChange: (value: number) => void
142    onTabsChange: (value: number) => void
143    onVisibleAreaChange: (isVisible: boolean, currentRatio: number) => void
144    lazyRangeStart: LazyRangeStartMarker
145    lazyRangeEnd: LazyRangeEndMarker
146    onAppear: () => void
147    onDisappear: () => void
148    onScrollIndex: (first: number, last: number) => void
149    onNavigatorClick: () => void
150    onAction: (event: GesturePeerEvent) => void
151    onActionStart: (event: GesturePeerEvent) => void
152    onActionUpdate: (event: GesturePeerEvent) => void
153    onActionEnd: (event: GesturePeerEvent) => void
154    onActionCancel: () => void
155    onTextInput: (text: string) => void
156    onSwiperAnimationStart: (index: number, targetIndex: number, currentOffset: number, targetOffset: number, velocity: number) => void
157    onSwiperAnimationEnd: (index: number, currentOffset: number, targetOffset: number, velocity: number) => void
158    onSwiperGestureSwipe: (index: number, currentOffset: number, targetOffset: number, velocity: number) => void
159    onAreaChange: (event: AreaChangePeerEvent) => void
160    onBlur: () => void
161    onCanvasReady: () => void
162    onListScroll:(scrollOffset: number, scrollState: number) => void
163    onListScrollIndex:(start: number, end: number, center: number) => void
164    onListScrollStart:() => void
165    onListScrollStop:() => void
166    onWebLoadIntercept: (event: WebLoadInterceptDataType) => boolean
167    onToggleChange: (isOn: boolean) => void
168    onTextInputEditChange: (isEditing: boolean) => void
169    onTextInputSubmit: (enterKey: PeerEnterKeyType, event: PeerSubmitEvent) => void
170    onTextInputChange: (value: string) => void
171    onSliderChange: (value: number, mode: number) => void
172    onHover: (isHover: boolean) => void
173    onKeyEvent: (event: KeyPeerEvent) => void
174    onMouse: (event: MousePeerEvent) => void
175    onImageComplete: (event: ImageCompletePeerEvent) => void
176    onImageError: (event: ImageErrorPeerEvent) => void
177    onRefreshStateChange: (state: number) => void
178    onRefreshing:() => void
179    onRadioChange: (isChecked: boolean) => void
180    onGridScroll:(scrollOffset: number, scrollState: number) => void
181    onGridScrollStart:() => void
182    onGridScrollStop:() => void
183    onSideBarChange:(value: boolean) => void
184    onXComponentLoad: (event: XComponentLoadPeerEvent) => void
185    onXComponentDestroy: () => void
186    onNavBarStateChange: (isVisible: boolean) => void
187    navDestination: (name: string, param: unknown) => void
188}
189
190/** @memo */
191export function UseProperties(properties: Partial<Properties>) {
192    const parent = contextNode<PeerNode>(PeerNodeType)
193    DataNode.attach(PartialPropertiesType, properties, () => {
194        parent.invalidateProperties()
195    })
196}
197
198function findPeerNode(node: IncrementalNode): PeerNode | undefined {
199    if (node.isKind(PeerNodeType)) return node as PeerNode
200    for (let child = node.firstChild; child; child = child.nextSibling) {
201        let peer = findPeerNode(child)
202        if (peer) return peer
203    }
204    return undefined
205}
206
207let currentPostman = (node: PeerNode, peerEvent: PeerEvent, props: Partial<Properties>) => { }
208
209export function setEventDeliverer(postman:
210    (node: PeerNode, peerEvent: PeerEvent, props: Partial<Properties>) => void) {
211    currentPostman = postman
212}
213
214function intersect(interval1: number[], interval2: number[]): number[] | undefined {
215    let min = Math.max(interval1[0], interval2[0])
216    let max = Math.min(interval1[1], interval2[1])
217    return max < min ? undefined : [min, max]
218}
219
220export enum NativeNodeFlags {
221    None = 0,
222    CustomMeasure = 1, // 1 << 0,
223    CustomLayout = 2, // 1 << 1,
224    CustomDraw = 4, // 1 << 2,
225}
226
227export enum CustomNodeOpType {
228    Measure = 1,
229    Layout = 2,
230    Draw = 3
231}
232
233export const UndefinedDimensionUnit = -1
234const initialId = 999
235export class PeerNode extends IncrementalNode implements CustomizableNode {
236    peer: NativePeerNode
237    private id: number
238    private insertMark: pointer = nullptr
239    private insertDirection: int32 = 0
240
241    setInsertMark(mark: pointer, upDirection: boolean) {
242        // console.log(`setInsertMark 0x${mark.toString(16)} ${upDirection ? "up" : "down"}`)
243        this.insertMark = mark
244        this.insertDirection = upDirection ? 0 : 1
245    }
246
247    getId(): number { return this.id }
248    getPeerId(): number { return this.peer.id }
249    getPeerPtr(): pointer { return this.peer.ptr }
250    static nextId(): int32 { return ++PeerNode.currentId }
251
252    private flags: int32
253    getFlags(): int32 { return this.flags }
254
255    alignment: int32 = ArkAlignment.Center
256    measureResult: Float32Array = new Float32Array(4)
257    layoutResult: Float32Array = new Float32Array(4)
258
259    protected static currentId = initialId
260    private static peerNodeMap = new Map<number, PeerNode>()
261
262    static findPeerByNativeId(id: number): PeerNode | undefined {
263        return PeerNode.peerNodeMap.get(id)
264    }
265
266    static deliverEventFromPeer(event: PeerEvent) {
267        let peer = PeerNode.findPeerByNativeId(event.nodeId)
268        peer?.deliverEvent(event)
269    }
270
271    _name: string = "PeerNode"
272    constructor(peerPtr: pointer, id: int32, name: string, flags: int32) {
273        super(PeerNodeType)
274        this.id = id
275        this.peer = NativePeerNode.create(this, peerPtr, flags)
276
277        this.flags = flags
278        this._name = name
279        PeerNode.peerNodeMap.set(this.id, this)
280        this.onChildInserted = (child: IncrementalNode) => {
281            let peer = findPeerNode(child)!
282            if (peer) {
283                let peerPtr = peer.getPeerPtr()
284                if (this.insertMark != nullptr) {
285                    if (this.insertDirection == 0) {
286                        this.peer.insertChildBefore(peerPtr, this.insertMark)
287                    } else {
288                        this.peer.insertChildAfter(peerPtr, this.insertMark)
289                    }
290                    this.insertMark = peerPtr
291                    return
292                }
293
294                // Find the closest peer node backward.
295                // TODO: rework to avoid search
296                let sibling: PeerNode | undefined = undefined
297                for (let node = child.previousSibling; node; node = node.previousSibling) {
298                    if (node.isKind(PeerNodeType)) {
299                        sibling = node as PeerNode
300                        break
301                    }
302                }
303                this.peer.insertChildAfter(peerPtr, sibling?.getPeerPtr() ?? nullptr)
304            }
305        }
306        this.onChildRemoved = (child: IncrementalNode) => {
307            let peer = findPeerNode(child)
308            if (peer) {
309                this.peer.removeChild(peer.peer)
310            }
311        }
312    }
313
314    customizationCallback(args: Int32Array): int32 {
315        switch (args[0]) {
316            case CustomNodeOpType.Measure: {
317                return this.onMeasure(IntToFloatArray(args, 1))
318            }
319            case CustomNodeOpType.Layout: {
320                return this.onLayout(IntToFloatArray(args, 1))
321            }
322            case CustomNodeOpType.Draw: return this.onDraw(new PeerCanvas(bitsToPtr(args, 1)), IntToFloatArray(args, 3))
323        }
324        return 0
325    }
326
327    onMeasure(args: Float32Array): int32 {
328        if ((this.flags & NativeNodeFlags.CustomMeasure) == 0) {
329            // This node doesn't have managed measure, call native instead.
330            let result = 0
331            withFloat32Array(args, Access.READ | Access.WRITE, (argsPtr: KFloat32ArrayPtr) => {
332                // Call native measure
333                result = ArkUINativeModule._MeasureNode(this.peer.ptr, argsPtr)
334            })
335            return result
336        }
337        if ((this.getDirty() & DirtyFlags.Geometry) == 0) return 0
338        // default behavior
339        const measureArray = new Float32Array(args)
340        for (let child = this.firstChildNode(); child != undefined; child = child.nextSiblingNode()) {
341            let childPeer = child as PeerNode
342            childPeer.onMeasure(measureArray)
343        }
344        this.measureResult.set(args)
345        ArkUINativeModule._SetMeasureWidth(this.peer.ptr, this.measureResult[0])
346        ArkUINativeModule._SetMeasureHeight(this.peer.ptr, this.measureResult[1])
347        return this._dirtyFlags
348    }
349
350    onLayout(args: Float32Array): int32 {
351        if ((this.flags & NativeNodeFlags.CustomLayout) == 0) {
352            // This node doesn't have managed layout, call native instead.
353            let result = 0
354            withFloat32Array(args, Access.READ | Access.WRITE, (argsPtr: KFloat32ArrayPtr) => {
355                // Call native layout.
356                result = ArkUINativeModule._LayoutNode(this.peer.ptr, argsPtr)
357            })
358            return result
359        }
360        if ((this.getDirty() & DirtyFlags.Geometry) == 0) return 0
361        // default behavior
362        let layoutArray = new Float32Array(args)
363        for (let child = this.firstChildNode(); child != undefined; child = child.nextSiblingNode()) {
364            let childPeer = child as PeerNode
365            childPeer.onLayout(layoutArray)
366        }
367        this.layoutResult.set(args)
368        let dirty = this._dirtyFlags
369        this.clearDirty(DirtyFlags.Geometry)
370        return dirty
371    }
372
373    onDraw(canvas: PeerCanvas, args: Float32Array): int32 {
374        if ((this.flags & NativeNodeFlags.CustomDraw) == 0) {
375            // This node doesn't have managed layout, call native instead.
376            let result = 0
377            withFloat32Array(args, Access.READ | Access.WRITE, (argsPtr: KFloat32ArrayPtr) => {
378                // Call native draw.
379                result = ArkUINativeModule._DrawNode(this.peer.ptr, argsPtr)
380            })
381            return result
382        }
383        // default behavior
384        let drawArray = new Float32Array(4)
385        for (let child = this.firstChildNode(); child != undefined; child = child.nextSiblingNode()) {
386            let childPeer = child as PeerNode
387            drawArray.set(args)
388            childPeer.onDraw(canvas, drawArray)
389        }
390        return this._dirtyFlags
391    }
392
393
394    protected _dirtyFlags: int32 = 0
395    markDirty(flags: int32) {
396        this._dirtyFlags |= flags
397        let node: PeerNode = this
398        while (node != undefined) {
399            node._dirtyFlags |= flags
400            node = node.parent as PeerNode
401        }
402    }
403    getDirty(): int32 {
404        return this._dirtyFlags
405    }
406    clearDirty(flags: int32): void {
407        this._dirtyFlags &= ~(flags as number)
408    }
409    firstChildNode(): CustomizableNode|undefined {
410        let child = this.firstChild
411        if (child?.isKind(PeerNodeType))
412            return child as Object as CustomizableNode
413        else
414            return undefined
415    }
416    previousSiblingNode(): CustomizableNode|undefined {
417        let sibling = this.previousSibling
418        if (sibling?.isKind(PeerNodeType))
419            return sibling as Object as CustomizableNode
420        else
421            return undefined
422
423    }
424    nextSiblingNode(): CustomizableNode|undefined {
425        let sibling = this.nextSibling
426        if (sibling?.isKind(PeerNodeType))
427            return sibling as Object as CustomizableNode
428        else
429            return undefined
430    }
431    parentNode(): CustomizableNode|undefined {
432        return this.parent as Object as CustomizableNode
433    }
434
435    get childrenCountOfPeer(): number {
436        // TODO: add some cache for it.
437        let totalCount = 0
438        let inLazyRange = false
439        for (let child = this.firstChild; child; child = child.nextSibling) {
440            if (child.isKind(PeerNodeType)) {
441                if (!inLazyRange) {
442                    ++totalCount
443                }
444                continue
445            }
446            const properties = DataNode.extract<Partial<Properties>>(PartialPropertiesType, child)
447            if (properties) {
448                const lazyRangeStart = properties.lazyRangeStart
449                if (lazyRangeStart != undefined) {
450                    inLazyRange = true
451                    totalCount = totalCount + lazyRangeStart.rangeSize()
452                    continue
453                }
454                if (properties.lazyRangeEnd) {
455                    inLazyRange = false
456                }
457            }
458        }
459        return totalCount
460    }
461
462    // We only need that for container nodes where LazyForEach can appear.
463    private hasRangeUpdater = false
464    setRangeUpdater(needed: boolean) {
465        if (!needed && this.hasRangeUpdater) {
466            ArkUINativeModule._SetRangeUpdater(this.peer.ptr, 0)
467            this.hasRangeUpdater = false
468            return
469        }
470        if (needed && !this.hasRangeUpdater) {
471            this.hasRangeUpdater = true
472            ArkUINativeModule._SetLazyItemIndexer(this.peer.ptr, wrapCallback((args1: Uint8Array, length: int32) => {
473                const args = new Int32Array(args1.buffer)
474                let requestedIndex = args[0]
475                let logicalIndex = 0
476                let currentRangeStart = 0, currentRangeEnd = 0, currentRangeSize = 0
477                for (let child = this.firstChild; child; child = child.nextSibling) {
478                    if (child.isKind(PeerNodeType)) {
479                        if (logicalIndex == requestedIndex) {
480                            let bits = ptrToBits((child as PeerNode).peer.ptr)!
481                            args[1] = bits[0]
482                            args[2] = bits[1]
483                            return 1
484                        }
485                        logicalIndex++
486                        continue
487                    }
488                    const properties = DataNode.extract<Partial<Properties>>(PartialPropertiesType, child)
489                    if (properties) {
490                        const lazyRangeStart = properties.lazyRangeStart
491                        if (lazyRangeStart != undefined) {
492                            currentRangeSize = lazyRangeStart.rangeSize()
493                            currentRangeStart = lazyRangeStart.currentStart()
494                            currentRangeEnd = lazyRangeStart.currentEnd()
495                            logicalIndex += currentRangeStart
496                        }
497                        if (properties.lazyRangeEnd) {
498                            logicalIndex += currentRangeSize - (currentRangeEnd - currentRangeStart)
499                            currentRangeStart = 0
500                            currentRangeEnd = 0
501                            currentRangeSize = 0
502                        }
503                    }
504                }
505                return 0
506            }, false))
507            /**
508             * Theory of operations.
509             *
510             * Native side can send us range update request for essentially range of items it wants to materialize,
511             * we call it `nativeStartIndex` and `nativeEndIndex`.
512             * When there's LazyForEach we insert two markers: `start` and `end` into the tree.
513             * `start` contains a reference to data source, so it knows the presumable span generatable by this lazy list.
514             * We walk through the children of container node, and compute the actual native index for every node.
515             * We compute intersection between lazy range span and requested range, and update LazyForEach range accordingly.
516             * It adds or remove elements as needed.
517             */
518            ArkUINativeModule._SetRangeUpdater(this.peer.ptr, CallbackPeerEvent.wrap((args: Int32Array) => {
519                let nativeStartIndex = args[0]
520                let nativeEndIndex = args[1]
521                let inLazyRange = false
522                let currentNativeIndex = 0
523                // TODO: we may want some caching here.
524                for (let child = this.firstChild; child; child = child.nextSibling) {
525                    if (child.isKind(PeerNodeType)) {
526                        if (!inLazyRange) currentNativeIndex++
527                    }
528                    const properties = DataNode.extract<Partial<Properties>>(PartialPropertiesType, child)
529                    if (properties) {
530                        const lazyRangeStart = properties.lazyRangeStart
531                        if (lazyRangeStart != undefined) {
532                            inLazyRange = true
533                            let rangeSize = lazyRangeStart.rangeSize()
534                            // We need to intersect `[currentNativeIndex, currentNativeIndex + rangeSize]` with `[nativeStartIndex, nativeEndIndex]`
535                            // produce `[intersectionStart, intersectionEnd]` and use `intersectionStart - currentNativeIndex` as base of new range.
536                            let intersection = intersect(
537                                [currentNativeIndex, currentNativeIndex + rangeSize],
538                                [nativeStartIndex, nativeEndIndex])
539                            if (intersection != undefined) {
540                                lazyRangeStart.onRangeUpdate(intersection[0] - currentNativeIndex, intersection[1] - currentNativeIndex)
541                            }
542                            currentNativeIndex += rangeSize
543                        }
544                        if (properties.lazyRangeEnd) {
545                            inLazyRange = false
546                        }
547                    }
548                    // Nothing else to update.
549                    if (currentNativeIndex > nativeEndIndex) break
550                }
551            }, false))
552        }
553    }
554
555    invalidateProperties() {
556        // This is probably a bit heavyweight - we iterate over all partial properties and if there
557        // is lazy iterators - install range updater.
558        let needed = false
559        for (let child = this.firstChild; child; child = child.nextSibling) {
560            const properties = DataNode.extract<Partial<Properties>>(PartialPropertiesType, child)
561            if (properties?.lazyRangeStart != undefined) {
562                needed = true
563                break
564            }
565        }
566        // can be replaced with the following code when lambda processing will be fast enough:
567        // const needed = DataNode.findFirst<Partial<Properties>>(PartialPropertiesType, this.children, properties => properties.lazyRangeStart != undefined) != undefined
568        this.setRangeUpdater(needed)
569    }
570
571    deliverEvent(event: PeerEvent) {
572        for (let child = this.firstChild; child; child = child.nextSibling) {
573            const properties = DataNode.extract<Partial<Properties>>(PartialPropertiesType, child)
574            if (properties) currentPostman(this, event, properties)
575        }
576        // can be replaced with the following code when lambda processing will be fast enough:
577        // DataNode.forEach<Partial<Properties>>(PartialPropertiesType, this.children, properties => currentPostman(this, event, properties))
578    }
579
580    get name(): string {
581        return this.peer.getAttribute("name")
582    }
583
584    set name(value: string) {
585        this.peer.setAttribute("name", value)
586    }
587
588    dispose(): void {
589        let parent = parentWithPeer(this)
590        parent?.peer?.removeChild(this.peer)
591        this.peer.dispose()
592        PeerNode.peerNodeMap.delete(this.id)
593        super.dispose()
594    }
595
596    toString(): string {
597        return `${this.constructor.name}: peer=[${this.peer}], size=[${this.getMeasureWidth(this.peer.ptr)}x${this.getMeasureHeight(this.peer.ptr)}], id=${this.getId()}`
598    }
599
600    // access to NativeModule
601    protected measureNode(node: KPointer, data: KFloat32ArrayPtr): KInt {
602        return ArkUINativeModule._MeasureNode(node, data)
603    }
604    protected layoutNode(node: KPointer, data: KFloat32ArrayPtr): KInt {
605        return ArkUINativeModule._LayoutNode(node, data)
606    }
607    protected drawNode(node: KPointer, data: KFloat32ArrayPtr): KInt {
608        return ArkUINativeModule._DrawNode(node, data)
609    }
610    protected setMeasureWidth(node: KPointer, value: int32) {
611        ArkUINativeModule._SetMeasureWidth(node, value)
612    }
613    protected getMeasureWidth(node: KPointer): KFloat {
614        return ArkUINativeModule._GetMeasureWidth(node)
615    }
616    protected setMeasureHeight(node: KPointer, value: KFloat) {
617        ArkUINativeModule._SetMeasureHeight(node, value)
618    }
619    protected getMeasureHeight(node: KPointer): KFloat {
620        return ArkUINativeModule._GetMeasureHeight(node)
621    }
622    protected setX(node: KPointer, value: int32) {
623        ArkUINativeModule._SetX(node, value)
624    }
625    protected setY(node: KPointer, value: int32) {
626        ArkUINativeModule._SetY(node, value)
627    }
628    protected getAlignment(node: KPointer): KInt {
629        return ArkUINativeModule._GetAlignment(node)
630    }
631
632    applyAttributes(attributes: object): void {}
633}