Skip to content

Commit

Permalink
17% perf optimization for rogues using energy thresholds
Browse files Browse the repository at this point in the history
  • Loading branch information
jimmyt857 committed Oct 14, 2023
1 parent 87b96cf commit b1f2d47
Show file tree
Hide file tree
Showing 5 changed files with 732 additions and 624 deletions.
13 changes: 13 additions & 0 deletions sim/core/apl_values_operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,19 @@ func (rot *APLRotation) coerceToSameType(value1 APLValue, value2 APLValue) (APLV
return coerced[0], coerced[1]
}

// Utility function which returns the constant float value of a Const or Coerced(Const) APL value.
// Returns -1 if the value is not a constant, or does not have a float value.
func getConstAPLFloatValue(value APLValue) float64 {
if constValue, isConst := value.(*APLValueConst); isConst {
return constValue.GetFloat(nil)
} else if coercedValue, isCoerced := value.(*APLValueCoerced); isCoerced {
if _, innerIsConst := coercedValue.inner.(*APLValueConst); innerIsConst {
return coercedValue.GetFloat(nil)
}
}
return -1
}

type APLValueCompare struct {
DefaultAPLValueImpl
op proto.APLValueCompare_ComparisonOperator
Expand Down
103 changes: 93 additions & 10 deletions sim/core/energy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package core

import (
"fmt"
"math"
"slices"
"time"

"github.com/wowsims/wotlk/sim/core/proto"
Expand All @@ -22,7 +24,16 @@ type energyBar struct {

comboPoints int32

onEnergyGain OnEnergyGain
// List of energy levels that might affect APL decisions. E.g:
// [10, 15, 20, 30, 60, 85]
energyDecisionThresholds []int

// Slice with len == 110 with each index corresponding to an amount of energy. Looks like this:
// [0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, ...]
// Increments by 1 at each value of energyDecisionThresholds.
cumulativeEnergyDecisionThresholds []int

onEnergyGain func(*Simulation, bool)
tickAction *PendingAction

// Multiplies energy regen from ticks.
Expand All @@ -38,14 +49,16 @@ func (unit *Unit) EnableEnergyBar(maxEnergy float64, onEnergyGain OnEnergyGain)
unit.energyBar = energyBar{
unit: unit,
maxEnergy: max(100, maxEnergy),
onEnergyGain: func(sim *Simulation) {
onEnergyGain: func(sim *Simulation, crossedThreshold bool) {
if sim.CurrentTime < 0 {
return
}

if !sim.Options.Interactive && (!unit.IsWaitingForEnergy() || unit.DoneWaitingForEnergy(sim)) {
if unit.IsUsingAPL {
unit.Rotation.DoNextAction(sim)
if crossedThreshold {
unit.Rotation.DoNextAction(sim)
}
} else {
onEnergyGain(sim)
}
Expand All @@ -57,6 +70,73 @@ func (unit *Unit) EnableEnergyBar(maxEnergy float64, onEnergyGain OnEnergyGain)
}
}

// Computes the energy thresholds.
func (eb *energyBar) setupEnergyThresholds() {
if !eb.unit.IsUsingAPL {
return
}
var energyThresholds []int

// Energy thresholds from spell costs.
for _, action := range eb.unit.Rotation.allAPLActions() {
for _, spell := range action.GetAllSpells() {
if _, ok := spell.Cost.(*EnergyCost); ok {
energyThresholds = append(energyThresholds, int(math.Ceil(spell.DefaultCast.Cost)))
}
}
}

// Energy thresholds from conditional comparisons.
for _, action := range eb.unit.Rotation.allAPLActions() {
for _, value := range action.GetAllAPLValues() {
if cmpValue, ok := value.(*APLValueCompare); ok {
_, lhsIsEnergy := cmpValue.lhs.(*APLValueCurrentEnergy)
_, rhsIsEnergy := cmpValue.rhs.(*APLValueCurrentEnergy)
if !lhsIsEnergy && !rhsIsEnergy {
continue
}

lhsConstVal := getConstAPLFloatValue(cmpValue.lhs)
rhsConstVal := getConstAPLFloatValue(cmpValue.rhs)

if lhsIsEnergy && rhsConstVal != -1 {
energyThresholds = append(energyThresholds, int(math.Ceil(rhsConstVal)))
} else if rhsIsEnergy && lhsConstVal != -1 {
energyThresholds = append(energyThresholds, int(math.Ceil(lhsConstVal)))
}
}
}
}

slices.SortStableFunc(energyThresholds, func(t1, t2 int) int {
return t1 - t2
})

// Add each unique value to the final thresholds list.
curVal := 0
for _, threshold := range energyThresholds {
if threshold > curVal {
eb.energyDecisionThresholds = append(eb.energyDecisionThresholds, threshold)
curVal = threshold
}
}

curEnergy := 0
cumulativeVal := 0
eb.cumulativeEnergyDecisionThresholds = make([]int, int(eb.maxEnergy)+1)
for _, threshold := range eb.energyDecisionThresholds {
for curEnergy < threshold {
eb.cumulativeEnergyDecisionThresholds[curEnergy] = cumulativeVal
curEnergy++
}
cumulativeVal++
}
for curEnergy < len(eb.cumulativeEnergyDecisionThresholds) {
eb.cumulativeEnergyDecisionThresholds[curEnergy] = cumulativeVal
curEnergy++
}
}

func (unit *Unit) HasEnergyBar() bool {
return unit.energyBar.unit != nil
}
Expand All @@ -69,7 +149,7 @@ func (eb *energyBar) NextEnergyTickAt() time.Duration {
return eb.tickAction.NextActionAt
}

func (eb *energyBar) addEnergyInternal(sim *Simulation, amount float64, metrics *ResourceMetrics) {
func (eb *energyBar) addEnergyInternal(sim *Simulation, amount float64, metrics *ResourceMetrics) bool {
if amount < 0 {
panic("Trying to add negative energy!")
}
Expand All @@ -81,11 +161,14 @@ func (eb *energyBar) addEnergyInternal(sim *Simulation, amount float64, metrics
eb.unit.Log(sim, "Gained %0.3f energy from %s (%0.3f --> %0.3f).", amount, metrics.ActionID, eb.currentEnergy, newEnergy)
}

crossedThreshold := eb.cumulativeEnergyDecisionThresholds != nil && eb.cumulativeEnergyDecisionThresholds[int(eb.currentEnergy)] != eb.cumulativeEnergyDecisionThresholds[int(newEnergy)]
eb.currentEnergy = newEnergy

return crossedThreshold
}
func (eb *energyBar) AddEnergy(sim *Simulation, amount float64, metrics *ResourceMetrics) {
eb.addEnergyInternal(sim, amount, metrics)
eb.onEnergyGain(sim)
crossedThreshold := eb.addEnergyInternal(sim, amount, metrics)
eb.onEnergyGain(sim, crossedThreshold)
}

func (eb *energyBar) SpendEnergy(sim *Simulation, amount float64, metrics *ResourceMetrics) {
Expand All @@ -112,8 +195,8 @@ func (eb *energyBar) ResetEnergyTick(sim *Simulation) {
timeSinceLastTick := sim.CurrentTime - (eb.NextEnergyTickAt() - EnergyTickDuration)
partialTickAmount := (EnergyPerTick * eb.EnergyTickMultiplier) * (float64(timeSinceLastTick) / float64(EnergyTickDuration))

eb.addEnergyInternal(sim, partialTickAmount, eb.regenMetrics)
eb.onEnergyGain(sim)
crossedThreshold := eb.addEnergyInternal(sim, partialTickAmount, eb.regenMetrics)
eb.onEnergyGain(sim, crossedThreshold)

eb.newTickAction(sim, false, sim.CurrentTime)
}
Expand Down Expand Up @@ -152,8 +235,8 @@ func (eb *energyBar) newTickAction(sim *Simulation, randomTickTime bool, startAt
Priority: ActionPriorityRegen,
}
pa.OnAction = func(sim *Simulation) {
eb.addEnergyInternal(sim, EnergyPerTick*eb.EnergyTickMultiplier, eb.regenMetrics)
eb.onEnergyGain(sim)
crossedThreshold := eb.addEnergyInternal(sim, EnergyPerTick*eb.EnergyTickMultiplier, eb.regenMetrics)
eb.onEnergyGain(sim, crossedThreshold)

pa.NextActionAt = sim.CurrentTime + EnergyTickDuration
sim.AddPendingAction(pa)
Expand Down
11 changes: 11 additions & 0 deletions sim/core/unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,17 @@ func (unit *Unit) finalize() {
for _, spell := range unit.Spellbook {
spell.finalize()
}

if unit.HasEnergyBar() {
// For now, restrict this optimization to rogues only. Ferals will require
// some extra logic to handle their ExcessEnergy() calc.
agent := unit.Env.Raid.GetPlayerFromUnit(unit)
if agent != nil && agent.GetCharacter().Class == proto.Class_ClassRogue {
unit.Env.RegisterPostFinalizeEffect(func() {
unit.energyBar.setupEnergyThresholds()
})
}
}
}

func (unit *Unit) init(sim *Simulation) {
Expand Down
Loading

0 comments on commit b1f2d47

Please sign in to comment.