# timer event, something to do and when/how often to do it
class Action
-
+
# when this action is due next (updated by tick())
attr_reader :in
-
+
# is this action blocked? if so it won't be run
attr_accessor :blocked
# data:: optional data to pass to the proc
# once:: optional, if true, this action will be run once then removed
# func:: associate a block to be called to perform the action
- #
+ #
# create a new action
def initialize(period, data=nil, once=false, &func)
@blocked = false
@last_tick = Time.new
end
- def inspect
+ def inspect
"#<#{self.class}:#{@period}s:#{@once ? 'once' : 'repeat'}>"
end
# really short duration timers can overrun and leave @in negative,
# for these we set @in to @period
@in = @period if @in <= 0
- if(@data)
- @func.call(@data)
- else
- @func.call
+ begin
+ if(@data)
+ @func.call(@data)
+ else
+ @func.call
+ end
+ rescue => e
+ error "Timer action #{self.inspect} with function #{@func.inspect} failed with error #{e.inspect}"
+ error e.backtrace.join("\n")
+ # TODO maybe we want to block this Action?
end
return @once
end
+
+ # reschedule the Action to change its period
+ def reschedule(new_period)
+ @period = new_period
+ @in = new_period
+ end
end
-
+
# timer handler, manage multiple Action objects, calling them when required.
# The timer must be ticked by whatever controls it, i.e. regular calls to
# tick() at whatever granularity suits your application's needs.
- #
+ #
# Alternatively you can call run(), and the timer will spawn a thread and
# tick itself, intelligently shutting down the thread if there are no
# pending actions.
@thread = false
@next_action_time = 0
end
-
+
# period:: how often (seconds) to run the action
# data:: optional data to pass to the action's proc
# func:: associate a block with add() to perform the action
- #
+ #
# add an action to the timer
def add(period, data=nil, &func)
+ debug "adding timer, period #{period}"
@handle += 1
@timers[@handle] = Action.new(period, data, &func)
start_on_add
# period:: how long (seconds) until the action is run
# data:: optional data to pass to the action's proc
# func:: associate a block with add() to perform the action
- #
+ #
# add an action to the timer which will be run just once, after +period+
def add_once(period, data=nil, &func)
+ debug "adding one-off timer, period #{period}"
@handle += 1
@timers[@handle] = Action.new(period, data, true, &func)
start_on_add
def remove(handle)
@timers.delete(handle)
end
-
+
# block action with handle +handle+
def block(handle)
@timers[handle].blocked = true
@timers[handle].blocked = false
end
+ # reschedule action with handle +handle+ to change its period
+ def reschedule(handle, period)
+ @timers[handle].reschedule(period)
+ end
+
# you can call this when you know you're idle, or you can split off a
# thread and call the run() method to do it for you.
- def tick
+ def tick
if(@lasttime == 0)
# don't do anything on the first tick
@lasttime = Time.now
@next_action_time = timer.in
end
}
+ #debug "ticked. now #{@timers.length} timers remain"
+ #debug "next timer due at #{@next_action_time}"
end
# for backwards compat - this is a bit primitive
@should_be_running = false
stop_thread
end
-
+
private
-
+
def start_on_add
if running?
stop_thread
start_thread
end
end
-
+
def stop_thread
return unless running?
@thread.kill
end
-
+
def start_thread
return if running?
@thread = Thread.new do