@ -1,200 +0,0 @@ | |||||
-module(behavior_tree). | |||||
-include("eBhv3.hrl"). | |||||
-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 | |||||
]). | |||||
-export([get_btree_id_by_title/2, get_btree/2, get_btree_node/2]). | |||||
%% @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). | |||||
%% 解析行为树 | |||||
-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(<<C/utf8, Other/binary>>) when $A =< C, C =< $Z -> | |||||
Other1 = name_convert_1(Other), | |||||
<<"_"/utf8, (C + 32)/utf8, Other1/binary>>; | |||||
name_convert_1(<<C/utf8, Other/binary>>) -> | |||||
Other1 = name_convert_1(Other), | |||||
<<C/utf8, Other1/binary>>; | |||||
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. |
@ -1,3 +1,200 @@ | |||||
-module(eBhv3). | -module(eBhv3). | ||||
-export([]). | |||||
-include("eBhv3.hrl"). | |||||
-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 | |||||
]). | |||||
-export([get_btree_id_by_title/2, get_btree/2, get_btree_node/2]). | |||||
%% @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 = jiffy: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} = eBhv3:get_btree(BTNodeID, TreeMaps), | |||||
#{id := NodeID} = eBhv3: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). | |||||
%% 解析行为树 | |||||
-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(<<C/utf8, Other/binary>>) when $A =< C, C =< $Z -> | |||||
Other1 = name_convert_1(Other), | |||||
<<"_"/utf8, (C + 32)/utf8, Other1/binary>>; | |||||
name_convert_1(<<C/utf8, Other/binary>>) -> | |||||
Other1 = name_convert_1(Other), | |||||
<<C/utf8, Other1/binary>>; | |||||
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. |