Skip to content

Commit

Permalink
Merge pull request #3880 from wowsims/apl
Browse files Browse the repository at this point in the history
Reduce APL calls for Rogue sim using energy thresholds
  • Loading branch information
jimmyt857 committed Oct 14, 2023
2 parents 8aae262 + 10b45b2 commit 06fb5be
Show file tree
Hide file tree
Showing 8 changed files with 751 additions and 629 deletions.
12 changes: 9 additions & 3 deletions sim/core/apl_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,16 @@ func (action *APLAction) GetAllActions() []*APLAction {
func (action *APLAction) GetAllAPLValues() []APLValue {
var values []APLValue
for _, a := range action.GetAllActions() {
values = append(values, a.impl.GetAPLValues()...)
unprocessed := a.impl.GetAPLValues()
if a.condition != nil {
values = append(values, a.condition)
values = append(values, a.condition.GetInnerValues()...)
unprocessed = append(unprocessed, a.condition)
}

for len(unprocessed) > 0 {
next := unprocessed[len(unprocessed)-1]
unprocessed = unprocessed[:len(unprocessed)-1]
values = append(values, next)
unprocessed = append(unprocessed, next.GetInnerValues()...)
}
}
return FilterSlice(values, func(val APLValue) bool { return val != nil })
Expand Down
3 changes: 3 additions & 0 deletions sim/core/apl_actions_casting.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ func (rot *APLRotation) newActionChannelSpell(config *proto.APLActionChannelSpel
allowRecast: config.AllowRecast,
}
}
func (action *APLActionChannelSpell) GetAPLValues() []APLValue {
return []APLValue{action.interruptIf}
}
func (action *APLActionChannelSpell) IsReady(sim *Simulation) bool {
return action.spell.CanCast(sim, action.target.Get())
}
Expand Down
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
110 changes: 100 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 == maxEnergy+1 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,76 @@ func (unit *Unit) EnableEnergyBar(maxEnergy float64, onEnergyGain OnEnergyGain)
}
}

// Computes the energy thresholds.
func (eb *energyBar) setupEnergyThresholds() {
if eb.unit == nil {
return
}
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 +152,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 +164,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 +198,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 +238,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 All @@ -170,6 +256,10 @@ func (eb *energyBar) reset(sim *Simulation) {
eb.currentEnergy = eb.maxEnergy
eb.comboPoints = 0
eb.newTickAction(sim, true, sim.Environment.PrepullStartTime())

if eb.cumulativeEnergyDecisionThresholds != nil && sim.Log != nil {
eb.unit.Log(sim, "[DEBUG] APL Energy decision thresholds: %v", eb.energyDecisionThresholds)
}
}

type EnergyCostOptions struct {
Expand Down
9 changes: 9 additions & 0 deletions sim/core/unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,15 @@ func (unit *Unit) finalize() {
for _, spell := range unit.Spellbook {
spell.finalize()
}

// 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
2 changes: 1 addition & 1 deletion sim/rogue/TestAssassination.results
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ character_stats_results: {
final_stats: 221
final_stats: 0
final_stats: 5504.84
final_stats: 469.94995
final_stats: 469.94994
final_stats: 2072.9756
final_stats: 221
final_stats: 94
Expand Down
Loading

0 comments on commit 06fb5be

Please sign in to comment.