package engine
import (
"github.com/juju/errors"
"github.com/newm4n/grool/context"
"github.com/newm4n/grool/model"
log "github.com/sirupsen/logrus"
"sort"
)
func NewGroolEngine() *Grool {
return &Grool{
MaxCycle: 5000,
}
}
type Grool struct {
MaxCycle uint64
}
func (g *Grool) Execute(dataCtx *context.DataContext, knowledge *model.KnowledgeBase) error {
defunc := &model.GroolFunctions{
Knowledge: knowledge,
}
kctx := &context.KnowledgeContext{}
rctx := &context.RuleContext{}
dataCtx.Add("DEFUNC", defunc)
knowledge.Reset()
for _, v := range knowledge.RuleEntries {
v.Initialize(kctx, rctx, dataCtx)
}
var cycle uint64
Un-limitted loop as long as there are rule to exsecute.
We need to add safety mechanism to detect unlimitted loop as there are posibility executed rule are not changing
data context which makes rules to get executed again and again.
*/
for {
cycle++
if cycle > g.MaxCycle {
return errors.Errorf("Grool successfully selected rule candidate for execution after %d cycles, this could possibly caused by rule entry(s) that keep added into execution pool but when executed it does not change any data in context. Please evaluate your rule entries \"When\" and \"Then\" scope. You can adjust the maximum cycle using Grool.MaxCycle variable.", g.MaxCycle)
}
runnable := make([]*model.RuleEntry, 0)
for _, v := range knowledge.RuleEntries {
can, err := v.CanExecute()
if err != nil {
log.Errorf("Failed testing condition for rule : %s. Got error %v", v.RuleName, err)
}
if can {
runnable = append(runnable, v)
}
}
if len(runnable) > 0 {
if len(runnable) > 1 {
sort.SliceStable(runnable, func(i, j int) bool {
return runnable[i].Salience > runnable[j].Salience
})
}
cycleDone := true
for _, r := range runnable {
dataCtx.VariableChangeCount = 0
log.Infof("Executing rule : %s. Salience %d", r.RuleName, r.Salience)
err := r.Execute()
if err != nil {
log.Errorf("Failed execution rule : %s. Got error %v", r.RuleName, err)
return errors.Trace(err)
}
if dataCtx.VariableChangeCount > 0 {
cycleDone = false
break
}
}
if cycleDone {
break
}
} else {
break
}
}
log.Infof("Finished Rules execution. Total #%d cycles.", cycle)
return nil
}