Last active
November 18, 2021 22:15
-
-
Save noteflakes/9e2ffbc39d75a8b7433ea6df7c5ddea8 to your computer and use it in GitHub Desktop.
Real-world Concurrency with Ruby and Polyphony: a Telnet-based Chat App
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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