Current section
Files
Jump to
Current section
Files
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()
}).
-export([
%% api
start_link/1,
start_link/2,
start_link/3,
tick/2,
tick_async/2,
get_tree/1,
get_blackboard/1,
%% cb
init/1,
handle_call/3,
handle_cast/2,
handle_info/2
]).
%% API
start_link({Tree, BB}) ->
gen_server:start_link(?MODULE, [Tree, BB], []).
%% yaml tree
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}).
%% 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}).
tick(Pid, Event) ->
gen_server:call(Pid, {tick, Event}).
%% can't use gen_server:cast because we need to send the response back to caller
%% once bt has been ticked
tick_async(Pid, Event) ->
gen_server:call(Pid, {tick_async, Event}).
get_tree(Pid) ->
gen_server:call(Pid, get_tree).
get_blackboard(Pid) ->
gen_server:call(Pid, get_blackboard).
%% Callbacks
init([Tree, BB]) ->
{ok,
#state{
bt = Tree,
blackboard = BB
}
}.
handle_call({tick, Event}, _From, State) ->
{Status, NewState} = run_bt(Event, State),
%% send both new tree and blackboard in response!
{reply, {Status, NewState#state.bt, NewState#state.blackboard}, NewState};
handle_call({tick_async, Event}, From, State) ->
erlang:send_after(0, self(), {tick_async, Event, From}),
{reply, ok, State};
handle_call(get_tree, _From, State = #state{bt = Tree}) ->
{reply, Tree, State};
handle_call(get_blackboard, _From, State = #state{blackboard = BB}) ->
{reply, BB, State}.
%% not using
handle_cast(_, State) -> {noreply, State}.
%% for async tick
handle_info({tick_async, Event, {FromPid, FromRef}}, State) ->
{Status, NewState} = run_bt(Event, State),
%% TODO: right now just sending a message without any api etc.
%% is this going to cause confusion? actually can't use any api since we shouldn't
%% bind anything to butler system.
%% send both new tree and blackboard in response!
FromPid ! {tick_async_response, FromRef, {Status, NewState#state.bt, NewState#state.blackboard}},
{noreply, NewState}.
%% private
-spec run_bt(Event :: bt_event(), State :: #state{}) -> {Status :: bt_status(), NewState :: #state{}}.
run_bt(Event, #state{bt = Tree, blackboard = BB}) ->
%% clear metadata before execution
TreeWithoutMetadata = bt_runner:clear_metadata(Tree),
{Status, NewTree, NewBB} = bt_runner:run(TreeWithoutMetadata, BB, Event),
{
Status,
#state{
bt = NewTree,
blackboard = NewBB
}
}.