Made prs-rs an optional module.

Compression will fall back into the JS implementation if program is not built with prs-rs.
This commit is contained in:
jtuu 2020-06-01 01:17:26 +03:00
parent 172b8e2fd4
commit 6cb6be92d7
6 changed files with 127 additions and 51 deletions

View File

@ -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",

View File

@ -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,
);
}

View File

@ -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,
);
}

View File

@ -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");

View File

@ -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;
}

16
typedefs/prs-rs.d.ts vendored Normal file
View File

@ -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;
}