Current section

Files

Jump to
erlang_behaviour_trees src bt_engine blackboard bt_blackboard.erl
Raw

src/bt_engine/blackboard/bt_blackboard.erl

-module(bt_blackboard).
-export([
get_empty/0,
get_bb_key/2,
is_set/2,
%% not exporting read since eval is enough. otherwise its easy to make mistakes
% read/3,
write/3,
promise/2,
clear_namespace/2,
eval/2,
eval_with_default_fn/3,
throw_error_fn/1
]).
-record(bb_key, {
namespace :: list(string()),
key :: string()
}).
-record(bb_promise, {
args :: list(term()),
promise_fn :: function()
}).
%% since implemented as map
get_empty() ->
#{}.
get_bb_key(Namespace, Key) ->
#bb_key{namespace = Namespace, key = Key}.
is_set(BBKey, Blackboard) when is_record(BBKey, bb_key) ->
case maps:get(BBKey, Blackboard) of
null -> false;
{value, _} -> true
end;
%% is not a key, hence set
is_set(_NotKey, _Blackboard) ->
true.
read(BBKey, Blackboard, OnNull) when is_record(BBKey, bb_key), is_function(OnNull) ->
case maps:get(BBKey, Blackboard) of
null ->
OnNull(BBKey);
{value, Val} -> Val;
ErrorVal ->
ErrorReason = lists:flatten(io_lib:format("Incorrect value stored for key '~p' as '~p'~n", [BBKey, ErrorVal])),
erlang:error(ErrorReason)
end;
%% is not a key, so return it.
read(NotKey, _Blackboard, _OnNull) ->
NotKey.
write(BBKey, Value, Blackboard) when is_record(BBKey, bb_key)->
maps:put(BBKey, {value, Value}, Blackboard);
write(NotKey, _Value, _Blackboard) ->
ErrorReason = lists:flatten(io_lib:format("Trying to write to ~p which is not a bb key~n", [NotKey])),
erlang:error(ErrorReason).
%% make a promise that will be evaluated at runtime!
%% eg. from converting between coordinate and barcode, this can be used
%% to skip a conversion subtask.
promise(Args, PromiseFn) ->
#bb_promise{
args = Args,
promise_fn = PromiseFn
}.
%% set all keys within this namespace to null
%% sometimes namespace is undefined for a sequence/selector, in
%% that case do nothing.
clear_namespace(undefined, Blackboard) ->
Blackboard;
clear_namespace(Namespace, Blackboard) ->
BBAsProplist = maps:to_list(Blackboard),
%% find stuff that is prefix of namespace and set value to null
NewBBProplist = lists:map(
fun({BBKey, BBVal}) ->
BBNS = BBKey#bb_key.namespace,
case lists:prefix(Namespace, BBNS) of
true -> {BBKey, null};
false -> {BBKey, BBVal}
end
end,
BBAsProplist
),
maps:from_list(NewBBProplist).
%% evaluation function!
%% input can be key, array of keys, tuple of keys, non-key
eval(BBKey, Blackboard)->
eval_internal(BBKey, Blackboard, fun bt_blackboard:throw_error_fn/1 ).
%% with function that returns default value in case bb key value is null
eval_with_default_fn(BBKey, Blackboard, OnNull)->
eval_internal(BBKey, Blackboard, OnNull).
%% evaluate promise
eval_internal(BBPromise = #bb_promise{args = Args, promise_fn = PromiseFn}, Blackboard, OnNull) when is_record(BBPromise, bb_promise) ->
EvaledArgs = eval_internal(Args, Blackboard, OnNull),
erlang:apply(PromiseFn, EvaledArgs);
eval_internal(BBKey, Blackboard, OnNull) when is_record(BBKey, bb_key) ->
read(BBKey, Blackboard, OnNull);
eval_internal(ListOfVals, Blackboard, OnNull) when is_list(ListOfVals) ->
[eval_internal(X, Blackboard, OnNull) || X <- ListOfVals];
eval_internal(TupleOfVals, Blackboard, OnNull) when is_tuple(TupleOfVals) ->
L = tuple_to_list(TupleOfVals),
%% need to convert back to tuple too...
L2 = eval_internal(L, Blackboard, OnNull),
list_to_tuple(L2);
eval_internal(Native, _Blackboard, _OnNull) ->
Native.
throw_error_fn(BBKey) ->
ErrorReason = lists:flatten(io_lib:format("Trying to read uninitalized key '~p'~n", [BBKey])),
erlang:error(ErrorReason).