From e238b796dda0e7fa9bdff1849106ef7617a9b29b Mon Sep 17 00:00:00 2001 From: Filip Hoffmann Date: Mon, 10 Nov 2025 23:08:38 +0100 Subject: [PATCH] my processes are dying and i don't like it --- examples/README.md | 24 --- examples/src/examples.gleam | 129 -------------- examples/test/examples_test.gleam | 12 -- .../.github/workflows/test.yml | 4 +- examples/{ => your_first_bot}/.gitignore | 0 examples/your_first_bot/README.md | 24 +++ examples/{ => your_first_bot}/gleam.toml | 8 +- examples/{ => your_first_bot}/manifest.toml | 11 +- .../your_first_bot/src/your_first_bot.gleam | 160 ++++++++++++++++++ .../test/your_first_bot_test.gleam | 13 ++ src/grom.gleam | 1 + src/grom/gateway.gleam | 80 +++++---- src/grom/gateway/connection.gleam | 52 ++++++ src/grom/gateway/connection_pid.gleam | 38 ----- src/grom/gateway/user_message.gleam | 1 + 15 files changed, 311 insertions(+), 246 deletions(-) delete mode 100644 examples/README.md delete mode 100644 examples/src/examples.gleam delete mode 100644 examples/test/examples_test.gleam rename examples/{ => your_first_bot}/.github/workflows/test.yml (85%) rename examples/{ => your_first_bot}/.gitignore (100%) create mode 100644 examples/your_first_bot/README.md rename examples/{ => your_first_bot}/gleam.toml (82%) rename examples/{ => your_first_bot}/manifest.toml (92%) create mode 100644 examples/your_first_bot/src/your_first_bot.gleam create mode 100644 examples/your_first_bot/test/your_first_bot_test.gleam create mode 100644 src/grom/gateway/connection.gleam delete mode 100644 src/grom/gateway/connection_pid.gleam diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index 89f7517..0000000 --- a/examples/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# examples - -[![Package Version](https://img.shields.io/hexpm/v/examples)](https://hex.pm/packages/examples) -[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/examples/) - -```sh -gleam add examples@1 -``` -```gleam -import examples - -pub fn main() { - // TODO: An example of the project in use -} -``` - -Further documentation can be found at . - -## Development - -```sh -gleam run # Run the project -gleam test # Run the tests -``` diff --git a/examples/src/examples.gleam b/examples/src/examples.gleam deleted file mode 100644 index 79ab8bc..0000000 --- a/examples/src/examples.gleam +++ /dev/null @@ -1,129 +0,0 @@ -import gleam/erlang/process -import gleam/option.{Some} -import gleam/otp/actor -import grom -import grom/application/current_application -import grom/command -import grom/component/container.{Container} -import grom/component/text_display -import grom/gateway -import grom/gateway/intent -import grom/interaction.{type Interaction} -import grom/message -import grom/permission - -const token = "super secret token" - -pub type State { - State(client: grom.Client) -} - -// Assume there's error handling all over this code -pub fn main() { - let client = grom.Client(token:) - - let assert Ok(application) = - client - |> current_application.get() - - let assert Ok(_commands) = - client - |> command.bulk_overwrite_global(of: application.id, new: [ - command.CreateGlobalSlash( - command.CreateGlobalSlashCommand( - ..command.new_create_global_slash_command( - named: "hello", - description: "A welcoming message", - ), - // Require users to be administrators in order to use this command. - default_member_permissions: Some([ - permission.Administrator, - ]), - ), - ), - command.CreateGlobalUser(command.new_create_global_user_command( - named: "Get avatar", - )), - ]) - - let identify = gateway.identify(client, intent.all_unprivileged) - - let state = State(client:) - - let assert Ok(actor) = - actor.new(state) - |> actor.on_message(on_event) - |> actor.start - let actor = actor.data - - let assert Ok(_) = gateway.start(client, identify, actor) - process.sleep_forever() -} - -fn on_event(state: State, event: gateway.Event) { - case event { - gateway.InteractionCreatedEvent(interaction) -> { - on_interaction_created(state, interaction) - actor.continue(state) - } - _ -> actor.continue(state) - } -} - -fn on_interaction_created(state: State, interaction: Interaction) { - case interaction.data { - interaction.CommandExecuted(command) -> - on_command_executed(state, interaction, command) - _ -> Nil - } -} - -fn on_command_executed( - state: State, - interaction: Interaction, - command: interaction.CommandExecution, -) { - case command { - interaction.SlashCommandExecuted(executed) -> - on_slash_command_executed(state, interaction, executed) - _ -> Nil - } -} - -fn on_slash_command_executed( - state: State, - interaction: Interaction, - executed: interaction.SlashCommandExecution, -) { - case executed.command_name { - "hello" -> on_hello_command(state, interaction) - _ -> Nil - } -} - -fn on_hello_command(state: State, interaction: Interaction) { - let _ = - state.client - |> interaction.respond( - to: interaction, - using: interaction.RespondWithChannelMessageWithSource( - interaction.ResponseMessage( - ..interaction.new_response_message(), - flags: Some([interaction.ResponseMessageWithComponentsV2]), - components: Some([ - message.Container( - Container( - ..container.new(containing: [ - container.TextDisplay(text_display.new( - showing: "# Welcome to Grom!", - )), - ]), - accent_color: Some(0xffaff3), - ), - ), - ]), - ), - ), - ) - Nil -} diff --git a/examples/test/examples_test.gleam b/examples/test/examples_test.gleam deleted file mode 100644 index 3831e7a..0000000 --- a/examples/test/examples_test.gleam +++ /dev/null @@ -1,12 +0,0 @@ -import gleeunit -import gleeunit/should - -pub fn main() { - gleeunit.main() -} - -// gleeunit test functions end in `_test` -pub fn hello_world_test() { - 1 - |> should.equal(1) -} diff --git a/examples/.github/workflows/test.yml b/examples/your_first_bot/.github/workflows/test.yml similarity index 85% rename from examples/.github/workflows/test.yml rename to examples/your_first_bot/.github/workflows/test.yml index bfb787f..f6e42b5 100644 --- a/examples/.github/workflows/test.yml +++ b/examples/your_first_bot/.github/workflows/test.yml @@ -14,8 +14,8 @@ jobs: - uses: actions/checkout@v4 - uses: erlef/setup-beam@v1 with: - otp-version: "27.1.2" - gleam-version: "1.9.1" + otp-version: "28" + gleam-version: "1.13.0" rebar3-version: "3" # elixir-version: "1" - run: gleam deps download diff --git a/examples/.gitignore b/examples/your_first_bot/.gitignore similarity index 100% rename from examples/.gitignore rename to examples/your_first_bot/.gitignore diff --git a/examples/your_first_bot/README.md b/examples/your_first_bot/README.md new file mode 100644 index 0000000..be5f04e --- /dev/null +++ b/examples/your_first_bot/README.md @@ -0,0 +1,24 @@ +# your_first_bot + +[![Package Version](https://img.shields.io/hexpm/v/your_first_bot)](https://hex.pm/packages/your_first_bot) +[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/your_first_bot/) + +```sh +gleam add your_first_bot@1 +``` +```gleam +import your_first_bot + +pub fn main() -> Nil { + // TODO: An example of the project in use +} +``` + +Further documentation can be found at . + +## Development + +```sh +gleam run # Run the project +gleam test # Run the tests +``` diff --git a/examples/gleam.toml b/examples/your_first_bot/gleam.toml similarity index 82% rename from examples/gleam.toml rename to examples/your_first_bot/gleam.toml index 4104217..fe619a2 100644 --- a/examples/gleam.toml +++ b/examples/your_first_bot/gleam.toml @@ -1,4 +1,4 @@ -name = "examples" +name = "your_first_bot" version = "1.0.0" # Fill out these fields if you intend to generate HTML documentation or publish @@ -14,9 +14,9 @@ version = "1.0.0" [dependencies] gleam_stdlib = ">= 0.44.0 and < 2.0.0" -grom = { path = ".." } -envoy = ">= 1.0.2 and < 2.0.0" -gleam_otp = ">= 1.1.0 and < 2.0.0" +grom = { path = "../.." } +logging = ">= 1.3.0 and < 2.0.0" +gleam_otp = ">= 1.2.0 and < 2.0.0" gleam_erlang = ">= 1.3.0 and < 2.0.0" [dev-dependencies] diff --git a/examples/manifest.toml b/examples/your_first_bot/manifest.toml similarity index 92% rename from examples/manifest.toml rename to examples/your_first_bot/manifest.toml index 50027e8..49dbc17 100644 --- a/examples/manifest.toml +++ b/examples/your_first_bot/manifest.toml @@ -3,7 +3,6 @@ packages = [ { name = "discord_gleam_stratus", version = "1.0.2", build_tools = ["gleam"], requirements = ["exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "gramps", "logging"], otp_app = "discord_gleam_stratus", source = "hex", outer_checksum = "30CABB6B27B6A98B28B9871BDC53B58AD011AB74E2193D981901CB70A48D206F" }, - { name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" }, { name = "exception", version = "2.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" }, { name = "gleam_crypto", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "50774BAFFF1144E7872814C566C5D653D83A3EBF23ACC3156B757A1B6819086E" }, { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, @@ -15,7 +14,7 @@ packages = [ { name = "gleam_time", version = "1.5.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "D560E672C7279C89908981E068DF07FD16D0C859DCA266F908B18F04DF0EB8E6" }, { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, { name = "gramps", version = "6.0.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "8B7195978FBFD30B43DF791A8A272041B81E45D245314D7A41FC57237AA882A0" }, - { name = "grom", version = "0.0.0", build_tools = ["gleam"], requirements = ["discord_gleam_stratus", "gleam_erlang", "gleam_http", "gleam_httpc", "gleam_json", "gleam_otp", "gleam_stdlib", "gleam_time", "multipart_form", "operating_system", "repeatedly", "status_code"], source = "local", path = ".." }, + { name = "grom", version = "0.0.0", build_tools = ["gleam"], requirements = ["discord_gleam_stratus", "gleam_erlang", "gleam_http", "gleam_httpc", "gleam_json", "gleam_otp", "gleam_stdlib", "gleam_time", "multipart_form", "operating_system", "repeatedly", "status_code"], source = "local", path = "../.." }, { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, { name = "multipart_form", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_http", "gleam_stdlib"], otp_app = "multipart_form", source = "hex", outer_checksum = "082C77A0C3BB1128FCD55491665E9B72BC943E849B67D02B08CFA6808AD8E47C" }, { name = "operating_system", version = "1.0.1", build_tools = ["gleam"], requirements = [], otp_app = "operating_system", source = "hex", outer_checksum = "682D4D19496E607A6E6229AA51A38A1400ED5067C2C54D56843DBCB0C61FFA6D" }, @@ -24,9 +23,9 @@ packages = [ ] [requirements] -envoy = { version = ">= 1.0.2 and < 2.0.0" } -gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" } -gleam_otp = { version = ">= 1.1.0 and < 2.0.0" } gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } gleeunit = { version = ">= 1.0.0 and < 2.0.0" } -grom = { path = ".." } +grom = { path = "../.." } +logging = { version = ">= 1.3.0 and < 2.0.0" } +gleam_otp = { version = ">= 1.2.0 and < 2.0.0" } +gleam_erlang = { version = ">= 1.3.0 and < 2.0.0" } diff --git a/examples/your_first_bot/src/your_first_bot.gleam b/examples/your_first_bot/src/your_first_bot.gleam new file mode 100644 index 0000000..609a947 --- /dev/null +++ b/examples/your_first_bot/src/your_first_bot.gleam @@ -0,0 +1,160 @@ +import gleam/erlang/process +import gleam/option.{Some} +import gleam/otp/actor +import gleam/string +import grom +import grom/command +import grom/gateway +import grom/gateway/intent +import grom/interaction.{type Interaction} +import logging + +const token = "super secret token" + +type State { + State(client: grom.Client) +} + +pub fn main() -> Nil { + logging.configure() + let client = grom.Client(token:) + + let identify = + client + |> gateway.identify(intents: intent.all_unprivileged) + + let state = State(client:) + + use actor <- create_actor(state) + + let gateway_start_result = + client + |> gateway.start(identify, notify: actor) + + case gateway_start_result { + Ok(_) -> { + logging.log(logging.Info, "Started the gateway!") + process.sleep_forever() + } + Error(err) -> { + logging.log( + logging.Error, + "Couldn't start the gateway: " <> string.inspect(err), + ) + } + } +} + +fn create_actor( + state: State, + next: fn(process.Subject(gateway.Event)) -> Nil, +) -> Nil { + let actor = + actor.new(state) + |> actor.on_message(on_event) + |> actor.start + + case actor { + Ok(actor) -> next(actor.data) + Error(err) -> + logging.log( + logging.Critical, + "Couldn't start the gateway: " <> string.inspect(err), + ) + } +} + +fn on_event(state: State, event: gateway.Event) { + case event { + gateway.ErrorEvent(error) -> { + logging.log(logging.Error, "Error: " <> string.inspect(error)) + actor.stop() + } + gateway.ReadyEvent(ready) -> on_ready(state, ready) + gateway.InteractionCreatedEvent(interaction) -> + on_interaction_created(state, interaction) + _ -> actor.continue(state) + } +} + +fn on_ready(state: State, ready: gateway.ReadyMessage) { + logging.log(logging.Info, "Ready!") + + let global_commands = [ + command.CreateGlobalSlash(command.new_create_global_slash_command( + named: "ping", + description: "Ping-pong! 🏓", + )), + ] + + let bulk_overwrite_result = + state.client + |> command.bulk_overwrite_global( + of: ready.application.id, + new: global_commands, + ) + + case bulk_overwrite_result { + Ok(_) -> { + logging.log( + logging.Info, + "Overwritten the commands for " <> ready.application.id, + ) + actor.continue(state) + } + Error(err) -> { + logging.log( + logging.Error, + "Couldn't bulk overwrite global commands: " <> string.inspect(err), + ) + actor.stop() + } + } +} + +fn on_interaction_created(state: State, interaction: Interaction) { + case interaction.data { + interaction.CommandExecuted(command) -> + on_command_executed(state, interaction, command) + _ -> actor.continue(state) + } +} + +fn on_command_executed( + state: State, + interaction: Interaction, + command: interaction.CommandExecution, +) { + case command { + interaction.SlashCommandExecuted(command) -> + on_slash_command_executed(state, interaction, command) + _ -> actor.continue(state) + } +} + +fn on_slash_command_executed( + state: State, + interaction: Interaction, + command: interaction.SlashCommandExecution, +) { + case command.name { + "ping" -> on_ping_command(state, interaction) + _ -> actor.continue(state) + } +} + +fn on_ping_command(state: State, interaction: Interaction) { + let response = + interaction.RespondWithChannelMessageWithSource( + interaction.ResponseMessage( + ..interaction.new_response_message(), + content: Some("Pong!"), + ), + ) + + let _response_result = + state.client + |> interaction.respond(to: interaction, using: response) + + actor.continue(state) +} diff --git a/examples/your_first_bot/test/your_first_bot_test.gleam b/examples/your_first_bot/test/your_first_bot_test.gleam new file mode 100644 index 0000000..fba3c88 --- /dev/null +++ b/examples/your_first_bot/test/your_first_bot_test.gleam @@ -0,0 +1,13 @@ +import gleeunit + +pub fn main() -> Nil { + gleeunit.main() +} + +// gleeunit test functions end in `_test` +pub fn hello_world_test() { + let name = "Joe" + let greeting = "Hello, " <> name <> "!" + + assert greeting == "Hello, Joe!" +} diff --git a/src/grom.gleam b/src/grom.gleam index 0aad911..a2414a4 100644 --- a/src/grom.gleam +++ b/src/grom.gleam @@ -16,6 +16,7 @@ pub type Error { StatusCodeUnsuccessful(Response(String)) ResponseNotValidUtf8(BitArray) InvalidGatewayUrl(String) + NoConnectionFound CouldNotInitializeWebsocketConnection(stratus.InitializationError) CouldNotStartActor(actor.StartError) CouldNotSendEvent(stratus.SocketReason) diff --git a/src/grom/gateway.gleam b/src/grom/gateway.gleam index 99251f9..9fa6451 100644 --- a/src/grom/gateway.gleam +++ b/src/grom/gateway.gleam @@ -22,7 +22,7 @@ import grom/channel/thread.{type Thread} import grom/command import grom/emoji.{type Emoji} import grom/entitlement.{type Entitlement} -import grom/gateway/connection_pid +import grom/gateway/connection import grom/gateway/heartbeat import grom/gateway/intent.{type Intent} import grom/gateway/resuming @@ -172,7 +172,7 @@ pub opaque type State { sequence_holder: Subject(sequence.Message), heartbeat_counter: Subject(heartbeat.Message), resuming_info_holder: Subject(resuming.Message), - connection_pid_holder: Subject(connection_pid.Message), + connection_holder: Subject(connection.Message), identify: IdentifyMessage, user_message_subject_holder: Subject(user_message.Message), ) @@ -2165,7 +2165,7 @@ pub fn start( |> result.replace_error(actor.InitFailed("couldn't init state")), ) - use _ <- result.try( + use _supervisor <- result.try( static_supervisor.new(static_supervisor.OneForOne) |> static_supervisor.add(supervised(client, state)) |> static_supervisor.start, @@ -2175,9 +2175,6 @@ pub fn start( } fn supervised(client: grom.Client, state: State) { - state.connection_pid_holder - |> connection_pid.set(to: process.self()) - let start = case resuming.get_info(state.resuming_info_holder) { Some(info) -> { case resuming.is_possible(info) { @@ -2197,6 +2194,9 @@ fn supervised(client: grom.Client, state: State) { fn new_connection(client: grom.Client, state: State) { fn() { + state.connection_holder + |> connection.set_pid(to: process.self()) + use gateway_data <- result.try( client |> get_data @@ -2231,12 +2231,18 @@ fn new_connection(client: grom.Client, state: State) { state.user_message_subject_holder |> user_message.set_subject(subject.data) + subject.data + |> actor.send(user_message.StartNewConnection |> stratus.to_user_message) + Ok(subject) } } fn resume(client: grom.Client, state: State, info: resuming.Info) { fn() { + state.connection_holder + |> connection.set_pid(to: process.self()) + use connection_request <- result.try( request.to(info.resume_gateway_url) |> result.replace_error(actor.InitFailed("couldn't parse connection url")), @@ -2282,7 +2288,7 @@ fn on_close(state: State, close_reason: stratus.CloseReason) { // consult on if this is a bug, no idea tbh // i think it's impossible state for the connection_pid to be none by the time this function gets called // typing requires work, impossible states defined as possible values i think - case connection_pid.get(state.connection_pid_holder) { + case connection.get_pid(state.connection_holder) { Some(pid) -> process.kill(pid) None -> Nil } @@ -2394,11 +2400,11 @@ fn init_state(actor: Subject(Event), identify: IdentifyMessage) { ) let user_message_subject_holder = user_message_subject_holder.data - use connection_pid_holder <- result.try( - connection_pid.new_holder() + use connection_holder <- result.try( + connection.new_holder() |> result.map_error(string.inspect), ) - let connection_pid_holder = connection_pid_holder.data + let connection_holder = connection_holder.data let state = State( @@ -2408,7 +2414,7 @@ fn init_state(actor: Subject(Event), identify: IdentifyMessage) { resuming_info_holder:, identify:, user_message_subject_holder:, - connection_pid_holder:, + connection_holder:, ) Ok(state) @@ -2423,6 +2429,7 @@ fn on_message( case message { stratus.Text(text_message) -> on_text_message(state, connection, text_message) + stratus.Binary(_) -> stratus.continue(state) stratus.User(user_message.StartResume) -> start_resume(client, state, connection) stratus.User(user_message.StartPresenceUpdate(msg)) -> @@ -2433,10 +2440,18 @@ fn on_message( start_guild_members_request(state, connection, msg) stratus.User(user_message.StartSoundboardSoundsRequest(guild_ids)) -> start_soundboard_sounds_request(state, connection, guild_ids) - _ -> stratus.continue(state) + stratus.User(user_message.StartNewConnection) -> + start_new_connection(state, connection) } } +fn start_new_connection(state: State, connection: stratus.Connection) { + state.connection_holder + |> connection.set(to: connection) + + stratus.continue(state) +} + fn start_guild_members_request( state: State, connection: stratus.Connection, @@ -2530,7 +2545,7 @@ fn on_text_message( Hello(event) -> on_hello_event(state, connection, event) Dispatch(sequence, message) -> on_dispatch(state, sequence, message) HeartbeatAcknowledged -> on_heartbeat_acknowledged(state) - HeartbeatRequest -> on_heartbeat_request(state, connection) + HeartbeatRequest -> on_heartbeat_request(state) ReconnectRequest -> on_reconnect_request(state, connection) InvalidSession(can_reconnect) -> on_invalid_session(state, connection, can_reconnect) @@ -2567,7 +2582,7 @@ fn on_invalid_session( _, _ -> Nil } - case connection_pid.get(state.connection_pid_holder) { + case connection.get_pid(state.connection_holder) { Some(pid) -> process.kill(pid) None -> Nil } @@ -2592,7 +2607,7 @@ fn on_reconnect_request(state: State, connection: stratus.Connection) -> Nil { None -> Nil } - case connection_pid.get(state.connection_pid_holder) { + case connection.get_pid(state.connection_holder) { Some(pid) -> process.kill(pid) None -> Nil } @@ -2603,6 +2618,9 @@ fn start_resume( state: State, connection: stratus.Connection, ) { + state.connection_holder + |> connection.set(to: connection) + let resuming_info = resuming.get_info(state.resuming_info_holder) let last_sequence = sequence.get(state.sequence_holder) @@ -2623,7 +2641,7 @@ fn start_resume( let heartbeat_counter = heartbeat.get(state.heartbeat_counter) - start_heartbeats(state, connection, heartbeat_counter.interval) + start_heartbeats(state, heartbeat_counter.interval) stratus.continue(state) } @@ -2775,8 +2793,8 @@ fn on_ready(state: State, message: ReadyMessage) { |> actor.send(ReadyEvent(message)) } -fn on_heartbeat_request(state: State, connection: stratus.Connection) -> Nil { - case send_heartbeat(state, connection) { +fn on_heartbeat_request(state: State) -> Nil { + case send_heartbeat(state) { Ok(_) -> Nil Error(err) -> { state.actor @@ -2795,7 +2813,7 @@ fn on_hello_event( connection: stratus.Connection, event: HelloMessage, ) { - start_heartbeats(state, connection, event.heartbeat_interval) + start_heartbeats(state, event.heartbeat_interval) send_identify(state, connection) } @@ -2813,12 +2831,7 @@ fn send_identify(state: State, connection: stratus.Connection) { } } -/// returns the pid of the process taking care of the heartbeat loop -fn start_heartbeats( - state: State, - connection: stratus.Connection, - interval: Duration, -) { +fn start_heartbeats(state: State, interval: Duration) { state.heartbeat_counter |> heartbeat.interval(interval) @@ -2841,7 +2854,7 @@ fn start_heartbeats( use <- fn(next) { case - send_heartbeat(state, connection) + send_heartbeat(state) |> result.map_error(grom.CouldNotStartHeartbeatCycle) { Ok(_) -> next() @@ -2850,7 +2863,7 @@ fn start_heartbeats( } repeatedly.call(regular_wait_duration, Nil, fn(_state, _i) { - case send_heartbeat(state, connection) { + case send_heartbeat(state) { Ok(_) -> Nil Error(error) -> actor.send(state.actor, ErrorEvent(error)) } @@ -2859,10 +2872,15 @@ fn start_heartbeats( }) } -fn send_heartbeat( - state: State, - connection: stratus.Connection, -) -> Result(Nil, grom.Error) { +fn send_heartbeat(state: State) -> Result(Nil, grom.Error) { + use connection <- + fn(next) { + case state.connection_holder |> connection.get { + Some(connection) -> next(connection) + None -> Error(grom.NoConnectionFound) + } + } + let last_sequence = sequence.get(state.sequence_holder) let counter = heartbeat.get(state.heartbeat_counter) diff --git a/src/grom/gateway/connection.gleam b/src/grom/gateway/connection.gleam new file mode 100644 index 0000000..0c8c9f8 --- /dev/null +++ b/src/grom/gateway/connection.gleam @@ -0,0 +1,52 @@ +import gleam/erlang/process.{type Subject} +import gleam/option.{type Option, None, Some} +import gleam/otp/actor +import stratus + +pub opaque type Message { + GetPid(reply_to: Subject(Option(process.Pid))) + SetPid(new: process.Pid) + Get(reply_to: Subject(Option(stratus.Connection))) + Set(new: stratus.Connection) +} + +pub type State { + State(pid: Option(process.Pid), connection: Option(stratus.Connection)) +} + +pub fn new_holder() { + actor.new(State(None, None)) + |> actor.on_message(on_message) + |> actor.start +} + +pub fn get_pid(actor: Subject(Message)) -> Option(process.Pid) { + actor.call(actor, 10, GetPid) +} + +pub fn set_pid(actor: Subject(Message), to new: process.Pid) -> Nil { + actor.send(actor, SetPid(new:)) +} + +pub fn get(actor: Subject(Message)) -> Option(stratus.Connection) { + actor.call(actor, 10, Get) +} + +pub fn set(actor: Subject(Message), to new: stratus.Connection) { + actor.send(actor, Set(new)) +} + +fn on_message(current: State, message: Message) { + case message { + GetPid(..) -> { + actor.send(message.reply_to, current.pid) + actor.continue(current) + } + SetPid(new:) -> actor.continue(State(..current, pid: Some(new))) + Get(..) -> { + actor.send(message.reply_to, current.connection) + actor.continue(current) + } + Set(new:) -> actor.continue(State(..current, connection: Some(new))) + } +} diff --git a/src/grom/gateway/connection_pid.gleam b/src/grom/gateway/connection_pid.gleam deleted file mode 100644 index 6f70961..0000000 --- a/src/grom/gateway/connection_pid.gleam +++ /dev/null @@ -1,38 +0,0 @@ -import gleam/erlang/process.{type Subject} -import gleam/option.{type Option, None, Some} -import gleam/otp/actor - -pub opaque type Message { - Get(reply_to: Subject(Option(process.Pid))) - Set(new: process.Pid) - Reset -} - -pub fn new_holder() { - actor.new(None) - |> actor.on_message(on_message) - |> actor.start -} - -pub fn get(actor: Subject(Message)) -> Option(process.Pid) { - actor.call(actor, 10, Get) -} - -pub fn set(actor: Subject(Message), to new: process.Pid) -> Nil { - actor.send(actor, Set(new:)) -} - -pub fn reset(actor: Subject(Message)) -> Nil { - actor.send(actor, Reset) -} - -fn on_message(current: Option(process.Pid), message: Message) { - case message { - Get(..) -> { - actor.send(message.reply_to, current) - actor.continue(current) - } - Set(new:) -> actor.continue(Some(new)) - Reset -> actor.continue(None) - } -} diff --git a/src/grom/gateway/user_message.gleam b/src/grom/gateway/user_message.gleam index f268d06..76cdea0 100644 --- a/src/grom/gateway/user_message.gleam +++ b/src/grom/gateway/user_message.gleam @@ -13,6 +13,7 @@ import stratus @internal pub type UserMessage { StartResume + StartNewConnection StartPresenceUpdate(UpdatePresenceMessage) StartVoiceStateUpdate(UpdateVoiceStateMessage) StartGuildMembersRequest(RequestGuildMembersMessage) -- 2.51.2