3 # timer event, something to do and when/how often to do it
6 # when this action is due next (updated by tick())
9 # is this action blocked? if so it won't be run
10 attr_accessor :blocked
12 # period:: how often (seconds) to run the action
13 # data:: optional data to pass to the proc
14 # once:: optional, if true, this action will be run once then removed
15 # func:: associate a block to be called to perform the action
18 def initialize(period, data=nil, once=false, &func)
29 diff = Time.new - @last_tick
35 "#<#{self.class}:#{@period}s:#{@once ? 'once' : 'repeat'}>"
42 # run the action by calling its proc
45 # really short duration timers can overrun and leave @in negative,
46 # for these we set @in to @period
47 @in = @period if @in <= 0
55 error "Timer action #{self.inspect} with function #{@func.inspect} failed!"
56 error e.pretty_inspect
57 # TODO maybe we want to block this Action?
62 # reschedule the Action to change its period
63 def reschedule(new_period)
69 # timer handler, manage multiple Action objects, calling them when required.
70 # The timer must be ticked by whatever controls it, i.e. regular calls to
71 # tick() at whatever granularity suits your application's needs.
73 # Alternatively you can call run(), and the timer will spawn a thread and
74 # tick itself, intelligently shutting down the thread if there are no
77 def initialize(granularity = 0.1)
78 @granularity = granularity
82 @should_be_running = false
87 # period:: how often (seconds) to run the action
88 # data:: optional data to pass to the action's proc
89 # func:: associate a block with add() to perform the action
91 # add an action to the timer
92 def add(period, data=nil, &func)
93 debug "adding timer, period #{period}"
95 @timers[@handle] = Action.new(period, data, &func)
100 # period:: how long (seconds) until the action is run
101 # data:: optional data to pass to the action's proc
102 # func:: associate a block with add() to perform the action
104 # add an action to the timer which will be run just once, after +period+
105 def add_once(period, data=nil, &func)
106 debug "adding one-off timer, period #{period}"
108 @timers[@handle] = Action.new(period, data, true, &func)
113 # remove action with handle +handle+ from the timer
115 @timers.delete(handle)
118 # block action with handle +handle+
120 raise "no such timer #{handle}" unless @timers[handle]
121 @timers[handle].blocked = true
124 # unblock action with handle +handle+
126 raise "no such timer #{handle}" unless @timers[handle]
127 @timers[handle].blocked = false
130 # reschedule action with handle +handle+ to change its period
131 def reschedule(handle, period)
132 raise "no such timer #{handle}" unless @timers[handle]
133 @timers[handle].reschedule(period)
137 # you can call this when you know you're idle, or you can split off a
138 # thread and call the run() method to do it for you.
141 # don't do anything on the first tick
145 @next_action_time = 0
146 diff = (Time.now - @lasttime).to_f
148 @timers.each { |key,timer|
150 next if timer.blocked
157 if @next_action_time == 0 || timer.in < @next_action_time
158 @next_action_time = timer.in
161 #debug "ticked. now #{@timers.length} timers remain"
162 #debug "next timer due at #{@next_action_time}"
165 # for backwards compat - this is a bit primitive
166 def run(granularity=0.1)
174 @thread && @thread.alive?
177 # return the number of seconds until the next action is due, or 0 if
178 # none are outstanding - will only be accurate immediately after a
184 # start the timer, it spawns a thread to tick the timer, intelligently
185 # shutting down if no events remain and starting again when needed.
188 @should_be_running = true
189 start_thread unless @timers.empty?
192 # stop the timer from ticking
194 @should_be_running = false
204 elsif @should_be_running
210 return unless running?
216 @thread = Thread.new do
219 exit if @timers.empty?
220 sleep(@next_action_time)