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