require 'fiber'
class Actor < Fiber
class << self
def spawn(&routine)
actor = new &routine
actor.instance_eval { @mailbox = Mailbox.new }
Scheduler << actor
actor
end
def receive(&filter)
raise "receive must be called in the context of an Actor" unless current.is_a?(Actor)
current.instance_eval { @mailbox.receive &filter }
end
end
def <<(message)
@mailbox << message
Scheduler << self
message
end
class Scheduler
@@queue = []
@@running = false
class << self
def <<(actor)
@@queue << actor
run unless @@running
end
def run
return if @@running
@@running = true
while not @@queue.empty?
run_queue = @@queue
@@queue = []
run_queue.each { |actor| actor.resume }
end
@@running = false
end
end
end
class Mailbox
def initialize
@queue = []
end
def <<(message)
@queue << message
end
def receive
raise ArgumentError, "No filter block given" unless block_given?
filter = Filter.new
yield filter
raise ArgumentError, "Empty filter" if filter.empty?
matched_index = matched_message = action = nil
while action.nil?
@queue.each_with_index do |message, index|
next unless (action = filter.match message)
matched_index = index
matched_message = message
break
end
Fiber.yield unless action
end
@queue.delete_at matched_index
return action.call(matched_message)
end
class Filter
def initialize
@ruleset = []
end
def when(pattern, &action)
raise ArgumentError, "No block given" unless action
@ruleset << [pattern, action]
end
def match(message)
_, action = @ruleset.find do |pattern, _|
case pattern
when Proc then pattern.call message
else pattern === message
end
end
action
end
def empty?
@ruleset.empty?
end
end
end
end