%% 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 } }.