summaryrefslogtreecommitdiff
path: root/indra/newview/scripts/lua/require/timers.lua
blob: ab1615ffbf6c4e13b243d6f3964c6ca9515caad4 (plain)
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
-- Access to the viewer's time-delay facilities

local leap = require 'leap'
local util = require 'util'

local timers = {}

local function dbg(...) end
-- local dbg = require 'printf'

timers.Timer = {}

-- delay:    time in seconds until callback
-- callback: 'wait', or function to call when timer fires (self:tick if nil)
-- iterate:  if non-nil, call callback repeatedly until it returns non-nil
--           (ignored if 'wait')
function timers.Timer:new(delay, callback, iterate)
    local obj = setmetatable({}, self)
    self.__index = self

    if callback == 'wait' then
        dbg('scheduleAfter(%d):', delay)
        sequence = leap.generate('Timers', {op='scheduleAfter', after=delay})
        -- ignore the immediate return
        dbg('scheduleAfter(%d) -> %s', delay,
        sequence.next())
        -- this call is where we wait for real
        dbg('next():')
        dbg('next() -> %s',
        sequence.next())
        sequence.done()
        return
    end

    callback = callback or function() obj:tick() end

    local calls = 0
    if iterate then
        -- With iterative timers, beware of running a timer callback which
        -- performs async actions lasting longer than the timer interval. The
        -- lengthy callback suspends, allowing leap to retrieve the next
        -- event, which is a timer tick. leap calls a new instance of the
        -- callback, even though the previous callback call is still
        -- suspended... etc. 'in_callback' defends against that recursive
        -- case. Rather than re-enter the suspended callback, drop the
        -- too-soon timer event. (We could count the too-soon timer events and
        -- iterate calling the callback, but it's a bathtub problem: the
        -- callback could end up getting farther and farther behind.)
        local in_callback = false
        obj.id = leap.eventstream(
            'Timers',
            {op='scheduleEvery', every=delay},
            function (event)
                local reqid = event.reqid
                calls += 1
                if calls == 1 then
                    dbg('timer(%s) first callback', reqid)
                    -- discard the first (immediate) response: don't call callback
                    return nil
                else
                    if in_callback then
                        dbg('dropping timer(%s) callback %d', reqid, calls)
                    else
                        dbg('timer(%s) callback %d', reqid, calls)
                        in_callback = true
                        local ret = callback(event)
                        in_callback = false
                        return ret
                    end
                end
            end
        ).reqid
    else                        -- (not iterate)
        obj.id = leap.eventstream(
            'Timers',
            {op='scheduleAfter', after=delay},
            function (event)
                calls += 1
                -- Arrange to return nil the first time, true the second. This
                -- callback is called immediately with the response to
                -- 'scheduleAfter', and if we immediately returned true, we'd
                -- be done, and the subsequent timer event would be discarded.
                if calls == 1 then
                    -- Caller doesn't expect an immediate callback.
                    return nil
                else
                    callback(event)
                    -- Since caller doesn't want to iterate, the value
                    -- returned by the callback is irrelevant: just stop after
                    -- this one and only call.
                    return true
                end
            end
        ).reqid
    end

    return obj
end

util.classctor(timers.Timer)

function timers.Timer:tick()
    error('Pass a callback to Timer:new(), or override Timer:tick()')
end

function timers.Timer:cancel()
    local ok = leap.request('Timers', {op='cancel', id=self.id}).ok
    leap.cancelreq(self.id)
    return ok
end

function timers.Timer:isRunning()
    return leap.request('Timers', {op='isRunning', id=self.id}).running
end

-- returns (true, seconds left) for a live timer, else (false, 0)
function timers.Timer:timeUntilCall()
    local result = leap.request('Timers', {op='timeUntilCall', id=self.id})
    return result.ok, result.remaining
end

return timers