From 1c03701e17a079949f3a0d2d89f680b5aeb2bd9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 18 Mar 2024 18:06:45 -0500 Subject: [PATCH] feat: ratings leaderboard disk format --- .vscode/settings.json | 1 + docs/guide/bkrl.md | 50 +++++++++++++ docs/guide/cdon.md | 4 +- src/core/streams/bkrl.ts | 157 +++++++++++++++++++++++++++++++++++++++ src/core/streams/cdon.ts | 2 +- 5 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 docs/guide/bkrl.md create mode 100644 src/core/streams/bkrl.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 8c41337cf..55ef783ec 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,6 +16,7 @@ "betakrieg", "BINORMAL", "Binormals", + "BKRL", "bkwd", "blitzstars", "Camo", diff --git a/docs/guide/bkrl.md b/docs/guide/bkrl.md new file mode 100644 index 000000000..b5a109be7 --- /dev/null +++ b/docs/guide/bkrl.md @@ -0,0 +1,50 @@ +# Blitzkrieg Ratings Leaderboard + +A straightforward way of storing the entire World of Tanks Blitz ratings leaderboard to disk. + +```cpp +primary BlitzkriegRatingsLeaderboard { + utf8"BKRL" magic; + Header header; + primary Body
body; +} + +Header { + uint16 version; // always 1 + Format format; + uint32 count; +} + +Format enum { + Minimal, + Comprehensive1, +} + +Body
match { + Minimal { + primary { + uint32 id; + uint16 score; + }[header.count] entries; + }; + + Comprehensive1 { + primary { + uint32 id; + uint16 score; + + uint32 battles; + uint32 wins; + uint32 survived; + + uint32 damageDealt; + uint32 damageReceived; + + uint32 shots; + uint32 hits; + + uint32 kills; + }[header.count] entries; + }; +} +``` diff --git a/docs/guide/cdon.md b/docs/guide/cdon.md index 025126b93..ddb36bed5 100644 --- a/docs/guide/cdon.md +++ b/docs/guide/cdon.md @@ -1,7 +1,5 @@ # Cedilla Object Notation -This documentation uses an experimental binary notation syntax called "Buffer Up". - 1. JSON compressed as CDON, written directly to disk (a direct 50% reduction). 2. CDON further compressed with LZ4 (a clean 90% reduction). 3. The raw JSON file as fetched from a random data source. @@ -19,7 +17,7 @@ primary CedillaObjectNotation { } Header { - uint16 version; + uint16 version; // always 1 FSFormat fsFormat; StringTable stringTable; } diff --git a/src/core/streams/bkrl.ts b/src/core/streams/bkrl.ts new file mode 100644 index 000000000..09150be31 --- /dev/null +++ b/src/core/streams/bkrl.ts @@ -0,0 +1,157 @@ +import { times } from 'lodash'; +import { ReadStream, WriteStream } from './buffer'; + +enum Format { + Minimal, + Comprehensive1, +} + +interface BkrlMinimalEntry { + id: number; + score: number; +} + +interface BkrlComprehensive1Entry { + id: number; + score: number; + + battles: number; + wins: number; + survived: number; + + damageDealt: number; + damageReceived: number; + + shots: number; + hits: number; + + kills: number; +} + +type DiscriminatedEntries = + | { + format: Format.Minimal; + entries: BkrlMinimalEntry[]; + } + | { + format: Format.Comprehensive1; + entries: BkrlComprehensive1Entry[]; + }; + +export class BkrlReadStream extends ReadStream { + bkrl() { + this.magic(); + const header = this.header(); + return this.body(header); + } + + body(header: ReturnType): DiscriminatedEntries { + switch (header.format) { + case Format.Minimal: { + return { + format: Format.Minimal, + entries: times( + header.count, + () => + ({ + id: this.uint32(), + score: this.uint16(), + }) satisfies BkrlMinimalEntry, + ), + }; + } + + case Format.Comprehensive1: { + return { + format: Format.Comprehensive1, + entries: times( + header.count, + () => + ({ + id: this.uint32(), + score: this.uint16(), + + battles: this.uint32(), + wins: this.uint32(), + survived: this.uint32(), + + damageDealt: this.uint32(), + damageReceived: this.uint32(), + + shots: this.uint32(), + hits: this.uint32(), + + kills: this.uint32(), + }) satisfies BkrlComprehensive1Entry, + ), + }; + } + } + } + + header() { + return { + version: this.uint16(), + format: this.uint8() as Format, + count: this.uint32(), + }; + } + + magic() { + const magic = this.utf8(4); + + if (magic !== 'BKRL') { + throw new Error(`Invalid BKRL magic number: "${magic}"`); + } + } +} + +export class BkrlWriteStream extends WriteStream { + bkrl(discriminatedEntries: DiscriminatedEntries) { + this.magic(); + this.header(discriminatedEntries); + this.body(discriminatedEntries); + } + + body(discriminatedEntries: DiscriminatedEntries) { + switch (discriminatedEntries.format) { + case Format.Minimal: { + discriminatedEntries.entries.forEach((entry) => { + this.uint32(entry.id); + this.uint16(entry.score); + }); + break; + } + + case Format.Comprehensive1: { + discriminatedEntries.entries.forEach((entry) => { + this.uint32(entry.id); + this.uint16(entry.score); + + this.uint32(entry.battles); + this.uint32(entry.wins); + this.uint32(entry.survived); + + this.uint32(entry.damageDealt); + this.uint32(entry.damageReceived); + + this.uint32(entry.shots); + this.uint32(entry.hits); + + this.uint32(entry.kills); + }); + break; + } + } + } + + header(discriminatedEntries: DiscriminatedEntries) { + this.uint16(1); + this.uint8(discriminatedEntries.format); + this.uint32(discriminatedEntries.entries.length); + } + + magic() { + this.utf8('BKRL'); + } +} diff --git a/src/core/streams/cdon.ts b/src/core/streams/cdon.ts index 478b72638..f9257b8fb 100644 --- a/src/core/streams/cdon.ts +++ b/src/core/streams/cdon.ts @@ -48,7 +48,7 @@ export class CdonReadStream extends ReadStream { const magic = this.utf8(4); if (magic !== 'CDON') { - throw new Error(`Invalid Cdon magic number: "${magic}"`); + throw new Error(`Invalid CDON magic number: "${magic}"`); } }