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_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_prs_rs_testing": "yarn build_prs_rs -t nodejs -d 'test/pkg'",
"build_bundle": "webpack --config webpack.prod.js", "build_bundle": "webpack --config webpack.prod.js",
"build": "yarn build_prs_rs_browser && yarn build_bundle", "build": "yarn build_bundle",
"test": "jest", "test": "jest",
"update_generic_data": "ts-node --project=tsconfig-scripts.json assets_generation/update_generic_data.ts", "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", "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 { ResizableBufferCursor } from "../../cursor/ResizableBufferCursor";
import { WritableCursor } from "../../cursor/WritableCursor"; import { WritableCursor } from "../../cursor/WritableCursor";
import { ResizableBuffer } from "../../ResizableBuffer"; import { ResizableBuffer } from "../../ResizableBuffer";
import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor";
import * as prs_wasm from "prs-rs";
import { browser_supports_webassembly } from "../../../util"; 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. * Automatically picks the best available compression method.
*/ */
export function prs_compress(cursor: Cursor): Cursor { export function prs_compress(cursor: Cursor): Cursor {
if (browser_supports_webassembly()) { if (browser_supports_webassembly() && prs_wasm) {
return prs_compress_wasm(cursor); return prs_wasm.prs_compress_wasm(cursor);
} else { } else {
return prs_compress_js(cursor); 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 { WritableCursor } from "../../cursor/WritableCursor";
import { ResizableBuffer } from "../../ResizableBuffer"; import { ResizableBuffer } from "../../ResizableBuffer";
import { LogManager } from "../../../Logger"; import { LogManager } from "../../../Logger";
import * as prs_wasm from "prs-rs";
import { browser_supports_webassembly } from "../../../util"; import { browser_supports_webassembly } from "../../../util";
import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor"; import { get_prs_wasm_module } from "./prs_wasm";
import { Endianness } from "../../Endianness";
const logger = LogManager.get("core/data_formats/compression/prs/decompress"); const logger = LogManager.get("core/data_formats/compression/prs/decompress");
const prs_wasm = get_prs_wasm_module();
/** /**
* Automatically picks the best available decompression method. * Automatically picks the best available decompression method.
*/ */
export function prs_decompress(cursor: Cursor): Cursor { export function prs_decompress(cursor: Cursor): Cursor {
if (browser_supports_webassembly()) { if (browser_supports_webassembly() && prs_wasm) {
return prs_decompress_wasm(cursor); return prs_wasm.prs_decompress_wasm(cursor);
} else { } else {
return prs_decompress_js(cursor); return prs_decompress_js(cursor);
} }
@ -135,14 +135,3 @@ class Context {
this.dst.write_cursor(buf.take(length % buf_size)); 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 { Endianness } from "../../Endianness";
import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor"; import { ArrayBufferCursor } from "../../cursor/ArrayBufferCursor";
import { BufferCursor } from "../../cursor/BufferCursor"; import { BufferCursor } from "../../cursor/BufferCursor";
import { prs_compress_js, prs_compress_wasm } from "./compress"; import { prs_compress_js } from "./compress";
import { prs_decompress_js, prs_decompress_wasm } from "./decompress"; import { prs_decompress_js } from "./decompress";
import { Cursor } from "../../cursor/Cursor"; import { Cursor } from "../../cursor/Cursor";
import { get_prs_wasm_module } from "./prs_wasm";
type CompressionFunction = (cursor: Cursor) => Cursor; 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_nop = (cursor: Cursor): Cursor => cursor;
const prs_wasm: CompressionMethod = ["WASM", prs_compress_wasm, prs_decompress_wasm] as const;
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( function test_with_bytes(
compress_fn: CompressionFunction, compress_fn: CompressionFunction,
@ -40,7 +65,7 @@ function test_with_bytes(
expect(test_cursor.position).toBe(test_cursor.size); 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", "%s PRS compression and decompression, best case",
( (
_name: string, _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", "%s PRS compression and decompression, worst case",
( (
_name: string, _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", "%s PRS compression and decompression, typical case",
( (
_name: string, _name: string,
@ -87,16 +112,22 @@ test.each([[prs_js, 14924].flat(), [prs_wasm, 12901].flat()])(
}, },
); );
test.each([ test.each(
[prs_js[0], 0, prs_js[1], prs_js[2], [], 3], prepare_test_data(
[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], 0, prs_js[1], prs_js[2], [], 3],
[prs_js[0], 3, prs_js[1], prs_js[2], [56, 237, 158], 6], [prs_js[0], 1, prs_js[1], prs_js[2], [111], 4],
[prs_wasm[0], 0, prs_wasm[1], prs_wasm[2], [], 3], [prs_js[0], 2, prs_js[1], prs_js[2], [111, 224], 5],
[prs_wasm[0], 1, prs_wasm[1], prs_wasm[2], [111], 4], [prs_js[0], 3, prs_js[1], prs_js[2], [56, 237, 158], 6],
[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], [
])( [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", "%s PRS compression and decompression, %d bytes",
( (
_name: string, _name: string,
@ -140,7 +171,7 @@ function test_with_quest(
expect(matching_bytes).toBe(orig.size); 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", "%s PRS compression and decompression of quest118_e.bin",
(_name: string, compress_fn: CompressionFunction, decompress_fn: CompressionFunction) => { (_name: string, compress_fn: CompressionFunction, decompress_fn: CompressionFunction) => {
test_with_quest(compress_fn, decompress_fn, "test/resources/quest118_e.bin"); 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;
}