idlize
309 строк · 10.4 Кб
1/*
2* Copyright (c) 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*/
15import { float32, int32 } from "@koalaui/common"16import { pointer } from "@koalaui/interop"17import { nativeModule } from "@koalaui/arkoala"18import { wrapCallback } from "./callback_registry"19import { FinalizableBase } from "./Finalizable"20
21// imports required intarfaces (now generation is disabled)
22// import { Resource, Length, PixelMap } from "@arkoala/arkui"
23/**
24* Value representing possible JS runtime object type.
25* Must be synced with "enum RuntimeType" in C++.
26*/
27export enum RuntimeType {28UNEXPECTED = -1,29NUMBER = 1,30STRING = 2,31OBJECT = 3,32BOOLEAN = 4,33UNDEFINED = 5,34BIGINT = 6,35FUNCTION = 7,36SYMBOL = 8,37MATERIALIZED = 9,38}
39
40/**
41* Value representing object type in serialized data.
42* Must be synced with "enum Tags" in C++.
43*/
44export enum Tags {45UNDEFINED = 101,46INT32 = 102,47FLOAT32 = 103,48STRING = 104,49LENGTH = 105,50RESOURCE = 106,51OBJECT = 107,52}
53
54export function runtimeType(value: any): int32 {55let type = typeof value56if (type == "number") return RuntimeType.NUMBER57if (type == "string") return RuntimeType.STRING58if (type == "undefined") return RuntimeType.UNDEFINED59if (type == "object") return RuntimeType.OBJECT60if (type == "boolean") return RuntimeType.BOOLEAN61if (type == "bigint") return RuntimeType.BIGINT62if (type == "function") return RuntimeType.FUNCTION63if (type == "symbol") return RuntimeType.SYMBOL64
65throw new Error(`bug: ${value} is ${type}`)66}
67
68export function isPixelMap(value: Object): value is PixelMap {69// Object.hasOwn need es202270return value.hasOwnProperty('isEditable') && value.hasOwnProperty('isStrideAlignment')71}
72
73export function isResource(value: Object): value is Resource {74return value.hasOwnProperty("bundleName") && value.hasOwnProperty("moduleName")75}
76
77// Poor man's instanceof, fails on subclasses
78export function isInstanceOf(className: string, value: Object): boolean {79return value.constructor.name === className80}
81
82export function withLength(valueLength: Length|undefined, body: (type: int32, value: float32, unit: int32, resource: int32) => void) {83let type = runtimeType(valueLength)84let value = 085let unit = 1 // vp86let resource = 087switch (type) {88case RuntimeType.UNDEFINED:89value = 090unit = 091break92case RuntimeType.NUMBER:93value = valueLength as float3294break95case RuntimeType.STRING:96let valueStr = valueLength as string97// TODO: faster parse.98if (valueStr.endsWith("vp")) {99unit = 1 // vp100value = Number(valueStr.substring(0, valueStr.length - 2))101} else if (valueStr.endsWith("%")) {102unit = 3 // percent103value = Number(valueStr.substring(0, valueStr.length - 1))104} else if (valueStr.endsWith("lpx")) {105unit = 4 // lpx106value = Number(valueStr.substring(0, valueStr.length - 3))107} else if (valueStr.endsWith("px")) {108unit = 0 // px109value = Number(valueStr.substring(0, valueStr.length - 2))110}111break112case RuntimeType.OBJECT:113resource = (valueLength as Resource).id114break115}116body(type, value, unit, resource)117}
118
119export function withLengthArray(valueLength: Length|undefined, body: (valuePtr: Int32Array) => void) {120withLength(valueLength, (type: int32, value, unit, resource) => {121let array = new Int32Array(4)122array[0] = type123array[1] = value124array[2] = unit125array[3] = resource126body(array)127})128}
129
130export function registerCallback(value: object|undefined): int32 {131return wrapCallback((args: Uint8Array, length: int32) => {132// TBD: deserialize the callback arguments and call the callback133return 42134})135}
136
137export function registerMaterialized(value: object|undefined): number {138// TODO: fix me!139return 42140}
141
142class SerializersCache {143cache: Array<SerializerBase|undefined>144constructor(maxCount: number) {145this.cache = new Array<SerializerBase|undefined>(maxCount)146}147get<T extends SerializerBase>(factory: () => T, index: int32): T {148let result = this.cache[index]149if (result) {150result.resetCurrentPosition()151return result as T152}153result = factory()154this.cache[index] = result155return result as T156}157}
158
159/* Serialization extension point */
160export abstract class CustomSerializer {161constructor(protected supported: Array<string>) {}162supports(kind: string): boolean { return this.supported.includes(kind) }163abstract serialize(serializer: SerializerBase, value: any, kind: string): void164next: CustomSerializer | undefined = undefined165}
166
167export class SerializerBase {168private static cache = new SerializersCache(22)169
170private position = 0171private buffer: ArrayBuffer172private view: DataView173
174private static customSerializers: CustomSerializer | undefined = undefined175static registerCustomSerializer(serializer: CustomSerializer) {176if (SerializerBase.customSerializers == undefined) {177SerializerBase.customSerializers = serializer178} else {179let current = SerializerBase.customSerializers180while (current.next != undefined) { current = current.next }181current.next = serializer182}183}184constructor() {185this.buffer = new ArrayBuffer(96)186this.view = new DataView(this.buffer)187}188
189static get<T extends SerializerBase>(factory: () => T, index: int32): T {190return SerializerBase.cache.get<T>(factory, index)191}192asArray(): Uint8Array {193return new Uint8Array(this.buffer)194}195length(): int32 {196return this.position197}198currentPosition(): int32 { return this.position }199resetCurrentPosition(): void { this.position = 0 }200
201private checkCapacity(value: int32) {202if (value < 1) {203throw new Error(`${value} is less than 1`)204}205let buffSize = this.buffer.byteLength206if (this.position > buffSize - value) {207const minSize = this.position + value208const resizedSize = Math.max(minSize, Math.round(3 * buffSize / 2))209let resizedBuffer = new ArrayBuffer(resizedSize)210// TODO: can we grow without new?211new Uint8Array(resizedBuffer).set(new Uint8Array(this.buffer))212this.buffer = resizedBuffer213this.view = new DataView(resizedBuffer)214}215}216writeCustomObject(kind: string, value: any) {217let current = SerializerBase.customSerializers218while (current) {219if (current.supports(kind)) {220current.serialize(this, value, kind)221return222}223current = current.next224}225console.log(`Unsupported custom serialization for ${kind}, write undefined`)226this.writeInt8(Tags.UNDEFINED)227}228writeNumber(value: number|undefined) {229this.checkCapacity(5)230if (value == undefined) {231this.view.setInt8(this.position, Tags.UNDEFINED)232this.position++233return234}235if (value == Math.round(value)) {236this.view.setInt8(this.position, Tags.INT32)237this.view.setInt32(this.position + 1, value, true)238this.position += 5239return240}241this.view.setInt8(this.position, Tags.FLOAT32)242this.view.setFloat32(this.position + 1, value, true)243this.position += 5244}245writeInt8(value: int32) {246this.checkCapacity(1)247this.view.setInt8(this.position, value)248this.position += 1249}250writeInt32(value: int32) {251this.checkCapacity(4)252this.view.setInt32(this.position, value, true)253this.position += 4254}255writePointer(value: pointer) {256this.checkCapacity(8)257this.view.setBigInt64(this.position, BigInt(value), true)258this.position += 8259}260writeFloat32(value: float32) {261this.checkCapacity(4)262this.view.setFloat32(this.position, value, true)263this.position += 4264}265writeBoolean(value: boolean|undefined) {266this.checkCapacity(1)267this.view.setInt8(this.position, value == undefined ? RuntimeType.UNDEFINED : +value)268this.position++269}270writeFunction(value: object | undefined) {271this.writeInt32(registerCallback(value))272}273writeMaterialized(value: object | undefined) {274this.writePointer(value ? (value as FinalizableBase).ptr : 0)275}276writeString(value: string) {277this.checkCapacity(4 + value.length * 4) // length, data278let encodedLength =279nativeModule()._ManagedStringWrite(value, new Uint8Array(this.view.buffer, 0), this.position + 4)280this.view.setInt32(this.position, encodedLength, true)281this.position += encodedLength + 4282}283// Length is an important common case.284writeLength(value: Length|undefined) {285this.checkCapacity(1)286let valueType = runtimeType(value)287this.writeInt8(valueType)288if (valueType == RuntimeType.NUMBER) {289this.writeFloat32(value as number)290} else if (valueType == RuntimeType.STRING) {291this.writeString(value as string)292} else if (valueType == RuntimeType.OBJECT) {293this.writeInt32((value as Resource).id)294}295}296}
297
298class OurCustomSerializer extends CustomSerializer {299constructor() {300super(["Resource", "Pixmap"])301}302serialize(serializer: SerializerBase, value: any, kind: string): void {303// console.log(`managed serialize() for ${kind}`)304serializer.writeString(JSON.stringify(value))305}306}
307
308// TODO, remove me!
309SerializerBase.registerCustomSerializer(new OurCustomSerializer())