event_state

<img src=“https://secure.travis-ci.org/jdleesmiller/event_state.png”/>

SYNOPSIS

A small embedded DSL for implementing stateful protocols in EventMachine using finite state machines. The protocol is specified in terms of states and messages. The processing happens in the states, and the messages that can be sent or received from each state are declared by name using the DSL.

Here’s everyone’s favorite example: an echo server. It starts in the :listening state, in which it can receive a Noise message. It then transitions to the :speaking state. After a short delay (EM.add_timer), it sends the noise back to the client, which causes it to transition back to the listening state.

class MessageEchoServer < EventState::ObjectMachine
  Noise = Struct.new(:content)

  protocol do
    state :listening do
      on_recv Noise, :speaking
    end

    state :speaking do
      on_send Noise, :listening

      on_enter do |noise|
        EM.add_timer 0.5 do
          send_message Noise.new(noise.content)
        end
      end
    end
  end
end

In a picture (generated from the code above using EventState::Machine.print_state_machine_dot):

Here the start state is indicated by a double circle, a blue arrow is a message that can be received, and a red arrow is a message that can be sent.

The EventState::ObjectMachine base class extends EventState::Machine, which in turn extends EventMachine::Connection. ObjectMachine handles serializing and deserializing the ruby objects using EventMachine::ObjectProtocol, and (by default) it uses the class of the message object as the message name. In this example, the message name is Noise. Machine provides the state machine DSL and the primitives for handling arbitrary kinds of messages.

Here is the corresponding client and a demo showing how to run it:

class MessageEchoClient < EventState::ObjectMachine
  Noise = MessageEchoServer::Noise

  def initialize noises
    super
    @noises = noises
  end

  protocol do
    state :speaking do
      on_send Noise, :listening

      on_enter do
        if @noises.empty?
          EM.stop
        else
          send_message MessageEchoServer::Noise.new(@noises.shift)
        end
      end
    end

    state :listening do
      on_recv Noise, :speaking

      on_enter do |noise|
        puts "heard: #{noise.content}"
      end
    end
  end

  def self.demo
    EM.run do
      EM.start_server('localhost', 14159, MessageEchoServer)
      EM.connect('localhost', 14159, MessageEchoClient, %w(foo bar baz))
    end
  end
end

Output:

heard: foo
heard: bar
heard: baz

INSTALLATION

gem install event_state

RELATED PROJECTS

This library was inspired by slagyr.github.com/statemachine which provides a nice DSL for defining state machines but doesn’t integrate directly with EventMachine. See also the www.complang.org/ragel state machine compiler and Zed Shaw’s zedshaw.com/essays/ragel_state_charts.html blog post about it.

LICENSE

(The MIT License)

Copyright © 2011 John Lees-Miller

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.