mirror of
https://github.com/blitzkit/blitzkit.git
synced 2026-10-21 18:57:03 +02:00
feat: migrate performance
This commit is contained in:
1 parent
b31c7a6738
commit
8d51ce2bea
20 files changed
+122
-149
No files matched your search
Vendored
+1
@@ -104,6 +104,7 @@
|
||||
"mmnow",
|
||||
"modeltransform",
|
||||
"MURICA",
|
||||
"nanostores",
|
||||
"NEGATIVEX",
|
||||
"NEGATIVEY",
|
||||
"NEGATIVEZ",
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -7,5 +7,9 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["src/**/*", ".next/types/**/*.ts"]
|
||||
"include": [
|
||||
"src/**/*",
|
||||
".next/types/**/*.ts",
|
||||
"../website/src/components/Performance/PerformanceInfo.tsx"
|
||||
]
|
||||
}
|
||||
File renamed without changes.
+1
-1
@@ -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() {
|
||||
+3
-3
@@ -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();
|
||||
+1
-1
@@ -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),
|
||||
+2
-2
@@ -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(
|
||||
+2
-2
@@ -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
-1
@@ -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';
|
||||
+8
-7
@@ -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,
|
||||
+32
-19
@@ -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;
|
||||
|
||||
+14
-11
@@ -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,
|
||||
})),
|
||||
);
|
||||
Reference in new issue
Block a user