feat: more compression

This commit is contained in:
TrèsAbhi committed 2024-02-05 13:33:23 -06:00
1 parent afe041f969
commit 1575b803c0
11 files changed
+135 -59

No files matched your search

+11 -2
View File
@@ -22,6 +22,7 @@ struct String {
- Everything is little endian - Everything is little endian
- Magic is always `"BKON"` - Magic is always `"BKON"`
- The index of fast strings must be read as mentioned by the fast string format
```cpp ```cpp
struct BlitzkriegObjectNotation { struct BlitzkriegObjectNotation {
@@ -38,11 +39,19 @@ struct BlitzkriegObjectNotation {
```cpp ```cpp
struct Header { struct Header {
uint16 version; uint16 version;
FastStringFormat fastStringFormat;
StringTable stringTable; StringTable stringTable;
} }
// read as uint8
enum FastStringFormat {
Format8,
Format16,
Format32,
}
struct StringTable { struct StringTable {
uint16 count; (uint8 | uint16 | uint32) count;
String[count] strings; String[count] strings;
} }
``` ```
@@ -142,7 +151,7 @@ struct ValueString {
struct ValueFastString { struct ValueFastString {
ValueType.FastString type; ValueType.FastString type;
uint16 index; (uint8 | uint16 | uint32) index;
} }
struct ValueArray { struct ValueArray {
+16 -15
View File
@@ -21,6 +21,7 @@ import {
ModelDefinitions, ModelDefinitions,
} from '../../src/core/blitzkrieg/modelDefinitions'; } from '../../src/core/blitzkrieg/modelDefinitions';
import { ProvisionDefinitions } from '../../src/core/blitzkrieg/provisionDefinitions'; import { ProvisionDefinitions } from '../../src/core/blitzkrieg/provisionDefinitions';
import { superCompress } from '../../src/core/blitzkrieg/superCompress';
import { import {
GunDefinition, GunDefinition,
ShellType, ShellType,
@@ -912,29 +913,29 @@ export async function definitions(production: boolean) {
'definitions', 'definitions',
[ [
{ {
content: JSON.stringify(tankDefinitions), content: superCompress(tankDefinitions),
encoding: 'utf-8', encoding: 'base64',
path: 'definitions/tanks.json', path: 'definitions/tanks.bkon.lz4',
}, },
{ {
content: JSON.stringify(modelDefinitions), content: superCompress(modelDefinitions),
encoding: 'utf-8', encoding: 'base64',
path: 'definitions/models.json', path: 'definitions/models.bkon.lz4',
}, },
{ {
content: JSON.stringify(equipmentDefinitions), content: superCompress(equipmentDefinitions),
encoding: 'utf-8', encoding: 'base64',
path: 'definitions/equipment.json', path: 'definitions/equipment.bkon.lz4',
}, },
{ {
content: JSON.stringify(consumableDefinitions), content: superCompress(consumableDefinitions),
encoding: 'utf-8', encoding: 'base64',
path: 'definitions/consumables.json', path: 'definitions/consumables.bkon.lz4',
}, },
{ {
content: JSON.stringify(provisionDefinitions), content: superCompress(provisionDefinitions),
encoding: 'utf-8', encoding: 'base64',
path: 'definitions/provisions.json', path: 'definitions/provisions.bkon.lz4',
}, },
], ],
production, production,
+4 -6
View File
@@ -1,4 +1,5 @@
import { asset } from './asset'; import { asset } from './asset';
import { fetchBkonLz4 } from './fetchBkonLz4';
export type TankFilterDefinitionCategory = 'clip'; export type TankFilterDefinitionCategory = 'clip';
@@ -33,9 +34,6 @@ export interface ConsumableDefinitions {
[key: string]: ConsumableEntry; [key: string]: ConsumableEntry;
} }
export const consumableDefinitions = fetch( export const consumableDefinitions = fetchBkonLz4<ConsumableDefinitions>(
asset('definitions/consumables.json'), asset('definitions/consumables.bkon.lz4'),
{ );
cache: 'no-cache',
},
).then(async (response) => response.json() as Promise<ConsumableDefinitions>);
+4 -3
View File
@@ -1,4 +1,5 @@
import { asset } from './asset'; import { asset } from './asset';
import { fetchBkonLz4 } from './fetchBkonLz4';
type EquipmentOptions = [number, number]; type EquipmentOptions = [number, number];
@@ -21,6 +22,6 @@ export interface EquipmentDefinitions {
}; };
} }
export const equipmentDefinitions = fetch(asset('definitions/equipment.json'), { export const equipmentDefinitions = fetchBkonLz4<EquipmentDefinitions>(
cache: 'no-cache', asset('definitions/equipment.bkon.lz4'),
}).then(async (response) => response.json() as Promise<EquipmentDefinitions>); );
+9
View File
@@ -0,0 +1,9 @@
import { superDecompress } from './superDecompress';
export async function fetchBkonLz4<Type>(url: string) {
return await fetch(url, {
cache: 'no-cache',
}).then(async (response) => {
return superDecompress(Buffer.from(await response.arrayBuffer())) as Type;
});
}
+4 -3
View File
@@ -1,5 +1,6 @@
import { Vector3Tuple } from 'three'; import { Vector3Tuple } from 'three';
import { asset } from './asset'; import { asset } from './asset';
import { fetchBkonLz4 } from './fetchBkonLz4';
export type ModelDefinitions = Record<number, ModelDefinition>; export type ModelDefinitions = Record<number, ModelDefinition>;
@@ -53,6 +54,6 @@ export interface PitchLimits {
transition?: number; transition?: number;
} }
export const modelDefinitions = fetch(asset('definitions/models.json'), { export const modelDefinitions = fetchBkonLz4<ModelDefinitions>(
cache: 'no-cache', asset('definitions/models.bkon.lz4'),
}).then(async (response) => response.json() as Promise<ModelDefinitions>); );
+4 -6
View File
@@ -1,5 +1,6 @@
import { asset } from './asset'; import { asset } from './asset';
import { TankFilterDefinition } from './consumableDefinitions'; import { TankFilterDefinition } from './consumableDefinitions';
import { fetchBkonLz4 } from './fetchBkonLz4';
export interface ProvisionEntry { export interface ProvisionEntry {
id: number; id: number;
@@ -13,9 +14,6 @@ export interface ProvisionDefinitions {
[key: string]: ProvisionEntry; [key: string]: ProvisionEntry;
} }
export const provisionDefinitions = fetch( export const provisionDefinitions = fetchBkonLz4<ProvisionDefinitions>(
asset('definitions/provisions.json'), asset('definitions/provisions.bkon.lz4'),
{ );
cache: 'no-cache',
},
).then(async (response) => response.json() as Promise<ProvisionDefinitions>);
+8
View File
@@ -0,0 +1,8 @@
import { encode } from 'lz4';
import { BkonWriteStream } from '../streams/bkon';
export function superCompress(object: any) {
const write = new BkonWriteStream().bkon(object);
const compressed = encode(write.buffer);
return compressed.toString('base64');
}
+8
View File
@@ -0,0 +1,8 @@
import { decode } from 'lz4';
import { BkonReadStream } from '../streams/bkon';
export function superDecompress<Type>(buffer: Buffer) {
const decompressed = decode(buffer);
const read = new BkonReadStream(decompressed);
return read.bkon() as Type;
}
+4 -3
View File
@@ -2,6 +2,7 @@ import { deburr } from 'lodash';
import { Vector2Tuple } from 'three'; import { Vector2Tuple } from 'three';
import { TankType, TreeType } from '../../components/Tanks'; import { TankType, TreeType } from '../../components/Tanks';
import { asset } from './asset'; import { asset } from './asset';
import { fetchBkonLz4 } from './fetchBkonLz4';
export type ShellType = 'ap' | 'ap_cr' | 'hc' | 'he'; export type ShellType = 'ap' | 'ap_cr' | 'hc' | 'he';
@@ -117,9 +118,9 @@ export const GUN_TYPE_NAMES: Record<GunDefinition['type'], string> = {
autoReloader: 'Auto reloader', autoReloader: 'Auto reloader',
}; };
export const tankDefinitions = fetch(asset('definitions/tanks.json'), { export const tankDefinitions = fetchBkonLz4<TankDefinitions>(
cache: 'no-cache', asset('definitions/tanks.bkon.lz4'),
}).then(async (response) => response.json() as Promise<TankDefinitions>); );
const entries = new Promise<TankDefinition[]>(async (resolve) => { const entries = new Promise<TankDefinition[]>(async (resolve) => {
resolve(Object.entries(await tankDefinitions).map(([, entry]) => entry)); resolve(Object.entries(await tankDefinitions).map(([, entry]) => entry));
+63 -21
View File
@@ -20,20 +20,26 @@ enum ValueType {
Object, Object,
} }
type Value = enum FastStringFormat {
Format8,
Format16,
Format32,
}
export type BkonValue =
| null | null
| boolean | boolean
| number | number
| bigint | bigint
| string | string
| Value[] | BkonValue[]
| { [key: string]: Value }; | { [key: string]: BkonValue };
export class BkonReadStream extends ReadStream { export class BkonReadStream extends ReadStream {
bkon() { bkon() {
this.magic(); this.magic();
const header = this.header(); const header = this.header();
const body = this.body(header.stringTable); const body = this.body(header.fastStringFormat, header.stringTable);
return body; return body;
} }
@@ -47,12 +53,16 @@ export class BkonReadStream extends ReadStream {
header() { header() {
return { return {
version: this.uint16(), version: this.uint16(),
fastStringFormat: this.uint8() as FastStringFormat,
stringTable: this.stringTable(), stringTable: this.stringTable(),
}; };
} }
body(stringTable: Record<number, string>) { body(
return this.value(stringTable); fastStringFormat: FastStringFormat,
stringTable: Record<number, string>,
) {
return this.value(fastStringFormat, stringTable);
} }
stringTable() { stringTable() {
@@ -75,7 +85,10 @@ export class BkonReadStream extends ReadStream {
return string; return string;
} }
value(stringTable: Record<number, string>): Value { value(
fastStringFormat: FastStringFormat,
stringTable: Record<number, string>,
): BkonValue {
const type = this.uint8(); const type = this.uint8();
switch (type) { switch (type) {
@@ -118,17 +131,34 @@ export class BkonReadStream extends ReadStream {
case ValueType.String: case ValueType.String:
return this.string(); return this.string();
case ValueType.FastString: case ValueType.FastString: {
return stringTable[this.uint16()]; switch (fastStringFormat) {
case FastStringFormat.Format8:
return stringTable[this.uint8()];
case FastStringFormat.Format16:
return stringTable[this.uint16()];
case FastStringFormat.Format32:
return stringTable[this.uint32()];
}
}
case ValueType.Array: case ValueType.Array:
return times(this.uint32(), () => this.value(stringTable)); return times(this.uint32(), () =>
this.value(fastStringFormat, stringTable),
);
case ValueType.Object: { case ValueType.Object: {
const count = this.uint32(); const count = this.uint32();
const keys = times(count, () => this.value(stringTable) as string); const keys = times(
const values = times(count, () => this.value(stringTable)); count,
const object: Record<string, Value> = {}; () => this.value(fastStringFormat, stringTable) as string,
);
const values = times(count, () =>
this.value(fastStringFormat, stringTable),
);
const object: Record<string, BkonValue> = {};
keys.forEach((string, index) => (object[string] = values[index])); keys.forEach((string, index) => (object[string] = values[index]));
@@ -142,18 +172,20 @@ export class BkonReadStream extends ReadStream {
} }
export class BkonWriteStream extends WriteStream { export class BkonWriteStream extends WriteStream {
bkon(object: Value) { bkon(object: BkonValue) {
this.magic(); this.magic();
const stringTable = this.header(object); const stringTable = this.header(object);
this.body(object, stringTable); this.body(object, stringTable);
return this;
} }
body(object: Value, stringTable: Map<string, number>) { body(object: BkonValue, stringTable: Map<string, number>) {
this.value(object, stringTable); this.value(object, stringTable);
return this;
} }
value(object: Value, stringTable: Map<string, number>) { value(object: BkonValue, stringTable: Map<string, number>) {
if (object === null) { if (object === null || object === undefined) {
this.uint8(ValueType.Null); this.uint8(ValueType.Null);
} else if (typeof object === 'boolean') { } else if (typeof object === 'boolean') {
this.uint8(ValueType.Boolean); this.uint8(ValueType.Boolean);
@@ -226,17 +258,27 @@ export class BkonWriteStream extends WriteStream {
} }
} }
header(object: Value) { header(object: BkonValue) {
this.uint16(1); this.uint16(1);
return this.stringTable(object); return this.stringTable(object);
} }
stringTable(object: Value) { stringTable(object: BkonValue) {
const stringTable = this.buildStringTable( const stringTable = this.buildStringTable(
this.collectStringCounts(object, new Map()), this.collectStringCounts(object, new Map()),
); );
this.uint16(stringTable.size); if (stringTable.size <= 0xff) {
this.uint8(FastStringFormat.Format8);
this.uint8(stringTable.size);
} else if (stringTable.size <= 0xffff) {
this.uint8(FastStringFormat.Format16);
this.uint16(stringTable.size);
} else {
this.uint8(FastStringFormat.Format32);
this.uint32(stringTable.size);
}
stringTable.forEach((_index, string) => this.string(string)); stringTable.forEach((_index, string) => this.string(string));
return stringTable; return stringTable;
@@ -252,7 +294,7 @@ export class BkonWriteStream extends WriteStream {
return stringTable; return stringTable;
} }
collectStringCounts(object: Value, stringCounts: Map<string, number>) { collectStringCounts(object: BkonValue, stringCounts: Map<string, number>) {
if (typeof object === 'string') { if (typeof object === 'string') {
if (stringCounts.has(object)) { if (stringCounts.has(object)) {
stringCounts.set(object, stringCounts.get(object)! + 1); stringCounts.set(object, stringCounts.get(object)! + 1);