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

Reduce APL calls for Rogue sim using energy thresholds #3880

Merged
merged 5 commits into from
Oct 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
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