Skip to content

Instantly share code, notes, and snippets.

@noteflakes
Last active November 18, 2021 22:15
Show Gist options
  • Save noteflakes/9e2ffbc39d75a8b7433ea6df7c5ddea8 to your computer and use it in GitHub Desktop.
Save noteflakes/9e2ffbc39d75a8b7433ea6df7c5ddea8 to your computer and use it in GitHub Desktop.
Real-world Concurrency with Ruby and Polyphony: a Telnet-based Chat App
# See also: https://noteflakes.com/articles/2021-11-13-real-world-polyphony-chat
require 'polyphony'
server = spin do
server_socket = TCPServer.new('0.0.0.0', 1234)
server_socket.accept_loop do |s|
spin { handle_session(s) }
end
end
def handle_session(socket)
socket << 'Please enter your name: '
name = socket.gets.chomp
socket.puts "Hello, #{name}!"
user_fiber = spin { run_user(name, socket) }
while (line = socket.gets.chomp)
user_fiber << [:input, line]
end
ensure
user_fiber << [:close]
end
def run_user(name, socket)
current_room = nil
loop do
event, message = receive
case event
when :close
break
when :input
case message
when /\:enter\s+(.+)/
leave_room(current_room, name) if current_room
current_room = enter_room($1, name)
when ':leave'
leave_room(current_room, name) if current_room
else
say(current_room, name, message)
end
when :message
socket.puts message
end
end
ensure
leave_room(current_room, name) if current_room
end
def leave_room(room_fiber, name)
room_fiber << [:leave, name, Fiber.current]
end
def enter_room(room_name, name)
room_fiber = find_room(room_name)
room_fiber << [:enter, name, Fiber.current]
room_fiber
end
def say(room_fiber, name, message)
room_fiber << [:say, name, message]
end
@room_fibers = {}
@main_fiber = Fiber.current
def find_room(room_name)
@room_fibers[room_name] ||= @main_fiber.spin { run_room(room_name) }
end
def run_room(room_name)
@users = {}
loop do
event, *args = receive
case event
when :leave
name, fiber = args
@users.delete(fiber)
broadcast(@users.keys, "#{name} has left the room.")
break if @users.empty?
when :enter
name, fiber = args
@users[fiber] = true
broadcast(@users.keys, "#{name} has entered the room.")
when :say
name, message = args
broadcast(@users.keys, "#{name}: #{message}")
end
end
ensure
@room_fibers.delete(room_name)
end
def broadcast(fibers, message)
fibers.each { |f| f << [:message, message] }
end
server.await
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment