-module(grammy). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([get_supervisor/1, new/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(NSM) :: {internal_user, NSM} | {udp_packet, {integer(), integer(), integer(), integer()}, integer(), bitstring()} | unknown. -opaque state(NSN, NSO) :: {state, socket(), NSN, gleam@dict:dict({{integer(), integer(), integer(), integer()}, integer()}, gleam@erlang@process:subject(message(NSO)))}. -type builder(NSP, NSQ) :: {builder, fun(() -> {NSP, gleam@option:option(gleam@erlang@process:selector(NSQ))}), fun((NSP) -> nil), integer(), fun((message(NSQ), connection(), NSP) -> gleam@otp@actor:next(NSQ, NSP))}. -type message(NSR) :: {user, NSR} | {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(() -> {NTH, gleam@option:option(gleam@erlang@process:selector(NTI))}), fun((message(NTI), connection(), NTH) -> gleam@otp@actor:next(NTI, NTH)) ) -> builder(NTH, NTI). new(Init, Handler) -> {builder, Init, fun(_) -> nil end, 4000, Handler}. -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(4, 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(5, 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(5, 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(4, 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).