Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add light cone optimization #2533

Merged
merged 8 commits into from
Nov 10, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions libs/sr/page-team/src/Optimize/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CardThemed } from '@genshin-optimizer/common/ui'
import { characterKeyToGenderedKey } from '@genshin-optimizer/sr/assets'
import type { RelicSlotKey } from '@genshin-optimizer/sr/consts'
import { type ICachedRelic } from '@genshin-optimizer/sr/db'
import {
Expand All @@ -7,6 +8,7 @@ import {
} from '@genshin-optimizer/sr/db-ui'
import type { ProgressResult } from '@genshin-optimizer/sr/solver'
import { MAX_BUILDS, Solver } from '@genshin-optimizer/sr/solver'
import { getCharStat, getLightConeStat } from '@genshin-optimizer/sr/stats'
import { useSrCalcContext } from '@genshin-optimizer/sr/ui'
import CloseIcon from '@mui/icons-material/Close'
import TrendingUpIcon from '@mui/icons-material/TrendingUp'
Expand Down Expand Up @@ -106,9 +108,21 @@ function OptimizeWrapper() {
),
[database.relics.values]
)
const totalPermutations = Object.values(relicsBySlot).reduce(
(total, relics) => total * relics.length,
1
const lightCones = useMemo(() => {
const { path } = getCharStat(characterKeyToGenderedKey(characterKey))
return database.lightCones.values.filter(({ key }) => {
// filter by path
const { path: lcPath } = getLightConeStat(key)
return path === lcPath
})
}, [characterKey, database.lightCones.values])
const totalPermutations = useMemo(
() =>
Object.values(relicsBySlot).reduce(
(total, relics) => total * relics.length,
1
) * lightCones.length,
[lightCones.length, relicsBySlot]
)

const [optimizing, setOptimizing] = useState(false)
Expand Down Expand Up @@ -137,6 +151,7 @@ function OptimizeWrapper() {
calc,
team.frames,
statFilters,
lightCones,
relicsBySlot,
numWorkers,
setProgress
Expand All @@ -152,7 +167,7 @@ function OptimizeWrapper() {
// Save results to optConfig
if (results.length)
database.optConfigs.newOrSetGeneratedBuildList(optConfigId, {
builds: results.slice(0, 5).map(({ ids, value }) => ({
builds: results.slice(0, 5).map(({ relicIds: ids, value }) => ({
lightConeId: equippedLightCone,
relicIds: ids,
value,
Expand All @@ -164,6 +179,7 @@ function OptimizeWrapper() {
optConfig.statFilters,
characterKey,
team.frames,
lightCones,
relicsBySlot,
numWorkers,
database.optConfigs,
Expand Down
85 changes: 46 additions & 39 deletions libs/sr/solver/src/childWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@ import type { NumTagFree } from '@genshin-optimizer/pando/engine'
import { compile } from '@genshin-optimizer/pando/engine'
import type { RelicSlotKey } from '@genshin-optimizer/sr/consts'
import { MAX_BUILDS } from './common'
import type { RelicStats } from './parentWorker'
import type { LightConeStats, RelicStats } from './parentWorker'
import type { BuildResult } from './solver'

const MAX_BUILDS_TO_SEND = 200_000
let compiledCalcFunction: (relicStats: RelicStats['stats'][]) => number[]
let lightConeStats: LightConeStats[]
let relicStatsBySlot: Record<RelicSlotKey, RelicStats[]>
let constraints: Array<{ value: number; isMax: boolean }> = []

export interface ChildCommandInit {
command: 'init'
lightConeStats: LightConeStats[]
relicStatsBySlot: Record<RelicSlotKey, RelicStats[]>
constraints: Array<{ value: number; isMax: boolean }>
detachedNodes: NumTagFree[]
Expand Down Expand Up @@ -71,6 +73,7 @@ async function handleEvent(e: MessageEvent<ChildCommand>): Promise<void> {

// Create compiledCalcFunction
async function init({
lightConeStats: lcs,
relicStatsBySlot: relics,
detachedNodes: combinedNodes,
constraints: initCons,
Expand All @@ -83,6 +86,7 @@ async function init({
{} // Initial values
// Header; includes custom formulas, such as `res`
)
lightConeStats = lcs
relicStatsBySlot = relics
constraints = initCons

Expand All @@ -109,44 +113,47 @@ async function start() {
builds = []
skipped = 0
}

relicStatsBySlot.head.forEach((head) => {
relicStatsBySlot.hands.forEach((hands) => {
relicStatsBySlot.feet.forEach((feet) => {
relicStatsBySlot.body.forEach((body) => {
relicStatsBySlot.sphere.forEach((sphere) => {
relicStatsBySlot.rope.forEach((rope) => {
// Step 5: Calculate the value
const results = compiledCalcFunction([
head.stats,
hands.stats,
feet.stats,
body.stats,
sphere.stats,
rope.stats,
])
if (
constraints.every(({ value, isMax }, i) =>
isMax ? results[i + 1] <= value : results[i + 1] >= value
)
) {
builds.push({
value: results[0], // We only pass 1 target to calculate, so just grab the 1st result
ids: {
head: head.id,
hands: hands.id,
feet: feet.id,
body: body.id,
sphere: sphere.id,
rope: rope.id,
},
})
} else {
skipped++
}
if (builds.length > MAX_BUILDS_TO_SEND) {
sliceSortSendBuilds()
}
lightConeStats.forEach((lightCone) => {
relicStatsBySlot.head.forEach((head) => {
relicStatsBySlot.hands.forEach((hands) => {
relicStatsBySlot.feet.forEach((feet) => {
relicStatsBySlot.body.forEach((body) => {
relicStatsBySlot.sphere.forEach((sphere) => {
relicStatsBySlot.rope.forEach((rope) => {
// Step 5: Calculate the value
const results = compiledCalcFunction([
lightCone.stats,
head.stats,
hands.stats,
feet.stats,
body.stats,
sphere.stats,
rope.stats,
])
if (
constraints.every(({ value, isMax }, i) =>
isMax ? results[i + 1] <= value : results[i + 1] >= value
)
) {
builds.push({
value: results[0], // We only pass 1 target to calculate, so just grab the 1st result
lightConeId: lightCone.id,
relicIds: {
head: head.id,
hands: hands.id,
feet: feet.id,
body: body.id,
sphere: sphere.id,
rope: rope.id,
},
})
} else {
skipped++
}
if (builds.length > MAX_BUILDS_TO_SEND) {
sliceSortSendBuilds()
}
})
frzyc marked this conversation as resolved.
Show resolved Hide resolved
})
})
})
Expand Down
31 changes: 29 additions & 2 deletions libs/sr/solver/src/parentWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import {
allRelicSlotKeys,
type RelicSlotKey,
} from '@genshin-optimizer/sr/consts'
import type { ICachedRelic } from '@genshin-optimizer/sr/db'
import type { ICachedLightCone, ICachedRelic } from '@genshin-optimizer/sr/db'
import {
lightConeData,
own,
srCalculatorWithEntries,
} from '@genshin-optimizer/sr/formula'
import { getRelicMainStatVal } from '@genshin-optimizer/sr/util'
import type { ChildCommandInit, ChildMessage } from './childWorker'
import { MAX_BUILDS } from './common'
Expand All @@ -15,6 +20,7 @@ let workers: Worker[]

export interface ParentCommandStart {
command: 'start'
lightCones: ICachedLightCone[]
relicsBySlot: Record<RelicSlotKey, ICachedRelic[]>
detachedNodes: NumTagFree[]
constraints: Array<{ value: number; isMax: boolean }>
Expand Down Expand Up @@ -53,6 +59,10 @@ export type RelicStats = {
id: string
stats: Record<string, number>
}
export type LightConeStats = {
id: string
stats: Record<string, number>
}

// Get proper typings for posting a message back to main thread
declare function postMessage(message: ParentMessage): void
Expand Down Expand Up @@ -81,6 +91,7 @@ async function handleEvent(e: MessageEvent<ParentCommand>): Promise<void> {
}

async function start({
lightCones,
relicsBySlot,
detachedNodes,
constraints,
Expand All @@ -89,7 +100,7 @@ async function start({
// Step 3: Optimize nodes, as needed
detachedNodes = flatten(detachedNodes)
detachedNodes = combineConst(detachedNodes)

const lightConeStats = lightCones.map(convertLightConeToStats)
const relicStatsBySlot = objKeyMap(allRelicSlotKeys, (slot) =>
relicsBySlot[slot].map(convertRelicToStats)
)
Expand Down Expand Up @@ -172,6 +183,7 @@ async function start({
// Initialize worker
const message: ChildCommandInit = {
command: 'init',
lightConeStats,
relicStatsBySlot: chunkedRelicStatsBySlot[index],
detachedNodes,
constraints,
Expand Down Expand Up @@ -216,3 +228,18 @@ function convertRelicToStats(relic: ICachedRelic): RelicStats {
},
}
}

function convertLightConeToStats(lightCone: ICachedLightCone): LightConeStats {
const calc = srCalculatorWithEntries(lightConeData(lightCone))

return {
id: lightCone.id,
stats: {
...objKeyMap(
['hp', 'atk', 'def'] as const,
(stat) => calc.compute(own.base[stat].with('sheet', 'lightCone')).val
),
[lightCone.key]: 1,
frzyc marked this conversation as resolved.
Show resolved Hide resolved
},
}
}
14 changes: 12 additions & 2 deletions libs/sr/solver/src/solver.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { detach, sum } from '@genshin-optimizer/pando/engine'
import type { CharacterKey, RelicSlotKey } from '@genshin-optimizer/sr/consts'
import { allRelicSetKeys } from '@genshin-optimizer/sr/consts'
import type { ICachedRelic, StatFilter, Team } from '@genshin-optimizer/sr/db'
import type {
ICachedLightCone,
ICachedRelic,
StatFilter,
Team,
} from '@genshin-optimizer/sr/db'
import {
Read,
type Calculator,
Expand All @@ -16,7 +21,8 @@ import type {

export interface BuildResult {
value: number
ids: Record<RelicSlotKey, string>
lightConeId: string
relicIds: Record<RelicSlotKey, string>
}

export interface ProgressResult {
Expand All @@ -28,6 +34,7 @@ export class Solver {
private calc: Calculator
private frames: Team['frames']
private statFilters: Array<Omit<StatFilter, 'disabled'>>
private lightCones: ICachedLightCone[]
private relicsBySlot: Record<RelicSlotKey, ICachedRelic[]>
private numWorkers: number
private setProgress: (progress: ProgressResult) => void
Expand All @@ -39,6 +46,7 @@ export class Solver {
calc: Calculator,
frames: Team['frames'],
statFilters: Array<Omit<StatFilter, 'disabled'>>,
lightCones: ICachedLightCone[],
relicsBySlot: Record<RelicSlotKey, ICachedRelic[]>,
numWorkers: number,
setProgress: (progress: ProgressResult) => void
Expand All @@ -47,6 +55,7 @@ export class Solver {
this.calc = calc
this.frames = frames
this.statFilters = statFilters
this.lightCones = lightCones
this.relicsBySlot = relicsBySlot
this.numWorkers = numWorkers
this.setProgress = setProgress
Expand Down Expand Up @@ -78,6 +87,7 @@ export class Solver {
// Start worker
const message: ParentCommandStart = {
command: 'start',
lightCones: this.lightCones,
relicsBySlot: this.relicsBySlot,
detachedNodes: this.detachNodes(),
constraints: this.statFilters.map(({ value, isMax }) => ({
Expand Down