e03362583a333ff0a9a74a850dfec728eee6a7e1
[god.git] / lib / god / timer.rb
blobe03362583a333ff0a9a74a850dfec728eee6a7e1
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, :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       @conditions = []
48       @mutex = Monitor.new
49       
50       @timer = Thread.new do
51         loop do
52           begin
53             # get the current time
54             t = Time.now.to_i
55             
56             # iterate over each event and trigger any that are due
57             @mutex.synchronize do
58               triggered = []
59               
60               @events.each do |event|
61                 if t >= event.at
62                   # trigger the event and mark it for removal
63                   self.trigger(event)
64                   triggered << event
65                 else
66                   # events are ordered, so we can bail on first miss
67                   break
68                 end
69               end
70               
71               # remove all triggered events
72               triggered.each do |event|
73                 @conditions.delete(event.condition)
74                 @events.delete(event)
75               end
76             end
77           rescue Exception => e
78             message = format("Unhandled exception (%s): %s\n%s",
79                              e.class, e.message, e.backtrace.join("\n"))
80             applog(nil, :fatal, message)
81           ensure
82             # sleep until next check
83             sleep INTERVAL
84           end
85         end
86       end
87     end
88     
89     # Create and register a new TimerEvent
90     #   +condition+ is the Condition
91     #   +delay+ is the number of seconds to delay (default: interval defined in condition)
92     #
93     # Returns nothing
94     def schedule(condition, delay = condition.interval)
95       @mutex.synchronize do
96         unless @conditions.include?(condition)
97           applog(nil, :debug, "timer schedule #{condition} in #{delay}")
98           @events << TimerEvent.new(condition, delay)
99           @conditions << condition
100           @events.sort! { |x, y| x.at <=> y.at }
101         end
102       end
103     end
104     
105     # Remove any TimerEvents for the given condition
106     #   +condition+ is the Condition
107     #
108     # Returns nothing
109     def unschedule(condition)
110       @mutex.synchronize do
111         @conditions.delete(condition)
112         @events.reject! { |x| x.condition == condition }
113       end
114     end
115     
116     # Trigger the event's condition to be evaluated
117     #   +event+ is the TimerEvent to trigger
118     #
119     # Returns nothing
120     def trigger(event)
121       applog(nil, :debug, "timer trigger #{event}")
122       Hub.trigger(event.condition, event.phase)
123     end
124     
125     # Join the timer thread
126     #
127     # Returns nothing
128     def join
129       @timer.join
130     end
131   end
132