Current section

Files

Jump to
macula_neuroevolution src neuroevolution_events.erl
Raw

src/neuroevolution_events.erl

%% @doc Event publishing abstraction for neuroevolution.
%%
%% This module provides a pluggable event system that can use different
%% backends for event distribution:
%%
%% - `neuroevolution_events_local' - Local pg-based pubsub (default)
%% - `neuroevolution_events_macula' - Distributed via Macula mesh (future)
%%
%% == Event Types ==
%%
%% Commands (requests for work):
%% - `{evaluate_request, #{request_id, realm, individual_id, network, options}}'
%% - `{evaluate_batch_request, #{request_id, realm, individuals, options}}'
%%
%% Events (facts that happened):
%% - `{evaluated, #{request_id, individual_id, metrics, evaluator_node}}'
%% - `{generation_started, #{realm, generation, population_size, timestamp}}'
%% - `{generation_completed, #{realm, generation, best_fitness, avg_fitness, ...}}'
%% - `{training_started, #{realm, config}}'
%% - `{training_stopped, #{realm, generation, reason}}'
%%
%% == Topic Design ==
%%
%% Topics follow the pattern: `neuro.<realm>.<type>'
%% - `neuro.default.evaluate' - Evaluation requests
%% - `neuro.default.evaluated' - Evaluation results
%% - `neuro.default.events' - Training lifecycle events
%%
%% @author Macula.io
%% @copyright 2025 Macula.io
-module(neuroevolution_events).
-export([
publish/2,
subscribe/1,
subscribe/2,
unsubscribe/1,
set_backend/1,
get_backend/0,
%% Topic helpers
evaluate_topic/1,
evaluated_topic/1,
events_topic/1
]).
%% Behaviour callbacks that backends must implement
-callback publish(Topic :: binary(), Event :: term()) -> ok.
-callback subscribe(Topic :: binary(), Pid :: pid()) -> ok.
-callback unsubscribe(Topic :: binary(), Pid :: pid()) -> ok.
%%% ============================================================================
%%% API Functions
%%% ============================================================================
%% @doc Publish an event to a topic.
%%
%% The event will be delivered to all subscribers of the topic.
%% Event format is typically `{EventType, EventData}' where EventData is a map.
-spec publish(Topic, Event) -> ok when
Topic :: binary(),
Event :: term().
publish(Topic, Event) ->
Backend = get_backend(),
Backend:publish(Topic, Event).
%% @doc Subscribe the calling process to a topic.
-spec subscribe(Topic) -> ok when
Topic :: binary().
subscribe(Topic) ->
subscribe(Topic, self()).
%% @doc Subscribe a specific process to a topic.
-spec subscribe(Topic, Pid) -> ok when
Topic :: binary(),
Pid :: pid().
subscribe(Topic, Pid) ->
Backend = get_backend(),
Backend:subscribe(Topic, Pid).
%% @doc Unsubscribe the calling process from a topic.
-spec unsubscribe(Topic) -> ok when
Topic :: binary().
unsubscribe(Topic) ->
Backend = get_backend(),
Backend:unsubscribe(Topic, self()).
%% @doc Set the event backend module.
%%
%% The backend module must implement the neuroevolution_events behaviour.
%% Default is `neuroevolution_events_local'.
-spec set_backend(Module) -> ok when
Module :: module().
set_backend(Module) ->
persistent_term:put({?MODULE, backend}, Module),
ok.
%% @doc Get the current event backend module.
-spec get_backend() -> module().
get_backend() ->
persistent_term:get({?MODULE, backend}, neuroevolution_events_local).
%%% ============================================================================
%%% Helper Functions for Topic Construction
%%% ============================================================================
%% @doc Get the evaluation request topic for a realm.
-spec evaluate_topic(Realm) -> binary() when
Realm :: binary().
evaluate_topic(Realm) ->
<<"neuro.", Realm/binary, ".evaluate">>.
%% @doc Get the evaluation results topic for a realm.
-spec evaluated_topic(Realm) -> binary() when
Realm :: binary().
evaluated_topic(Realm) ->
<<"neuro.", Realm/binary, ".evaluated">>.
%% @doc Get the training events topic for a realm.
-spec events_topic(Realm) -> binary() when
Realm :: binary().
events_topic(Realm) ->
<<"neuro.", Realm/binary, ".events">>.