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
|
||||
// a single rule by an identifier that is 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
|
||||
@ -84,6 +87,10 @@ type RuleCollection[C StatsCollection] struct {
|
||||
byLabel map[string][]RuleID
|
||||
nextID RuleID
|
||||
steps []int
|
||||
|
||||
rulesRunning bool
|
||||
insertLater []*keyedRule[C]
|
||||
deleteLater []RuleID
|
||||
}
|
||||
|
||||
// NewRuleCollection initializes an empty RuleCollection.
|
||||
@ -101,20 +108,27 @@ func (r *RuleCollection[C]) Insert(rule Rule[C]) RuleID {
|
||||
id := r.nextID
|
||||
r.nextID++
|
||||
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()]
|
||||
if s == nil {
|
||||
r.steps = nil
|
||||
}
|
||||
s = append(s, id)
|
||||
s = append(s, k.id)
|
||||
r.byStep[k.Step()] = s
|
||||
|
||||
lbl := r.byLabel[k.Label()]
|
||||
lbl = append(lbl, id)
|
||||
lbl = append(lbl, k.id)
|
||||
r.byLabel[k.Label()] = lbl
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return false, fmt.Errorf("%w: %q", ErrNotUnique, label)
|
||||
}
|
||||
if r.rulesRunning {
|
||||
r.deleteLater = append(r.deleteLater, target[0])
|
||||
return true, nil
|
||||
}
|
||||
|
||||
id := target[0]
|
||||
rule := r.rules[id]
|
||||
@ -213,6 +231,10 @@ func (r *RuleCollection[C]) RemoveUniqueLabel(label string) (bool, error) {
|
||||
// rules were thus removed.
|
||||
func (r *RuleCollection[C]) RemoveAllLabel(label string) int {
|
||||
target := r.byLabel[label]
|
||||
if r.rulesRunning {
|
||||
r.deleteLater = append(r.deleteLater, target...)
|
||||
return len(target)
|
||||
}
|
||||
delete(r.byLabel, label)
|
||||
for _, t := range target {
|
||||
// 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 {
|
||||
return false, fmt.Errorf("%w: %d", ErrNotUnique, step)
|
||||
}
|
||||
if r.rulesRunning {
|
||||
r.deleteLater = append(r.deleteLater, target[0])
|
||||
return true, nil
|
||||
}
|
||||
|
||||
id := target[0]
|
||||
rule := r.rules[id]
|
||||
@ -249,6 +275,10 @@ func (r *RuleCollection[C]) RemoveAllStep(step int) int {
|
||||
if target == nil {
|
||||
return 0
|
||||
}
|
||||
if r.rulesRunning {
|
||||
r.deleteLater = append(r.deleteLater, target...)
|
||||
return len(target)
|
||||
}
|
||||
delete(r.byStep, step)
|
||||
r.steps = nil
|
||||
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
|
||||
// Rule.Enact as having special meaning to the rules engine.
|
||||
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
|
||||
if steps == nil {
|
||||
// Step set changed, recalculate.
|
||||
@ -324,3 +361,16 @@ func (r *RuleCollection[C]) Run(p *Player[C]) error {
|
||||
}
|
||||
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