more serialization

This commit is contained in:
2023-07-08 16:05:49 +02:00
parent 1d7c573985
commit c771706353
5 changed files with 67 additions and 109 deletions

View File

@@ -2,8 +2,6 @@ import {describe, it, expect} from "vitest"
import {
chordAsCommandCompatible,
chordFromCommandCompatible,
chordsFromFile,
chordsToFile,
deserializeActions,
serializeActions,
} from "./chord"
@@ -41,16 +39,4 @@ describe("chords", function () {
})
})
})
describe("chl file format", function () {
const fileData: Chord[] = [
{phrase: [1, 2, 3, 4], actions: [5, 6, 7, 8, 9]},
{phrase: [10, 11], actions: [12, 13, 14, 15]},
{phrase: [16], actions: [17]},
]
it("should should convert back-forth a file", function () {
expect(chordsFromFile(chordsToFile(fileData))).toEqual(fileData)
})
})
})

View File

@@ -57,97 +57,3 @@ export function deserializeActions(native: bigint): number[] {
return actions
}
const CHL_VERSION = 1
const CHL_MAGIC = "CHL"
/**
* Binary serialization of the chord library
*
* Layout is as follows:
* ```rs
* struct Chords {
* magic: "CHL"
* version: u8
* chordCount: u32
* chords: chord[]
* }
*
* struct Chord {
* id: u32
* phrase: u128
* actionCount: u16
* actions: u8[]
* }
* ```
* Serialized as little endian.
*
* @param chords
*/
export function chordsToFile(chords: Chord[]): ArrayBuffer {
const actionsTotalCount = chords.reduce((size, chord) => size + chord.actions.length, 0)
const buffer = new ArrayBuffer(4 + 4 + chords.length * (4 + 16 + 2) + actionsTotalCount)
const view = new DataView(buffer)
let byteOffset = 0
for (const byte of CHL_MAGIC.split("")) {
view.setUint8(byteOffset++, byte.codePointAt(0)!)
}
view.setUint8(byteOffset++, CHL_VERSION)
view.setUint32(byteOffset, chords.length, true)
byteOffset += 4
for (const chord of chords) {
const actions = serializeActions(chord.actions)
view.setBigUint64(byteOffset, actions >> 64n, true)
byteOffset += 8
view.setBigUint64(byteOffset, actions & 0xffff_ffff_ffff_ffffn, true)
byteOffset += 8
view.setUint16(byteOffset, chord.phrase.length, true)
byteOffset += 2
for (const action of chord.phrase) {
view.setUint8(byteOffset++, action)
}
}
return buffer
}
/**
* @see {chordsToFile}
*/
export function chordsFromFile(buffer: ArrayBuffer): Chord[] {
const view = new DataView(buffer)
let byteOffset = 0
const magic = []
for (let i = 0; i < CHL_MAGIC.length; i++) {
magic.push(view.getUint8(byteOffset++))
}
const magicString = String.fromCodePoint(...magic)
if (magicString !== CHL_MAGIC) throw new Error(`Not a .chl file [magic ${magicString}]`)
if (view.getUint8(byteOffset++) !== CHL_VERSION) throw Error("Invalid .chl [version]")
const chords: Chord[] = Array.from({length: view.getUint32(byteOffset, true)})
byteOffset += 4
for (let i = 0; i < chords.length; i++) {
let actions = view.getBigUint64(byteOffset, true) << 64n
byteOffset += 8
actions |= view.getBigUint64(byteOffset, true)
byteOffset += 8
const phrase: number[] = Array.from({length: view.getUint16(byteOffset, true)})
byteOffset += 2
for (let i = 0; i < phrase.length; i++) {
phrase[i] = view.getUint8(byteOffset++)
}
chords[i] = {
actions: deserializeActions(actions),
phrase,
}
}
return chords
}

View File

@@ -0,0 +1,33 @@
/**
* Compress JSON.stringify with gzip
*/
export async function stringifyCompressed(chords: any): Promise<Blob> {
const stream = new Blob([JSON.stringify(chords)]).stream().pipeThrough(new CompressionStream("gzip"))
return await new Response(stream).blob()
}
/**
* Decompress JSON.parse with gzip
*/
export async function parseCompressed<T>(blob: Blob): Promise<T> {
const stream = blob.stream().pipeThrough(new DecompressionStream("gzip"))
return await new Response(stream).json()
}
/**
* Share JS object as url query param
*/
export async function getSharableUrl(name: string, data: any, baseHref = window.location.href): Promise<URL> {
return new Promise(async resolve => {
const reader = new FileReader()
reader.onloadend = function () {
const base64String = (reader.result as string)
.replace(/^data:application\/octet-stream;base64,/, "")
.replace(/==$/, "")
const url = new URL(baseHref)
url.searchParams.set(name, base64String)
resolve(url)
}
reader.readAsDataURL(await stringifyCompressed(data))
})
}