Skip to content

Instantly share code, notes, and snippets.

@josevalim
Forked from alco/sample output
Created May 24, 2012 17:53
Show Gist options
  • Save josevalim/2783092 to your computer and use it in GitHub Desktop.
Save josevalim/2783092 to your computer and use it in GitHub Desktop.
Updated Elixir chat demo
defmodule Chat.Client do
# In all of the following functions 'server' stands for the server's pid
def join(server) do
send server, :join
end
def say(server, message) do
send server, { :say, message }
end
def leave(server) do
send server, :leave
end
# Receive a pending message from 'server' and print it
def flush(server) do
receive do
# The caret '^' is used to match against the value of 'server',
# it is a basic filtering based on the sender
{ ^server, {:message, message} } ->
IO.puts message
end
end
# Send 'message' to 'server' and wait for a reply
# Notice how this function is declared using 'defp' meaning
# it's private and can only be called inside this module
defp send(server, message) do
server <- { Process.self, message }
receive do
{ ^server, response } ->
response
after
1000 ->
IO.puts "Connection to room timed out"
:timeout
end
end
end
defmodule Chat.Server do
# A Room record acts as a reference to 'this' or 'self' in other langs
defrecord Room, clients: []
# The main receive loop
def msg_loop(room) do
receive do
{pid, :join } ->
notify_all room, Process.self, "Some user with pid #{inspect pid} joined"
pid <- { Process.self, :ok }
# The 'prepend_clients' function is generated automatically.
# See section 4.1.1 in http://elixir-lang.org/getting_started/4.html
msg_loop room.prepend_clients([pid])
{pid, {:say, message}} ->
notify_all room, pid, "#{inspect pid}: " <> message
pid <- { Process.self, :ok }
msg_loop room
{pid, :leave} ->
pid <- { Process.self, :ok }
# 'update_clients' is another automatically generated function
new_room = room.update_clients(fn(clients) -> List.delete(clients, pid) end)
notify_all new_room, Process.self, "User with pid #{inspect pid} left"
msg_loop new_room
end
end
# Send 'message' to all clients except 'sender'
defp notify_all({Room, clients}, sender, message) do
Enum.each clients, fn(pid) ->
if pid != sender do
pid <- { Process.self, {:message, message} }
end
end
end
end
# Init a Room record with the current process
room = Chat.Server.Room.new(clients: [Process.self])
# Spawn a server process
server_pid = spawn fn() -> Chat.Server.msg_loop room end
# Spawn another process as client
spawn fn() ->
Chat.Client.join server_pid
Chat.Client.say server_pid, "Hi!"
Chat.Client.leave server_pid
end
# And another one
spawn fn() ->
Chat.Client.join server_pid
Chat.Client.say server_pid, "What's up?"
Chat.Client.leave server_pid
end
# By this time we have 6 notifications pending
# (corresponding to three 'notify_all' calls per each client)
Enum.times 6, fn() ->
Chat.Client.flush server_pid
end
$ elixir chat_demo.ex
Some user with pid <0.41.0> joined
Some user with pid <0.42.0> joined
<0.41.0>: Hi!
User with pid <0.41.0> left
<0.42.0>: What's up?
User with pid <0.42.0> left
@aidansteele
Copy link

It seems Enum.times/2 no longer exists, we should now use Enum.each (0..n) instead. This took me a while to figure out, hope it helps others who stumble across this :)

@masato25
Copy link

This sample seems not works now. (I'm using Elixir (0.13.3))
I really want to know how build a simple chartroom with elixir. any other good sample?

@namxam
Copy link

namxam commented Jun 29, 2014

@masato25 Have a look at my fork. I had the same issue and it took me a while to update the code (as a complete noobie). But it seems to be working 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment