Current section

Files

Jump to
erlang_behaviour_trees src bt_engine bt_engine.erl
Raw

src/bt_engine/bt_engine.erl

%% a process for running an instance of the bt
-module(bt_engine).
-behaviour(gen_server).
-include("bt_nodes.hrl").
-record(state, {
bt :: any(),
blackboard :: blackboard(),
num_ticks = 0 :: integer(),
%% after every tick, to_notify will be informed of event/result.
%% so it doesn't matter who ticks the bt, to_notify will be informed
to_notify_pid :: pid()
}).
-export([
%% api
%% not exposing start_link/1 as it's just internal
% start_link/1,
start_link/2,
start_link/3,
tick/2,
get_tree/1,
get_blackboard/1,
%% cb
init/1,
handle_call/3,
handle_cast/2,
handle_info/2
]).
%% private
start_link({Tree, BB, ToNotifyPid}) ->
gen_server:start_link(?MODULE, [Tree, BB, ToNotifyPid], []).
%% API
%% yaml tree
%% it is assumed that the calling process is the one that needs to be notified!
start_link(RootFilePath, TreeName, Args) when is_list(RootFilePath), is_list(TreeName) ->
{Tree, BB} = bt_yaml_parser:parse(RootFilePath, TreeName, Args),
start_link({Tree, BB, self()}).
%% erlang tree
start_link(BtErlTree, Args) when is_record(BtErlTree, bt_erl_tree) ->
{Tree, BB} = bt_erl_parser:parse(BtErlTree, Args),
start_link({Tree, BB, self()}).
%% tick is async since result is always notified to to_notify later on.
%% making a sync tick is dangerous since bt_engine can easily get stuck waiting on caller itself
tick(Pid, Event) ->
gen_server:cast(Pid, {tick, Event}).
get_tree(Pid) ->
gen_server:call(Pid, get_tree).
get_blackboard(Pid) ->
gen_server:call(Pid, get_blackboard).
%% Callbacks
init([Tree, BB, ToNotifyPid]) ->
{ok,
#state{
bt = Tree,
blackboard = BB,
num_ticks = 0,
to_notify_pid = ToNotifyPid
}
}.
handle_call(get_tree, _From, State = #state{bt = Tree}) ->
{reply, Tree, State};
handle_call(get_blackboard, _From, State = #state{blackboard = BB}) ->
{reply, BB, State}.
handle_cast({tick, Event}, State) ->
erlang:send_after(0, self(), {tick, Event}),
{noreply, State}.
%% for async tick
handle_info({tick, Event}, State = #state{to_notify_pid = ToNotifyPid}) ->
{Status, NewState} = run_bt(Event, State),
%% TODO: right now just sending a message to to_notify
%% is this going to cause confusion? actually can't use any api since we shouldn't
%% bind anything to butler system.
%% need event in response as well since tree might not have been ticked through to_notify
%% (i.e. it was ticked by the tree process itself)
ToNotifyPid ! make_result(Event, Status, NewState),
{noreply, NewState};
handle_info(_, State) ->
{noreply, State}.
%% private
-spec run_bt(Event :: bt_event(), State :: #state{}) -> {Status :: bt_status(), NewState :: #state{}}.
run_bt(Event, State = #state{bt = Tree, blackboard = BB, num_ticks = NumTicks}) ->
%% clear metadata before execution
TreeWithoutMetadata = bt_runner:clear_metadata(Tree),
{Status, NewTree, NewBB} = bt_runner:run(TreeWithoutMetadata, BB, Event),
{
Status,
State#state{
bt = NewTree,
blackboard = NewBB,
num_ticks = NumTicks + 1
}
}.
make_result(Event, Status, #state{bt = Tree, blackboard = BB, num_ticks = NumTicks}) ->
#bt_tick_result{
event = Event,
status = Status,
new_tree = Tree,
new_bb = BB,
tick_id = NumTicks
}.