46a38fea0be077e316d0e4cc6686626a655d2a06
[god.git] / lib / god / timer.rb
blob46a38fea0be077e316d0e4cc6686626a655d2a06
1 module God
2   
3   class TimerEvent
4     attr_accessor :condition, :at, :phase
5     
6     # Instantiate a new TimerEvent that will be triggered after the specified delay
7     #   +condition+ is the Condition
8     #   +delay+ is the number of seconds from now at which to trigger
9     #
10     # Returns TimerEvent
11     def initialize(condition, delay)
12       self.condition = condition
13       self.phase = condition.watch.phase
14       
15       now = (Time.now.to_f * 4).round / 4.0
16       self.at = now + delay
17     end
18   end
19   
20   class Timer
21     INTERVAL = 0.25
22     
23     attr_reader :events, :pending_events, :conditions, :timer
24     
25     @@timer = nil
26     
27     # Get the singleton Timer
28     #
29     # Returns Timer
30     def self.get
31       @@timer ||= Timer.new
32     end
33     
34     # Reset the singleton Timer so the next call to Timer.get will
35     # create a new one
36     #
37     # Returns nothing
38     def self.reset
39       @@timer = nil
40     end
41     
42     # Instantiate a new Timer and start the scheduler loop to handle events
43     #
44     # Returns Timer
45     def initialize
46       @events = []
47       @pending_events = []
48       @conditions = []
49       @pending_mutex = Mutex.new
50       
51       @timer = Thread.new do
52         loop do
53           # applog(nil, :debug, "timer main loop, #{@events.size} events pending")
54           
55           begin
56             # pull in pending events
57             @pending_mutex.synchronize do
58               @pending_events.each { |e| @events << e }
59               @pending_events.clear
60             end
61             
62             @events.sort! { |x, y| x.at <=> y.at }
63             
64             # get the current time
65             t = Time.now.to_i
66             
67             # iterate over each event and trigger any that are due
68             triggered = []
69             
70             @events.each do |event|
71               if t >= event.at
72                 # trigger the event and mark it for removal
73                 self.trigger(event)
74                 triggered << event
75               else
76                 # events are ordered, so we can bail on first miss
77                 break
78               end
79             end
80             
81             # remove all triggered events
82             triggered.each do |event|
83               @conditions.delete(event.condition)
84               @events.delete(event)
85             end
86           rescue Exception => e
87             message = format("Unhandled exception (%s): %s\n%s",
88                              e.class, e.message, e.backtrace.join("\n"))
89             applog(nil, :fatal, message)
90           ensure
91             # sleep until next check
92             GC.start
93             sleep INTERVAL
94           end
95         end
96       end
97     end
98     
99     # Create and register a new TimerEvent
100     #   +condition+ is the Condition
101     #   +delay+ is the number of seconds to delay (default: interval defined in condition)
102     #
103     # Returns nothing
104     def schedule(condition, delay = condition.interval)
105       applog(nil, :debug, "timer schedule #{condition} in #{delay} seconds")
106       unless @conditions.include?(condition)
107         @pending_mutex.synchronize do
108           @pending_events << TimerEvent.new(condition, delay)
109         end
110         @conditions << condition
111       end
112     end
113     
114     # Remove any TimerEvents for the given condition
115     #   +condition+ is the Condition
116     #
117     # Returns nothing
118     def unschedule(condition)
119       applog(nil, :debug, "timer unschedule #{condition}")
120       @conditions.delete(condition)
121     end
122     
123     # Trigger the event's condition to be evaluated
124     #   +event+ is the TimerEvent to trigger
125     #
126     # Returns nothing
127     def trigger(event)
128       applog(nil, :debug, "timer trigger #{event}")
129       Hub.trigger(event.condition, event.phase)
130     end
131     
132     # Join the timer thread
133     #
134     # Returns nothing
135     def join
136       @timer.join
137     end
138   end
139