-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Sacha Froment <[email protected]> Co-authored-by: hoangquocvietuet <[email protected]> Co-authored-by: droak <[email protected]>
- Loading branch information
1 parent
e72e81b
commit 64ff132
Showing
7 changed files
with
428 additions
and
143 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { SetDRP } from "@ts-drp/blueprints/src/index.js"; | ||
import { DRPObject } from "@ts-drp/object/src/index.js"; | ||
import { beforeAll, describe, expect, test } from "vitest"; | ||
|
||
import { deserializeStateMessage, serializeStateMessage } from "../src/utils.js"; | ||
|
||
describe("State message utils", () => { | ||
let object: DRPObject; | ||
|
||
beforeAll(async () => { | ||
object = DRPObject.createObject({ | ||
peerId: "test", | ||
id: "test", | ||
drp: new SetDRP<number>(), | ||
}); | ||
(object.drp as SetDRP<number>).add(1); | ||
(object.drp as SetDRP<number>).add(2); | ||
(object.drp as SetDRP<number>).add(3); | ||
}); | ||
|
||
test("Should serialize/deserialize state message", async () => { | ||
const state = object["_computeDRPState"].bind(object); | ||
const serialized = serializeStateMessage(state(object.hashGraph.getFrontier())); | ||
const deserialized = deserializeStateMessage(serialized); | ||
expect(deserialized).toStrictEqual(state(object.hashGraph.getFrontier())); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,141 +1,68 @@ | ||
import { Value } from "../proto/google/protobuf/struct_pb.js"; | ||
|
||
export function serializeValue(obj: any): Uint8Array { | ||
const serialized = _serializeToJSON(obj); | ||
return Value.encode(Value.wrap(serialized)).finish(); | ||
} | ||
|
||
export function deserializeValue(value: any): any { | ||
const bytes = new Uint8Array(_objectValues(value)); | ||
const v = Value.decode(bytes); | ||
const unwrapped = Value.unwrap(v); | ||
return _deserializeFromJSON(unwrapped); | ||
} | ||
|
||
function _objectValues(obj: any): any[] { | ||
const tmp: any[] = []; | ||
for (const key in obj) { | ||
tmp.push(obj[key]); | ||
} | ||
return tmp; | ||
} | ||
|
||
function _serializeToJSON(obj: any): any { | ||
// Handle null/undefined | ||
if (obj == null) return null; | ||
|
||
// Handle primitive types | ||
if (typeof obj !== "object") return obj; | ||
|
||
// Handle Date objects | ||
if (obj instanceof Date) { | ||
return { | ||
__type: "Date", | ||
value: obj.toISOString(), | ||
}; | ||
} | ||
|
||
// Handle Maps | ||
if (obj instanceof Map) { | ||
return { | ||
__type: "Map", | ||
value: Array.from(obj.entries()), | ||
}; | ||
} | ||
|
||
// Handle Sets | ||
if (obj instanceof Set) { | ||
return { | ||
__type: "Set", | ||
value: Array.from(obj.values()), | ||
}; | ||
} | ||
|
||
// Handle regular arrays | ||
if (Array.isArray(obj)) { | ||
return obj.map((item) => _serializeToJSON(item)); | ||
} | ||
|
||
// Handle regular objects | ||
const result: any = {}; | ||
for (const [key, value] of Object.entries(obj)) { | ||
// Skip non-enumerable properties and functions | ||
if (typeof value === "function") continue; | ||
|
||
// Handle circular references | ||
try { | ||
result[key] = _serializeToJSON(value); | ||
} catch (e) { | ||
console.warn(`Circular reference detected for key: ${key}`); | ||
result[key] = null; | ||
import { encode, decode, ExtensionCodec } from "@msgpack/msgpack"; | ||
|
||
const extensionCodec = new ExtensionCodec(); | ||
|
||
const SET_EXT_TYPE = 0; // Any in 0-127 | ||
extensionCodec.register({ | ||
type: SET_EXT_TYPE, | ||
encode: (object: unknown): Uint8Array | null => { | ||
if (object instanceof Set) { | ||
return encode([...object], { extensionCodec }); | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
// Add class name if available | ||
if (obj.constructor && obj.constructor.name !== "Object") { | ||
result.__type = obj.constructor.name; | ||
} | ||
|
||
return result; | ||
} | ||
|
||
function _deserializeFromJSON(obj: any): any { | ||
// Handle null/undefined | ||
if (obj == null) return obj; | ||
|
||
// Handle primitive types | ||
if (typeof obj !== "object") return obj; | ||
|
||
// Handle arrays | ||
if (Array.isArray(obj)) { | ||
return obj.map((item) => _deserializeFromJSON(item)); | ||
} | ||
|
||
// Handle special types | ||
if (obj.__type) { | ||
switch (obj.__type) { | ||
case "Date": | ||
return new Date(obj.value); | ||
|
||
case "Map": | ||
return new Map( | ||
obj.value.map(([k, v]: [any, any]) => [_deserializeFromJSON(k), _deserializeFromJSON(v)]) | ||
); | ||
|
||
case "Set": | ||
return new Set(obj.value.map((v: any) => _deserializeFromJSON(v))); | ||
|
||
case "Uint8Array": | ||
return new Uint8Array(obj.value); | ||
|
||
case "Float32Array": | ||
return new Float32Array(obj.value); | ||
|
||
// Add other TypedArrays as needed | ||
|
||
default: | ||
// Try to reconstruct custom class if available | ||
try { | ||
const CustomClass = globalThis[obj.__type as keyof typeof globalThis]; | ||
if (typeof CustomClass === "function") { | ||
return Object.assign( | ||
new CustomClass(), | ||
_deserializeFromJSON({ ...obj, __type: undefined }) | ||
); | ||
} | ||
} catch (e) { | ||
console.warn(`Could not reconstruct class ${obj.__type}`); | ||
} | ||
}, | ||
decode: (data: Uint8Array) => { | ||
const array = decode(data, { extensionCodec }) as Array<unknown>; | ||
return new Set(array); | ||
}, | ||
}); | ||
|
||
// Map<K, V> | ||
const MAP_EXT_TYPE = 1; // Any in 0-127 | ||
extensionCodec.register({ | ||
type: MAP_EXT_TYPE, | ||
encode: (object: unknown): Uint8Array | null => { | ||
if (object instanceof Map) { | ||
return encode([...object], { extensionCodec }); | ||
} else { | ||
return null; | ||
} | ||
} | ||
|
||
// Handle regular objects | ||
const result: any = {}; | ||
for (const [key, value] of Object.entries(obj)) { | ||
if (key !== "__type") { | ||
result[key] = _deserializeFromJSON(value); | ||
}, | ||
decode: (data: Uint8Array) => { | ||
const array = decode(data, { extensionCodec }) as Array<[unknown, unknown]>; | ||
return new Map(array); | ||
}, | ||
}); | ||
|
||
const FLOAT_32_ARRAY_EXT_TYPE = 2; // Any in 0-127 | ||
extensionCodec.register({ | ||
type: FLOAT_32_ARRAY_EXT_TYPE, | ||
encode: (object: unknown): Uint8Array | null => { | ||
if (object instanceof Float32Array) { | ||
return encode([...object], { extensionCodec }); | ||
} else { | ||
return null; | ||
} | ||
} | ||
}, | ||
decode: (data: Uint8Array) => { | ||
const array = decode(data, { extensionCodec }) as Array<number>; | ||
return new Float32Array(array); | ||
}, | ||
}); | ||
|
||
/** | ||
* Main entry point for serialization. | ||
* Converts any value into a Uint8Array using Protocol Buffers. | ||
*/ | ||
export function serializeValue(obj: unknown): Uint8Array { | ||
return encode(obj, { extensionCodec }); | ||
} | ||
|
||
return result; | ||
/** | ||
* Main entry point for deserialization. | ||
* Converts a Uint8Array back into the original value structure. | ||
*/ | ||
export function deserializeValue(value: Uint8Array): unknown { | ||
return decode(value, { extensionCodec }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
import Benchmark from "benchmark"; | ||
|
||
import { deserializeValue } from "../dist/src/index.js"; | ||
import { serializeValue } from "../src/utils/serializer.js"; | ||
function createNestedObject(depth: number, breadth: number): any { | ||
if (depth <= 0) { | ||
return { | ||
num: Math.random(), | ||
str: "test", | ||
date: new Date(), | ||
set: new Set([1, 2, 3]), | ||
map: new Map([ | ||
["a", 1], | ||
["b", 2], | ||
]), | ||
array: new Uint8Array([1, 2, 3, 4]), | ||
float: new Float32Array([1.1, 2.2, 3.3]), | ||
}; | ||
} | ||
const obj: any = {}; | ||
for (let i = 0; i < breadth; i++) { | ||
obj[`child${i}`] = createNestedObject(depth - 1, breadth); | ||
} | ||
return obj; | ||
} | ||
|
||
const suite = new Benchmark.Suite(); | ||
function benchmarkSerializeValue(depth: number, breadth: number) { | ||
return suite.add(`Serialize ${depth} depth ${breadth} breadth`, () => { | ||
// Create a deeply nested structure | ||
// Create test data with depth=5 and breadth=3 | ||
// This creates 3^5 = 243 leaf nodes, each with 7 complex properties | ||
const deepObject = createNestedObject(depth, breadth); | ||
// Warm up | ||
for (let i = 0; i < 3; i++) { | ||
serializeValue(deepObject); | ||
} | ||
// Benchmark | ||
const iterations = 100; | ||
const start = performance.now(); | ||
for (let i = 0; i < iterations; i++) { | ||
serializeValue(deepObject); | ||
} | ||
const end = performance.now(); | ||
const avgMs = (end - start) / iterations; | ||
const leaf = Math.pow(depth, breadth); | ||
console.log(`Average serialization time: ${avgMs.toFixed(2)}ms`); | ||
console.log(`Object stats: | ||
- Depth: ${depth} | ||
- Breadth: ${breadth} | ||
- Leaf nodes: ${leaf} | ||
- Complex properties per leaf: 7 | ||
- Total complex values: ${leaf * 7} | ||
`); | ||
}); | ||
} | ||
|
||
benchmarkSerializeValue(5, 5); | ||
|
||
suite | ||
.on("cycle", (event: Benchmark.Event) => { | ||
console.log(String(event.target)); | ||
}) | ||
.on("complete", function (this: Benchmark.Suite) { | ||
console.log(`Fastest is ${this.filter("fastest").map("name")}`); | ||
}) | ||
.run({ async: true }); | ||
|
||
function benchmarkDeserializeValue(depth: number, breadth: number) { | ||
return suite.add(`Deserialize ${depth} depth ${breadth} breadth`, () => { | ||
// Create a deeply nested structure | ||
// Create test data with depth=5 and breadth=3 | ||
// This creates 3^5 = 243 leaf nodes, each with 7 complex properties | ||
const deepObject = createNestedObject(depth, breadth); | ||
const serialized = serializeValue(deepObject); | ||
// Warm up | ||
for (let i = 0; i < 3; i++) { | ||
deserializeValue(serialized); | ||
} | ||
// Benchmark | ||
const iterations = 100; | ||
const start = performance.now(); | ||
for (let i = 0; i < iterations; i++) { | ||
deserializeValue(serialized); | ||
} | ||
const end = performance.now(); | ||
const avgMs = (end - start) / iterations; | ||
const leaf = Math.pow(depth, breadth); | ||
console.log(`Average deserialization time: ${avgMs.toFixed(2)}ms`); | ||
console.log(`Object stats: | ||
- Depth: ${depth} | ||
- Breadth: ${breadth} | ||
- Leaf nodes: ${leaf} | ||
- Complex properties per leaf: 7 | ||
- Total complex values: ${leaf * 7} | ||
`); | ||
}); | ||
} | ||
|
||
benchmarkDeserializeValue(5, 5); | ||
|
||
suite | ||
.on("cycle", (event: Benchmark.Event) => { | ||
console.log(String(event.target)); | ||
}) | ||
.on("complete", function (this: Benchmark.Suite) { | ||
console.log(`Fastest is ${this.filter("fastest").map("name")}`); | ||
}) | ||
.run({ async: true }); |
Oops, something went wrong.