Skip to content

Commit

Permalink
Merge pull request #3400 from wowsims/apl
Browse files Browse the repository at this point in the history
Detect infinite loops in APL and give better debug info
  • Loading branch information
jimmyt857 authored Jul 30, 2023
2 parents 67110e9 + e03e1eb commit b94e55c
Show file tree
Hide file tree
Showing 17 changed files with 239 additions and 21 deletions.
18 changes: 18 additions & 0 deletions sim/core/apl.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ type APLRotation struct {
curWarnings []string
prepullWarnings [][]string
priorityListWarnings [][]string

// Used for detecting infinite loops.
prevTime time.Duration
actionsWithoutAdvancing int
}

func (rot *APLRotation) validationWarning(message string, vals ...interface{}) {
Expand Down Expand Up @@ -141,6 +145,8 @@ func (rot *APLRotation) allPrepullActions() []*APLAction {

func (rot *APLRotation) reset(sim *Simulation) {
rot.strictSequence = nil
rot.actionsWithoutAdvancing = 0
rot.prevTime = 0
for _, action := range rot.allAPLActions() {
action.impl.Reset(sim)
}
Expand All @@ -157,6 +163,18 @@ func (apl *APLRotation) DoNextAction(sim *Simulation) {
if apl.unit.GCD.IsReady(sim) {
apl.unit.WaitUntil(sim, sim.CurrentTime)
}

// Detect infinite loops.
if sim.CurrentTime == apl.prevTime {
apl.actionsWithoutAdvancing++
if apl.actionsWithoutAdvancing > 1000 {
panic(fmt.Sprintf("[USER_ERROR] Infinite loop detected, current action:\n%s", action))
}
} else {
apl.prevTime = sim.CurrentTime
apl.actionsWithoutAdvancing = 0
}

return
}
}
Expand Down
21 changes: 21 additions & 0 deletions sim/core/apl_action.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package core

import (
"fmt"

"github.com/wowsims/wotlk/sim/core/proto"
)

Expand All @@ -24,6 +26,14 @@ func (action *APLAction) GetAllActions() []*APLAction {
return actions
}

func (action *APLAction) String() string {
if action.condition == nil {
return fmt.Sprintf("ACTION = %s", action.impl)
} else {
return fmt.Sprintf("IF = %s\nACTION = %s", action.condition, action.impl)
}
}

type APLActionImpl interface {
// Returns all inner Actions.
GetInnerActions() []*APLAction
Expand All @@ -39,8 +49,19 @@ type APLActionImpl interface {

// Performs the action.
Execute(*Simulation)

// Pretty-print string for debugging.
String() string
}

// Provides empty implementations for the action impl interface functions.
type defaultAPLActionImpl struct {
}

func (impl defaultAPLActionImpl) GetInnerActions() []*APLAction { return nil }
func (impl defaultAPLActionImpl) Finalize(*APLRotation) {}
func (impl defaultAPLActionImpl) Reset(*Simulation) {}

func (rot *APLRotation) newAPLAction(config *proto.APLAction) *APLAction {
if config == nil {
return nil
Expand Down
28 changes: 18 additions & 10 deletions sim/core/apl_actions_core.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package core

import (
"fmt"

"github.com/wowsims/wotlk/sim/core/proto"
)

type APLActionCastSpell struct {
defaultAPLActionImpl
spell *Spell
target UnitReference
}
Expand All @@ -23,17 +26,18 @@ func (rot *APLRotation) newActionCastSpell(config *proto.APLActionCastSpell) APL
target: target,
}
}
func (action *APLActionCastSpell) GetInnerActions() []*APLAction { return nil }
func (action *APLActionCastSpell) Finalize(*APLRotation) {}
func (action *APLActionCastSpell) Reset(*Simulation) {}
func (action *APLActionCastSpell) IsReady(sim *Simulation) bool {
return action.spell.CanCast(sim, action.target.Get())
}
func (action *APLActionCastSpell) Execute(sim *Simulation) {
action.spell.Cast(sim, action.target.Get())
}
func (action *APLActionCastSpell) String() string {
return fmt.Sprintf("Cast Spell(%s)", action.spell.ActionID)
}

type APLActionMultidot struct {
defaultAPLActionImpl
spell *Spell
maxDots int32
maxOverlap APLValue
Expand Down Expand Up @@ -67,8 +71,6 @@ func (rot *APLRotation) newActionMultidot(config *proto.APLActionMultidot) APLAc
maxOverlap: maxOverlap,
}
}
func (action *APLActionMultidot) GetInnerActions() []*APLAction { return nil }
func (action *APLActionMultidot) Finalize(*APLRotation) {}
func (action *APLActionMultidot) Reset(*Simulation) {
action.nextTarget = nil
}
Expand All @@ -88,8 +90,12 @@ func (action *APLActionMultidot) IsReady(sim *Simulation) bool {
func (action *APLActionMultidot) Execute(sim *Simulation) {
action.spell.Cast(sim, action.nextTarget)
}
func (action *APLActionMultidot) String() string {
return fmt.Sprintf("Multidot(%s)", action.spell.ActionID)
}

type APLActionAutocastOtherCooldowns struct {
defaultAPLActionImpl
character *Character

nextReadyMCD *MajorCooldown
Expand All @@ -101,8 +107,6 @@ func (rot *APLRotation) newActionAutocastOtherCooldowns(config *proto.APLActionA
character: unit.Env.Raid.GetPlayerFromUnit(unit).GetCharacter(),
}
}
func (action *APLActionAutocastOtherCooldowns) GetInnerActions() []*APLAction { return nil }
func (action *APLActionAutocastOtherCooldowns) Finalize(*APLRotation) {}
func (action *APLActionAutocastOtherCooldowns) Reset(*Simulation) {
action.nextReadyMCD = nil
}
Expand All @@ -114,8 +118,12 @@ func (action *APLActionAutocastOtherCooldowns) Execute(sim *Simulation) {
action.nextReadyMCD.tryActivateHelper(sim, action.character)
action.character.UpdateMajorCooldowns()
}
func (action *APLActionAutocastOtherCooldowns) String() string {
return fmt.Sprintf("Autocast Other Cooldowns")
}

type APLActionWait struct {
defaultAPLActionImpl
unit *Unit
duration APLValue
}
Expand All @@ -127,12 +135,12 @@ func (rot *APLRotation) newActionWait(config *proto.APLActionWait) APLActionImpl
duration: rot.coerceTo(rot.newAPLValue(config.Duration), proto.APLValueType_ValueTypeDuration),
}
}
func (action *APLActionWait) GetInnerActions() []*APLAction { return nil }
func (action *APLActionWait) Finalize(*APLRotation) {}
func (action *APLActionWait) Reset(*Simulation) {}
func (action *APLActionWait) IsReady(sim *Simulation) bool {
return action.duration != nil
}
func (action *APLActionWait) Execute(sim *Simulation) {
action.unit.WaitUntil(sim, sim.CurrentTime+action.duration.GetDuration(sim))
}
func (action *APLActionWait) String() string {
return fmt.Sprintf("Wait(%s)", action.duration)
}
23 changes: 14 additions & 9 deletions sim/core/apl_actions_misc.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package core

import (
"fmt"

"github.com/wowsims/wotlk/sim/core/proto"
)

type APLActionChangeTarget struct {
defaultAPLActionImpl
unit *Unit
newTarget UnitReference
}
Expand All @@ -18,9 +21,6 @@ func (rot *APLRotation) newActionChangeTarget(config *proto.APLActionChangeTarge
newTarget: newTarget,
}
}
func (action *APLActionChangeTarget) GetInnerActions() []*APLAction { return nil }
func (action *APLActionChangeTarget) Finalize(*APLRotation) {}
func (action *APLActionChangeTarget) Reset(*Simulation) {}
func (action *APLActionChangeTarget) IsReady(sim *Simulation) bool {
return action.unit.CurrentTarget != action.newTarget.Get()
}
Expand All @@ -30,8 +30,12 @@ func (action *APLActionChangeTarget) Execute(sim *Simulation) {
}
action.unit.CurrentTarget = action.newTarget.Get()
}
func (action *APLActionChangeTarget) String() string {
return fmt.Sprintf("Change Target(%s)", action.newTarget.Get().Label)
}

type APLActionCancelAura struct {
defaultAPLActionImpl
aura *Aura
}

Expand All @@ -44,9 +48,6 @@ func (rot *APLRotation) newActionCancelAura(config *proto.APLActionCancelAura) A
aura: aura.Get(),
}
}
func (action *APLActionCancelAura) GetInnerActions() []*APLAction { return nil }
func (action *APLActionCancelAura) Finalize(*APLRotation) {}
func (action *APLActionCancelAura) Reset(*Simulation) {}
func (action *APLActionCancelAura) IsReady(sim *Simulation) bool {
return action.aura.IsActive()
}
Expand All @@ -56,8 +57,12 @@ func (action *APLActionCancelAura) Execute(sim *Simulation) {
}
action.aura.Deactivate(sim)
}
func (action *APLActionCancelAura) String() string {
return fmt.Sprintf("Cancel Aura(%s)", action.aura.ActionID)
}

type APLActionTriggerICD struct {
defaultAPLActionImpl
aura *Aura
}

Expand All @@ -70,9 +75,6 @@ func (rot *APLRotation) newActionTriggerICD(config *proto.APLActionTriggerICD) A
aura: aura.Get(),
}
}
func (action *APLActionTriggerICD) GetInnerActions() []*APLAction { return nil }
func (action *APLActionTriggerICD) Finalize(*APLRotation) {}
func (action *APLActionTriggerICD) Reset(*Simulation) {}
func (action *APLActionTriggerICD) IsReady(sim *Simulation) bool {
return action.aura.IsActive()
}
Expand All @@ -82,3 +84,6 @@ func (action *APLActionTriggerICD) Execute(sim *Simulation) {
}
action.aura.Icd.Use(sim)
}
func (action *APLActionTriggerICD) String() string {
return fmt.Sprintf("Trigger ICD(%s)", action.aura.ActionID)
}
17 changes: 15 additions & 2 deletions sim/core/apl_actions_sequences.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package core

import (
"fmt"
"strings"

"github.com/wowsims/wotlk/sim/core/proto"
)

type APLActionSequence struct {
defaultAPLActionImpl
unit *Unit
name string
actions []*APLAction
Expand Down Expand Up @@ -41,8 +45,12 @@ func (action *APLActionSequence) Execute(sim *Simulation) {
action.actions[action.curIdx].Execute(sim)
action.curIdx++
}
func (action *APLActionSequence) String() string {
return "Sequence(" + strings.Join(MapSlice(action.actions, func(subaction *APLAction) string { return fmt.Sprintf("(%s)", subaction) }), "+") + ")"
}

type APLActionResetSequence struct {
defaultAPLActionImpl
unit *Unit
name string
sequence *APLActionSequence
Expand All @@ -58,7 +66,6 @@ func (rot *APLRotation) newActionResetSequence(config *proto.APLActionResetSeque
name: config.SequenceName,
}
}
func (action *APLActionResetSequence) GetInnerActions() []*APLAction { return nil }
func (action *APLActionResetSequence) Finalize(rot *APLRotation) {
for _, otherAction := range rot.allAPLActions() {
if sequence, ok := otherAction.impl.(*APLActionSequence); ok && sequence.name == action.name {
Expand All @@ -68,15 +75,18 @@ func (action *APLActionResetSequence) Finalize(rot *APLRotation) {
}
rot.validationWarning("No sequence with name: '%s'", action.name)
}
func (action *APLActionResetSequence) Reset(*Simulation) {}
func (action *APLActionResetSequence) IsReady(sim *Simulation) bool {
return action.sequence != nil
}
func (action *APLActionResetSequence) Execute(sim *Simulation) {
action.sequence.curIdx = 0
}
func (action *APLActionResetSequence) String() string {
return fmt.Sprintf("Reset Sequence(name = '%s')", action.name)
}

type APLActionStrictSequence struct {
defaultAPLActionImpl
unit *Unit
actions []*APLAction
curIdx int
Expand Down Expand Up @@ -128,3 +138,6 @@ func (action *APLActionStrictSequence) Execute(sim *Simulation) {
action.unit.Rotation.strictSequence = nil
}
}
func (action *APLActionStrictSequence) String() string {
return "Strict Sequence(" + strings.Join(MapSlice(action.actions, func(subaction *APLAction) string { return fmt.Sprintf("(%s)", subaction) }), "+") + ")"
}
8 changes: 8 additions & 0 deletions sim/core/apl_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ func (ur UnitReference) Get() *Unit {
}
}

func (ur *UnitReference) String() string {
return ur.Get().Label
}

func NewUnitReference(ref *proto.UnitReference, contextUnit *Unit) UnitReference {
if ref == nil || ref.Type == proto.UnitReference_Unknown {
return UnitReference{}
Expand Down Expand Up @@ -70,6 +74,10 @@ func (ar *AuraReference) Get() *Aura {
}
}

func (ar *AuraReference) String() string {
return ar.Get().ActionID.String()
}

func newAuraReferenceHelper(sourceUnit UnitReference, auraId *proto.ActionID, auraGetter func(*Unit, ActionID) *Aura) AuraReference {
if sourceUnit.Get() == nil {
return AuraReference{}
Expand Down
3 changes: 3 additions & 0 deletions sim/core/apl_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ type APLValue interface {
GetFloat(*Simulation) float64
GetDuration(*Simulation) time.Duration
GetString(*Simulation) string

// Pretty-print string for debugging.
String() string
}

// Provides empty implementations for the GetX() value interface functions.
Expand Down
13 changes: 13 additions & 0 deletions sim/core/apl_values_aura.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package core

import (
"fmt"
"time"

"github.com/wowsims/wotlk/sim/core/proto"
Expand All @@ -26,6 +27,9 @@ func (value *APLValueAuraIsActive) Type() proto.APLValueType {
func (value *APLValueAuraIsActive) GetBool(sim *Simulation) bool {
return value.aura.Get().IsActive()
}
func (value *APLValueAuraIsActive) String() string {
return fmt.Sprintf("Aura Is Active(%s)", value.aura.String())
}

type APLValueAuraRemainingTime struct {
defaultAPLValueImpl
Expand All @@ -47,6 +51,9 @@ func (value *APLValueAuraRemainingTime) Type() proto.APLValueType {
func (value *APLValueAuraRemainingTime) GetDuration(sim *Simulation) time.Duration {
return value.aura.Get().RemainingDuration(sim)
}
func (value *APLValueAuraRemainingTime) String() string {
return fmt.Sprintf("Aura Remaining Time(%s)", value.aura.String())
}

type APLValueAuraNumStacks struct {
defaultAPLValueImpl
Expand All @@ -72,6 +79,9 @@ func (value *APLValueAuraNumStacks) Type() proto.APLValueType {
func (value *APLValueAuraNumStacks) GetInt(sim *Simulation) int32 {
return value.aura.Get().GetStacks()
}
func (value *APLValueAuraNumStacks) String() string {
return fmt.Sprintf("Aura Num Stacks(%s)", value.aura.String())
}

type APLValueAuraInternalCooldown struct {
defaultAPLValueImpl
Expand All @@ -93,3 +103,6 @@ func (value *APLValueAuraInternalCooldown) Type() proto.APLValueType {
func (value *APLValueAuraInternalCooldown) GetDuration(sim *Simulation) time.Duration {
return value.aura.Get().Icd.TimeToReady(sim)
}
func (value *APLValueAuraInternalCooldown) String() string {
return fmt.Sprintf("Aura ICD(%s)", value.aura.String())
}
Loading

0 comments on commit b94e55c

Please sign in to comment.