X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=lib%2Frbot%2Ftimer.rb;h=8adb14d8d42f955480c22402ec48f478e1b4468f;hb=b1debac35f5e45545066da07027ddaaf6c9faca7;hp=8939b6722a9e1b250978ee46981c7418cf5cb68c;hpb=683fcb33ad03e3bd9a36692e5559d887d757f19c;p=user%2Fhenk%2Fcode%2Fruby%2Frbot.git diff --git a/lib/rbot/timer.rb b/lib/rbot/timer.rb index 8939b672..8adb14d8 100644 --- a/lib/rbot/timer.rb +++ b/lib/rbot/timer.rb @@ -12,10 +12,34 @@ require 'thread' require 'monitor' +# Timer handler, manage multiple Action objects, calling them when required. +# When the Timer is constructed, a new Thread is created to manage timed +# delays and run Actions. +# +# XXX: there is no way to stop the timer currently. I'm keeping it this way +# to weed out old Timer implementation legacy in rbot code. -jsn. class Timer + + # class representing individual timed action class Action + + # Time when the Action should be called next attr_accessor :next + # Options are: + # start:: Time when the Action should be run for the first time. + # Repeatable Actions will be repeated after that, see + # :period. One-time Actions will not (obviously) + # Default: Time.now + :period + # period:: How often repeatable Action should be run, in seconds. + # Default: 1 + # blocked:: if true, Action starts as blocked (i.e. will stay dormant + # until unblocked) + # args:: Arguments to pass to the Action callback. Default: [] + # repeat:: Should the Action be called repeatedly? Default: false + # code:: You can specify the Action body using &block, *or* using + # this option. + def initialize(options = {}, &block) opts = { :period => 1, @@ -30,6 +54,8 @@ class Timer debug("added #{self}") end + # Provides for on-the-fly reconfiguration of the Actions + # Accept the same arguments as the constructor def configure(opts = {}, &block) @period = opts[:period] if opts.include? :period @blocked = opts[:blocked] if opts.include? :blocked @@ -53,14 +79,17 @@ class Timer end end + # modify the Action period def reschedule(period, &block) self.configure(:period => period, &block) end + # blocks an Action, so it won't be run def block @blocked = true end + # unblocks a blocked Action def unblock @blocked = false end @@ -69,6 +98,8 @@ class Timer @blocked end + # calls the Action callback, resets .next to the Time of the next call, + # if the Action is repeatable. def run(now = Time.now) raise 'inappropriate time to run()' unless self.next && self.next <= now self.next = nil @@ -77,6 +108,7 @@ class Timer rescue Exception => e error "Timer action #{self.inspect}: block #{@block.inspect} failed!" error e.pretty_inspect + debug e.backtrace.join("\n") end if @repeat && @period > 0 @@ -87,6 +119,7 @@ class Timer end end + # creates a new Timer and starts it. def initialize self.extend(MonitorMixin) @tick = self.new_cond @@ -96,6 +129,12 @@ class Timer self.start end + # Creates and installs a new Action, repeatable by default. + # _period_:: Action period + # _opts_:: options for Action#new, see there + # _block_:: Action callback code + # + # Returns the id of the created Action def add(period, opts = {}, &block) a = Action.new({:repeat => true, :period => period}.merge(opts), &block) self.synchronize do @@ -105,15 +144,25 @@ class Timer return a.object_id end + # Creates and installs a new Action, one-time by default. + # _period_:: Action delay + # _opts_:: options for Action#new, see there + # _block_:: Action callback code + # + # Returns the id of the created Action def add_once(period, opts = {}, &block) self.add(period, {:repeat => false}.merge(opts), &block) end + # blocks an existing Action + # _aid_:: Action id, obtained previously from add() or add_once() def block(aid) debug "blocking #{aid}" self.synchronize { self[aid].block } end + # unblocks an existing blocked Action + # _aid_:: Action id, obtained previously from add() or add_once() def unblock(aid) debug "unblocking #{aid}" self.synchronize do @@ -122,6 +171,8 @@ class Timer end end + # removes an existing blocked Action + # _aid_:: Action id, obtained previously from add() or add_once() def remove(aid) self.synchronize do @actions.delete(aid) # or raise "nonexistent action #{aid}" @@ -130,6 +181,10 @@ class Timer alias :delete :remove + # Provides for on-the-fly reconfiguration of Actions + # _aid_:: Action id, obtained previously from add() or add_once() + # _opts_:: see Action#new + # _block_:: (optional) new Action callback code def configure(aid, opts = {}, &block) self.synchronize do self[aid].configure(opts, &block) @@ -137,22 +192,39 @@ class Timer end end + # changes Action period + # _aid_:: Action id + # _period_:: new period + # _block_:: (optional) new Action callback code def reschedule(aid, period, &block) self.configure(aid, :period => period, &block) end - protected - def start - raise 'double-started timer' if @thread + raise 'already started' if @thread + @stopping = false + debug "starting timer #{self}" @thread = Thread.new do loop do tmout = self.run_actions + break if tmout and tmout < 0 self.synchronize { @tick.wait(tmout) } end end end + def stop + raise 'already stopped' unless @thread + debug "stopping timer #{self}..." + @stopping = true + self.synchronize { @tick.signal } + @thread.join(60) or @thread.kill + debug "timer #{self} stopped" + @thread = nil + end + + protected + def [](aid) aid ||= @current raise "no current action" unless aid @@ -163,6 +235,7 @@ class Timer def run_actions(now = Time.now) nxt = nil @actions.keys.each do |k| + return -1 if @stopping a = @actions[k] next if (!a) or a.blocked? @@ -173,7 +246,7 @@ class Timer ensure @current = nil end - + unless v @actions.delete k next