Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User Messages don't seem to work and there is no doc to how to use it correctly #23

Open
brandonpollack23 opened this issue Oct 19, 2024 · 1 comment

Comments

@brandonpollack23
Copy link

import gleam/bytes_builder
import gleam/erlang/process
import gleam/int
import gleam/io
import gleam/iterator
import gleam/option.{None, Some}
import gleam/otp/actor
import gleam/otp/task
import glisten

pub const clrf = <<"\r\n":utf8>>

type MyMessage {
  Integer(i: Int)
  String(s: String)
}

pub fn main() {
  io.println("Logs from your program will appear here!")

  let int_subject = process.new_subject()
  let string_subject = process.new_subject()
  let selector =
    process.new_selector()
    |> process.selecting(int_subject, Integer)
    |> process.selecting(string_subject, String)

  let assert Ok(subject) =
    glisten.handler(fn(_conn) { #(Nil, Some(selector)) }, fn(msg, state, conn) {
      case msg {
        glisten.Packet(_) -> io.debug("Received a packet")
        glisten.User(Integer(some_int)) ->
          io.debug("Received a user message int: " <> int.to_string(some_int))
        glisten.User(String(s)) ->
          io.debug("Received a user message string: " <> s)
      }

      let assert Ok(_) =
        bytes_builder.new()
        |> bytes_builder.append(<<"HTTP/1.1 200 OK":utf8, clrf:bits>>)
        |> bytes_builder.append(clrf)
        |> glisten.send(conn, _)

      actor.continue(state)
    })
    |> glisten.serve(4221)

  task.async(fn() {
    process.sleep(1000)
    process.send(int_subject, 42)
    process.send(string_subject, "Hello, world!")
  })

  iterator.repeatedly(fn() {
    case process.select(selector, 5000) {
      Ok(Integer(some_int)) ->
        io.debug("Received a user message int: " <> int.to_string(some_int))
      Ok(String(s)) -> io.debug("Received a user message string: " <> s)
      Error(_) -> io.debug("ERROR")
    }
  })
  |> iterator.run

  process.sleep_forever()
}

These messages are (naturally since the subject is created on the initial process) not sent to the handler. How can we achieve this?

@rawhat
Copy link
Owner

rawhat commented Oct 25, 2024

This question has come up a few times. The approach you'd generally need to take is: create the Subject in your on_init, send it to whatever you want to be able to communicate with it, and then send to it.

The complication comes from the fact that each connection is its own handler.

It's a little difficult to come up with generalized examples because it sort of depends on what you're trying to accomplish. The most common thing I've seen questions about is some sort of pub-sub setup. In that case, you'd likely want to use some type of "registry" process. You would "register" your handler when a connection is opened and send over its Subject. And then that would know how to delegate sending messages to your handler.

If you're just messing around with things with your example, and you know you'd only ever have one connection, you have a few options.

  1. You don't explicitly need the serve setup if that's the case, as that spins up multiple "acceptor" processes. You could drop down to the lower-level functions to listen/accept yourself and then do something similar to (2).

  2. A similar setup to what you have that should work (I have not explicitly tested this) is the following:

let parent = process.new_subject()
let get_handler = process.new_selector() |> process.selecting(parent, fn(subj) { subj })

glisten.handler(fn(_conn) {
  let receiver = process.new_subject()
  process.send(parent, receiver)
  let user_selector = process.new_selector() |> process.selecting(receiver, fn(subj) { subj })
  #(Nil, Some(user_selector))
}, fn(msg, state, conn) {
  case msg {
    User(Integer(..)) -> ...
    ...
  }
})
|> glisten.serve(...)

let handler = process.select(get_handler, 1_000)
process.send(handler, Integer(...))
process.send(handler, String(...))

In the code example above in a more robust setup would be the "registry" I mentioned above. That is a bit more involved, so I will leave that out for now.

Hope this helps! Let me know if you have any other questions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants