Skip to content

Commit

Permalink
Add EM::TickLoop
Browse files Browse the repository at this point in the history
  • Loading branch information
raggi committed Feb 3, 2010
1 parent d71e404 commit 30ad75d
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 0 deletions.
15 changes: 15 additions & 0 deletions examples/ex_tick_loop_array.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require File.dirname(__FILE__) + '/helper'

EM.run do
array = (1..100).to_a

tickloop = EM.tick_loop do
if array.empty?
:stop
else
puts array.shift
end
end

tickloop.on_stop { EM.stop }
end
32 changes: 32 additions & 0 deletions examples/ex_tick_loop_counter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
require File.dirname(__FILE__) + '/helper'

class TickCounter
attr_reader :start_time, :count

def initialize
reset
@tick_loop = EM.tick_loop(method(:tick))
end

def reset
@count = 0
@start_time = EM.current_time
end

def tick
@count += 1
end

def rate
@count / (EM.current_time - @start_time)
end
end

period = 5
EM.run do
counter = TickCounter.new
EM.add_periodic_timer(period) do
puts "Ticks per second: #{counter.rate} (mean of last #{period}s)"
counter.reset
end
end
85 changes: 85 additions & 0 deletions lib/em/tick_loop.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
module EventMachine
# Creates and immediately starts an EventMachine::TickLoop
def self.tick_loop(*a, &b)
TickLoop.new(*a, &b).start
end

# A TickLoop is useful when one needs to distribute amounts of work
# throughout ticks in order to maintain response times. It is also useful for
# simple repeated checks and metrics.
#
# # Here we run through an array one item per tick until it is empty,
# # printing each element.
# # When the array is empty, we return :stop from the callback, and the
# # loop will terminate.
# # When the loop terminates, the on_stop callbacks will be called.
# EM.run do
# array = (1..100).to_a
#
# tickloop = EM.tick_loop do
# if array.empty?
# :stop
# else
# puts array.shift
# end
# end
#
# tickloop.on_stop { EM.stop }
# end
#
class TickLoop

# Arguments: A callback (EM::Callback) to call each tick. If the call
# returns +:stop+ then the loop will be stopped. Any other value is
# ignored.
def initialize(*a, &b)
@work = EM::Callback(*a, &b)
@stops = []
@stopped = true
end

# Arguments: A callback (EM::Callback) to call once on the next stop (or
# immediately if already stopped).
def on_stop(*a, &b)
if @stopped
EM::Callback(*a, &b).call
else
@stops << EM::Callback(*a, &b)
end
end

# Stop the tick loop immediately, and call it's on_stop callbacks.
def stop
@stopped = true
until @stops.empty?
@stops.shift.call
end
end

# Query if the loop is stopped.
def stopped?
@stopped
end

# Start the tick loop, will raise argument error if the loop is already
# running.
def start
raise ArgumentError, "double start" unless @stopped
@stopped = false
schedule
end

private
def schedule
EM.next_tick do
next if @stopped
if @work.call == :stop
stop
else
schedule
end
end
self
end
end
end
1 change: 1 addition & 0 deletions lib/eventmachine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
require 'em/channel'
require 'em/file_watch'
require 'em/process_watch'
require 'em/tick_loop'

require 'shellwords'
require 'thread'
Expand Down
59 changes: 59 additions & 0 deletions tests/test_tick_loop.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
require "test/unit"
require "eventmachine"

class TestEmTickLoop < Test::Unit::TestCase
def test_em_tick_loop
i = 0
EM.tick_loop { i += 1; EM.stop if i == 10 }
EM.run { EM.add_timer(1) { EM.stop } }
assert_equal i, 10
end

def test_tick_loop_on_stop
t = nil
tick_loop = EM.tick_loop { :stop }
tick_loop.on_stop { t = true }
EM.run { EM.next_tick { EM.stop } }
assert t
end

def test_start_twice
i = 0
s = 0
tick_loop = EM.tick_loop { i += 1; :stop }
tick_loop.on_stop { s += 1; EM.stop }
EM.run { EM.next_tick { EM.stop } }
assert_equal 1, i
assert_equal 1, s
tick_loop.start
EM.run { EM.next_tick { EM.stop } }
assert_equal 2, i
assert_equal 1, s # stop callbacks are only called once
end

def test_stop
i, s = 0, 0
tick_loop = EM.tick_loop { i += 1 }
tick_loop.on_stop { s += 1 }
EM.run { EM.next_tick { tick_loop.stop; EM.next_tick { EM.stop } } }
assert tick_loop.stopped?
assert_equal 1, i
assert_equal 1, s
end

def test_immediate_stops
s = 0
tick_loop = EM::TickLoop.new { }
tick_loop.on_stop { s += 1 }
tick_loop.on_stop { s += 1 }
assert_equal 2, s
end

def test_stopped
tick_loop = EM::TickLoop.new { }
assert tick_loop.stopped?
tick_loop.start
assert !tick_loop.stopped?
end

end

0 comments on commit 30ad75d

Please sign in to comment.