From 6cb6be92d73278bc413b5cc29a21db423bfe76e8 Mon Sep 17 00:00:00 2001 From: jtuu Date: Mon, 1 Jun 2020 01:17:26 +0300 Subject: [PATCH] Made prs-rs an optional module. Compression will fall back into the JS implementation if program is not built with prs-rs. --- package.json | 2 +- .../data_formats/compression/prs/compress.ts | 20 ++---- .../compression/prs/decompress.ts | 21 ++---- .../compression/prs/index.test.ts | 69 ++++++++++++++----- .../data_formats/compression/prs/prs_wasm.ts | 50 ++++++++++++++ typedefs/prs-rs.d.ts | 16 +++++ 6 files changed, 127 insertions(+), 51 deletions(-) create mode 100644 src/core/data_formats/compression/prs/prs_wasm.ts create mode 100644 typedefs/prs-rs.d.ts diff --git a/package.json b/package.json index 65b9d40b..3238b1e9 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "build_prs_rs_browser": "yarn build_prs_rs -t bundler && yarn upgrade prs-rs", "build_prs_rs_testing": "yarn build_prs_rs -t nodejs -d 'test/pkg'", "build_bundle": "webpack --config webpack.prod.js", - "build": "yarn build_prs_rs_browser && yarn build_bundle", + "build": "yarn build_bundle", "test": "jest", "update_generic_data": "ts-node --project=tsconfig-scripts.json assets_generation/update_generic_data.ts", "update_ephinea_data": "ts-node --project=tsconfig-scripts.json assets_generation/update_ephinea_data.ts", diff --git a/src/core/data_formats/compression/prs/compress.ts b/src/core/data_formats/compression/prs/compress.ts index 90ea9194..fa50957f 100644 --- a/src/core/data_formats/compression/prs/compress.ts +++ b/src/core/data_formats/compression/prs/compress.ts @@ -3,16 +3,17 @@ import { Cursor } from "../../cursor/Cursor"; import { ResizableBufferCursor } from "../../cursor/ResizableBufferCursor"; import { WritableCursor } from "../../cursor/WritableCursor"; import { ResizableBuffer } from "../../ResizableBuffer"; -import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor"; -import * as prs_wasm from "prs-rs"; import { browser_supports_webassembly } from "../../../util"; +import { get_prs_wasm_module } from "./prs_wasm"; + +const prs_wasm = get_prs_wasm_module(); /** * Automatically picks the best available compression method. */ export function prs_compress(cursor: Cursor): Cursor { - if (browser_supports_webassembly()) { - return prs_compress_wasm(cursor); + if (browser_supports_webassembly() && prs_wasm) { + return prs_wasm.prs_compress_wasm(cursor); } else { return prs_compress_js(cursor); } @@ -149,14 +150,3 @@ class Context { } } } - -export function prs_compress_wasm(cursor: Cursor): Cursor { - const bytes = new Uint8Array(cursor.array_buffer()); - const result = prs_wasm.compress(bytes); - return new ArrayBufferCursor( - result.buffer, - Endianness.Little, - result.byteOffset, - result.length, - ); -} diff --git a/src/core/data_formats/compression/prs/decompress.ts b/src/core/data_formats/compression/prs/decompress.ts index 720a5ca7..dd7ef3ed 100644 --- a/src/core/data_formats/compression/prs/decompress.ts +++ b/src/core/data_formats/compression/prs/decompress.ts @@ -3,19 +3,19 @@ import { ResizableBufferCursor } from "../../cursor/ResizableBufferCursor"; import { WritableCursor } from "../../cursor/WritableCursor"; import { ResizableBuffer } from "../../ResizableBuffer"; import { LogManager } from "../../../Logger"; -import * as prs_wasm from "prs-rs"; import { browser_supports_webassembly } from "../../../util"; -import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor"; -import { Endianness } from "../../Endianness"; +import { get_prs_wasm_module } from "./prs_wasm"; const logger = LogManager.get("core/data_formats/compression/prs/decompress"); +const prs_wasm = get_prs_wasm_module(); + /** * Automatically picks the best available decompression method. */ export function prs_decompress(cursor: Cursor): Cursor { - if (browser_supports_webassembly()) { - return prs_decompress_wasm(cursor); + if (browser_supports_webassembly() && prs_wasm) { + return prs_wasm.prs_decompress_wasm(cursor); } else { return prs_decompress_js(cursor); } @@ -135,14 +135,3 @@ class Context { this.dst.write_cursor(buf.take(length % buf_size)); } } - -export function prs_decompress_wasm(cursor: Cursor): Cursor { - const bytes = new Uint8Array(cursor.array_buffer()); - const result = prs_wasm.decompress(bytes); - return new ArrayBufferCursor( - result.buffer, - Endianness.Little, - result.byteOffset, - result.length, - ); -} diff --git a/src/core/data_formats/compression/prs/index.test.ts b/src/core/data_formats/compression/prs/index.test.ts index 03aaadae..b333fa50 100644 --- a/src/core/data_formats/compression/prs/index.test.ts +++ b/src/core/data_formats/compression/prs/index.test.ts @@ -2,16 +2,41 @@ import { readFileSync } from "fs"; import { Endianness } from "../../Endianness"; import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor"; import { BufferCursor } from "../../cursor/BufferCursor"; -import { prs_compress_js, prs_compress_wasm } from "./compress"; -import { prs_decompress_js, prs_decompress_wasm } from "./decompress"; +import { prs_compress_js } from "./compress"; +import { prs_decompress_js } from "./decompress"; import { Cursor } from "../../cursor/Cursor"; +import { get_prs_wasm_module } from "./prs_wasm"; type CompressionFunction = (cursor: Cursor) => Cursor; -type CompressionMethod = readonly [string, CompressionFunction, CompressionFunction]; +type CompressionMethod = [string, CompressionFunction, CompressionFunction]; -const prs_js: CompressionMethod = ["JS", prs_compress_js, prs_decompress_js] as const; -const prs_wasm: CompressionMethod = ["WASM", prs_compress_wasm, prs_decompress_wasm] as const; +const prs_nop = (cursor: Cursor): Cursor => cursor; + +const prs_wasm_module = get_prs_wasm_module(); + +const should_run_wasm_tests = prs_wasm_module !== undefined; + +const prs_compress_wasm = prs_wasm_module + ? prs_wasm_module.prs_compress_wasm.bind(prs_wasm_module) + : prs_nop; +const prs_decompress_wasm = prs_wasm_module + ? prs_wasm_module.prs_decompress_wasm.bind(prs_wasm_module) + : prs_nop; + +const prs_js: CompressionMethod = ["JS", prs_compress_js, prs_decompress_js]; +const prs_wasm: CompressionMethod = ["WASM", prs_compress_wasm, prs_decompress_wasm]; + +/** + * Helper function that removes wasm tests if wasm module is not available. + */ +function prepare_test_data(js_test_data: any[][], wasm_test_data: any[][]): any[] { + if (should_run_wasm_tests) { + return [...js_test_data, ...wasm_test_data]; + } else { + return js_test_data; + } +} function test_with_bytes( compress_fn: CompressionFunction, @@ -40,7 +65,7 @@ function test_with_bytes( expect(test_cursor.position).toBe(test_cursor.size); } -test.each([[prs_js, 475].flat(), [prs_wasm, 134].flat()])( +test.each(prepare_test_data([[...prs_js, 475]], [[...prs_wasm, 134]]))( "%s PRS compression and decompression, best case", ( _name: string, @@ -52,7 +77,7 @@ test.each([[prs_js, 475].flat(), [prs_wasm, 134].flat()])( }, ); -test.each([[prs_js, 11253].flat(), [prs_wasm, 11250].flat()])( +test.each(prepare_test_data([[...prs_js, 11253]], [[...prs_wasm, 11250]]))( "%s PRS compression and decompression, worst case", ( _name: string, @@ -70,7 +95,7 @@ test.each([[prs_js, 11253].flat(), [prs_wasm, 11250].flat()])( }, ); -test.each([[prs_js, 14924].flat(), [prs_wasm, 12901].flat()])( +test.each(prepare_test_data([[...prs_js, 14924]], [[...prs_wasm, 12901]]))( "%s PRS compression and decompression, typical case", ( _name: string, @@ -87,16 +112,22 @@ test.each([[prs_js, 14924].flat(), [prs_wasm, 12901].flat()])( }, ); -test.each([ - [prs_js[0], 0, prs_js[1], prs_js[2], [], 3], - [prs_js[0], 1, prs_js[1], prs_js[2], [111], 4], - [prs_js[0], 2, prs_js[1], prs_js[2], [111, 224], 5], - [prs_js[0], 3, prs_js[1], prs_js[2], [56, 237, 158], 6], - [prs_wasm[0], 0, prs_wasm[1], prs_wasm[2], [], 3], - [prs_wasm[0], 1, prs_wasm[1], prs_wasm[2], [111], 4], - [prs_wasm[0], 2, prs_wasm[1], prs_wasm[2], [111, 224], 5], - [prs_wasm[0], 3, prs_wasm[1], prs_wasm[2], [56, 237, 158], 6], -])( +test.each( + prepare_test_data( + [ + [prs_js[0], 0, prs_js[1], prs_js[2], [], 3], + [prs_js[0], 1, prs_js[1], prs_js[2], [111], 4], + [prs_js[0], 2, prs_js[1], prs_js[2], [111, 224], 5], + [prs_js[0], 3, prs_js[1], prs_js[2], [56, 237, 158], 6], + ], + [ + [prs_wasm[0], 0, prs_wasm[1], prs_wasm[2], [], 3], + [prs_wasm[0], 1, prs_wasm[1], prs_wasm[2], [111], 4], + [prs_wasm[0], 2, prs_wasm[1], prs_wasm[2], [111, 224], 5], + [prs_wasm[0], 3, prs_wasm[1], prs_wasm[2], [56, 237, 158], 6], + ], + ), +)( "%s PRS compression and decompression, %d bytes", ( _name: string, @@ -140,7 +171,7 @@ function test_with_quest( expect(matching_bytes).toBe(orig.size); } -test.each([prs_js, prs_wasm])( +test.each(prepare_test_data([prs_js], [prs_wasm]))( "%s PRS compression and decompression of quest118_e.bin", (_name: string, compress_fn: CompressionFunction, decompress_fn: CompressionFunction) => { test_with_quest(compress_fn, decompress_fn, "test/resources/quest118_e.bin"); diff --git a/src/core/data_formats/compression/prs/prs_wasm.ts b/src/core/data_formats/compression/prs/prs_wasm.ts new file mode 100644 index 00000000..d2d60d52 --- /dev/null +++ b/src/core/data_formats/compression/prs/prs_wasm.ts @@ -0,0 +1,50 @@ +/** + * @file Exposes the WASM PRS functions depending on whether the WASM module is available or not. + */ + +import { browser_supports_webassembly } from "../../../util"; +import { Cursor } from "../../cursor/Cursor"; +import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor"; +import { Endianness } from "../../Endianness"; + +type PrsRsModule = typeof import("prs-rs"); + +class PrsWasm { + constructor(private module: PrsRsModule) {} + + public prs_compress_wasm(cursor: Cursor): Cursor { + const bytes = new Uint8Array(cursor.array_buffer()); + const result = this.module.compress(bytes); + return new ArrayBufferCursor( + result.buffer, + Endianness.Little, + result.byteOffset, + result.length, + ); + } + + public prs_decompress_wasm(cursor: Cursor): Cursor { + const bytes = new Uint8Array(cursor.array_buffer()); + const result = this.module.decompress(bytes); + return new ArrayBufferCursor( + result.buffer, + Endianness.Little, + result.byteOffset, + result.length, + ); + } +} + +// Load prs-rs if it exists. +let prs_wasm: PrsWasm | undefined = undefined; +try { + if (browser_supports_webassembly()) { + prs_wasm = new PrsWasm(require("prs-rs")); + } +} catch (e) { + // Webpack will emit a warning if module is missing. +} + +export function get_prs_wasm_module(): PrsWasm | undefined { + return prs_wasm; +} diff --git a/typedefs/prs-rs.d.ts b/typedefs/prs-rs.d.ts new file mode 100644 index 00000000..d2240790 --- /dev/null +++ b/typedefs/prs-rs.d.ts @@ -0,0 +1,16 @@ +/** + * @file This file is needed because prs-rs is an optional module and typescript will complain if code refers to an undeclared module. + */ + +declare module "prs-rs" { + /** + * @param {Uint8Array} data + * @returns {Uint8Array} + */ + export function compress(data: Uint8Array): Uint8Array; + /** + * @param {Uint8Array} data + * @returns {Uint8Array} + */ + export function decompress(data: Uint8Array): Uint8Array; +}