-
Notifications
You must be signed in to change notification settings - Fork 0
/
greenstalk.go
129 lines (111 loc) · 3.32 KB
/
greenstalk.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package greenstalk
import (
"context"
"errors"
"fmt"
"github.com/jbcpollak/greenstalk/core"
"github.com/jbcpollak/greenstalk/internal"
"github.com/jbcpollak/greenstalk/util"
)
// BehaviorTree ...
type behaviorTree[Blackboard any] struct {
ctx context.Context
Root core.Node[Blackboard]
Blackboard Blackboard
events chan core.Event
visitor core.Visitor[Blackboard]
}
func NewBehaviorTree[Blackboard any](
root core.Node[Blackboard],
bb Blackboard,
opts ...TreeOption[Blackboard],
) (*behaviorTree[Blackboard], error) {
var eb internal.ErrorBuilder
eb.SetMessage("NewBehaviorTree")
if root == nil {
eb.Write("Config.Root is nil")
}
if eb.Error() != nil {
return nil, eb.Error()
}
tree := &behaviorTree[Blackboard]{
ctx: context.TODO(),
Root: root,
Blackboard: bb,
events: make(chan core.Event, 100 /* arbitrary */),
visitor: func(n core.Walkable[Blackboard]) {},
}
// Apply all options to the tree.
for _, opt := range opts {
opt(tree)
}
return tree, nil
}
// Update propagates an update call down the behavior tree.
func (bt *behaviorTree[Blackboard]) Update(evt core.Event) core.ResultDetails {
result := core.Update(bt.ctx, bt.Root, bt.Blackboard, evt)
status := result.Status()
if status == core.StatusError {
if details, ok := result.(core.ErrorResultDetails); !ok {
// Handle if we somehow get an error result that is not an ErrorResultDetails
return core.ErrorResult(fmt.Errorf("erroneous status encountered %v", details))
}
}
switch status {
case core.StatusError:
case core.StatusSuccess:
// whatever
case core.StatusFailure:
// whatever
case core.StatusRunning:
if running, ok := result.(core.InitRunningResultDetails); ok {
go func() {
err := running.RunningFn(bt.ctx, func(evt core.Event) error {
bt.events <- evt
return nil
})
// If we aren't shutting down, feed the error back through the event loop.
if err != nil && !errors.Is(err, context.Canceled) {
internal.Logger.Error("Error in running function", "err", err)
// TODO: put sender's ID in the event so it can be notified
bt.events <- core.ErrorEvent{Err: err}
}
}()
}
default:
return core.ErrorResult(fmt.Errorf("invalid status %v", status))
}
bt.visitor(bt.Root)
return result
}
func (bt *behaviorTree[Blackboard]) EventLoop(evt core.Event) error {
defer close(bt.events)
// Put the first event on the queue.
bt.events <- evt
for {
select {
case <-bt.ctx.Done():
return nil
case evt := <-bt.events:
if errEvt, ok := evt.(core.ErrorEvent); ok {
return errEvt.Err
}
internal.Logger.Info("Updating with Event", "event", evt)
result := bt.Update(evt)
if result.Status() == core.StatusError {
if details, ok := result.(core.ErrorResultDetails); ok {
return details.Err
} else {
// we should not be able to get here because currently Update ensures that an error status always
// has ErrorResultDetails, but if that ever changes and we get here, we should still emit an error
return fmt.Errorf("BT Update returned an error with no details %v", details)
}
}
}
}
}
// String creates a string representation of the behavior tree
// by traversing it and writing lexical elements to a string.
func (bt *behaviorTree[Blackboard]) String() string {
return util.NodeToString(bt.Root)
}