Report abuse


			
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