Delay rules updates during rule execution.
Any RuleCollection change while the rule collection is running a turn is now delayed until all rules are evaluated. This gives consistent semantics for when rule changes invoked my rules themselves are applied.
This commit is contained in:
parent
45bbfe4e8f
commit
8a2664c305
@ -44,6 +44,9 @@ var (
|
|||||||
// ErrNotUnique matches errors from functions attempting to identify
|
// ErrNotUnique matches errors from functions attempting to identify
|
||||||
// a single rule by an identifier that is not unique.
|
// a single rule by an identifier that is not unique.
|
||||||
ErrNotUnique = errors.New("not unique")
|
ErrNotUnique = errors.New("not unique")
|
||||||
|
|
||||||
|
// ErrAlreadyRunning means you tried to run the rules engine from inside a rule.
|
||||||
|
ErrAlreadyRunning = errors.New("cannot run a turn while running a turn")
|
||||||
)
|
)
|
||||||
|
|
||||||
// RuleFunc implements a Rule represented by a single function. It is the most
|
// RuleFunc implements a Rule represented by a single function. It is the most
|
||||||
@ -84,6 +87,10 @@ type RuleCollection[C StatsCollection] struct {
|
|||||||
byLabel map[string][]RuleID
|
byLabel map[string][]RuleID
|
||||||
nextID RuleID
|
nextID RuleID
|
||||||
steps []int
|
steps []int
|
||||||
|
|
||||||
|
rulesRunning bool
|
||||||
|
insertLater []*keyedRule[C]
|
||||||
|
deleteLater []RuleID
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRuleCollection initializes an empty RuleCollection.
|
// NewRuleCollection initializes an empty RuleCollection.
|
||||||
@ -101,20 +108,27 @@ func (r *RuleCollection[C]) Insert(rule Rule[C]) RuleID {
|
|||||||
id := r.nextID
|
id := r.nextID
|
||||||
r.nextID++
|
r.nextID++
|
||||||
k := &keyedRule[C]{id, rule}
|
k := &keyedRule[C]{id, rule}
|
||||||
r.rules[id] = k
|
if r.rulesRunning {
|
||||||
|
r.insertLater = append(r.insertLater, k)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
r.performInsert(k)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuleCollection[C]) performInsert(k *keyedRule[C]) {
|
||||||
|
r.rules[k.id] = k
|
||||||
|
|
||||||
s := r.byStep[k.Step()]
|
s := r.byStep[k.Step()]
|
||||||
if s == nil {
|
if s == nil {
|
||||||
r.steps = nil
|
r.steps = nil
|
||||||
}
|
}
|
||||||
s = append(s, id)
|
s = append(s, k.id)
|
||||||
r.byStep[k.Step()] = s
|
r.byStep[k.Step()] = s
|
||||||
|
|
||||||
lbl := r.byLabel[k.Label()]
|
lbl := r.byLabel[k.Label()]
|
||||||
lbl = append(lbl, id)
|
lbl = append(lbl, k.id)
|
||||||
r.byLabel[k.Label()] = lbl
|
r.byLabel[k.Label()] = lbl
|
||||||
|
|
||||||
return id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveID removes the rule with the given ID from the collection, if present.
|
// RemoveID removes the rule with the given ID from the collection, if present.
|
||||||
@ -199,6 +213,10 @@ func (r *RuleCollection[C]) RemoveUniqueLabel(label string) (bool, error) {
|
|||||||
if len(target) != 1 {
|
if len(target) != 1 {
|
||||||
return false, fmt.Errorf("%w: %q", ErrNotUnique, label)
|
return false, fmt.Errorf("%w: %q", ErrNotUnique, label)
|
||||||
}
|
}
|
||||||
|
if r.rulesRunning {
|
||||||
|
r.deleteLater = append(r.deleteLater, target[0])
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
id := target[0]
|
id := target[0]
|
||||||
rule := r.rules[id]
|
rule := r.rules[id]
|
||||||
@ -213,6 +231,10 @@ func (r *RuleCollection[C]) RemoveUniqueLabel(label string) (bool, error) {
|
|||||||
// rules were thus removed.
|
// rules were thus removed.
|
||||||
func (r *RuleCollection[C]) RemoveAllLabel(label string) int {
|
func (r *RuleCollection[C]) RemoveAllLabel(label string) int {
|
||||||
target := r.byLabel[label]
|
target := r.byLabel[label]
|
||||||
|
if r.rulesRunning {
|
||||||
|
r.deleteLater = append(r.deleteLater, target...)
|
||||||
|
return len(target)
|
||||||
|
}
|
||||||
delete(r.byLabel, label)
|
delete(r.byLabel, label)
|
||||||
for _, t := range target {
|
for _, t := range target {
|
||||||
// RemoveID works fine when the label is already completely gone; it
|
// RemoveID works fine when the label is already completely gone; it
|
||||||
@ -232,6 +254,10 @@ func (r *RuleCollection[C]) RemoveUniqueStep(step int) (bool, error) {
|
|||||||
if len(target) != 1 {
|
if len(target) != 1 {
|
||||||
return false, fmt.Errorf("%w: %d", ErrNotUnique, step)
|
return false, fmt.Errorf("%w: %d", ErrNotUnique, step)
|
||||||
}
|
}
|
||||||
|
if r.rulesRunning {
|
||||||
|
r.deleteLater = append(r.deleteLater, target[0])
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
id := target[0]
|
id := target[0]
|
||||||
rule := r.rules[id]
|
rule := r.rules[id]
|
||||||
@ -249,6 +275,10 @@ func (r *RuleCollection[C]) RemoveAllStep(step int) int {
|
|||||||
if target == nil {
|
if target == nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
if r.rulesRunning {
|
||||||
|
r.deleteLater = append(r.deleteLater, target...)
|
||||||
|
return len(target)
|
||||||
|
}
|
||||||
delete(r.byStep, step)
|
delete(r.byStep, step)
|
||||||
r.steps = nil
|
r.steps = nil
|
||||||
for _, t := range target {
|
for _, t := range target {
|
||||||
@ -265,6 +295,13 @@ func (r *RuleCollection[C]) RemoveAllStep(step int) int {
|
|||||||
// It stops early if it observes a failure other than the errors described in
|
// It stops early if it observes a failure other than the errors described in
|
||||||
// Rule.Enact as having special meaning to the rules engine.
|
// Rule.Enact as having special meaning to the rules engine.
|
||||||
func (r *RuleCollection[C]) Run(p *Player[C]) error {
|
func (r *RuleCollection[C]) Run(p *Player[C]) error {
|
||||||
|
if r.rulesRunning {
|
||||||
|
return ErrAlreadyRunning
|
||||||
|
}
|
||||||
|
r.rulesRunning = true
|
||||||
|
defer r.applyDelayedUpdates()
|
||||||
|
defer func() { r.rulesRunning = false }()
|
||||||
|
|
||||||
steps := r.steps
|
steps := r.steps
|
||||||
if steps == nil {
|
if steps == nil {
|
||||||
// Step set changed, recalculate.
|
// Step set changed, recalculate.
|
||||||
@ -324,3 +361,16 @@ func (r *RuleCollection[C]) Run(p *Player[C]) error {
|
|||||||
}
|
}
|
||||||
return errs.Emit()
|
return errs.Emit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RuleCollection[C]) applyDelayedUpdates() {
|
||||||
|
insert := r.insertLater
|
||||||
|
r.insertLater = nil
|
||||||
|
remove := r.deleteLater
|
||||||
|
r.deleteLater = nil
|
||||||
|
for _, k := range insert {
|
||||||
|
r.performInsert(k)
|
||||||
|
}
|
||||||
|
for _, id := range remove {
|
||||||
|
r.RemoveID(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user