1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
module Timer
# timer event, something to do and when/how often to do it
class Action
# when this action is due next (updated by tick())
attr_accessor :in
# is this action blocked? if so it won't be run
attr_accessor :blocked
# period:: how often (seconds) to run the action
# 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
@period = period
@in = period
@func = func
@data = data
@once = once
end
# run the action by calling its proc
def run
@in += @period
if(@data)
@func.call(@data)
else
@func.call
end
return @once
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 tick itself, but this
# blocks so you gotta do it in a thread (remember ruby's threads block on
# syscalls so that can suck).
class Timer
def initialize
@timers = Array.new
@handle = 0
@lasttime = 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)
@handle += 1
@timers[@handle] = Action.new(period, data, &func)
return @handle
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 which will be run just once, after +period+
def add_once(period, data=nil, &func)
@handle += 1
@timers[@handle] = Action.new(period, data, true, &func)
return @handle
end
# remove action with handle +handle+ from the timer
def remove(handle)
@timers.delete_at(handle)
end
# block action with handle +handle+
def block(handle)
@timers[handle].blocked = true
end
# unblock action with handle +handle+
def unblock(handle)
@timers[handle].blocked = false
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
if(@lasttime != 0)
diff = (Time.now - @lasttime).to_f
@lasttime = Time.now
@timers.compact.each { |timer|
timer.in = timer.in - diff
}
@timers.compact.each { |timer|
if (!timer.blocked)
if(timer.in <= 0)
if(timer.run)
# run once
@timers.delete(timer)
end
end
end
}
else
# don't do anything on the first tick
@lasttime = Time.now
end
end
# the timer will tick() itself. this blocks, so run it in a thread, and
# watch out for blocking syscalls
def run(granularity=0.1)
while(true)
sleep(granularity)
tick
end
end
end
end
|