Packages

A simple PubSub library using Gleam actors

Current section

Files

Jump to
glubsub src glubsub.erl
Raw

src/glubsub.erl

-module(glubsub).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([destroy_topic/1, broadcast/2, new_topic/0, subscribe/2, unsubscribe/2, get_subscribers/1]).
-export_type([topic/1, message/1, glubsub_error/0, state/1, subscriber/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" This module implements a simple pubsub system using gleam actors.\n"
"\n"
).
-type topic(HHF) :: {topic, gleam@erlang@process:subject(message(HHF))}.
-opaque message(HHG) :: {subscribe,
gleam@erlang@process:subject({ok, nil} | {error, glubsub_error()}),
gleam@erlang@process:subject(HHG)} |
{unsubscribe,
gleam@erlang@process:subject({ok, nil} | {error, glubsub_error()}),
gleam@erlang@process:subject(HHG)} |
{broadcast, HHG} |
{get_subscribers, gleam@erlang@process:subject(list(subscriber(HHG)))} |
{subscriber_down, gleam@erlang@process:process_down()} |
shutdown.
-opaque glubsub_error() :: already_subscribed |
not_subscribed |
{start_error, gleam@otp@actor:start_error()}.
-type state(HHH) :: {state,
list(subscriber(HHH)),
gleam@erlang@process:selector(message(HHH))}.
-type subscriber(HHI) :: {subscriber,
gleam@erlang@process:subject(HHI),
gleam@erlang@process:process_monitor()}.
-file("src/glubsub.gleam", 176).
-spec topic_to_subject(topic(HIO)) -> gleam@erlang@process:subject(message(HIO)).
topic_to_subject(Topic) ->
{topic, Subject} = Topic,
Subject.
-file("src/glubsub.gleam", 56).
?DOC(" Destroys the given topic, unsubscribing all clients.\n").
-spec destroy_topic(topic(any())) -> nil.
destroy_topic(Topic) ->
gleam@otp@actor:send(topic_to_subject(Topic), shutdown).
-file("src/glubsub.gleam", 85).
?DOC(" Broadcasts a message to all subscribers of the given topic.\n").
-spec broadcast(topic(HHZ), HHZ) -> {ok, nil} | {error, nil}.
broadcast(Topic, Message) ->
_pipe = gleam@otp@actor:send(topic_to_subject(Topic), {broadcast, Message}),
{ok, _pipe}.
-file("src/glubsub.gleam", 181).
-spec remove_subscriber(list(subscriber(HIS)), subscriber(HIS)) -> list(subscriber(HIS)).
remove_subscriber(Subscribers, Unsubscriber) ->
gleam@list:filter(Subscribers, fun(Sub) -> Sub /= Unsubscriber end).
-file("src/glubsub.gleam", 188).
-spec selector_down_from_subscribers(
gleam@erlang@process:selector(message(HIY)),
list(subscriber(HIY))
) -> gleam@erlang@process:selector(message(HIY)).
selector_down_from_subscribers(Selector, Subscribers) ->
gleam@list:fold(
Subscribers,
Selector,
fun(Selector@1, Sub) ->
gleam@erlang@process:selecting_process_down(
Selector@1,
erlang:element(3, Sub),
fun(Field@0) -> {subscriber_down, Field@0} end
)
end
).
-file("src/glubsub.gleam", 95).
-spec handle_message(message(HIH), state(HIH)) -> gleam@otp@actor:next(message(HIH), state(HIH)).
handle_message(Message, State) ->
case Message of
{subscribe, Reply, Client} ->
case gleam@list:find(
erlang:element(2, State),
fun(Sub) -> erlang:element(2, Sub) =:= Client end
) of
{ok, _} ->
gleam@otp@actor:send(Reply, {error, already_subscribed}),
gleam@otp@actor:continue(State);
{error, nil} ->
Monitor = gleam@erlang@process:monitor_process(
gleam@erlang@process:subject_owner(Client)
),
New_selector = begin
_pipe = erlang:element(3, State),
gleam@erlang@process:selecting_process_down(
_pipe,
Monitor,
fun(Field@0) -> {subscriber_down, Field@0} end
)
end,
New_subs = [{subscriber, Client, Monitor} |
erlang:element(2, State)],
gleam@otp@actor:send(Reply, {ok, nil}),
_pipe@1 = gleam@otp@actor:continue(
{state, New_subs, New_selector}
),
gleam@otp@actor:with_selector(_pipe@1, New_selector)
end;
{unsubscribe, Reply@1, Client@1} ->
case gleam@list:find(
erlang:element(2, State),
fun(Sub@1) -> erlang:element(2, Sub@1) =:= Client@1 end
) of
{error, nil} ->
gleam@otp@actor:send(Reply@1, {error, not_subscribed}),
gleam@otp@actor:continue(State);
{ok, Unsub} ->
New_subs@1 = remove_subscriber(
erlang:element(2, State),
Unsub
),
New_selector@1 = begin
_pipe@2 = gleam_erlang_ffi:new_selector(),
selector_down_from_subscribers(_pipe@2, New_subs@1)
end,
gleam@otp@actor:send(Reply@1, {ok, nil}),
gleam@otp@actor:continue(
{state, New_subs@1, New_selector@1}
)
end;
{broadcast, Message@1} ->
_pipe@3 = erlang:element(2, State),
gleam@list:each(
_pipe@3,
fun(Sub@2) ->
gleam@otp@actor:send(erlang:element(2, Sub@2), Message@1)
end
),
gleam@otp@actor:continue(State);
{get_subscribers, Reply@2} ->
gleam@otp@actor:send(Reply@2, erlang:element(2, State)),
gleam@otp@actor:continue(State);
{subscriber_down, Client@2} ->
case begin
_pipe@4 = erlang:element(2, State),
gleam@list:find(
_pipe@4,
fun(Sub@3) ->
gleam@erlang@process:subject_owner(
erlang:element(2, Sub@3)
)
=:= erlang:element(2, Client@2)
end
)
end of
{error, nil} ->
gleam@otp@actor:continue(State);
{ok, Unsub@1} ->
gleam@erlang@process:demonitor_process(
erlang:element(3, Unsub@1)
),
New_subs@2 = remove_subscriber(
erlang:element(2, State),
Unsub@1
),
New_selector@2 = begin
_pipe@5 = gleam_erlang_ffi:new_selector(),
selector_down_from_subscribers(_pipe@5, New_subs@2)
end,
gleam@otp@actor:continue(
{state, New_subs@2, New_selector@2}
)
end;
shutdown ->
{stop, normal}
end.
-file("src/glubsub.gleam", 42).
?DOC(" Creates a new topic. Which is a pubsub channel that clients can subscribe to.\n").
-spec new_topic() -> {ok, topic(any())} | {error, glubsub_error()}.
new_topic() ->
_pipe = gleam@otp@actor:start_spec(
{spec,
fun() ->
Selector = gleam_erlang_ffi:new_selector(),
{ready, {state, [], Selector}, Selector}
end,
1000,
fun handle_message/2}
),
_pipe@1 = gleam@result:map(_pipe, fun(Subject) -> {topic, Subject} end),
gleam@result:map_error(_pipe@1, fun(Err) -> {start_error, Err} end).
-file("src/glubsub.gleam", 61).
?DOC(" Subscribes the given client to the given topic.\n").
-spec subscribe(topic(HHP), gleam@erlang@process:subject(HHP)) -> {ok, nil} |
{error, glubsub_error()}.
subscribe(Topic, Client) ->
gleam@otp@actor:call(
topic_to_subject(Topic),
fun(Self) -> {subscribe, Self, Client} end,
1000
).
-file("src/glubsub.gleam", 73).
?DOC(" Unsubscribes the given client from the given topic.\n").
-spec unsubscribe(topic(HHU), gleam@erlang@process:subject(HHU)) -> {ok, nil} |
{error, glubsub_error()}.
unsubscribe(Topic, Client) ->
gleam@otp@actor:call(
topic_to_subject(Topic),
fun(Self) -> {unsubscribe, Self, Client} end,
1000
).
-file("src/glubsub.gleam", 91).
?DOC(" Returns a set of all subscribers to the given topic.\n").
-spec get_subscribers(topic(HID)) -> list(subscriber(HID)).
get_subscribers(Topic) ->
gleam@otp@actor:call(
topic_to_subject(Topic),
fun(Field@0) -> {get_subscribers, Field@0} end,
1000
).