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
|
local async = require('vim.async._core')
local runtime = require('vim.async._runtime')
--- An event can be used to notify multiple tasks that some event has
--- happened. An Event object manages an internal flag that can be set to true
--- with the `set()` method and reset to `false` with the `clear()` method.
--- The `wait()` method blocks until the flag is set to `true`. The flag is
--- set to `false` initially.
--- @class vim.async.Event
--- @field private _is_set boolean
--- @field private _waiters (function|false)[]
local Event = {}
Event.__index = Event
--- @param waiters (function|false)[]
--- @return boolean
local function has_waiters(waiters)
for _, waiter in ipairs(waiters) do
if waiter then
return true
end
end
return false
end
--- Set the event.
---
--- All tasks waiting for event to be set will be awakened on a later event-loop
--- turn.
---
--- If `max_woken` is provided, only up to `max_woken` waiters will be woken.
--- If waiters are woken this way, the event is reset because the signal is
--- reserved for those waiters.
--- @param max_woken? integer
function Event:set(max_woken)
if self._is_set then
return
end
local limited = max_woken ~= nil
if not has_waiters(self._waiters) then
self._is_set = true
return
end
self._is_set = true
if limited then
-- The signal is reserved for existing waiters and will be assigned on the
-- scheduled turn. New waiters must not consume it first.
self._is_set = false
end
runtime.schedule(function()
local waiters = self._waiters
local waiters_to_notify = {} --- @type function[]
local limit = max_woken or math.huge
while #waiters > 0 and #waiters_to_notify < limit do
local waiter = table.remove(waiters, 1)
if waiter then
waiters_to_notify[#waiters_to_notify + 1] = waiter
end
end
if limited and #waiters_to_notify == 0 and not has_waiters(waiters) then
self._is_set = true
end
for _, waiter in ipairs(waiters_to_notify) do
waiter()
end
end)
end
--- Wait until the event is set.
---
--- If the event is set, return immediately. Otherwise block until another
--- task calls set().
--- @async
function Event:wait()
async.await(function(callback)
if self._is_set then
callback()
else
table.insert(self._waiters, callback)
return {
close = function(_, on_close)
-- set() compacts the waiter list, so cancellation cannot rely on the
-- original insertion index still pointing at this callback.
for i, waiter in ipairs(self._waiters) do
if waiter == callback then
self._waiters[i] = false
break
end
end
if on_close then
on_close()
end
end,
}
end
end)
end
--- Clear (unset) the event.
---
--- Tasks awaiting on wait() will now block until the set() method is called
--- again.
function Event:clear()
self._is_set = false
end
--- Create a new event.
---
--- An event can signal to multiple listeners to resume execution.
--- The event can be set from a non-async context.
--- @return vim.async.Event
return function()
return setmetatable({
_waiters = {},
_is_set = false,
}, Event)
end
|