-module(behavior_tree). -compile([inline, {inline_size, 100}]). %%-------------------------------------------------------------------- %% include %%-------------------------------------------------------------------- -include("behavior3.hrl"). %%-------------------------------------------------------------------- %% export API %%-------------------------------------------------------------------- -export([ load_tree_file/1, init_btree_by_title/4, init_btree/3, execute/2, execute_child/2, close_btree_node/2, unload_btree/1 ]). %% Internal API -export([get_btree_id_by_title/2, get_btree/2, get_btree_node/2]). %%-------------------------------------------------------------------- %% API functions %%-------------------------------------------------------------------- %% @doc %% 载入行为树文件 -spec load_tree_file(file:name_all()) -> {#{string() => bt_node_id()}, #{bt_node_id() => btree()}, tree_nodes()}|{error, term()}. load_tree_file(JsonConfig) -> case file:read_file(JsonConfig) of {ok, Json} -> JsonTerm = jsx:decode(Json, [return_maps]), parse_btree(JsonTerm); {error, Reason} -> ?BT_LOG("Error:~w Reason:~w JsonConfig:~w", [error, Reason, JsonConfig]), {error, Reason} end. %% @doc %% 获取行为树根节点id -spec get_btree_id_by_title(string(), #{string() => bt_node_id()}) -> bt_node_id()|undefined. get_btree_id_by_title(Title, TitleMaps) -> case TitleMaps of #{Title := ID} -> ID; #{} -> undefined end. %% @doc %% 获取行为树根节点 -spec get_btree(bt_node_id(), #{bt_node_id() => btree()}) -> btree()|undefined. get_btree(BTNodeID, TreeMaps) -> case TreeMaps of #{BTNodeID := BTree} -> BTree; #{} -> undefined end. %% @doc %% 获取行为树子节点 -spec get_btree_node(bt_node_id(), tree_nodes()) -> uninit_bt_node()|undefined. get_btree_node(BTNodeID, TreeNodeMaps) -> case TreeNodeMaps of #{BTNodeID := BTNode} -> BTNode; #{} -> undefined end. %% @doc %% 根据给定节点名初始化整颗行为树 -spec init_btree_by_title(string(), #{string() => bt_node_id()}, #{bt_node_id() => btree()}, tree_nodes()) -> {ok, bt_uid()}|{error, term()}. init_btree_by_title(Title, TitleMaps, TreeMaps, TreeNodeMaps) -> BTNodeID = get_btree_id_by_title(Title, TitleMaps), init_btree(BTNodeID, TreeMaps, TreeNodeMaps). %% @doc %% 根据给定节点初始化整颗行为树 -spec init_btree(bt_node_id(), #{bt_node_id() => btree()}, tree_nodes()) -> {ok, bt_uid()}|{error, term()}. init_btree(BTNodeID, TreeMaps, TreeNodeMaps) -> #{root := Root} = behavior_tree:get_btree(BTNodeID, TreeMaps), #{id := NodeID} = behavior_tree:get_btree_node(Root, TreeNodeMaps), base_node:do_init(undefined, NodeID, TreeMaps, TreeNodeMaps). %% @doc %% 执行行为树节点 -spec execute(bt_uid(), bt_state()) -> {bt_status(), bt_state()}. execute(NodeID, BTState) -> BTState1 = blackboard:init_maps_keys(BTState), case base_node:execute(NodeID, BTState1) of {?BT_RUNNING, BTState2} -> {?BT_RUNNING, BTState2}; {BTStatus, BTState2} -> BTState3 = blackboard:erase_btree(BTState2), {BTStatus, BTState3} end. %% @doc %% 动态执行子节点 -spec execute_child(bt_uid(), bt_state()) -> {bt_status(), bt_state()}. execute_child(NodeID, BTState) -> base_node:execute(NodeID, BTState). %% @doc %% 强制关闭指定节点下的所有子节点 -spec close_btree_node(bt_uid(), bt_state()) -> bt_state(). close_btree_node(NodeID, BTState) -> BTState1 = do_close_btree_node(NodeID, BTState), blackboard:erase_btree(BTState1). %% @doc %% 卸载指定节点下的所有子节点 -spec unload_btree(bt_uid()) -> ok. unload_btree(NodeID) -> do_unload_btree_node(NodeID). %%-------------------------------------------------------------------- %% Internal functions %%-------------------------------------------------------------------- %% 解析行为树 -spec parse_btree(map()) -> {#{string() => bt_node_id()}, #{bt_node_id() => btree()}, tree_nodes()}. parse_btree(#{<<"trees">> := Trees}) -> parse_btree_1(Trees, #{}, #{}, #{}). parse_btree_1([Node | T], TitleMaps, TreeMaps, TreeNodeMaps) -> #{<<"id">> := ID, <<"title">> := Title, <<"root">> := Root, <<"nodes">> := Nodes} = Node, Iterator = maps:iterator(Nodes), {TreeNodes, TreeNodeMaps1} = parse_btree_node(maps:next(Iterator), #{}, TreeNodeMaps), BTree = #{ id => ID, root => Root, properties => parse_properties(Node), tree_nodes => TreeNodes }, parse_btree_1(T, TitleMaps#{Title => ID}, TreeMaps#{ID => BTree}, TreeNodeMaps1); parse_btree_1([], TitleMaps, TreeMaps, TreeNodeMaps) -> {TitleMaps, TreeMaps, TreeNodeMaps}. %% 解析树节点 -spec parse_btree_node({bt_node_id(), map(), maps:iterator()}|none, tree_nodes(), tree_nodes()) -> {tree_nodes(), tree_nodes()}. parse_btree_node({ID, Node, NextIterator}, TreeNodes, TreeNodeMaps) -> #{<<"name">> := Name, <<"category">> := Category} = Node, case Category of <<"tree">> -> Name1 = Name; _ -> Name1 = name_convert(Name) end, TreeNode = #{ id => ID, name => Name1, category => binary_to_atom(Category, utf8), properties => parse_properties(Node), children => parse_children(Node) }, parse_btree_node(maps:next(NextIterator), TreeNodes#{ID => TreeNode}, TreeNodeMaps#{ID => TreeNode}); parse_btree_node(none, TreeNodes, TreeNodeMaps) -> {TreeNodes, TreeNodeMaps}. %% 解析子节点属性数据, %% key会转为下划线分割小写atom,value保持不变 -spec parse_properties(map()) -> properties(). parse_properties(#{<<"properties">> := Properties} = _Node) -> Fun = fun(K, V, Map) -> Map#{name_convert(K) => V} end, maps:fold(Fun, #{}, Properties). %% 解析子节点 -spec parse_children(map()) -> [bt_node_id()]. parse_children(#{<<"child">> := Child} = _Node) -> [Child]; parse_children(#{<<"children">> := Children} = _Node) -> Children; parse_children(_Node) -> []. %% 模块名转换 %% 例:PlayerID -> player_id Name -> name -spec name_convert(unicode:chardata()) -> atom(). name_convert(Name) -> case name_convert_1(Name) of <<"_"/utf8, NewName/binary>> -> binary_to_atom(NewName, utf8); NewName -> binary_to_atom(NewName, utf8) end. name_convert_1(<>) when $A =< C, C =< $Z -> Other1 = name_convert_1(Other), <<"_"/utf8, (C + 32)/utf8, Other1/binary>>; name_convert_1(<>) -> Other1 = name_convert_1(Other), <>; name_convert_1(<<>>) -> <<>>. %% 执行关闭行为树节点 -spec do_close_btree_node(bt_uid(), bt_state()) -> bt_state(). do_close_btree_node(NodeID, BTState) -> BTNode = blackboard:get_btree_node(NodeID), base_node:do_close(BTNode, BTState). %% 执行卸载行为树节点 -spec do_unload_btree_node(bt_uid()) -> ok. do_unload_btree_node(NodeID) -> #{children := Children} = blackboard:get_btree_node(NodeID), do_unload_btree_node_1(Children), blackboard:erase_btree_node(NodeID), ok. do_unload_btree_node_1([NodeID | T]) -> do_unload_btree_node(NodeID), do_unload_btree_node_1(T); do_unload_btree_node_1([]) -> ok.