• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2025 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, float64, int8, int32, int64, int32BitsFromFloat } from "@koalaui/common"
16import { pointer, KUint8ArrayPtr } from "./InteropTypes"
17import { ResourceId, ResourceHolder } from "./ResourceManager"
18import { KBuffer } from "./buffer"
19import { NativeBuffer } from "./NativeBuffer"
20import { InteropNativeModule } from "./InteropNativeModule"
21
22/**
23 * Value representing possible JS runtime object type.
24 * Must be synced with "enum RuntimeType" in C++.
25 */
26export class RuntimeType {
27    static UNEXPECTED = -1
28    static NUMBER = 1
29    static STRING = 2
30    static OBJECT = 3
31    static BOOLEAN = 4
32    static UNDEFINED = 5
33    static BIGINT = 6
34    static FUNCTION = 7
35    static SYMBOL = 8
36    static MATERIALIZED = 9
37}
38
39/**
40 * Value representing object type in serialized data.
41 * Must be synced with "enum Tags" in C++.
42 */
43export class Tags {
44    static UNDEFINED = 101
45    static INT32 = 102
46    static FLOAT32 = 103
47    static STRING = 104
48    static LENGTH = 105
49    static RESOURCE = 106
50    static OBJECT = 107
51}
52
53export function runtimeType(value: Object|String|number|undefined|null): int32 {
54    let type = typeof value
55    if (type == "number") return RuntimeType.NUMBER
56    if (type == "string") return RuntimeType.STRING
57    if (type == "undefined") return RuntimeType.UNDEFINED
58    if (type == "object") return RuntimeType.OBJECT
59    if (type == "boolean") return RuntimeType.BOOLEAN
60    if (type == "bigint") return RuntimeType.BIGINT
61    if (type == "function") return RuntimeType.FUNCTION
62    if (type == "symbol") return RuntimeType.SYMBOL
63
64    throw new Error(`bug: ${value} is ${type}`)
65}
66
67export function registerCallback(value: object): int32 {
68    // TODO: fix me!
69    return 42
70}
71
72function registerMaterialized(value: Object): int32 {
73    // TODO: fix me!
74    return 42
75}
76
77export function isResource(value: Object|undefined): boolean {
78    // TODO: fix me!
79    return false
80}
81
82export function isInstanceOf(className: string, value: Object|undefined): boolean {
83    // TODO: fix me!
84    return false
85}
86
87export interface CallbackResource {
88    resourceId: int32
89    hold: pointer
90    release: pointer
91}
92
93/* Serialization extension point */
94export abstract class CustomSerializer {
95    protected supported: Array<string>
96    constructor(supported: Array<string>) {
97        this.supported = supported
98    }
99    supports(kind: string): boolean { return this.supported.includes(kind) }
100    abstract serialize(serializer: SerializerBase, value: object, kind: string): void
101    next: CustomSerializer | undefined = undefined
102}
103
104class DateSerializer extends CustomSerializer {
105    constructor() {
106        super(Array.of("Date" as string))
107    }
108
109    serialize(serializer: SerializerBase, value: object, kind: string): void {
110        serializer.writeString((value as Date).toISOString())
111    }
112}
113SerializerBase.registerCustomSerializer(new DateSerializer())
114
115export class SerializerBase {
116    private position = 0
117    private buffer: KBuffer
118
119    private static customSerializers: CustomSerializer | undefined = new DateSerializer()
120    static registerCustomSerializer(serializer: CustomSerializer) {
121        if (SerializerBase.customSerializers == undefined) {
122            SerializerBase.customSerializers = serializer
123        } else {
124            let current = SerializerBase.customSerializers
125            while (current!.next != undefined) {
126                current = current!.next
127            }
128            current!.next = serializer
129        }
130    }
131
132    resetCurrentPosition(): void { this.position = 0 }
133
134    constructor() {
135        this.buffer = new KBuffer(96)
136    }
137    public release() {
138        this.releaseResources()
139        this.position = 0
140    }
141    asArray(): KUint8ArrayPtr {
142        return this.buffer.buffer
143    }
144    length(): int32 {
145        return this.position
146    }
147    currentPosition(): int32 { return this.position }
148    private checkCapacity(value: int32) {
149        if (value < 1) {
150            throw new Error(`${value} is less than 1`)
151        }
152        let buffSize = this.buffer.length
153        if (this.position > buffSize - value) {
154            const minSize = this.position + value
155            const resizedSize = Math.max(minSize, Math.round(3 * buffSize / 2))
156            let resizedBuffer = new KBuffer(resizedSize as int32)
157            for (let i = 0; i < this.buffer.length; i++) {
158                resizedBuffer.set(i, this.buffer.get(i))
159            }
160            this.buffer = resizedBuffer
161        }
162    }
163    private heldResources: Array<ResourceId> = new Array<ResourceId>()
164    holdAndWriteCallback(callback: object, hold: pointer = 0, release: pointer = 0, call: pointer = 0, callSync: pointer = 0): ResourceId {
165        const resourceId = ResourceHolder.instance().registerAndHold(callback)
166        this.heldResources.push(resourceId)
167        this.writeInt32(resourceId)
168        this.writePointer(hold)
169        this.writePointer(release)
170        this.writePointer(call)
171        this.writePointer(callSync)
172        return resourceId
173    }
174    holdAndWriteCallbackForPromiseVoid(hold: pointer = 0, release: pointer = 0, call: pointer = 0): [Promise<void>, ResourceId] {
175        let resourceId: ResourceId
176        const promise = new Promise<void>((resolve: (value: PromiseLike<void>) => void, reject: (err: Object|null|undefined) => void) => {
177            const callback = (err: string[]|undefined) => {
178                if (err !== undefined)
179                    reject(err!)
180                else
181                    resolve(Promise.resolve())
182            }
183            resourceId = this.holdAndWriteCallback(callback, hold, release, call)
184        })
185        return [promise, resourceId]
186    }
187    holdAndWriteCallbackForPromise<T>(hold: pointer = 0, release: pointer = 0, call: pointer = 0): [Promise<T>, ResourceId] {
188        let resourceId: ResourceId
189        const promise = new Promise<T>((resolve: (value: T|PromiseLike<T>) => void, reject: (err: Object|null|undefined) => void) => {
190            const callback = (value: T|undefined, err: string[]|undefined) => {
191                if (err !== undefined)
192                    reject(err!)
193                else
194                    resolve(value!)
195            }
196            resourceId = this.holdAndWriteCallback(callback, hold, release, call)
197        })
198        return [promise, resourceId]
199    }
200    writeCallbackResource(resource: CallbackResource) {
201        this.writeInt32(resource.resourceId)
202        this.writePointer(resource.hold)
203        this.writePointer(resource.release)
204    }
205    writeResource(resource: object) {
206        const resourceId = ResourceHolder.instance().registerAndHold(resource)
207        this.heldResources.push(resourceId)
208        this.writeInt32(resourceId)
209    }
210    private releaseResources() {
211        for (const resourceId of this.heldResources)
212            InteropNativeModule._ReleaseCallbackResource(resourceId)
213        // todo think about effective array clearing/pushing
214        this.heldResources = new Array<ResourceId>()
215    }
216    writeCustomObject(kind: string, value: object) {
217        let current = SerializerBase.customSerializers
218        while (current) {
219            if (current!.supports(kind)) {
220                current!.serialize(this, value, kind)
221                return
222            }
223            current = current!.next
224        }
225        // console.log(`Unsupported custom serialization for ${kind}, write undefined`)
226        this.writeInt8(Tags.UNDEFINED as int32)
227    }
228    writeFunction(value: Object) {
229        this.writeInt32(registerCallback(value))
230    }
231    writeTag(tag: int32): void {
232        this.buffer.set(this.position, tag as int8)
233        this.position++
234    }
235    writeNumber(value: number|undefined) {
236        this.checkCapacity(5)
237        if (value == undefined) {
238            this.writeTag(Tags.UNDEFINED)
239            this.position++
240            return
241        }
242        if ((value as float64) == Math.round(value)) {
243            this.writeTag(Tags.INT32)
244            this.writeInt32(value as int32)
245            return
246        } else {
247            this.writeTag(Tags.FLOAT32)
248            this.writeFloat32(value as float32)
249        }
250    }
251    writeInt8(value: int32) {
252        this.checkCapacity(1)
253        this.buffer.set(this.position, value as int8)
254        this.position += 1
255    }
256    private setInt32(position: int32, value: int32): void {
257        this.buffer.set(position + 0, ((value      ) & 0xff) as int8)
258        this.buffer.set(position + 1, ((value >>  8) & 0xff) as int8)
259        this.buffer.set(position + 2, ((value >> 16) & 0xff) as int8)
260        this.buffer.set(position + 3, ((value >> 24) & 0xff) as int8)
261    }
262    writeInt32(value: int32) {
263        this.checkCapacity(4)
264        this.setInt32(this.position, value)
265        this.position += 4
266    }
267    writeInt64(value: int64) {
268        this.checkCapacity(8)
269        this.buffer.set(this.position + 0, ((value      ) & 0xff) as int8)
270        this.buffer.set(this.position + 1, ((value >>  8) & 0xff) as int8)
271        this.buffer.set(this.position + 2, ((value >> 16) & 0xff) as int8)
272        this.buffer.set(this.position + 3, ((value >> 24) & 0xff) as int8)
273        this.buffer.set(this.position + 4, ((value >> 32) & 0xff) as int8)
274        this.buffer.set(this.position + 5, ((value >> 40) & 0xff) as int8)
275        this.buffer.set(this.position + 6, ((value >> 48) & 0xff) as int8)
276        this.buffer.set(this.position + 7, ((value >> 56) & 0xff) as int8)
277        this.position += 8
278    }
279    writeFloat32(value: float32) {
280        let bits = int32BitsFromFloat(value)
281        // TODO: this is wrong!
282        this.checkCapacity(4)
283        this.buffer.set(this.position + 0, ((bits      ) & 0xff) as int8)
284        this.buffer.set(this.position + 1, ((bits >>  8) & 0xff) as int8)
285        this.buffer.set(this.position + 2, ((bits >> 16) & 0xff) as int8)
286        this.buffer.set(this.position + 3, ((bits >> 24) & 0xff) as int8)
287        this.position += 4
288    }
289    writePointer(value: pointer) {
290        if (typeof value === "bigint")
291            // todo where it is possible to be called from?
292            throw new Error("Not implemented")
293        this.writeInt64(value)
294    }
295    writeBoolean(value: boolean|undefined) {
296        this.checkCapacity(1)
297        if (value == undefined) {
298            this.buffer.set(this.position, RuntimeType.UNDEFINED as int32 as int8)
299        } else {
300            this.buffer.set(this.position, (value ? 1 : 0) as int8)
301        }
302        this.position++
303    }
304    writeMaterialized(value: Object) {
305        this.writePointer(registerMaterialized(value))
306    }
307    writeString(value: string) {
308        this.checkCapacity((4 + value.length * 4 + 1) as int32) // length, data
309        let encodedLength = InteropNativeModule._ManagedStringWrite(value, this.asArray(), this.position + 4)
310        this.setInt32(this.position, encodedLength)
311        this.position += encodedLength + 4
312    }
313    //TODO: Needs to be implemented
314    writeBuffer(value: NativeBuffer) {
315        this.writeCallbackResource({
316            resourceId: value.resourceId,
317            hold: value.hold,
318            release: value.release
319        })
320        this.writePointer(value.data)
321        this.writeInt64(value.length as int64)
322    }
323}
324
325