Current section

Files

Jump to
macula_neuroevolution src competitive_coevolution coevolution_sup.erl
Raw

src/competitive_coevolution/coevolution_sup.erl

%%% @doc Supervisor for competitive coevolution components.
%%%
%%% Manages lifecycle of Red Team archives and coevolution managers for
%%% different realms. Each realm gets its own Red Team and manager.
%%%
%%% == Starting Competitive Coevolution for a Realm ==
%%%
%%% To enable competitive coevolution for a realm:
%%%
%%% {ok, ManagerPid} = coevolution_sup:start_coevolution(my_realm, #{
%%% red_team_size => 30,
%%% immigration_rate => 0.05
%%% }).
%%%
%%% The ManagerPid can then be passed to evaluators in their options.
%%%
%%% @end
-module(coevolution_sup).
-behaviour(supervisor).
%% API
-export([
start_link/0,
start_coevolution/2,
stop_coevolution/1,
get_manager/1,
list_realms/0
]).
%% Supervisor callbacks
-export([init/1]).
-include_lib("kernel/include/logger.hrl").
-define(SERVER, ?MODULE).
%%====================================================================
%% API
%%====================================================================
%% @doc Start the competitive coevolution supervisor.
-spec start_link() -> {ok, pid()} | {error, term()}.
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).
%% @doc Start competitive coevolution for a realm.
%% Creates and manages a Red Team and coevolution manager.
%% Options are passed to coevolution_manager:start_link/2.
-spec start_coevolution(Realm :: atom() | binary(), Options :: map()) ->
{ok, ManagerPid :: pid()} | {error, term()}.
start_coevolution(Realm, Options) ->
ChildId = child_id(Realm),
%% Check if already running
case get_manager(Realm) of
{ok, ExistingPid} ->
?LOG_INFO("[coevolution_sup] Competitive coevolution already running for ~p", [Realm]),
{ok, ExistingPid};
{error, not_found} ->
%% Start new manager
ChildSpec = #{
id => ChildId,
start => {coevolution_manager, start_link, [Realm, Options]},
restart => transient,
shutdown => 5000,
type => worker,
modules => [coevolution_manager]
},
case supervisor:start_child(?SERVER, ChildSpec) of
{ok, Pid} ->
?LOG_INFO("[coevolution_sup] Started competitive coevolution for ~p (Red Team vs Blue Team)", [Realm]),
{ok, Pid};
{ok, Pid, _Info} ->
{ok, Pid};
{error, {already_started, Pid}} ->
{ok, Pid};
{error, Reason} = Error ->
?LOG_ERROR("[coevolution_sup] Failed to start competitive coevolution for ~p: ~p",
[Realm, Reason]),
Error
end
end.
%% @doc Stop competitive coevolution for a realm.
-spec stop_coevolution(Realm :: atom() | binary()) -> ok | {error, not_found}.
stop_coevolution(Realm) ->
ChildId = child_id(Realm),
case supervisor:terminate_child(?SERVER, ChildId) of
ok ->
supervisor:delete_child(?SERVER, ChildId),
?LOG_INFO("[coevolution_sup] Stopped competitive coevolution for ~p", [Realm]),
ok;
{error, not_found} ->
{error, not_found}
end.
%% @doc Get the coevolution manager PID for a realm.
-spec get_manager(Realm :: atom() | binary()) -> {ok, pid()} | {error, not_found}.
get_manager(Realm) ->
ChildId = child_id(Realm),
Children = supervisor:which_children(?SERVER),
case lists:keyfind(ChildId, 1, Children) of
{ChildId, Pid, worker, _} when is_pid(Pid) ->
{ok, Pid};
{ChildId, restarting, worker, _} ->
%% Child is restarting, wait and retry
timer:sleep(100),
get_manager(Realm);
_ ->
{error, not_found}
end.
%% @doc List all realms with active competitive coevolution.
-spec list_realms() -> [atom() | binary()].
list_realms() ->
Children = supervisor:which_children(?SERVER),
lists:filtermap(
fun({Id, Pid, worker, _}) when is_pid(Pid) ->
case extract_realm(Id) of
{ok, Realm} -> {true, Realm};
error -> false
end;
(_) -> false
end,
Children
).
%%====================================================================
%% Supervisor callbacks
%%====================================================================
init([]) ->
SupFlags = #{
strategy => one_for_one,
intensity => 5,
period => 60
},
%% Start with no children - realms are added dynamically
ChildSpecs = [],
?LOG_INFO("[coevolution_sup] Starting Competitive Coevolution supervisor"),
{ok, {SupFlags, ChildSpecs}}.
%%====================================================================
%% Internal functions
%%====================================================================
child_id(Realm) when is_atom(Realm) ->
list_to_atom("coevolution_" ++ atom_to_list(Realm));
child_id(Realm) when is_binary(Realm) ->
list_to_atom("coevolution_" ++ binary_to_list(Realm)).
extract_realm(ChildId) when is_atom(ChildId) ->
Name = atom_to_list(ChildId),
case lists:prefix("coevolution_", Name) of
true ->
RealmStr = lists:nthtail(length("coevolution_"), Name),
{ok, list_to_atom(RealmStr)};
false ->
error
end;
extract_realm(_) ->
error.