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
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
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.