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
7
* http://www.apache.org/licenses/LICENSE-2.0
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.
15
import { float32, int32 } from "@koalaui/common"
16
import { pointer } from "@koalaui/interop"
17
import { Length, Resource } from "./dts-exports"
18
import { NativeModule } from "@arkoala/arkui/NativeModule"
21
* Value representing possible JS runtime object type.
22
* Must be synced with "enum RuntimeType" in C++.
24
export enum RuntimeType {
38
* Value representing object type in serialized data.
39
* Must be synced with "enum Tags" in C++.
42
static UNDEFINED = 101
51
export function runtimeType(value: Object|String|number|undefined|null|void): RuntimeType {
52
let type = typeof value
53
if (type == "number") return RuntimeType.NUMBER
54
if (type == "string") return RuntimeType.STRING
55
if (type == "undefined") return RuntimeType.UNDEFINED
56
if (type == "object") return RuntimeType.OBJECT
57
if (type == "boolean") return RuntimeType.BOOLEAN
58
if (type == "bigint") return RuntimeType.BIGINT
59
if (type == "function") return RuntimeType.FUNCTION
60
if (type == "symbol") return RuntimeType.SYMBOL
62
throw new Error(`bug: ${value} is ${type}`)
65
export function registerCallback(value: object): int32 {
70
function registerMaterialized(value: Object): int32 {
75
export function isPixelMap(value: Object|undefined): boolean {
80
export function isResource(value: Object|undefined): boolean {
85
export function isInstanceOf(className: string, value: Object): boolean {
90
/* Serialization extension point */
91
export abstract class CustomSerializer {
92
protected supported: Array<string>
93
constructor(supported: Array<string>) {
94
this.supported = supported
96
supports(kind: string): boolean { return this.supported.includes(kind) }
97
abstract serialize(serializer: SerializerBase, value: object, kind: string): void
98
next: CustomSerializer | undefined = undefined
101
class SerializersCache {
102
cache: Array<SerializerBase|undefined>
103
constructor(maxCount: number) {
104
this.cache = new Array<SerializerBase|undefined>(maxCount)
106
get<T extends SerializerBase>(factory: () => T, index: int32): T {
107
let result = this.cache[index]
109
result.resetCurrentPosition()
113
this.cache[index] = result
118
export class SerializerBase {
119
private static cache = new SerializersCache(22)
122
private buffer: byte[]
124
private static customSerializers: CustomSerializer | undefined = undefined
125
static registerCustomSerializer(serializer: CustomSerializer) {
126
if (SerializerBase.customSerializers == undefined) {
127
SerializerBase.customSerializers = serializer
129
let current = SerializerBase.customSerializers
130
while (current!.next != undefined) {
131
current = current!.next
133
current!.next = serializer
136
resetCurrentPosition(): void { this.position = 0 }
139
this.buffer = new byte[96]
141
static get<T extends SerializerBase>(factory: () => T, index: int32): T {
142
return SerializerBase.cache.get<T>(factory, index)
150
currentPosition(): int32 { return this.position }
151
private checkCapacity(value: int32) {
153
throw new Error(`${value} is less than 1`)
155
let buffSize = this.buffer.length
156
if (this.position > buffSize - value) {
157
const minSize = this.position + value
158
const resizedSize = Math.max(minSize, Math.round(3 * buffSize / 2))
159
let resizedBuffer = new byte[resizedSize]
160
for (let i = 0; i < this.buffer.length; i++) {
161
resizedBuffer[i] = this.buffer[i]
163
this.buffer = resizedBuffer
166
writeCustomObject(kind: string, value: object) {
167
let current = SerializerBase.customSerializers
169
if (current!.supports(kind)) {
170
current!.serialize(this, value, kind)
173
current = current!.next
175
console.log(`Unsupported custom serialization for ${kind}, write undefined`)
176
this.writeInt8(Tags.UNDEFINED as int32)
178
writeFunction(value: Object) {
179
this.writeInt32(registerCallback(value))
181
writeTag(tag: int32): void {
182
this.buffer[this.position] = tag as byte
185
writeNumber(value: number|undefined) {
186
this.checkCapacity(5)
187
if (value == undefined) {
188
this.writeTag(Tags.UNDEFINED)
192
if ((value as double) == Math.round(value)) {
193
this.writeTag(Tags.INT32)
194
this.writeInt32(value as int32)
197
this.writeTag(Tags.FLOAT32)
198
this.writeFloat32(value as float32)
201
writeInt8(value: int32) {
202
this.checkCapacity(1)
203
this.buffer[this.position] = value as byte
206
private setInt32(position: int32, value: int32): void {
207
this.buffer[position + 0] = ((value ) & 0xff) as byte
208
this.buffer[position + 1] = ((value >> 8) & 0xff) as byte
209
this.buffer[position + 2] = ((value >> 16) & 0xff) as byte
210
this.buffer[position + 3] = ((value >> 24) & 0xff) as byte
212
writeInt32(value: int32) {
213
this.checkCapacity(4)
214
this.setInt32(this.position, value)
217
writeFloat32(value: float32) {
218
// TODO: this is wrong!
219
this.checkCapacity(4)
220
this.buffer[this.position + 0] = ((value ) & 0xff) as byte
221
this.buffer[this.position + 1] = ((value >> 8) & 0xff) as byte
222
this.buffer[this.position + 2] = ((value >> 16) & 0xff) as byte
223
this.buffer[this.position + 3] = ((value >> 24) & 0xff) as byte
226
writePointer(value: pointer) {
227
this.checkCapacity(8)
228
this.buffer[this.position + 0] = ((value ) & 0xff) as byte
229
this.buffer[this.position + 1] = ((value >> 8) & 0xff) as byte
230
this.buffer[this.position + 2] = ((value >> 16) & 0xff) as byte
231
this.buffer[this.position + 3] = ((value >> 24) & 0xff) as byte
232
this.buffer[this.position + 4] = ((value >> 32) & 0xff) as byte
233
this.buffer[this.position + 5] = ((value >> 40) & 0xff) as byte
234
this.buffer[this.position + 6] = ((value >> 48) & 0xff) as byte
235
this.buffer[this.position + 7] = ((value >> 56) & 0xff) as byte
238
writeBoolean(value: boolean|undefined) {
239
this.checkCapacity(1)
240
if (value == undefined) {
241
this.buffer[this.position] = RuntimeType.UNDEFINED as int32 as byte
243
this.buffer[this.position] = (value ? 1 : 0) as byte
247
writeMaterialized(value: Object) {
248
this.writePointer(registerMaterialized(value))
250
writeString(value: string) {
251
this.checkCapacity((4 + value.length * 4 + 1) as int32) // length, data
252
let encodedLength = NativeModule._ManagedStringWrite(value, this.buffer, this.position + 4)
253
this.setInt32(this.position, encodedLength)
254
this.position += encodedLength + 4
256
// Length is an important common case.
257
writeLength(value: Length|undefined) {
258
this.checkCapacity(1)
259
let valueType = runtimeType(value)
260
this.writeInt8(valueType as int32)
261
if (valueType == RuntimeType.NUMBER) {
262
this.writeFloat32(value as float32)
263
} else if (valueType == RuntimeType.STRING) {
264
this.writeString(value as string)
265
} else if (valueType == RuntimeType.OBJECT) {
266
this.writeInt32((value as Resource).id as int32)