feat: migrate performance

This commit is contained in:
TrèsAbhi committed 2024-10-07 17:31:13 -05:00
1 parent b31c7a6738
commit 8d51ce2bea
20 files changed
+122 -149

No files matched your search

+1
View File
@@ -104,6 +104,7 @@
"mmnow",
"modeltransform",
"MURICA",
"nanostores",
"NEGATIVEX",
"NEGATIVEY",
"NEGATIVEZ",
+1
View File
@@ -31,6 +31,7 @@ const args = [
'--ts_proto_opt=oneof=unions-value',
'--ts_proto_opt=removeEnumPrefix=true',
'--ts_proto_opt=unrecognizedEnum=false',
'--ts_proto_opt=snakeToCamel=false',
'--ts_proto_out=.',
...files.map((file) => `${ROOT}/${file}`),
];
@@ -1,82 +0,0 @@
import {
formatCompact,
TANK_CLASSES,
TankClass,
Tier,
TIER_ROMAN_NUMERALS,
} from '@blitzkit/core';
import strings from '@blitzkit/core/lang/en-US.json';
import { ResponsiveBar } from '@nivo/bar';
import { Box, Flex, Heading } from '@radix-ui/themes';
import { times } from 'lodash-es';
import { use } from 'react';
import { nivoTheme } from '../../../../core/nivo/theme';
export function TierBreakdown() {
const awaitedAverageDefinitionsArray = use(averageDefinitionsArray);
const awaitedTankDefinitions = use(tankDefinitions);
return (
<Flex justify="center" py="6">
<Flex direction="column" flexGrow="1" gap="4" maxWidth="640px">
<Heading ml="2" size="4">
Players (30d) per tier
</Heading>
<Box height="320px" width="100%">
<ResponsiveBar
{...nivoTheme}
data={times(10, (index) => {
const tier = (index + 1) as Tier;
const tierTanks = awaitedAverageDefinitionsArray.filter(
({ id }) => awaitedTankDefinitions[id]?.tier === tier,
);
const populations: Record<TankClass, number> = {
'AT-SPG': 0,
heavyTank: 0,
lightTank: 0,
mediumTank: 0,
};
tierTanks.forEach((stats) => {
const tank = awaitedTankDefinitions[stats.id];
populations[tank.class] +=
stats.mu.battles * stats.samples.d_30;
});
TANK_CLASSES.forEach((tankClass) => {
populations[tankClass] = Math.round(populations[tankClass]);
});
return { tier: TIER_ROMAN_NUMERALS[tier], ...populations };
})}
keys={[...TANK_CLASSES].reverse()}
indexBy="tier"
colors={{ scheme: 'dark2' }}
label={(d) => formatCompact(d.value as number)}
margin={{ right: 96, bottom: 20, left: 40 }}
legendLabel={(d) =>
strings.common.tank_class_short[d.id as TankClass]
}
axisLeft={{
tickSize: 0,
format: (value) => formatCompact(value as number),
}}
axisBottom={{ tickSize: 0 }}
legends={[
{
dataFrom: 'keys',
anchor: 'bottom-right',
direction: 'column',
translateX: 120,
itemsSpacing: 4,
itemWidth: 100,
itemHeight: 20,
},
]}
/>
</Box>
</Flex>
</Flex>
);
}
@@ -1,7 +1,7 @@
import { Controls } from '../../../../../website/src/components/Performance/Controls';
import { Info } from '../../../../../website/src/components/Performance/PerformanceInfo';
import { TankTable } from '../../../../../website/src/components/Performance/Table';
import { PageWrapper } from '../../../components/PageWrapper';
import { Controls } from './components/Controls';
import { Info } from './components/Info';
import { TankTable } from './components/Table';
export default function Page() {
return (
+5 -1
View File
@@ -7,5 +7,9 @@
}
]
},
"include": ["src/**/*", ".next/types/**/*.ts"]
"include": [
"src/**/*",
".next/types/**/*.ts",
"../website/src/components/Performance/PerformanceInfo.tsx"
]
}
@@ -1,4 +1,4 @@
import { FilterControl } from '../../../../../../website/src/components/TankSearch/components/FilterControl';
import { FilterControl } from '../TankSearch/components/FilterControl';
import { PlayerCountControl } from './PlayerCountControl';
export function Controls() {
@@ -4,12 +4,12 @@ import {
CaretUpIcon,
} from '@radix-ui/react-icons';
import { Flex, IconButton, Table } from '@radix-ui/themes';
import { StickyColumnHeaderCell } from '../../../../components/StickyColumnHeaderCell';
import * as TankPerformanceSort from '../../../../stores/tankPerformanceSort';
import { TankPerformanceSort } from '../../stores/tankPerformanceSort';
import {
TankPerformanceSortTypeNames,
TankPerformanceSortTypeNamesArray,
} from '../../../../stores/tankPerformanceSort/constants';
} from '../../stores/tankPerformanceSort/constants';
import { StickyColumnHeaderCell } from '../StickyColumnHeaderCell';
export function Header() {
const sort = TankPerformanceSort.use();
@@ -9,7 +9,7 @@ import { Callout, Flex } from '@radix-ui/themes';
const discoveredIdsDefinitions = await fetchDiscoveredIdsDefinitions();
const averageDefinitions = await fetchAverageDefinitions();
export function Info() {
export function PerformanceInfo() {
const samples = discoveredIdsDefinitions.count;
const minutesAgo = Math.floor(
(Date.now() - averageDefinitions.time) / (1000 * 60),
@@ -1,6 +1,6 @@
import { Flex, Select, Text } from '@radix-ui/themes';
import * as TankPerformancePersistent from '../../../../stores/tankPerformancePersistent';
import { PlayerCountPeriod } from '../../../../stores/tankPerformancePersistent/constants';
import { TankPerformancePersistent } from '../../stores/tankPerformancePersistent';
import { PlayerCountPeriod } from '../../stores/tankPerformancePersistent/constants';
export function PlayerCountControl() {
const playerCountPeriod = TankPerformancePersistent.use(
@@ -1,8 +1,8 @@
import { Skeleton, Table } from '@radix-ui/themes';
import { times } from 'lodash-es';
import { memo, useRef } from 'react';
import { useIntersection } from '../../../../hooks/useIntersection';
import { TankPerformanceSortTypeNamesArray } from '../../../../stores/tankPerformanceSort/constants';
import { useIntersection } from '../../hooks/useIntersection';
import { TankPerformanceSortTypeNamesArray } from '../../stores/tankPerformanceSort/constants';
interface RowLoaderProps {
onIntersection?: () => void;
@@ -1,6 +1,6 @@
import { Flex } from '@radix-ui/themes';
import { Suspense } from 'react';
import { StickyTableRoot } from '../../../../components/StickyTableRoot';
import { StickyTableRoot } from '../StickyTableRoot';
import { BodyLoader } from './BodyLoader';
import { Header } from './Header';
import { Tanks } from './Tanks';
@@ -1,22 +1,23 @@
import {
TankDefinition,
averageDefinitions,
fetchAverageDefinitions,
formatCompact,
} from '@blitzkit/core';
import { Table } from '@radix-ui/themes';
import { memo, use } from 'react';
import { TankRowHeaderCell } from '../../../../components/TankRowHeaderCell';
import { useAveragesExclusionRatio } from '../../../../hooks/useAveragesExclusionRatio';
import * as TankPerformancePersistent from '../../../../stores/tankPerformancePersistent';
import { memo } from 'react';
import { useAveragesExclusionRatio } from '../../hooks/useAveragesExclusionRatio';
import { TankPerformancePersistent } from '../../stores/tankPerformancePersistent';
import { TankRowHeaderCell } from '../TankRowHeaderCell';
interface TankRowProps {
tank: TankDefinition;
}
const averageDefinitions = await fetchAverageDefinitions();
export const TankRow = memo<TankRowProps>(
({ tank }) => {
const awaitedAverageDefinitions = use(averageDefinitions);
const averages = awaitedAverageDefinitions.averages[tank.id];
const averages = averageDefinitions.averages[tank.id];
const ratio = useAveragesExclusionRatio();
const playerCountPeriod = TankPerformancePersistent.use(
(state) => state.playerCountPeriod,
@@ -1,6 +1,12 @@
import { fetchAverageDefinitions, fetchTankDefinitions } from '@blitzkit/core';
import { useStore } from '@nanostores/react';
import { Table } from '@radix-ui/themes';
import { times } from 'lodash-es';
import { Suspense, use, useEffect, useMemo, useState } from 'react';
import { Suspense, useEffect, useMemo, useState } from 'react';
import { filterTank } from '../../core/blitzkit/filterTank';
import { $tankFilters } from '../../stores/tankFilters';
import { TankPerformancePersistent } from '../../stores/tankPerformancePersistent';
import { TankPerformanceSort } from '../../stores/tankPerformanceSort';
import { RowLoader } from './RowLoader';
import { TankRow } from './TankRow';
import { Total } from './Total';
@@ -8,94 +14,101 @@ import { Total } from './Total';
const PREVIEW_COUNT = 10;
const DEFAULT_LOADED_ROWS = 25;
const tankDefinitions = await fetchTankDefinitions();
const averageDefinitionsArray = await fetchAverageDefinitions().then(
({ averages }) =>
Object.entries(averages).map(([id, average]) => ({
id: Number(id),
...average,
})),
);
export function Tanks() {
const awaitedTankDefinitions = use(tankDefinitions);
const awaitedAverageDefinitionsArray = use(averageDefinitionsArray);
const filters = TankFilters.use();
const sort = TankPerformanceSort.use();
const tankPerformancePersistentStore = TankPerformancePersistent.useStore();
const filters = useStore($tankFilters);
const tanksSorted = useMemo(() => {
const { playerCountPeriod } = tankPerformancePersistentStore.getState();
switch (sort.type) {
case 'accuracy':
return awaitedAverageDefinitionsArray.sort(
return averageDefinitionsArray.sort(
(a, b) =>
sort.direction * (a.mu.hits / a.mu.shots - b.mu.hits / b.mu.shots),
);
case 'capturePoints':
return awaitedAverageDefinitionsArray.sort(
return averageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.capture_points / a.mu.battles -
b.mu.capture_points / b.mu.battles),
);
case 'damage':
return awaitedAverageDefinitionsArray.sort(
return averageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.damage_dealt / a.mu.battles -
b.mu.damage_dealt / b.mu.battles),
);
case 'damageRatio':
return awaitedAverageDefinitionsArray.sort(
return averageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.damage_dealt / a.mu.damage_received -
b.mu.damage_dealt / b.mu.damage_received),
);
case 'damageTaken':
return awaitedAverageDefinitionsArray.sort(
return averageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.damage_received / a.mu.battles -
b.mu.damage_received / b.mu.battles),
);
case 'hits':
return awaitedAverageDefinitionsArray.sort(
return averageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.hits / a.mu.battles - b.mu.hits / b.mu.battles),
);
case 'kills':
return awaitedAverageDefinitionsArray.sort(
return averageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.frags / a.mu.battles - b.mu.frags / b.mu.battles),
);
case 'shots':
return awaitedAverageDefinitionsArray.sort(
return averageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.shots / a.mu.battles - b.mu.shots / b.mu.battles),
);
case 'players':
return awaitedAverageDefinitionsArray.sort(
return averageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.samples[playerCountPeriod] - b.samples[playerCountPeriod]),
);
case 'spots':
return awaitedAverageDefinitionsArray.sort(
return averageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.spotted / a.mu.battles - b.mu.spotted / b.mu.battles),
);
case 'survival':
return awaitedAverageDefinitionsArray.sort(
return averageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.survived_battles / a.mu.battles -
b.mu.survived_battles / b.mu.battles),
);
case 'winrate':
return awaitedAverageDefinitionsArray.sort(
return averageDefinitionsArray.sort(
(a, b) =>
sort.direction *
(a.mu.wins / a.mu.battles - b.mu.wins / b.mu.battles),
);
case 'xp':
return awaitedAverageDefinitionsArray.sort(
return averageDefinitionsArray.sort(
(a, b) =>
sort.direction * (a.mu.xp / a.mu.battles - b.mu.xp / b.mu.battles),
);
@@ -103,7 +116,7 @@ export function Tanks() {
}, [sort]);
const tanks = useMemo(() => {
return tanksSorted.filter((tank) =>
filterTank(filters, awaitedTankDefinitions[tank.id]),
filterTank(filters, tankDefinitions.tanks[tank.id]),
);
}, [filters, sort]);
const [loadedRows, setLoadedRows] = useState(DEFAULT_LOADED_ROWS);
@@ -117,7 +130,7 @@ export function Tanks() {
<Total tanks={tanks} />
{tanks.slice(0, loadedRows).map((averages) => {
const tank = awaitedTankDefinitions[averages.id];
const tank = tankDefinitions.tanks[averages.id];
if (tank === undefined) return null;
@@ -1,22 +1,27 @@
import {
averageDefinitions,
AverageDefinitionsEntryWithId,
BlitzkitStats,
AverageDefinitionsEntry,
fetchAverageDefinitions,
formatCompact,
type BlitzkitStats,
} from '@blitzkit/core';
import { Table } from '@radix-ui/themes';
import { memo, use, useCallback } from 'react';
import { StickyRowHeaderCell } from '../../../../components/StickyRowHeaderCell';
import { useAveragesExclusionRatio } from '../../../../hooks/useAveragesExclusionRatio';
import * as TankPerformancePersistent from '../../../../stores/tankPerformancePersistent';
import { memo, useCallback } from 'react';
import { useAveragesExclusionRatio } from '../../hooks/useAveragesExclusionRatio';
import { TankPerformancePersistent } from '../../stores/tankPerformancePersistent';
import { StickyRowHeaderCell } from '../StickyRowHeaderCell';
interface AverageDefinitionsEntryWithId extends AverageDefinitionsEntry {
id: number;
}
interface TotalProps {
tanks: AverageDefinitionsEntryWithId[];
}
const averageDefinitions = await fetchAverageDefinitions();
export const Total = memo<TotalProps>(
({ tanks }) => {
const awaitedAverageDefinitions = use(averageDefinitions);
const playerCountPeriod = TankPerformancePersistent.use(
(state) => state.playerCountPeriod,
);
@@ -53,9 +58,7 @@ export const Total = memo<TotalProps>(
</StickyRowHeaderCell>
<Table.Cell align="center">{(winrate * 100).toFixed(1)}%</Table.Cell>
<Table.Cell align="center">
{formatCompact(
ratio * awaitedAverageDefinitions.samples[playerCountPeriod],
)}
{formatCompact(ratio * averageDefinitions.samples[playerCountPeriod])}
</Table.Cell>
<Table.Cell align="center">
{Math.round(damage).toLocaleString()}
@@ -0,0 +1,8 @@
---
import { Page } from '.';
import ToolLayout from '../../../layouts/ToolLayout.astro';
---
<ToolLayout>
<Page client:load />
</ToolLayout>
@@ -0,0 +1,26 @@
import { PageWrapper } from '../../../components/PageWrapper';
import { Controls } from '../../../components/Performance/Controls';
import { PerformanceInfo } from '../../../components/Performance/PerformanceInfo';
import { TankTable } from '../../../components/Performance/Table';
import { TankPerformancePersistent } from '../../../stores/tankPerformancePersistent';
import { TankPerformanceSort } from '../../../stores/tankPerformanceSort';
export function Page() {
return (
<TankPerformanceSort.Provider>
<TankPerformancePersistent.Provider>
<Content />
</TankPerformancePersistent.Provider>
</TankPerformanceSort.Provider>
);
}
function Content() {
return (
<PageWrapper color="jade" noMaxWidth>
<PerformanceInfo />
<Controls />
<TankTable />
</PageWrapper>
);
}
@@ -8,14 +8,13 @@ export interface TankPerformancePersistent {
playerCountPeriod: PlayerCountPeriod;
}
export const { Provider, use, useMutation, useStore } = createContextualStore(
() =>
create<TankPerformancePersistent>()(
persist(
subscribeWithSelector<TankPerformancePersistent>(() => ({
playerCountPeriod: PlayerCountPeriod.ThisMonth,
})),
{ name: 'tank-performance', merge: (a, b) => lodash.merge(b, a) },
),
export const TankPerformancePersistent = createContextualStore(() =>
create<TankPerformancePersistent>()(
persist(
subscribeWithSelector<TankPerformancePersistent>(() => ({
playerCountPeriod: PlayerCountPeriod.ThisMonth,
})),
{ name: 'tank-performance', merge: (a, b) => lodash.merge(b, a) },
),
),
);
@@ -1,4 +1,4 @@
import { TankPerformanceSortType } from '.';
import type { TankPerformanceSortType } from '.';
export const TankPerformanceSortTypeNames = {
winrate: 'Winrate',
@@ -9,10 +9,9 @@ export interface TankPerformanceSort {
direction: -1 | 1;
}
export const { Provider, use, useMutation, useStore } = createContextualStore(
() =>
create<TankPerformanceSort>()(() => ({
type: 'winrate',
direction: -1,
})),
export const TankPerformanceSort = createContextualStore(() =>
create<TankPerformanceSort>()(() => ({
type: 'winrate',
direction: -1,
})),
);