-
Notifications
You must be signed in to change notification settings - Fork 1
/
remote.lua
160 lines (137 loc) · 2.94 KB
/
remote.lua
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
local remote = {}
---@type function, function
local encode, decode
---@param coder table
function remote.set_coder(coder)
encode, decode = coder.encode, coder.decode
end
---@param f function
---@param ... any?
---@return thread
local function wrap(f, ...)
local c = coroutine.create(function(...)
assert(xpcall(f, debug.traceback, ...))
end)
assert(coroutine.resume(c, ...))
return c
end
---@param f function
---@return function
function remote.wrap(f)
return function(...)
return wrap(f, ...)
end
end
local tasks = {}
local event_id = 0
remote.timeout = 10
local timeouts = {}
---@param peer table
---@param id number?
---@param name string?
---@param ... any?
local function send(peer, id, name, ...)
peer.epeer:send(encode({
id = id,
name = name,
n = select("#", ...),
...
}))
end
---@param peer table
---@param name string
---@param ... any?
---@return any?...
local function run(peer, name, ...)
if name:sub(1, 1) == "_" then
send(peer, nil, name:sub(2), ...)
return
end
local c = coroutine.running()
if not c then
error("attempt to yield from outside a coroutine")
end
event_id = event_id + 1
local id = event_id
send(peer, id, name, ...)
local trace = debug.traceback(c)
timeouts[id] = os.time() + remote.timeout
tasks[id] = function(...)
tasks[id] = nil
timeouts[id] = nil
local status, err = coroutine.resume(c, ...)
if not status then
error(err .. "\n" .. trace)
end
end
return coroutine.yield()
end
local peer_mt = {}
---@param t table
---@param name string
---@return any
function peer_mt.__index(t, name)
return function(...)
return run(t, name, ...)
end
end
---@param a table
---@param b table
---@return boolean
function peer_mt.__eq(a, b)
return a.id == b.id
end
---@param epeer table|userdata
---@return table
function remote.peer(epeer)
return setmetatable({
epeer = epeer,
id = tonumber(tostring(epeer):match("^.+:(%d+)$")),
}, peer_mt)
end
---@param peer table
---@param e table
---@param handlers table
---@return any?...
local function _handle(peer, e, handlers)
local handler = handlers[e.name]
return handler and handler(peer, unpack(e, 1, e.n))
end
---@param peer table
---@param e table
---@param handlers table
local function handle(peer, e, handlers)
if not e.id then
_handle(peer, e, handlers)
return
end
local ok, err = xpcall(send, debug.traceback, peer, e.id, nil, _handle(peer, e, handlers))
if ok then
return
end
error(e.name .. "\n" .. err)
end
handle = remote.wrap(handle)
function remote.update()
local time = os.time()
for id, t in pairs(timeouts) do
if t <= time then
tasks[id](nil, "timeout")
end
end
end
---@param data any
---@param epeer table|userdata
---@param handlers table
function remote.receive(data, epeer, handlers)
local ok, e = pcall(decode, data)
if not ok or type(e) ~= "table" then
return
end
if e.name then
handle(remote.peer(epeer), e, handlers)
elseif e.id and tasks[e.id] then
tasks[e.id](unpack(e, 1, e.n))
end
end
return remote