Current section
Files
Jump to
Current section
Files
src/grammy.erl
-module(grammy).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([get_supervisor/1, new/2, port/2, ip_address_to_string/1, udp_send/4, send_to/4, start/1]).
-export_type([udp_server/0, internal_message/1, state/2, builder/2, message/1, connection/0, active_type/0, socket_option/0, socket/0]).
-opaque udp_server() :: {udp_server,
gleam@erlang@process:subject(gleam@otp@supervisor:message())}.
-type internal_message(ONL) :: {internal_user, ONL} |
{udp_packet,
{integer(), integer(), integer(), integer()},
integer(),
bitstring()} |
unknown.
-opaque state(ONM, ONN) :: {state,
socket(),
ONM,
gleam@dict:dict({{integer(), integer(), integer(), integer()},
integer()}, gleam@erlang@process:subject(message(ONN)))}.
-type builder(ONO, ONP) :: {builder,
fun(() -> {ONO, gleam@option:option(gleam@erlang@process:selector(ONP))}),
integer(),
fun((message(ONP), connection(), ONO) -> gleam@otp@actor:next(ONP, ONO))}.
-type message(ONQ) :: {user, ONQ} |
{packet,
{integer(), integer(), integer(), integer()},
integer(),
bitstring()}.
-opaque connection() :: {connection, socket()}.
-type active_type() :: once.
-type socket_option() :: binary |
{active, active_type()} |
{sndbuf, integer()} |
{recbuf, integer()}.
-type socket() :: any().
-spec get_supervisor(udp_server()) -> gleam@erlang@process:subject(gleam@otp@supervisor:message()).
get_supervisor(Server) ->
erlang:element(2, Server).
-spec udp_message_selector() -> gleam@erlang@process:selector(internal_message(any())).
udp_message_selector() ->
_pipe = gleam_erlang_ffi:new_selector(),
gleam@erlang@process:selecting_record5(
_pipe,
erlang:binary_to_atom(<<"udp"/utf8>>),
fun(_, Ip_address, Port, Message) ->
Ip = (gleam@dynamic:tuple4(
fun gleam@dynamic:int/1,
fun gleam@dynamic:int/1,
fun gleam@dynamic:int/1,
fun gleam@dynamic:int/1
))(Ip_address),
Port@1 = gleam@dynamic:int(Port),
Data = gleam@dynamic:bit_array(Message),
case {Ip, Port@1, Data} of
{{ok, Ip@1}, {ok, Port@2}, {ok, Data@1}} ->
{udp_packet, Ip@1, Port@2, Data@1};
{_, _, _} ->
unknown
end
end
).
-spec new(
fun(() -> {OOG, gleam@option:option(gleam@erlang@process:selector(OOH))}),
fun((message(OOH), connection(), OOG) -> gleam@otp@actor:next(OOH, OOG))
) -> builder(OOG, OOH).
new(Init, Handler) ->
{builder, Init, 4000, Handler}.
-spec port(builder(OOO, OOP), integer()) -> builder(OOO, OOP).
port(Builder, Port) ->
erlang:setelement(3, Builder, Port).
-spec ip_address_to_string({integer(), integer(), integer(), integer()}) -> binary().
ip_address_to_string(Ip) ->
<<<<<<<<<<<<(gleam@int:to_string(erlang:element(1, Ip)))/binary, "."/utf8>>/binary,
(gleam@int:to_string(erlang:element(2, Ip)))/binary>>/binary,
"."/utf8>>/binary,
(gleam@int:to_string(erlang:element(3, Ip)))/binary>>/binary,
"."/utf8>>/binary,
(gleam@int:to_string(erlang:element(4, Ip)))/binary>>.
-spec udp_send(
socket(),
{integer(), integer(), integer(), integer()},
integer(),
gleam@bytes_builder:bytes_builder()
) -> {ok, nil} | {error, nil}.
udp_send(Socket, Host, Port, Packet) ->
grammy_ffi:send(Socket, Host, Port, Packet).
-spec send_to(
connection(),
{integer(), integer(), integer(), integer()},
integer(),
gleam@bytes_builder:bytes_builder()
) -> {ok, nil} | {error, nil}.
send_to(Connection, Address, Port, Data) ->
grammy_ffi:send(erlang:element(2, Connection), Address, Port, Data).
-spec start(builder(any(), any())) -> {ok, udp_server()} |
{error, gleam@otp@actor:start_error()}.
start(Builder) ->
Ten_megabytes = (10 * 1024) * 1024,
Worker = gleam@otp@supervisor:worker(
fun(_) ->
_pipe@10 = gleam@otp@actor:start_spec(
{spec,
fun() ->
case gen_udp:open(
erlang:element(3, Builder),
[binary,
{active, once},
{sndbuf, Ten_megabytes},
{recbuf, Ten_megabytes}]
) of
{error, Reason} ->
{failed,
<<"Failed to open UDP socket: "/utf8,
(gleam@string:inspect(Reason))/binary>>};
{ok, Socket} ->
{User_state, User_selector} = (erlang:element(
2,
Builder
))(),
Message_selector = udp_message_selector(),
Selector = case User_selector of
{some, User} ->
_pipe = User,
_pipe@1 = gleam_erlang_ffi:map_selector(
_pipe,
fun(Field@0) -> {internal_user, Field@0} end
),
gleam_erlang_ffi:merge_selector(
_pipe@1,
Message_selector
);
none ->
Message_selector
end,
{ready,
{state,
Socket,
User_state,
gleam@dict:new()},
Selector}
end
end,
500,
fun(Message, State) ->
Conn = {connection, erlang:element(2, State)},
case Message of
unknown ->
logging:log(
warning,
<<"Discarding unknown message type"/utf8>>
),
gleam@otp@actor:continue(State);
{udp_packet, Address, Port, Data} ->
Resp = (erlang:element(4, Builder))(
{packet, Address, Port, Data},
Conn,
erlang:element(3, State)
),
case Resp of
{continue, New_state, User_selector@1} ->
_pipe@2 = erlang:element(2, State),
_pipe@3 = grammy_ffi:set_active(_pipe@2),
_pipe@6 = gleam@result:map(
_pipe@3,
fun(_) ->
Selector@2 = gleam@option:map(
User_selector@1,
fun(Selector@1) ->
_pipe@4 = Selector@1,
_pipe@5 = gleam_erlang_ffi:map_selector(
_pipe@4,
fun(Field@0) -> {internal_user, Field@0} end
),
gleam_erlang_ffi:merge_selector(
_pipe@5,
udp_message_selector(
)
)
end
),
{continue,
erlang:setelement(
3,
State,
New_state
),
Selector@2}
end
),
_pipe@7 = gleam@result:map_error(
_pipe@6,
fun(Err) ->
logging:log(
error,
<<"Failed to set UDP socket active: "/utf8,
(gleam@string:inspect(
Err
))/binary>>
),
{stop,
{abnormal,
<<"Failed to set UDP socket active"/utf8>>}}
end
),
gleam@result:unwrap_both(_pipe@7);
{stop, Reason@1} ->
{stop, Reason@1}
end;
{internal_user, User@1} ->
case (erlang:element(4, Builder))(
{user, User@1},
Conn,
erlang:element(3, State)
) of
{continue, New_state@1, none} ->
gleam@otp@actor:continue(
erlang:setelement(
3,
State,
New_state@1
)
);
{continue,
New_state@2,
{some, New_user_selector}} ->
{continue,
erlang:setelement(
3,
State,
New_state@2
),
{some,
begin
_pipe@8 = New_user_selector,
_pipe@9 = gleam_erlang_ffi:map_selector(
_pipe@8,
fun(Field@0) -> {internal_user, Field@0} end
),
gleam_erlang_ffi:merge_selector(
_pipe@9,
udp_message_selector()
)
end}};
{stop, Reason@2} ->
{stop, Reason@2}
end
end
end}
),
gleam@result:map(
_pipe@10,
fun(Subj) ->
gleam@io:println(
<<"UDP socket listening on port "/utf8,
(gleam@int:to_string(erlang:element(3, Builder)))/binary>>
),
Subj
end
)
end
),
_pipe@11 = gleam@otp@supervisor:start(
fun(Children) -> gleam@otp@supervisor:add(Children, Worker) end
),
gleam@result:map(_pipe@11, fun(Field@0) -> {udp_server, Field@0} end).