diff --git a/.vscode/settings.json b/.vscode/settings.json index 3be768de7..34fa56e76 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -49,6 +49,7 @@ "degressive", "devkrieg", "diacriticless", + "DIDS", "donotremove", "drei", "Droodles", diff --git a/docs/technical/dids.md b/docs/technical/dids.md new file mode 100644 index 000000000..6a02cabbf --- /dev/null +++ b/docs/technical/dids.md @@ -0,0 +1,18 @@ +# Discovered Ids + +```c +primary DiscoveredIds { + utf8"DIDS" magic; + Header header; + primary Body body; +} + +Header { + uint16 version; // always 1 +} + +Body { + uint32 count; + uint32[count] ids; +} +``` diff --git a/package.json b/package.json index 962bbead8..5650355f6 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "src/server.ts", "type": "module", "scripts": { + "build:averages": "vite-node --script scripts/averages", "build:db": "prisma generate", "build:bot": "vite-node --script scripts/build --production", "build:website": "yarn build:db && yarn build:sitemap && next build", diff --git a/scripts/averages.ts b/scripts/averages.ts new file mode 100644 index 000000000..5d4a98316 --- /dev/null +++ b/scripts/averages.ts @@ -0,0 +1,139 @@ +import { mkdir, writeFile } from 'fs/promises'; +import { times } from 'lodash'; +import { compress } from 'lz4js'; +import { Region, REGIONS } from '../src/constants/regions'; +import getTankStats from '../src/core/blitz/getTankStats'; +import { idToRegion, MAX_IDS, MIN_IDS } from '../src/core/blitz/idToRegion'; +import { asset } from '../src/core/blitzkit/asset'; +import { superCompress } from '../src/core/blitzkit/superCompress'; +import { DidsReadStream, DidsWriteStream } from '../src/core/streams/dids'; +import { IndividualTankStats } from '../src/types/tanksStats'; + +const RUN_TIME = 1000 * 60; +const MAX_REQUESTS = 10; +const startTime = Date.now(); + +const list: IndividualTankStats[][] = []; +const preDiscovered = await fetch(asset('averages/discovered.dids.lz4')).then( + async (response) => { + return [ + 1000000048, 500000058, 500000076, 2000000096, 2000000097, 2000000102, + 2000000103, 2000000109, 1000000116, 500000131, 2000000138, 2000000141, + 2000000140, 2000000143, 2000000144, 2000000145, 500000147, 2000000150, + 2000000173, 2000000174, 2000000178, 2000000177, 2000000182, 2000000189, + 500000191, 2000000191, 2000000192, 2000000196, + ]; + + if (response.status === 200) { + const buffer = await response.arrayBuffer(); + return new DidsReadStream(buffer).dids(); + } + + return undefined; + }, +); +const discoveredIds: number[] = []; +let processes = 0; + +if (preDiscovered) { + console.log( + `Revalidating ${preDiscovered.length} pre-discovered ids with ${MAX_REQUESTS} chains...`, + ); + + let preDiscoveredIndex = 0; + + async function chainPreDiscovery() { + processes++; + + if (preDiscoveredIndex >= preDiscovered!.length) { + processes--; + + if (processes !== 0) return; + + console.log(`Rediscovered ids in ${Date.now() - startTime}ms`); + discover(); + + return; + } + + const id = preDiscovered![preDiscoveredIndex++]; + const region = idToRegion(id); + + if (id > MAX_IDS[region]) return; + + const stats = await getTankStats(region, id, { felids: 'all' }); + + if (stats !== null && stats.length > 0) { + discoveredIds.push(id); + list.push(stats); + + console.log(`re-discovered ${id} in ${region}`); + } + + processes--; + setTimeout(chainPreDiscovery); // circumvent max call stack + } + + times(MAX_REQUESTS, chainPreDiscovery); +} else { + console.log('No pre-discovered ids found :('); + discover(); +} + +function discover() { + const index: Record = { + asia: + preDiscovered?.find((id) => idToRegion(id) === 'asia') ?? MIN_IDS.asia, + com: preDiscovered?.find((id) => idToRegion(id) === 'com') ?? MIN_IDS.com, + eu: preDiscovered?.find((id) => idToRegion(id) === 'eu') ?? MIN_IDS.eu, + }; + + let regionIndex = 0; + + async function chainDiscovery() { + processes++; + + if (Date.now() - startTime > RUN_TIME) { + processes--; + + if (processes !== 0) return; + + const discoveredIdsContent = compress( + new DidsWriteStream().dids(discoveredIds).uint8Array, + ); + + await mkdir('temp/averages', { recursive: true }); + writeFile('temp/averages/discovered.dids.lz4', discoveredIdsContent); + writeFile('temp/averages/list.cdon.lz4', superCompress(list)); + + console.log( + `Total run time: ${ + Date.now() - startTime + }ms\nDiscovered ids: ${discoveredIds.length}`, + ); + + return; + } + + regionIndex = (regionIndex + 1) % REGIONS.length; + const region = REGIONS[regionIndex]; + const id = index[region]++; + + if (id > MAX_IDS[region]) return; + + const stats = await getTankStats(region, id, { felids: 'all' }); + + if (stats !== null && stats.length > 0) { + discoveredIds.push(id); + list.push(stats); + + console.log(`discovered ${id} in ${region}`); + } + + processes--; + setTimeout(chainDiscovery); // circumvent max call stack + } + + console.log(`Spawning ${MAX_REQUESTS} discovery chains...`); + times(MAX_REQUESTS, chainDiscovery); +} diff --git a/src/core/blitz/idToRegion.ts b/src/core/blitz/idToRegion.ts index 81b6e5b4f..4eee68b40 100644 --- a/src/core/blitz/idToRegion.ts +++ b/src/core/blitz/idToRegion.ts @@ -1,5 +1,17 @@ import { Region } from '../../constants/regions'; +export const MIN_IDS: Record = { + asia: 20e8, + com: 10e8, + eu: 5e8, +}; + +export const MAX_IDS: Record = { + asia: 31e8 - 1, + com: MIN_IDS.asia - 1, + eu: MIN_IDS.com - 1, +}; + export function idToRegion(id: number): Region { if (id >= 42e8) { throw new Error('bot id'); diff --git a/src/core/streams/dids.ts b/src/core/streams/dids.ts new file mode 100644 index 000000000..ac57cd59e --- /dev/null +++ b/src/core/streams/dids.ts @@ -0,0 +1,51 @@ +import { times } from 'lodash'; +import { ReadStream, WriteStream } from './buffer'; + +export class DidsReadStream extends ReadStream { + dids() { + this.magic(); + this.header(); + return this.body(); + } + + magic() { + const magic = this.utf8(4); + if (magic !== 'DIDS') { + throw new Error(`Invalid DIDS magic number: "${magic}"`); + } + } + + header() { + return { + version: this.uint16(), + }; + } + + body() { + const count = this.uint32(); + return times(count, () => this.uint32()); + } +} + +export class DidsWriteStream extends WriteStream { + dids(ids: number[]) { + this.magic(); + this.header(); + this.body(ids); + + return this; + } + + magic() { + this.utf8('DIDS'); + } + + header() { + this.uint16(1); + } + + body(ids: number[]) { + this.uint32(ids.length); + ids.forEach((id) => this.uint32(id)); + } +}