roblox-opencode 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -122
- package/commands/setup-game.md +108 -108
- package/commands/sync-check.md +53 -53
- package/core/roblox-core.md +93 -93
- package/dist/server.js +189 -167
- package/package.json +35 -35
- package/skills/roblox-analytics/SKILL.md +277 -277
- package/skills/roblox-analytics/references/event-batcher.luau +75 -75
- package/skills/roblox-animation-vfx/SKILL.md +1325 -1325
- package/skills/roblox-architecture/SKILL.md +877 -863
- package/skills/roblox-architecture/references/combat-systems.md +1381 -1381
- package/skills/roblox-code-review/SKILL.md +686 -686
- package/skills/roblox-data/SKILL.md +889 -889
- package/skills/roblox-data/references/inventory-systems.md +1729 -1729
- package/skills/roblox-debug/SKILL.md +98 -98
- package/skills/roblox-gui/SKILL.md +1103 -1103
- package/skills/roblox-gui-fusion/SKILL.md +150 -150
- package/skills/roblox-gui-fusion/references/inventory.luau +427 -427
- package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -579
- package/skills/roblox-gui-fusion/references/shop.luau +411 -411
- package/skills/roblox-luau-mastery/SKILL.md +1618 -1519
- package/skills/roblox-monetization/SKILL.md +1084 -1084
- package/skills/roblox-monetization/references/process-receipt.luau +131 -131
- package/skills/roblox-networking/SKILL.md +669 -669
- package/skills/roblox-networking/references/remote-validator.luau +193 -193
- package/skills/roblox-publish-checklist/SKILL.md +127 -127
- package/skills/roblox-runtime/SKILL.md +753 -753
- package/skills/roblox-sharp-edges/SKILL.md +294 -294
- package/skills/roblox-sync/SKILL.md +126 -126
- package/skills/roblox-testing/SKILL.md +943 -943
- package/skills/roblox-tooling/SKILL.md +149 -149
- package/vendor/LICENSES/ProfileStore-LICENSE +201 -201
- package/vendor/LICENSES/RbxUtil-LICENSE +7 -7
- package/vendor/LICENSES/promise-LICENSE +20 -20
- package/vendor/LICENSES/t-LICENSE +21 -21
- package/vendor/LICENSES/testez-LICENSE +200 -200
- package/vendor/README.md +83 -83
- package/vendor/fusion/Animation/ExternalTime.luau +83 -83
- package/vendor/fusion/Animation/Spring.luau +321 -321
- package/vendor/fusion/Animation/Stopwatch.luau +127 -127
- package/vendor/fusion/Animation/Tween.luau +187 -187
- package/vendor/fusion/Animation/getTweenDuration.luau +27 -27
- package/vendor/fusion/Animation/getTweenRatio.luau +47 -47
- package/vendor/fusion/Animation/lerpType.luau +163 -163
- package/vendor/fusion/Animation/packType.luau +99 -99
- package/vendor/fusion/Animation/springCoefficients.luau +80 -80
- package/vendor/fusion/Animation/unpackType.luau +102 -102
- package/vendor/fusion/Colour/Oklab.luau +70 -70
- package/vendor/fusion/Colour/sRGB.luau +54 -54
- package/vendor/fusion/External.luau +167 -167
- package/vendor/fusion/ExternalDebug.luau +69 -69
- package/vendor/fusion/Graph/Observer.luau +113 -113
- package/vendor/fusion/Graph/castToGraph.luau +28 -28
- package/vendor/fusion/Graph/change.luau +80 -80
- package/vendor/fusion/Graph/depend.luau +32 -32
- package/vendor/fusion/Graph/evaluate.luau +55 -55
- package/vendor/fusion/Instances/Attribute.luau +57 -57
- package/vendor/fusion/Instances/AttributeChange.luau +46 -46
- package/vendor/fusion/Instances/AttributeOut.luau +63 -63
- package/vendor/fusion/Instances/Child.luau +21 -21
- package/vendor/fusion/Instances/Children.luau +147 -147
- package/vendor/fusion/Instances/Hydrate.luau +32 -32
- package/vendor/fusion/Instances/New.luau +52 -52
- package/vendor/fusion/Instances/OnChange.luau +49 -49
- package/vendor/fusion/Instances/OnEvent.luau +53 -53
- package/vendor/fusion/Instances/Out.luau +69 -69
- package/vendor/fusion/Instances/applyInstanceProps.luau +148 -148
- package/vendor/fusion/Instances/defaultProps.luau +194 -194
- package/vendor/fusion/LICENSE +21 -21
- package/vendor/fusion/Logging/formatError.luau +48 -48
- package/vendor/fusion/Logging/messages.luau +51 -51
- package/vendor/fusion/Logging/parseError.luau +24 -24
- package/vendor/fusion/Memory/checkLifetime.luau +133 -133
- package/vendor/fusion/Memory/deriveScope.luau +23 -23
- package/vendor/fusion/Memory/deriveScopeImpl.luau +44 -44
- package/vendor/fusion/Memory/doCleanup.luau +78 -78
- package/vendor/fusion/Memory/innerScope.luau +33 -33
- package/vendor/fusion/Memory/legacyCleanup.luau +17 -17
- package/vendor/fusion/Memory/needsDestruction.luau +16 -16
- package/vendor/fusion/Memory/poisonScope.luau +33 -33
- package/vendor/fusion/Memory/scopePool.luau +54 -54
- package/vendor/fusion/Memory/scoped.luau +26 -26
- package/vendor/fusion/Memory/whichLivesLonger.luau +74 -74
- package/vendor/fusion/RobloxExternal.luau +97 -97
- package/vendor/fusion/State/Computed.luau +138 -138
- package/vendor/fusion/State/For/Disassembly.luau +210 -210
- package/vendor/fusion/State/For/ForTypes.luau +30 -30
- package/vendor/fusion/State/For/init.luau +109 -109
- package/vendor/fusion/State/ForKeys.luau +93 -93
- package/vendor/fusion/State/ForPairs.luau +96 -96
- package/vendor/fusion/State/ForValues.luau +93 -93
- package/vendor/fusion/State/Value.luau +87 -87
- package/vendor/fusion/State/castToState.luau +25 -25
- package/vendor/fusion/State/peek.luau +30 -30
- package/vendor/fusion/Types.luau +314 -314
- package/vendor/fusion/Utility/Contextual.luau +90 -90
- package/vendor/fusion/Utility/Safe.luau +22 -22
- package/vendor/fusion/Utility/isSimilar.luau +29 -29
- package/vendor/fusion/Utility/merge.luau +35 -35
- package/vendor/fusion/Utility/nameOf.luau +34 -34
- package/vendor/fusion/Utility/never.luau +13 -13
- package/vendor/fusion/Utility/nicknames.luau +10 -10
- package/vendor/fusion/Utility/xtypeof.luau +26 -26
- package/vendor/fusion/init.luau +82 -82
- package/vendor/profilestore/init.luau +2242 -2242
- package/vendor/promise/init.luau +1982 -1982
- package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -25
- package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -228
- package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -269
- package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -223
- package/vendor/rbxutil/buffer-util/Types.luau +60 -60
- package/vendor/rbxutil/buffer-util/index.d.ts +153 -153
- package/vendor/rbxutil/buffer-util/init.luau +41 -41
- package/vendor/rbxutil/buffer-util/package.json +16 -16
- package/vendor/rbxutil/buffer-util/wally.toml +9 -9
- package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -232
- package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -156
- package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -109
- package/vendor/rbxutil/comm/Client/init.luau +135 -135
- package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -295
- package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -211
- package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -211
- package/vendor/rbxutil/comm/Server/init.luau +140 -140
- package/vendor/rbxutil/comm/Types.luau +18 -18
- package/vendor/rbxutil/comm/Util.luau +27 -27
- package/vendor/rbxutil/comm/init.luau +35 -35
- package/vendor/rbxutil/comm/wally.toml +13 -13
- package/vendor/rbxutil/component/init.luau +759 -759
- package/vendor/rbxutil/component/init.test.luau +311 -311
- package/vendor/rbxutil/component/wally.toml +14 -14
- package/vendor/rbxutil/concur/init.luau +542 -542
- package/vendor/rbxutil/concur/init.test.luau +364 -364
- package/vendor/rbxutil/concur/wally.toml +8 -8
- package/vendor/rbxutil/enum-list/init.luau +101 -101
- package/vendor/rbxutil/enum-list/init.test.luau +91 -91
- package/vendor/rbxutil/enum-list/wally.toml +8 -8
- package/vendor/rbxutil/find/index.d.ts +20 -20
- package/vendor/rbxutil/find/init.luau +44 -44
- package/vendor/rbxutil/find/package.json +17 -17
- package/vendor/rbxutil/find/wally.toml +8 -8
- package/vendor/rbxutil/input/Gamepad.luau +559 -559
- package/vendor/rbxutil/input/Keyboard.luau +124 -124
- package/vendor/rbxutil/input/Mouse.luau +278 -278
- package/vendor/rbxutil/input/PreferredInput.luau +91 -91
- package/vendor/rbxutil/input/Touch.luau +120 -120
- package/vendor/rbxutil/input/init.luau +33 -33
- package/vendor/rbxutil/input/wally.toml +12 -12
- package/vendor/rbxutil/loader/index.d.ts +15 -15
- package/vendor/rbxutil/loader/init.luau +137 -137
- package/vendor/rbxutil/loader/wally.toml +8 -8
- package/vendor/rbxutil/log/index.d.ts +38 -38
- package/vendor/rbxutil/log/init.luau +746 -746
- package/vendor/rbxutil/log/wally.toml +8 -8
- package/vendor/rbxutil/net/init.luau +190 -190
- package/vendor/rbxutil/net/wally.toml +8 -8
- package/vendor/rbxutil/option/index.d.ts +44 -44
- package/vendor/rbxutil/option/init.luau +489 -489
- package/vendor/rbxutil/option/init.test.luau +342 -342
- package/vendor/rbxutil/option/wally.toml +8 -8
- package/vendor/rbxutil/pid/index.d.ts +53 -53
- package/vendor/rbxutil/pid/init.luau +195 -195
- package/vendor/rbxutil/pid/package.json +16 -16
- package/vendor/rbxutil/pid/wally.toml +9 -9
- package/vendor/rbxutil/quaternion/index.d.ts +117 -117
- package/vendor/rbxutil/quaternion/init.luau +570 -570
- package/vendor/rbxutil/quaternion/package.json +16 -16
- package/vendor/rbxutil/quaternion/wally.toml +9 -9
- package/vendor/rbxutil/query/index.d.ts +43 -43
- package/vendor/rbxutil/query/init.luau +117 -117
- package/vendor/rbxutil/query/package.json +18 -18
- package/vendor/rbxutil/query/wally.toml +9 -9
- package/vendor/rbxutil/sequent/index.d.ts +28 -28
- package/vendor/rbxutil/sequent/init.luau +340 -340
- package/vendor/rbxutil/sequent/package.json +16 -16
- package/vendor/rbxutil/sequent/wally.toml +9 -9
- package/vendor/rbxutil/ser/init.luau +175 -175
- package/vendor/rbxutil/ser/init.test.luau +50 -50
- package/vendor/rbxutil/ser/wally.toml +11 -11
- package/vendor/rbxutil/shake/index.d.ts +36 -36
- package/vendor/rbxutil/shake/init.luau +532 -532
- package/vendor/rbxutil/shake/init.test.luau +267 -267
- package/vendor/rbxutil/shake/package.json +16 -16
- package/vendor/rbxutil/shake/wally.toml +9 -9
- package/vendor/rbxutil/signal/index.d.ts +100 -100
- package/vendor/rbxutil/signal/init.luau +432 -432
- package/vendor/rbxutil/signal/init.test.luau +190 -190
- package/vendor/rbxutil/signal/package.json +17 -17
- package/vendor/rbxutil/signal/wally.toml +9 -9
- package/vendor/rbxutil/silo/TableWatcher.luau +65 -65
- package/vendor/rbxutil/silo/Util.luau +55 -55
- package/vendor/rbxutil/silo/init.luau +338 -338
- package/vendor/rbxutil/silo/init.test.luau +215 -215
- package/vendor/rbxutil/silo/wally.toml +8 -8
- package/vendor/rbxutil/spring/index.d.ts +40 -40
- package/vendor/rbxutil/spring/init.luau +97 -97
- package/vendor/rbxutil/spring/package.json +17 -17
- package/vendor/rbxutil/spring/wally.toml +8 -8
- package/vendor/rbxutil/stream/index.d.ts +88 -88
- package/vendor/rbxutil/stream/init.luau +597 -597
- package/vendor/rbxutil/stream/package.json +18 -18
- package/vendor/rbxutil/stream/wally.toml +9 -9
- package/vendor/rbxutil/streamable/Streamable.luau +202 -202
- package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -80
- package/vendor/rbxutil/streamable/init.luau +8 -8
- package/vendor/rbxutil/streamable/wally.toml +12 -12
- package/vendor/rbxutil/symbol/init.luau +56 -56
- package/vendor/rbxutil/symbol/init.test.luau +37 -37
- package/vendor/rbxutil/symbol/wally.toml +8 -8
- package/vendor/rbxutil/table-util/init.luau +938 -938
- package/vendor/rbxutil/table-util/init.test.luau +439 -439
- package/vendor/rbxutil/task-queue/index.d.ts +27 -27
- package/vendor/rbxutil/task-queue/init.luau +97 -97
- package/vendor/rbxutil/task-queue/wally.toml +8 -8
- package/vendor/rbxutil/timer/index.d.ts +81 -81
- package/vendor/rbxutil/timer/init.luau +249 -249
- package/vendor/rbxutil/timer/init.test.luau +73 -73
- package/vendor/rbxutil/timer/wally.toml +11 -11
- package/vendor/rbxutil/tree/index.d.ts +15 -15
- package/vendor/rbxutil/tree/init.luau +137 -137
- package/vendor/rbxutil/tree/wally.toml +8 -8
- package/vendor/rbxutil/trove/index.d.ts +46 -46
- package/vendor/rbxutil/trove/init.luau +787 -787
- package/vendor/rbxutil/trove/init.test.luau +203 -203
- package/vendor/rbxutil/trove/wally.toml +8 -8
- package/vendor/rbxutil/typed-remote/init.luau +196 -196
- package/vendor/rbxutil/typed-remote/wally.toml +8 -8
- package/vendor/rbxutil/wait-for/index.d.ts +17 -17
- package/vendor/rbxutil/wait-for/init.luau +257 -257
- package/vendor/rbxutil/wait-for/init.test.luau +182 -182
- package/vendor/rbxutil/wait-for/wally.toml +11 -11
- package/vendor/t/t.lua +1350 -1350
- package/vendor/testez/Context.lua +26 -26
- package/vendor/testez/Expectation.lua +311 -311
- package/vendor/testez/ExpectationContext.lua +38 -38
- package/vendor/testez/LifecycleHooks.lua +89 -89
- package/vendor/testez/Reporters/TeamCityReporter.lua +101 -101
- package/vendor/testez/Reporters/TextReporter.lua +105 -105
- package/vendor/testez/Reporters/TextReporterQuiet.lua +96 -96
- package/vendor/testez/TestBootstrap.lua +146 -146
- package/vendor/testez/TestEnum.lua +27 -27
- package/vendor/testez/TestPlan.lua +304 -304
- package/vendor/testez/TestPlanner.lua +39 -39
- package/vendor/testez/TestResults.lua +111 -111
- package/vendor/testez/TestRunner.lua +188 -188
- package/vendor/testez/TestSession.lua +243 -243
- package/vendor/testez/init.lua +39 -39
|
@@ -1,542 +1,542 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
|
|
3
|
-
-- Concur
|
|
4
|
-
-- Stephen Leitnick
|
|
5
|
-
-- May 01, 2022
|
|
6
|
-
|
|
7
|
-
type Error = any
|
|
8
|
-
type AnyFn = (...any) -> ...any
|
|
9
|
-
|
|
10
|
-
--[=[
|
|
11
|
-
@class Concur
|
|
12
|
-
|
|
13
|
-
Concurrency class for helping run tasks concurrently. In other words, Concur allows
|
|
14
|
-
developers to watch coroutines/threads. Completion status, returned values, and
|
|
15
|
-
errors can all be tracked.
|
|
16
|
-
|
|
17
|
-
For instance, Concur could be used to concurrently save all player data
|
|
18
|
-
at the same time when the game closes down:
|
|
19
|
-
|
|
20
|
-
```lua
|
|
21
|
-
game:BindToClose(function()
|
|
22
|
-
local all = {}
|
|
23
|
-
for _,player in Players:GetPlayers() do
|
|
24
|
-
local save = Concur.spawn(function()
|
|
25
|
-
DoSomethingToSaveData(player)
|
|
26
|
-
end)
|
|
27
|
-
table.insert(all, save)
|
|
28
|
-
end
|
|
29
|
-
local allConcur = Concur.all(all)
|
|
30
|
-
allConcur:Await()
|
|
31
|
-
end)
|
|
32
|
-
```
|
|
33
|
-
]=]
|
|
34
|
-
local Concur = {}
|
|
35
|
-
Concur.__index = Concur
|
|
36
|
-
|
|
37
|
-
--[=[
|
|
38
|
-
@within Concur
|
|
39
|
-
@interface Errors
|
|
40
|
-
.Stopped "Stopped"
|
|
41
|
-
.Timeout "Timeout"
|
|
42
|
-
]=]
|
|
43
|
-
|
|
44
|
-
--[=[
|
|
45
|
-
@within Concur
|
|
46
|
-
@readonly
|
|
47
|
-
@prop Errors Errors
|
|
48
|
-
]=]
|
|
49
|
-
Concur.Errors = {
|
|
50
|
-
Stopped = "Stopped",
|
|
51
|
-
Timeout = "Timeout",
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function Concur._new(fn: AnyFn, spawner: AnyFn, ...: any): Concur
|
|
55
|
-
local self: Concur = setmetatable({
|
|
56
|
-
_completed = false,
|
|
57
|
-
_res = nil,
|
|
58
|
-
_err = nil,
|
|
59
|
-
_awaitingThreads = {},
|
|
60
|
-
_thread = nil,
|
|
61
|
-
}, Concur)
|
|
62
|
-
|
|
63
|
-
self._thread = spawner(function(...)
|
|
64
|
-
local pcallRes = table.pack(pcall(fn, ...))
|
|
65
|
-
self._completed = true
|
|
66
|
-
self._err = if not pcallRes[1] then pcallRes[2] else nil
|
|
67
|
-
if self._err ~= nil then
|
|
68
|
-
for _, thread in ipairs(self._awaitingThreads) do
|
|
69
|
-
task.spawn(thread, self._err)
|
|
70
|
-
end
|
|
71
|
-
else
|
|
72
|
-
local res = table.move(pcallRes, 2, #pcallRes, 1, table.create(#pcallRes - 1))
|
|
73
|
-
self._res = res
|
|
74
|
-
for _, thread in ipairs(self._awaitingThreads) do
|
|
75
|
-
task.spawn(thread, nil, table.unpack(res, 1, res.n))
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end, ...)
|
|
79
|
-
|
|
80
|
-
return self
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
--[=[
|
|
84
|
-
Spawns the function using `task.spawn`.
|
|
85
|
-
|
|
86
|
-
```lua
|
|
87
|
-
local c = Concur.spawn(function()
|
|
88
|
-
task.wait(5)
|
|
89
|
-
return "Hello!"
|
|
90
|
-
end)
|
|
91
|
-
|
|
92
|
-
c:OnCompleted(function(err, msg)
|
|
93
|
-
if err then
|
|
94
|
-
error(err)
|
|
95
|
-
end
|
|
96
|
-
print(msg) --> Hello!
|
|
97
|
-
end))
|
|
98
|
-
```
|
|
99
|
-
]=]
|
|
100
|
-
function Concur.spawn(fn: AnyFn, ...: any): Concur
|
|
101
|
-
if type(fn) ~= "function" then
|
|
102
|
-
error("Concur.spawn argument must be a function; got " .. type(fn), 2)
|
|
103
|
-
end
|
|
104
|
-
return Concur._new(fn, task.spawn, ...)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
--[=[
|
|
108
|
-
Same as `Concur.spawn`, but uses `task.defer` internally.
|
|
109
|
-
]=]
|
|
110
|
-
function Concur.defer(fn: AnyFn, ...: any): Concur
|
|
111
|
-
if type(fn) ~= "function" then
|
|
112
|
-
error("Concur.defer argument must be a function; got " .. type(fn), 2)
|
|
113
|
-
end
|
|
114
|
-
return Concur._new(fn, task.defer, ...)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
--[=[
|
|
118
|
-
Same as `Concur.spawn`, but uses `task.delay` internally.
|
|
119
|
-
]=]
|
|
120
|
-
function Concur.delay(delayTime: number, fn: AnyFn, ...: any): Concur
|
|
121
|
-
if type(fn) ~= "function" then
|
|
122
|
-
error("Concur.delay argument must be a function; got " .. type(fn), 2)
|
|
123
|
-
end
|
|
124
|
-
return Concur._new(fn, function(...)
|
|
125
|
-
return task.delay(delayTime, ...)
|
|
126
|
-
end, ...)
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
--[=[
|
|
130
|
-
Resolves to the given value right away.
|
|
131
|
-
|
|
132
|
-
```lua
|
|
133
|
-
local val = Concur.value(10)
|
|
134
|
-
val:OnCompleted(function(v)
|
|
135
|
-
print(v) --> 10
|
|
136
|
-
end)
|
|
137
|
-
```
|
|
138
|
-
]=]
|
|
139
|
-
function Concur.value(value: any): Concur
|
|
140
|
-
return Concur.spawn(function()
|
|
141
|
-
return value
|
|
142
|
-
end)
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
--[=[
|
|
146
|
-
Completes the Concur instance once the event is fired and the predicate
|
|
147
|
-
function returns `true` (if no predicate is given, then completes once
|
|
148
|
-
the event first fires).
|
|
149
|
-
|
|
150
|
-
The Concur instance will return the values given by the event.
|
|
151
|
-
|
|
152
|
-
```lua
|
|
153
|
-
-- Wait for next player to touch an object:
|
|
154
|
-
local touch = Concur.event(part.Touched, function(toucher)
|
|
155
|
-
return Players:GetPlayerFromCharacter(toucher.Parent) ~= nil
|
|
156
|
-
end)
|
|
157
|
-
|
|
158
|
-
touch:OnCompleted(function(err, toucher)
|
|
159
|
-
print(toucher)
|
|
160
|
-
end)
|
|
161
|
-
```
|
|
162
|
-
]=]
|
|
163
|
-
function Concur.event(event: RBXScriptSignal, predicate: ((...any) -> boolean)?)
|
|
164
|
-
local connection, thread
|
|
165
|
-
|
|
166
|
-
connection = event:Connect(function(...)
|
|
167
|
-
if not thread then
|
|
168
|
-
return
|
|
169
|
-
end
|
|
170
|
-
if predicate == nil or predicate(...) then
|
|
171
|
-
connection:Disconnect()
|
|
172
|
-
task.spawn(thread, ...)
|
|
173
|
-
end
|
|
174
|
-
end)
|
|
175
|
-
|
|
176
|
-
local c = Concur.spawn(function()
|
|
177
|
-
thread = coroutine.running()
|
|
178
|
-
return coroutine.yield()
|
|
179
|
-
end)
|
|
180
|
-
|
|
181
|
-
c:OnCompleted(function(err)
|
|
182
|
-
connection:Disconnect()
|
|
183
|
-
if coroutine.status(thread) == "suspended" then
|
|
184
|
-
task.spawn(thread, err)
|
|
185
|
-
end
|
|
186
|
-
end)
|
|
187
|
-
|
|
188
|
-
return c
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
--[=[
|
|
192
|
-
Completes once _all_ Concur instances have been completed. All values
|
|
193
|
-
will be available in a packed table in the same order they were passed.
|
|
194
|
-
|
|
195
|
-
```lua
|
|
196
|
-
local c1 = Concur.spawn(function()
|
|
197
|
-
return 10
|
|
198
|
-
end)
|
|
199
|
-
|
|
200
|
-
local c2 = Concur.delay(0.5, function()
|
|
201
|
-
return 15
|
|
202
|
-
end)
|
|
203
|
-
|
|
204
|
-
local c3 = Concur.value(20)
|
|
205
|
-
|
|
206
|
-
local c4 = Concur.spawn(function()
|
|
207
|
-
error("failed")
|
|
208
|
-
end)
|
|
209
|
-
|
|
210
|
-
Concur.all({c1, c2, c3}):OnCompleted(function(err, values)
|
|
211
|
-
print(values) --> {{nil, 10}, {nil, 15}, {nil, 20}, {"failed", nil}}
|
|
212
|
-
end)
|
|
213
|
-
```
|
|
214
|
-
]=]
|
|
215
|
-
function Concur.all(concurs: { Concur }): Concur
|
|
216
|
-
if #concurs == 0 then
|
|
217
|
-
return Concur.value(nil)
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
return Concur.spawn(function()
|
|
221
|
-
local numCompleted = 0
|
|
222
|
-
local total = #concurs
|
|
223
|
-
local thread = coroutine.running()
|
|
224
|
-
local allRes = table.create(total)
|
|
225
|
-
for i, concur in ipairs(concurs) do
|
|
226
|
-
concur:OnCompleted(function(...)
|
|
227
|
-
allRes[i] = table.pack(...)
|
|
228
|
-
numCompleted += 1
|
|
229
|
-
if numCompleted >= total and coroutine.status(thread) == "suspended" then
|
|
230
|
-
task.spawn(thread)
|
|
231
|
-
end
|
|
232
|
-
end)
|
|
233
|
-
end
|
|
234
|
-
if numCompleted < total then
|
|
235
|
-
coroutine.yield()
|
|
236
|
-
end
|
|
237
|
-
return allRes
|
|
238
|
-
end)
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
--[=[
|
|
242
|
-
Completes once the first Concur instance is completed _without an error_. All other Concur
|
|
243
|
-
instances are then stopped.
|
|
244
|
-
|
|
245
|
-
```lua
|
|
246
|
-
local c1 = Concur.delay(1, function()
|
|
247
|
-
return 10
|
|
248
|
-
end)
|
|
249
|
-
|
|
250
|
-
local c2 = Concur.delay(0.5, function()
|
|
251
|
-
return 5
|
|
252
|
-
end)
|
|
253
|
-
|
|
254
|
-
Concur.first({c1, c2}):OnCompleted(function(err, num)
|
|
255
|
-
print(num) --> 5
|
|
256
|
-
end)
|
|
257
|
-
```
|
|
258
|
-
]=]
|
|
259
|
-
function Concur.first(concurs: { Concur }): Concur
|
|
260
|
-
if #concurs == 0 then
|
|
261
|
-
return Concur.value(nil)
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
return Concur.spawn(function()
|
|
265
|
-
local thread = coroutine.running()
|
|
266
|
-
local res = nil
|
|
267
|
-
local firstConcur = nil
|
|
268
|
-
for _, concur in ipairs(concurs) do
|
|
269
|
-
concur:OnCompleted(function(err, ...)
|
|
270
|
-
if res or err ~= nil then
|
|
271
|
-
return
|
|
272
|
-
end
|
|
273
|
-
firstConcur = concur
|
|
274
|
-
res = table.pack(...)
|
|
275
|
-
if coroutine.status(thread) == "suspended" then
|
|
276
|
-
task.spawn(thread)
|
|
277
|
-
end
|
|
278
|
-
end)
|
|
279
|
-
end
|
|
280
|
-
if res == nil then
|
|
281
|
-
coroutine.yield()
|
|
282
|
-
end
|
|
283
|
-
for _, concur in ipairs(concurs) do
|
|
284
|
-
if concur == firstConcur then
|
|
285
|
-
continue
|
|
286
|
-
end
|
|
287
|
-
concur:Stop()
|
|
288
|
-
end
|
|
289
|
-
return table.unpack(res, 1, res.n)
|
|
290
|
-
end)
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
--[=[
|
|
294
|
-
Stops the Concur instance. The underlying thread will be cancelled using
|
|
295
|
-
`task.cancel`. Any bound `OnCompleted` functions or threads waiting with
|
|
296
|
-
`Await` will be completed with the error `Concur.Errors.Stopped`.
|
|
297
|
-
|
|
298
|
-
```lua
|
|
299
|
-
local c = Concur.spawn(function()
|
|
300
|
-
for i = 1,10 do
|
|
301
|
-
print(i)
|
|
302
|
-
task.wait(1)
|
|
303
|
-
end
|
|
304
|
-
end)
|
|
305
|
-
|
|
306
|
-
task.wait(2.5)
|
|
307
|
-
c:Stop() -- At this point, will have only printed 1 and 2
|
|
308
|
-
```
|
|
309
|
-
]=]
|
|
310
|
-
function Concur:Stop()
|
|
311
|
-
if self._completed then
|
|
312
|
-
return
|
|
313
|
-
end
|
|
314
|
-
self._completed = true
|
|
315
|
-
self._err = Concur.Errors.Stopped
|
|
316
|
-
task.cancel(self._thread)
|
|
317
|
-
for _, thread: thread in ipairs(self._awaitingThreads) do
|
|
318
|
-
task.spawn(thread, Concur.Errors.Stopped)
|
|
319
|
-
end
|
|
320
|
-
end
|
|
321
|
-
|
|
322
|
-
--[=[
|
|
323
|
-
Check if the Concur instance is finished.
|
|
324
|
-
]=]
|
|
325
|
-
function Concur:IsCompleted(): boolean
|
|
326
|
-
return self._completed
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
--[=[
|
|
330
|
-
@yields
|
|
331
|
-
Yields the calling thread until the Concur instance is completed:
|
|
332
|
-
|
|
333
|
-
```lua
|
|
334
|
-
local c = Concur.delay(5, function()
|
|
335
|
-
return "Hi"
|
|
336
|
-
end)
|
|
337
|
-
|
|
338
|
-
local err, msg = c:Await()
|
|
339
|
-
print(msg) --> Hi
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
The `Await` method can be called _after_ the Concur instance
|
|
343
|
-
has been completed too, in which case the completed values
|
|
344
|
-
will be returned immediately without yielding the thread:
|
|
345
|
-
|
|
346
|
-
```lua
|
|
347
|
-
local c = Concur.spawn(function()
|
|
348
|
-
return 10
|
|
349
|
-
end)
|
|
350
|
-
|
|
351
|
-
task.wait(5)
|
|
352
|
-
-- Called after 'c' has been completed, but still captures the value:
|
|
353
|
-
local err, num = c:Await()
|
|
354
|
-
print(num) --> 10
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
It is always good practice to make sure that the `err` value is handled
|
|
358
|
-
by checking if it is not nil:
|
|
359
|
-
|
|
360
|
-
```lua
|
|
361
|
-
local c = Concur.spawn(function()
|
|
362
|
-
error("failed")
|
|
363
|
-
end)
|
|
364
|
-
|
|
365
|
-
local err, value = c:Await()
|
|
366
|
-
|
|
367
|
-
if err ~= nil then
|
|
368
|
-
print(err) --> failed
|
|
369
|
-
-- Handle error `err`
|
|
370
|
-
else
|
|
371
|
-
-- Handle `value`
|
|
372
|
-
end
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
This will stop awaiting if the Concur instance was stopped
|
|
376
|
-
too, in which case the `err` will be equal to
|
|
377
|
-
`Concur.Errors.Stopped`:
|
|
378
|
-
|
|
379
|
-
```lua
|
|
380
|
-
local c = Concur.delay(10, function() end)
|
|
381
|
-
c:Stop()
|
|
382
|
-
local err = c:Await()
|
|
383
|
-
if err == Concur.Errors.Stopped then
|
|
384
|
-
print("Was stopped")
|
|
385
|
-
end
|
|
386
|
-
```
|
|
387
|
-
|
|
388
|
-
An optional timeout can be given, which will return the
|
|
389
|
-
`Concur.Errors.Timeout` error if timed out. Timing out
|
|
390
|
-
does _not_ stop the Concur instance, so other callers
|
|
391
|
-
to `Await` or `OnCompleted` can still grab the resulting
|
|
392
|
-
values.
|
|
393
|
-
|
|
394
|
-
```lua
|
|
395
|
-
local c = Concur.delay(10, function() end)
|
|
396
|
-
local err = c:Await(1)
|
|
397
|
-
if err == Concur.Errors.Timeout then
|
|
398
|
-
-- Handle timeout
|
|
399
|
-
end
|
|
400
|
-
```
|
|
401
|
-
]=]
|
|
402
|
-
function Concur:Await(timeout: number?): (Error, ...any?)
|
|
403
|
-
if self._completed then
|
|
404
|
-
if self._err ~= nil then
|
|
405
|
-
return self._err
|
|
406
|
-
else
|
|
407
|
-
return nil, if self._res == nil then nil else table.unpack(self._res, 1, self._res.n)
|
|
408
|
-
end
|
|
409
|
-
end
|
|
410
|
-
|
|
411
|
-
local thread = coroutine.running()
|
|
412
|
-
table.insert(self._awaitingThreads, thread)
|
|
413
|
-
|
|
414
|
-
if timeout then
|
|
415
|
-
local delayThread = task.delay(timeout, function()
|
|
416
|
-
local index = table.find(self._awaitingThreads, thread)
|
|
417
|
-
if index then
|
|
418
|
-
table.remove(self._awaitingThreads, index)
|
|
419
|
-
task.spawn(thread, Concur.Errors.Timeout)
|
|
420
|
-
end
|
|
421
|
-
end)
|
|
422
|
-
local res = table.pack(coroutine.yield())
|
|
423
|
-
if coroutine.status(delayThread) ~= "normal" then
|
|
424
|
-
task.cancel(delayThread)
|
|
425
|
-
end
|
|
426
|
-
return table.unpack(res, 1, res.n)
|
|
427
|
-
else
|
|
428
|
-
return coroutine.yield()
|
|
429
|
-
end
|
|
430
|
-
end
|
|
431
|
-
|
|
432
|
-
--[=[
|
|
433
|
-
Calls the given function once the Concur instance is completed:
|
|
434
|
-
|
|
435
|
-
```lua
|
|
436
|
-
local c = Concur.delay(5, function()
|
|
437
|
-
return "Hi"
|
|
438
|
-
end)
|
|
439
|
-
|
|
440
|
-
c:OnCompleted(function(err, msg)
|
|
441
|
-
print(msg) --> Hi
|
|
442
|
-
end)
|
|
443
|
-
```
|
|
444
|
-
|
|
445
|
-
A function is returned that can be used to unbind the function to
|
|
446
|
-
no longer fire when the Concur instance is completed:
|
|
447
|
-
|
|
448
|
-
```lua
|
|
449
|
-
local c = Concur.delay(5, function() end)
|
|
450
|
-
local unbind = c:OnCompleted(function()
|
|
451
|
-
print("Completed")
|
|
452
|
-
end)
|
|
453
|
-
unbind()
|
|
454
|
-
-- Never prints "Completed"
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
The `OnCompleted` method can be called _after_ the Concur instance
|
|
458
|
-
has been completed too, in which case the given function will be
|
|
459
|
-
called immediately with the completed values:
|
|
460
|
-
|
|
461
|
-
```lua
|
|
462
|
-
local c = Concur.spawn(function()
|
|
463
|
-
return 10
|
|
464
|
-
end)
|
|
465
|
-
|
|
466
|
-
task.wait(5)
|
|
467
|
-
-- Called after 'c' has been completed, but still captures the value:
|
|
468
|
-
c:OnCompleted(function(err, num)
|
|
469
|
-
print(num) --> 10
|
|
470
|
-
end)
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
It is always good practice to make sure that the `err` value is handled
|
|
474
|
-
by checking if it is not nil:
|
|
475
|
-
|
|
476
|
-
```lua
|
|
477
|
-
local c = Concur.spawn(function()
|
|
478
|
-
error("failed")
|
|
479
|
-
end)
|
|
480
|
-
|
|
481
|
-
c:OnCompleted(function(err, value)
|
|
482
|
-
if err ~= nil then
|
|
483
|
-
print(err) --> failed
|
|
484
|
-
-- Handle error `err`
|
|
485
|
-
return
|
|
486
|
-
end
|
|
487
|
-
-- Handle `value`
|
|
488
|
-
end)
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
This will call the function if the Concur instance was stopped
|
|
492
|
-
too, in which case the `err` will be equal to
|
|
493
|
-
`Concur.Errors.Stopped`:
|
|
494
|
-
|
|
495
|
-
```lua
|
|
496
|
-
local c = Concur.delay(10, function() end)
|
|
497
|
-
c:OnCompleted(function(err)
|
|
498
|
-
if err == Concur.Errors.Stopped then
|
|
499
|
-
print("Was stopped")
|
|
500
|
-
end
|
|
501
|
-
end)
|
|
502
|
-
c:Stop()
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
An optional timeout can also be supplied, which will call the
|
|
506
|
-
function with the `Concur.Errors.Timeout` error:
|
|
507
|
-
|
|
508
|
-
```lua
|
|
509
|
-
local c = Concur.delay(10, function() end)
|
|
510
|
-
c:OnCompleted(function(err)
|
|
511
|
-
if err == Concur.Errors.Timeout then
|
|
512
|
-
-- Handle timeout
|
|
513
|
-
end
|
|
514
|
-
end, 1)
|
|
515
|
-
```
|
|
516
|
-
]=]
|
|
517
|
-
function Concur:OnCompleted(fn: (Error, ...any?) -> (), timeout: number?): () -> ()
|
|
518
|
-
local thread = task.spawn(function()
|
|
519
|
-
fn(self:Await(timeout))
|
|
520
|
-
end)
|
|
521
|
-
|
|
522
|
-
-- Unbind:
|
|
523
|
-
return function()
|
|
524
|
-
task.cancel(thread)
|
|
525
|
-
local index = table.find(self._awaitingThreads, thread)
|
|
526
|
-
if index then
|
|
527
|
-
table.remove(self._awaitingThreads, index)
|
|
528
|
-
end
|
|
529
|
-
end
|
|
530
|
-
end
|
|
531
|
-
|
|
532
|
-
type ConcurObj = {
|
|
533
|
-
_completed: boolean,
|
|
534
|
-
_res: { any }?,
|
|
535
|
-
_err: string?,
|
|
536
|
-
_awaitingThreads: { thread },
|
|
537
|
-
_thread: thread?,
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
export type Concur = typeof(setmetatable({} :: ConcurObj, Concur))
|
|
541
|
-
|
|
542
|
-
return Concur
|
|
1
|
+
--!strict
|
|
2
|
+
|
|
3
|
+
-- Concur
|
|
4
|
+
-- Stephen Leitnick
|
|
5
|
+
-- May 01, 2022
|
|
6
|
+
|
|
7
|
+
type Error = any
|
|
8
|
+
type AnyFn = (...any) -> ...any
|
|
9
|
+
|
|
10
|
+
--[=[
|
|
11
|
+
@class Concur
|
|
12
|
+
|
|
13
|
+
Concurrency class for helping run tasks concurrently. In other words, Concur allows
|
|
14
|
+
developers to watch coroutines/threads. Completion status, returned values, and
|
|
15
|
+
errors can all be tracked.
|
|
16
|
+
|
|
17
|
+
For instance, Concur could be used to concurrently save all player data
|
|
18
|
+
at the same time when the game closes down:
|
|
19
|
+
|
|
20
|
+
```lua
|
|
21
|
+
game:BindToClose(function()
|
|
22
|
+
local all = {}
|
|
23
|
+
for _,player in Players:GetPlayers() do
|
|
24
|
+
local save = Concur.spawn(function()
|
|
25
|
+
DoSomethingToSaveData(player)
|
|
26
|
+
end)
|
|
27
|
+
table.insert(all, save)
|
|
28
|
+
end
|
|
29
|
+
local allConcur = Concur.all(all)
|
|
30
|
+
allConcur:Await()
|
|
31
|
+
end)
|
|
32
|
+
```
|
|
33
|
+
]=]
|
|
34
|
+
local Concur = {}
|
|
35
|
+
Concur.__index = Concur
|
|
36
|
+
|
|
37
|
+
--[=[
|
|
38
|
+
@within Concur
|
|
39
|
+
@interface Errors
|
|
40
|
+
.Stopped "Stopped"
|
|
41
|
+
.Timeout "Timeout"
|
|
42
|
+
]=]
|
|
43
|
+
|
|
44
|
+
--[=[
|
|
45
|
+
@within Concur
|
|
46
|
+
@readonly
|
|
47
|
+
@prop Errors Errors
|
|
48
|
+
]=]
|
|
49
|
+
Concur.Errors = {
|
|
50
|
+
Stopped = "Stopped",
|
|
51
|
+
Timeout = "Timeout",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function Concur._new(fn: AnyFn, spawner: AnyFn, ...: any): Concur
|
|
55
|
+
local self: Concur = setmetatable({
|
|
56
|
+
_completed = false,
|
|
57
|
+
_res = nil,
|
|
58
|
+
_err = nil,
|
|
59
|
+
_awaitingThreads = {},
|
|
60
|
+
_thread = nil,
|
|
61
|
+
}, Concur)
|
|
62
|
+
|
|
63
|
+
self._thread = spawner(function(...)
|
|
64
|
+
local pcallRes = table.pack(pcall(fn, ...))
|
|
65
|
+
self._completed = true
|
|
66
|
+
self._err = if not pcallRes[1] then pcallRes[2] else nil
|
|
67
|
+
if self._err ~= nil then
|
|
68
|
+
for _, thread in ipairs(self._awaitingThreads) do
|
|
69
|
+
task.spawn(thread, self._err)
|
|
70
|
+
end
|
|
71
|
+
else
|
|
72
|
+
local res = table.move(pcallRes, 2, #pcallRes, 1, table.create(#pcallRes - 1))
|
|
73
|
+
self._res = res
|
|
74
|
+
for _, thread in ipairs(self._awaitingThreads) do
|
|
75
|
+
task.spawn(thread, nil, table.unpack(res, 1, res.n))
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end, ...)
|
|
79
|
+
|
|
80
|
+
return self
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
--[=[
|
|
84
|
+
Spawns the function using `task.spawn`.
|
|
85
|
+
|
|
86
|
+
```lua
|
|
87
|
+
local c = Concur.spawn(function()
|
|
88
|
+
task.wait(5)
|
|
89
|
+
return "Hello!"
|
|
90
|
+
end)
|
|
91
|
+
|
|
92
|
+
c:OnCompleted(function(err, msg)
|
|
93
|
+
if err then
|
|
94
|
+
error(err)
|
|
95
|
+
end
|
|
96
|
+
print(msg) --> Hello!
|
|
97
|
+
end))
|
|
98
|
+
```
|
|
99
|
+
]=]
|
|
100
|
+
function Concur.spawn(fn: AnyFn, ...: any): Concur
|
|
101
|
+
if type(fn) ~= "function" then
|
|
102
|
+
error("Concur.spawn argument must be a function; got " .. type(fn), 2)
|
|
103
|
+
end
|
|
104
|
+
return Concur._new(fn, task.spawn, ...)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
--[=[
|
|
108
|
+
Same as `Concur.spawn`, but uses `task.defer` internally.
|
|
109
|
+
]=]
|
|
110
|
+
function Concur.defer(fn: AnyFn, ...: any): Concur
|
|
111
|
+
if type(fn) ~= "function" then
|
|
112
|
+
error("Concur.defer argument must be a function; got " .. type(fn), 2)
|
|
113
|
+
end
|
|
114
|
+
return Concur._new(fn, task.defer, ...)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
--[=[
|
|
118
|
+
Same as `Concur.spawn`, but uses `task.delay` internally.
|
|
119
|
+
]=]
|
|
120
|
+
function Concur.delay(delayTime: number, fn: AnyFn, ...: any): Concur
|
|
121
|
+
if type(fn) ~= "function" then
|
|
122
|
+
error("Concur.delay argument must be a function; got " .. type(fn), 2)
|
|
123
|
+
end
|
|
124
|
+
return Concur._new(fn, function(...)
|
|
125
|
+
return task.delay(delayTime, ...)
|
|
126
|
+
end, ...)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
--[=[
|
|
130
|
+
Resolves to the given value right away.
|
|
131
|
+
|
|
132
|
+
```lua
|
|
133
|
+
local val = Concur.value(10)
|
|
134
|
+
val:OnCompleted(function(v)
|
|
135
|
+
print(v) --> 10
|
|
136
|
+
end)
|
|
137
|
+
```
|
|
138
|
+
]=]
|
|
139
|
+
function Concur.value(value: any): Concur
|
|
140
|
+
return Concur.spawn(function()
|
|
141
|
+
return value
|
|
142
|
+
end)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
--[=[
|
|
146
|
+
Completes the Concur instance once the event is fired and the predicate
|
|
147
|
+
function returns `true` (if no predicate is given, then completes once
|
|
148
|
+
the event first fires).
|
|
149
|
+
|
|
150
|
+
The Concur instance will return the values given by the event.
|
|
151
|
+
|
|
152
|
+
```lua
|
|
153
|
+
-- Wait for next player to touch an object:
|
|
154
|
+
local touch = Concur.event(part.Touched, function(toucher)
|
|
155
|
+
return Players:GetPlayerFromCharacter(toucher.Parent) ~= nil
|
|
156
|
+
end)
|
|
157
|
+
|
|
158
|
+
touch:OnCompleted(function(err, toucher)
|
|
159
|
+
print(toucher)
|
|
160
|
+
end)
|
|
161
|
+
```
|
|
162
|
+
]=]
|
|
163
|
+
function Concur.event(event: RBXScriptSignal, predicate: ((...any) -> boolean)?)
|
|
164
|
+
local connection, thread
|
|
165
|
+
|
|
166
|
+
connection = event:Connect(function(...)
|
|
167
|
+
if not thread then
|
|
168
|
+
return
|
|
169
|
+
end
|
|
170
|
+
if predicate == nil or predicate(...) then
|
|
171
|
+
connection:Disconnect()
|
|
172
|
+
task.spawn(thread, ...)
|
|
173
|
+
end
|
|
174
|
+
end)
|
|
175
|
+
|
|
176
|
+
local c = Concur.spawn(function()
|
|
177
|
+
thread = coroutine.running()
|
|
178
|
+
return coroutine.yield()
|
|
179
|
+
end)
|
|
180
|
+
|
|
181
|
+
c:OnCompleted(function(err)
|
|
182
|
+
connection:Disconnect()
|
|
183
|
+
if coroutine.status(thread) == "suspended" then
|
|
184
|
+
task.spawn(thread, err)
|
|
185
|
+
end
|
|
186
|
+
end)
|
|
187
|
+
|
|
188
|
+
return c
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
--[=[
|
|
192
|
+
Completes once _all_ Concur instances have been completed. All values
|
|
193
|
+
will be available in a packed table in the same order they were passed.
|
|
194
|
+
|
|
195
|
+
```lua
|
|
196
|
+
local c1 = Concur.spawn(function()
|
|
197
|
+
return 10
|
|
198
|
+
end)
|
|
199
|
+
|
|
200
|
+
local c2 = Concur.delay(0.5, function()
|
|
201
|
+
return 15
|
|
202
|
+
end)
|
|
203
|
+
|
|
204
|
+
local c3 = Concur.value(20)
|
|
205
|
+
|
|
206
|
+
local c4 = Concur.spawn(function()
|
|
207
|
+
error("failed")
|
|
208
|
+
end)
|
|
209
|
+
|
|
210
|
+
Concur.all({c1, c2, c3}):OnCompleted(function(err, values)
|
|
211
|
+
print(values) --> {{nil, 10}, {nil, 15}, {nil, 20}, {"failed", nil}}
|
|
212
|
+
end)
|
|
213
|
+
```
|
|
214
|
+
]=]
|
|
215
|
+
function Concur.all(concurs: { Concur }): Concur
|
|
216
|
+
if #concurs == 0 then
|
|
217
|
+
return Concur.value(nil)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
return Concur.spawn(function()
|
|
221
|
+
local numCompleted = 0
|
|
222
|
+
local total = #concurs
|
|
223
|
+
local thread = coroutine.running()
|
|
224
|
+
local allRes = table.create(total)
|
|
225
|
+
for i, concur in ipairs(concurs) do
|
|
226
|
+
concur:OnCompleted(function(...)
|
|
227
|
+
allRes[i] = table.pack(...)
|
|
228
|
+
numCompleted += 1
|
|
229
|
+
if numCompleted >= total and coroutine.status(thread) == "suspended" then
|
|
230
|
+
task.spawn(thread)
|
|
231
|
+
end
|
|
232
|
+
end)
|
|
233
|
+
end
|
|
234
|
+
if numCompleted < total then
|
|
235
|
+
coroutine.yield()
|
|
236
|
+
end
|
|
237
|
+
return allRes
|
|
238
|
+
end)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
--[=[
|
|
242
|
+
Completes once the first Concur instance is completed _without an error_. All other Concur
|
|
243
|
+
instances are then stopped.
|
|
244
|
+
|
|
245
|
+
```lua
|
|
246
|
+
local c1 = Concur.delay(1, function()
|
|
247
|
+
return 10
|
|
248
|
+
end)
|
|
249
|
+
|
|
250
|
+
local c2 = Concur.delay(0.5, function()
|
|
251
|
+
return 5
|
|
252
|
+
end)
|
|
253
|
+
|
|
254
|
+
Concur.first({c1, c2}):OnCompleted(function(err, num)
|
|
255
|
+
print(num) --> 5
|
|
256
|
+
end)
|
|
257
|
+
```
|
|
258
|
+
]=]
|
|
259
|
+
function Concur.first(concurs: { Concur }): Concur
|
|
260
|
+
if #concurs == 0 then
|
|
261
|
+
return Concur.value(nil)
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
return Concur.spawn(function()
|
|
265
|
+
local thread = coroutine.running()
|
|
266
|
+
local res = nil
|
|
267
|
+
local firstConcur = nil
|
|
268
|
+
for _, concur in ipairs(concurs) do
|
|
269
|
+
concur:OnCompleted(function(err, ...)
|
|
270
|
+
if res or err ~= nil then
|
|
271
|
+
return
|
|
272
|
+
end
|
|
273
|
+
firstConcur = concur
|
|
274
|
+
res = table.pack(...)
|
|
275
|
+
if coroutine.status(thread) == "suspended" then
|
|
276
|
+
task.spawn(thread)
|
|
277
|
+
end
|
|
278
|
+
end)
|
|
279
|
+
end
|
|
280
|
+
if res == nil then
|
|
281
|
+
coroutine.yield()
|
|
282
|
+
end
|
|
283
|
+
for _, concur in ipairs(concurs) do
|
|
284
|
+
if concur == firstConcur then
|
|
285
|
+
continue
|
|
286
|
+
end
|
|
287
|
+
concur:Stop()
|
|
288
|
+
end
|
|
289
|
+
return table.unpack(res, 1, res.n)
|
|
290
|
+
end)
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
--[=[
|
|
294
|
+
Stops the Concur instance. The underlying thread will be cancelled using
|
|
295
|
+
`task.cancel`. Any bound `OnCompleted` functions or threads waiting with
|
|
296
|
+
`Await` will be completed with the error `Concur.Errors.Stopped`.
|
|
297
|
+
|
|
298
|
+
```lua
|
|
299
|
+
local c = Concur.spawn(function()
|
|
300
|
+
for i = 1,10 do
|
|
301
|
+
print(i)
|
|
302
|
+
task.wait(1)
|
|
303
|
+
end
|
|
304
|
+
end)
|
|
305
|
+
|
|
306
|
+
task.wait(2.5)
|
|
307
|
+
c:Stop() -- At this point, will have only printed 1 and 2
|
|
308
|
+
```
|
|
309
|
+
]=]
|
|
310
|
+
function Concur:Stop()
|
|
311
|
+
if self._completed then
|
|
312
|
+
return
|
|
313
|
+
end
|
|
314
|
+
self._completed = true
|
|
315
|
+
self._err = Concur.Errors.Stopped
|
|
316
|
+
task.cancel(self._thread)
|
|
317
|
+
for _, thread: thread in ipairs(self._awaitingThreads) do
|
|
318
|
+
task.spawn(thread, Concur.Errors.Stopped)
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
--[=[
|
|
323
|
+
Check if the Concur instance is finished.
|
|
324
|
+
]=]
|
|
325
|
+
function Concur:IsCompleted(): boolean
|
|
326
|
+
return self._completed
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
--[=[
|
|
330
|
+
@yields
|
|
331
|
+
Yields the calling thread until the Concur instance is completed:
|
|
332
|
+
|
|
333
|
+
```lua
|
|
334
|
+
local c = Concur.delay(5, function()
|
|
335
|
+
return "Hi"
|
|
336
|
+
end)
|
|
337
|
+
|
|
338
|
+
local err, msg = c:Await()
|
|
339
|
+
print(msg) --> Hi
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
The `Await` method can be called _after_ the Concur instance
|
|
343
|
+
has been completed too, in which case the completed values
|
|
344
|
+
will be returned immediately without yielding the thread:
|
|
345
|
+
|
|
346
|
+
```lua
|
|
347
|
+
local c = Concur.spawn(function()
|
|
348
|
+
return 10
|
|
349
|
+
end)
|
|
350
|
+
|
|
351
|
+
task.wait(5)
|
|
352
|
+
-- Called after 'c' has been completed, but still captures the value:
|
|
353
|
+
local err, num = c:Await()
|
|
354
|
+
print(num) --> 10
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
It is always good practice to make sure that the `err` value is handled
|
|
358
|
+
by checking if it is not nil:
|
|
359
|
+
|
|
360
|
+
```lua
|
|
361
|
+
local c = Concur.spawn(function()
|
|
362
|
+
error("failed")
|
|
363
|
+
end)
|
|
364
|
+
|
|
365
|
+
local err, value = c:Await()
|
|
366
|
+
|
|
367
|
+
if err ~= nil then
|
|
368
|
+
print(err) --> failed
|
|
369
|
+
-- Handle error `err`
|
|
370
|
+
else
|
|
371
|
+
-- Handle `value`
|
|
372
|
+
end
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
This will stop awaiting if the Concur instance was stopped
|
|
376
|
+
too, in which case the `err` will be equal to
|
|
377
|
+
`Concur.Errors.Stopped`:
|
|
378
|
+
|
|
379
|
+
```lua
|
|
380
|
+
local c = Concur.delay(10, function() end)
|
|
381
|
+
c:Stop()
|
|
382
|
+
local err = c:Await()
|
|
383
|
+
if err == Concur.Errors.Stopped then
|
|
384
|
+
print("Was stopped")
|
|
385
|
+
end
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
An optional timeout can be given, which will return the
|
|
389
|
+
`Concur.Errors.Timeout` error if timed out. Timing out
|
|
390
|
+
does _not_ stop the Concur instance, so other callers
|
|
391
|
+
to `Await` or `OnCompleted` can still grab the resulting
|
|
392
|
+
values.
|
|
393
|
+
|
|
394
|
+
```lua
|
|
395
|
+
local c = Concur.delay(10, function() end)
|
|
396
|
+
local err = c:Await(1)
|
|
397
|
+
if err == Concur.Errors.Timeout then
|
|
398
|
+
-- Handle timeout
|
|
399
|
+
end
|
|
400
|
+
```
|
|
401
|
+
]=]
|
|
402
|
+
function Concur:Await(timeout: number?): (Error, ...any?)
|
|
403
|
+
if self._completed then
|
|
404
|
+
if self._err ~= nil then
|
|
405
|
+
return self._err
|
|
406
|
+
else
|
|
407
|
+
return nil, if self._res == nil then nil else table.unpack(self._res, 1, self._res.n)
|
|
408
|
+
end
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
local thread = coroutine.running()
|
|
412
|
+
table.insert(self._awaitingThreads, thread)
|
|
413
|
+
|
|
414
|
+
if timeout then
|
|
415
|
+
local delayThread = task.delay(timeout, function()
|
|
416
|
+
local index = table.find(self._awaitingThreads, thread)
|
|
417
|
+
if index then
|
|
418
|
+
table.remove(self._awaitingThreads, index)
|
|
419
|
+
task.spawn(thread, Concur.Errors.Timeout)
|
|
420
|
+
end
|
|
421
|
+
end)
|
|
422
|
+
local res = table.pack(coroutine.yield())
|
|
423
|
+
if coroutine.status(delayThread) ~= "normal" then
|
|
424
|
+
task.cancel(delayThread)
|
|
425
|
+
end
|
|
426
|
+
return table.unpack(res, 1, res.n)
|
|
427
|
+
else
|
|
428
|
+
return coroutine.yield()
|
|
429
|
+
end
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
--[=[
|
|
433
|
+
Calls the given function once the Concur instance is completed:
|
|
434
|
+
|
|
435
|
+
```lua
|
|
436
|
+
local c = Concur.delay(5, function()
|
|
437
|
+
return "Hi"
|
|
438
|
+
end)
|
|
439
|
+
|
|
440
|
+
c:OnCompleted(function(err, msg)
|
|
441
|
+
print(msg) --> Hi
|
|
442
|
+
end)
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
A function is returned that can be used to unbind the function to
|
|
446
|
+
no longer fire when the Concur instance is completed:
|
|
447
|
+
|
|
448
|
+
```lua
|
|
449
|
+
local c = Concur.delay(5, function() end)
|
|
450
|
+
local unbind = c:OnCompleted(function()
|
|
451
|
+
print("Completed")
|
|
452
|
+
end)
|
|
453
|
+
unbind()
|
|
454
|
+
-- Never prints "Completed"
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
The `OnCompleted` method can be called _after_ the Concur instance
|
|
458
|
+
has been completed too, in which case the given function will be
|
|
459
|
+
called immediately with the completed values:
|
|
460
|
+
|
|
461
|
+
```lua
|
|
462
|
+
local c = Concur.spawn(function()
|
|
463
|
+
return 10
|
|
464
|
+
end)
|
|
465
|
+
|
|
466
|
+
task.wait(5)
|
|
467
|
+
-- Called after 'c' has been completed, but still captures the value:
|
|
468
|
+
c:OnCompleted(function(err, num)
|
|
469
|
+
print(num) --> 10
|
|
470
|
+
end)
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
It is always good practice to make sure that the `err` value is handled
|
|
474
|
+
by checking if it is not nil:
|
|
475
|
+
|
|
476
|
+
```lua
|
|
477
|
+
local c = Concur.spawn(function()
|
|
478
|
+
error("failed")
|
|
479
|
+
end)
|
|
480
|
+
|
|
481
|
+
c:OnCompleted(function(err, value)
|
|
482
|
+
if err ~= nil then
|
|
483
|
+
print(err) --> failed
|
|
484
|
+
-- Handle error `err`
|
|
485
|
+
return
|
|
486
|
+
end
|
|
487
|
+
-- Handle `value`
|
|
488
|
+
end)
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
This will call the function if the Concur instance was stopped
|
|
492
|
+
too, in which case the `err` will be equal to
|
|
493
|
+
`Concur.Errors.Stopped`:
|
|
494
|
+
|
|
495
|
+
```lua
|
|
496
|
+
local c = Concur.delay(10, function() end)
|
|
497
|
+
c:OnCompleted(function(err)
|
|
498
|
+
if err == Concur.Errors.Stopped then
|
|
499
|
+
print("Was stopped")
|
|
500
|
+
end
|
|
501
|
+
end)
|
|
502
|
+
c:Stop()
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
An optional timeout can also be supplied, which will call the
|
|
506
|
+
function with the `Concur.Errors.Timeout` error:
|
|
507
|
+
|
|
508
|
+
```lua
|
|
509
|
+
local c = Concur.delay(10, function() end)
|
|
510
|
+
c:OnCompleted(function(err)
|
|
511
|
+
if err == Concur.Errors.Timeout then
|
|
512
|
+
-- Handle timeout
|
|
513
|
+
end
|
|
514
|
+
end, 1)
|
|
515
|
+
```
|
|
516
|
+
]=]
|
|
517
|
+
function Concur:OnCompleted(fn: (Error, ...any?) -> (), timeout: number?): () -> ()
|
|
518
|
+
local thread = task.spawn(function()
|
|
519
|
+
fn(self:Await(timeout))
|
|
520
|
+
end)
|
|
521
|
+
|
|
522
|
+
-- Unbind:
|
|
523
|
+
return function()
|
|
524
|
+
task.cancel(thread)
|
|
525
|
+
local index = table.find(self._awaitingThreads, thread)
|
|
526
|
+
if index then
|
|
527
|
+
table.remove(self._awaitingThreads, index)
|
|
528
|
+
end
|
|
529
|
+
end
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
type ConcurObj = {
|
|
533
|
+
_completed: boolean,
|
|
534
|
+
_res: { any }?,
|
|
535
|
+
_err: string?,
|
|
536
|
+
_awaitingThreads: { thread },
|
|
537
|
+
_thread: thread?,
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export type Concur = typeof(setmetatable({} :: ConcurObj, Concur))
|
|
541
|
+
|
|
542
|
+
return Concur
|