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.
Files changed (246) hide show
  1. package/README.md +112 -122
  2. package/commands/setup-game.md +108 -108
  3. package/commands/sync-check.md +53 -53
  4. package/core/roblox-core.md +93 -93
  5. package/dist/server.js +189 -167
  6. package/package.json +35 -35
  7. package/skills/roblox-analytics/SKILL.md +277 -277
  8. package/skills/roblox-analytics/references/event-batcher.luau +75 -75
  9. package/skills/roblox-animation-vfx/SKILL.md +1325 -1325
  10. package/skills/roblox-architecture/SKILL.md +877 -863
  11. package/skills/roblox-architecture/references/combat-systems.md +1381 -1381
  12. package/skills/roblox-code-review/SKILL.md +686 -686
  13. package/skills/roblox-data/SKILL.md +889 -889
  14. package/skills/roblox-data/references/inventory-systems.md +1729 -1729
  15. package/skills/roblox-debug/SKILL.md +98 -98
  16. package/skills/roblox-gui/SKILL.md +1103 -1103
  17. package/skills/roblox-gui-fusion/SKILL.md +150 -150
  18. package/skills/roblox-gui-fusion/references/inventory.luau +427 -427
  19. package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -579
  20. package/skills/roblox-gui-fusion/references/shop.luau +411 -411
  21. package/skills/roblox-luau-mastery/SKILL.md +1618 -1519
  22. package/skills/roblox-monetization/SKILL.md +1084 -1084
  23. package/skills/roblox-monetization/references/process-receipt.luau +131 -131
  24. package/skills/roblox-networking/SKILL.md +669 -669
  25. package/skills/roblox-networking/references/remote-validator.luau +193 -193
  26. package/skills/roblox-publish-checklist/SKILL.md +127 -127
  27. package/skills/roblox-runtime/SKILL.md +753 -753
  28. package/skills/roblox-sharp-edges/SKILL.md +294 -294
  29. package/skills/roblox-sync/SKILL.md +126 -126
  30. package/skills/roblox-testing/SKILL.md +943 -943
  31. package/skills/roblox-tooling/SKILL.md +149 -149
  32. package/vendor/LICENSES/ProfileStore-LICENSE +201 -201
  33. package/vendor/LICENSES/RbxUtil-LICENSE +7 -7
  34. package/vendor/LICENSES/promise-LICENSE +20 -20
  35. package/vendor/LICENSES/t-LICENSE +21 -21
  36. package/vendor/LICENSES/testez-LICENSE +200 -200
  37. package/vendor/README.md +83 -83
  38. package/vendor/fusion/Animation/ExternalTime.luau +83 -83
  39. package/vendor/fusion/Animation/Spring.luau +321 -321
  40. package/vendor/fusion/Animation/Stopwatch.luau +127 -127
  41. package/vendor/fusion/Animation/Tween.luau +187 -187
  42. package/vendor/fusion/Animation/getTweenDuration.luau +27 -27
  43. package/vendor/fusion/Animation/getTweenRatio.luau +47 -47
  44. package/vendor/fusion/Animation/lerpType.luau +163 -163
  45. package/vendor/fusion/Animation/packType.luau +99 -99
  46. package/vendor/fusion/Animation/springCoefficients.luau +80 -80
  47. package/vendor/fusion/Animation/unpackType.luau +102 -102
  48. package/vendor/fusion/Colour/Oklab.luau +70 -70
  49. package/vendor/fusion/Colour/sRGB.luau +54 -54
  50. package/vendor/fusion/External.luau +167 -167
  51. package/vendor/fusion/ExternalDebug.luau +69 -69
  52. package/vendor/fusion/Graph/Observer.luau +113 -113
  53. package/vendor/fusion/Graph/castToGraph.luau +28 -28
  54. package/vendor/fusion/Graph/change.luau +80 -80
  55. package/vendor/fusion/Graph/depend.luau +32 -32
  56. package/vendor/fusion/Graph/evaluate.luau +55 -55
  57. package/vendor/fusion/Instances/Attribute.luau +57 -57
  58. package/vendor/fusion/Instances/AttributeChange.luau +46 -46
  59. package/vendor/fusion/Instances/AttributeOut.luau +63 -63
  60. package/vendor/fusion/Instances/Child.luau +21 -21
  61. package/vendor/fusion/Instances/Children.luau +147 -147
  62. package/vendor/fusion/Instances/Hydrate.luau +32 -32
  63. package/vendor/fusion/Instances/New.luau +52 -52
  64. package/vendor/fusion/Instances/OnChange.luau +49 -49
  65. package/vendor/fusion/Instances/OnEvent.luau +53 -53
  66. package/vendor/fusion/Instances/Out.luau +69 -69
  67. package/vendor/fusion/Instances/applyInstanceProps.luau +148 -148
  68. package/vendor/fusion/Instances/defaultProps.luau +194 -194
  69. package/vendor/fusion/LICENSE +21 -21
  70. package/vendor/fusion/Logging/formatError.luau +48 -48
  71. package/vendor/fusion/Logging/messages.luau +51 -51
  72. package/vendor/fusion/Logging/parseError.luau +24 -24
  73. package/vendor/fusion/Memory/checkLifetime.luau +133 -133
  74. package/vendor/fusion/Memory/deriveScope.luau +23 -23
  75. package/vendor/fusion/Memory/deriveScopeImpl.luau +44 -44
  76. package/vendor/fusion/Memory/doCleanup.luau +78 -78
  77. package/vendor/fusion/Memory/innerScope.luau +33 -33
  78. package/vendor/fusion/Memory/legacyCleanup.luau +17 -17
  79. package/vendor/fusion/Memory/needsDestruction.luau +16 -16
  80. package/vendor/fusion/Memory/poisonScope.luau +33 -33
  81. package/vendor/fusion/Memory/scopePool.luau +54 -54
  82. package/vendor/fusion/Memory/scoped.luau +26 -26
  83. package/vendor/fusion/Memory/whichLivesLonger.luau +74 -74
  84. package/vendor/fusion/RobloxExternal.luau +97 -97
  85. package/vendor/fusion/State/Computed.luau +138 -138
  86. package/vendor/fusion/State/For/Disassembly.luau +210 -210
  87. package/vendor/fusion/State/For/ForTypes.luau +30 -30
  88. package/vendor/fusion/State/For/init.luau +109 -109
  89. package/vendor/fusion/State/ForKeys.luau +93 -93
  90. package/vendor/fusion/State/ForPairs.luau +96 -96
  91. package/vendor/fusion/State/ForValues.luau +93 -93
  92. package/vendor/fusion/State/Value.luau +87 -87
  93. package/vendor/fusion/State/castToState.luau +25 -25
  94. package/vendor/fusion/State/peek.luau +30 -30
  95. package/vendor/fusion/Types.luau +314 -314
  96. package/vendor/fusion/Utility/Contextual.luau +90 -90
  97. package/vendor/fusion/Utility/Safe.luau +22 -22
  98. package/vendor/fusion/Utility/isSimilar.luau +29 -29
  99. package/vendor/fusion/Utility/merge.luau +35 -35
  100. package/vendor/fusion/Utility/nameOf.luau +34 -34
  101. package/vendor/fusion/Utility/never.luau +13 -13
  102. package/vendor/fusion/Utility/nicknames.luau +10 -10
  103. package/vendor/fusion/Utility/xtypeof.luau +26 -26
  104. package/vendor/fusion/init.luau +82 -82
  105. package/vendor/profilestore/init.luau +2242 -2242
  106. package/vendor/promise/init.luau +1982 -1982
  107. package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -25
  108. package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -228
  109. package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -269
  110. package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -223
  111. package/vendor/rbxutil/buffer-util/Types.luau +60 -60
  112. package/vendor/rbxutil/buffer-util/index.d.ts +153 -153
  113. package/vendor/rbxutil/buffer-util/init.luau +41 -41
  114. package/vendor/rbxutil/buffer-util/package.json +16 -16
  115. package/vendor/rbxutil/buffer-util/wally.toml +9 -9
  116. package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -232
  117. package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -156
  118. package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -109
  119. package/vendor/rbxutil/comm/Client/init.luau +135 -135
  120. package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -295
  121. package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -211
  122. package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -211
  123. package/vendor/rbxutil/comm/Server/init.luau +140 -140
  124. package/vendor/rbxutil/comm/Types.luau +18 -18
  125. package/vendor/rbxutil/comm/Util.luau +27 -27
  126. package/vendor/rbxutil/comm/init.luau +35 -35
  127. package/vendor/rbxutil/comm/wally.toml +13 -13
  128. package/vendor/rbxutil/component/init.luau +759 -759
  129. package/vendor/rbxutil/component/init.test.luau +311 -311
  130. package/vendor/rbxutil/component/wally.toml +14 -14
  131. package/vendor/rbxutil/concur/init.luau +542 -542
  132. package/vendor/rbxutil/concur/init.test.luau +364 -364
  133. package/vendor/rbxutil/concur/wally.toml +8 -8
  134. package/vendor/rbxutil/enum-list/init.luau +101 -101
  135. package/vendor/rbxutil/enum-list/init.test.luau +91 -91
  136. package/vendor/rbxutil/enum-list/wally.toml +8 -8
  137. package/vendor/rbxutil/find/index.d.ts +20 -20
  138. package/vendor/rbxutil/find/init.luau +44 -44
  139. package/vendor/rbxutil/find/package.json +17 -17
  140. package/vendor/rbxutil/find/wally.toml +8 -8
  141. package/vendor/rbxutil/input/Gamepad.luau +559 -559
  142. package/vendor/rbxutil/input/Keyboard.luau +124 -124
  143. package/vendor/rbxutil/input/Mouse.luau +278 -278
  144. package/vendor/rbxutil/input/PreferredInput.luau +91 -91
  145. package/vendor/rbxutil/input/Touch.luau +120 -120
  146. package/vendor/rbxutil/input/init.luau +33 -33
  147. package/vendor/rbxutil/input/wally.toml +12 -12
  148. package/vendor/rbxutil/loader/index.d.ts +15 -15
  149. package/vendor/rbxutil/loader/init.luau +137 -137
  150. package/vendor/rbxutil/loader/wally.toml +8 -8
  151. package/vendor/rbxutil/log/index.d.ts +38 -38
  152. package/vendor/rbxutil/log/init.luau +746 -746
  153. package/vendor/rbxutil/log/wally.toml +8 -8
  154. package/vendor/rbxutil/net/init.luau +190 -190
  155. package/vendor/rbxutil/net/wally.toml +8 -8
  156. package/vendor/rbxutil/option/index.d.ts +44 -44
  157. package/vendor/rbxutil/option/init.luau +489 -489
  158. package/vendor/rbxutil/option/init.test.luau +342 -342
  159. package/vendor/rbxutil/option/wally.toml +8 -8
  160. package/vendor/rbxutil/pid/index.d.ts +53 -53
  161. package/vendor/rbxutil/pid/init.luau +195 -195
  162. package/vendor/rbxutil/pid/package.json +16 -16
  163. package/vendor/rbxutil/pid/wally.toml +9 -9
  164. package/vendor/rbxutil/quaternion/index.d.ts +117 -117
  165. package/vendor/rbxutil/quaternion/init.luau +570 -570
  166. package/vendor/rbxutil/quaternion/package.json +16 -16
  167. package/vendor/rbxutil/quaternion/wally.toml +9 -9
  168. package/vendor/rbxutil/query/index.d.ts +43 -43
  169. package/vendor/rbxutil/query/init.luau +117 -117
  170. package/vendor/rbxutil/query/package.json +18 -18
  171. package/vendor/rbxutil/query/wally.toml +9 -9
  172. package/vendor/rbxutil/sequent/index.d.ts +28 -28
  173. package/vendor/rbxutil/sequent/init.luau +340 -340
  174. package/vendor/rbxutil/sequent/package.json +16 -16
  175. package/vendor/rbxutil/sequent/wally.toml +9 -9
  176. package/vendor/rbxutil/ser/init.luau +175 -175
  177. package/vendor/rbxutil/ser/init.test.luau +50 -50
  178. package/vendor/rbxutil/ser/wally.toml +11 -11
  179. package/vendor/rbxutil/shake/index.d.ts +36 -36
  180. package/vendor/rbxutil/shake/init.luau +532 -532
  181. package/vendor/rbxutil/shake/init.test.luau +267 -267
  182. package/vendor/rbxutil/shake/package.json +16 -16
  183. package/vendor/rbxutil/shake/wally.toml +9 -9
  184. package/vendor/rbxutil/signal/index.d.ts +100 -100
  185. package/vendor/rbxutil/signal/init.luau +432 -432
  186. package/vendor/rbxutil/signal/init.test.luau +190 -190
  187. package/vendor/rbxutil/signal/package.json +17 -17
  188. package/vendor/rbxutil/signal/wally.toml +9 -9
  189. package/vendor/rbxutil/silo/TableWatcher.luau +65 -65
  190. package/vendor/rbxutil/silo/Util.luau +55 -55
  191. package/vendor/rbxutil/silo/init.luau +338 -338
  192. package/vendor/rbxutil/silo/init.test.luau +215 -215
  193. package/vendor/rbxutil/silo/wally.toml +8 -8
  194. package/vendor/rbxutil/spring/index.d.ts +40 -40
  195. package/vendor/rbxutil/spring/init.luau +97 -97
  196. package/vendor/rbxutil/spring/package.json +17 -17
  197. package/vendor/rbxutil/spring/wally.toml +8 -8
  198. package/vendor/rbxutil/stream/index.d.ts +88 -88
  199. package/vendor/rbxutil/stream/init.luau +597 -597
  200. package/vendor/rbxutil/stream/package.json +18 -18
  201. package/vendor/rbxutil/stream/wally.toml +9 -9
  202. package/vendor/rbxutil/streamable/Streamable.luau +202 -202
  203. package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -80
  204. package/vendor/rbxutil/streamable/init.luau +8 -8
  205. package/vendor/rbxutil/streamable/wally.toml +12 -12
  206. package/vendor/rbxutil/symbol/init.luau +56 -56
  207. package/vendor/rbxutil/symbol/init.test.luau +37 -37
  208. package/vendor/rbxutil/symbol/wally.toml +8 -8
  209. package/vendor/rbxutil/table-util/init.luau +938 -938
  210. package/vendor/rbxutil/table-util/init.test.luau +439 -439
  211. package/vendor/rbxutil/task-queue/index.d.ts +27 -27
  212. package/vendor/rbxutil/task-queue/init.luau +97 -97
  213. package/vendor/rbxutil/task-queue/wally.toml +8 -8
  214. package/vendor/rbxutil/timer/index.d.ts +81 -81
  215. package/vendor/rbxutil/timer/init.luau +249 -249
  216. package/vendor/rbxutil/timer/init.test.luau +73 -73
  217. package/vendor/rbxutil/timer/wally.toml +11 -11
  218. package/vendor/rbxutil/tree/index.d.ts +15 -15
  219. package/vendor/rbxutil/tree/init.luau +137 -137
  220. package/vendor/rbxutil/tree/wally.toml +8 -8
  221. package/vendor/rbxutil/trove/index.d.ts +46 -46
  222. package/vendor/rbxutil/trove/init.luau +787 -787
  223. package/vendor/rbxutil/trove/init.test.luau +203 -203
  224. package/vendor/rbxutil/trove/wally.toml +8 -8
  225. package/vendor/rbxutil/typed-remote/init.luau +196 -196
  226. package/vendor/rbxutil/typed-remote/wally.toml +8 -8
  227. package/vendor/rbxutil/wait-for/index.d.ts +17 -17
  228. package/vendor/rbxutil/wait-for/init.luau +257 -257
  229. package/vendor/rbxutil/wait-for/init.test.luau +182 -182
  230. package/vendor/rbxutil/wait-for/wally.toml +11 -11
  231. package/vendor/t/t.lua +1350 -1350
  232. package/vendor/testez/Context.lua +26 -26
  233. package/vendor/testez/Expectation.lua +311 -311
  234. package/vendor/testez/ExpectationContext.lua +38 -38
  235. package/vendor/testez/LifecycleHooks.lua +89 -89
  236. package/vendor/testez/Reporters/TeamCityReporter.lua +101 -101
  237. package/vendor/testez/Reporters/TextReporter.lua +105 -105
  238. package/vendor/testez/Reporters/TextReporterQuiet.lua +96 -96
  239. package/vendor/testez/TestBootstrap.lua +146 -146
  240. package/vendor/testez/TestEnum.lua +27 -27
  241. package/vendor/testez/TestPlan.lua +304 -304
  242. package/vendor/testez/TestPlanner.lua +39 -39
  243. package/vendor/testez/TestResults.lua +111 -111
  244. package/vendor/testez/TestRunner.lua +188 -188
  245. package/vendor/testez/TestSession.lua +243 -243
  246. package/vendor/testez/init.lua +39 -39
@@ -1,432 +1,432 @@
1
- -- -----------------------------------------------------------------------------
2
- -- Batched Yield-Safe Signal Implementation --
3
- -- This is a Signal class which has effectively identical behavior to a --
4
- -- normal RBXScriptSignal, with the only difference being a couple extra --
5
- -- stack frames at the bottom of the stack trace when an error is thrown. --
6
- -- This implementation caches runner coroutines, so the ability to yield in --
7
- -- the signal handlers comes at minimal extra cost over a naive signal --
8
- -- implementation that either always or never spawns a thread. --
9
- -- --
10
- -- License: --
11
- -- Licensed under the MIT license. --
12
- -- --
13
- -- Authors: --
14
- -- stravant - July 31st, 2021 - Created the file. --
15
- -- sleitnick - August 3rd, 2021 - Modified for Knit. --
16
- -- -----------------------------------------------------------------------------
17
-
18
- -- Signal types
19
- export type Connection = {
20
- Disconnect: (self: Connection) -> (),
21
- Destroy: (self: Connection) -> (),
22
- Connected: boolean,
23
- }
24
-
25
- export type Signal<T...> = {
26
- Fire: (self: Signal<T...>, T...) -> (),
27
- FireDeferred: (self: Signal<T...>, T...) -> (),
28
- Connect: (self: Signal<T...>, fn: (T...) -> ()) -> Connection,
29
- Once: (self: Signal<T...>, fn: (T...) -> ()) -> Connection,
30
- DisconnectAll: (self: Signal<T...>) -> (),
31
- GetConnections: (self: Signal<T...>) -> { Connection },
32
- Destroy: (self: Signal<T...>) -> (),
33
- Wait: (self: Signal<T...>) -> T...,
34
- }
35
-
36
- -- The currently idle thread to run the next handler on
37
- local freeRunnerThread = nil
38
-
39
- -- Function which acquires the currently idle handler runner thread, runs the
40
- -- function fn on it, and then releases the thread, returning it to being the
41
- -- currently idle one.
42
- -- If there was a currently idle runner thread already, that's okay, that old
43
- -- one will just get thrown and eventually GCed.
44
- local function acquireRunnerThreadAndCallEventHandler(fn, ...)
45
- local acquiredRunnerThread = freeRunnerThread
46
- freeRunnerThread = nil
47
- fn(...)
48
- -- The handler finished running, this runner thread is free again.
49
- freeRunnerThread = acquiredRunnerThread
50
- end
51
-
52
- -- Coroutine runner that we create coroutines of. The coroutine can be
53
- -- repeatedly resumed with functions to run followed by the argument to run
54
- -- them with.
55
- local function runEventHandlerInFreeThread(...)
56
- acquireRunnerThreadAndCallEventHandler(...)
57
- while true do
58
- acquireRunnerThreadAndCallEventHandler(coroutine.yield())
59
- end
60
- end
61
-
62
- --[=[
63
- @within Signal
64
- @interface SignalConnection
65
- .Connected boolean
66
- .Disconnect (SignalConnection) -> ()
67
-
68
- Represents a connection to a signal.
69
- ```lua
70
- local connection = signal:Connect(function() end)
71
- print(connection.Connected) --> true
72
- connection:Disconnect()
73
- print(connection.Connected) --> false
74
- ```
75
- ]=]
76
-
77
- -- Connection class
78
- local Connection = {}
79
- Connection.__index = Connection
80
-
81
- function Connection:Disconnect()
82
- if not self.Connected then
83
- return
84
- end
85
- self.Connected = false
86
-
87
- -- Unhook the node, but DON'T clear it. That way any fire calls that are
88
- -- currently sitting on this node will be able to iterate forwards off of
89
- -- it, but any subsequent fire calls will not hit it, and it will be GCed
90
- -- when no more fire calls are sitting on it.
91
- if self._signal._handlerListHead == self then
92
- self._signal._handlerListHead = self._next
93
- else
94
- local prev = self._signal._handlerListHead
95
- while prev and prev._next ~= self do
96
- prev = prev._next
97
- end
98
- if prev then
99
- prev._next = self._next
100
- end
101
- end
102
- end
103
-
104
- Connection.Destroy = Connection.Disconnect
105
-
106
- -- Make Connection strict
107
- setmetatable(Connection, {
108
- __index = function(_tb, key)
109
- error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2)
110
- end,
111
- __newindex = function(_tb, key, _value)
112
- error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2)
113
- end,
114
- })
115
-
116
- --[=[
117
- @within Signal
118
- @type ConnectionFn (...any) -> ()
119
-
120
- A function connected to a signal.
121
- ]=]
122
-
123
- --[=[
124
- @class Signal
125
-
126
- A Signal is a data structure that allows events to be dispatched
127
- and observed.
128
-
129
- This implementation is a direct copy of the de facto standard, [GoodSignal](https://devforum.roblox.com/t/lua-signal-class-comparison-optimal-goodsignal-class/1387063),
130
- with some added methods and typings.
131
-
132
- For example:
133
- ```lua
134
- local signal = Signal.new()
135
-
136
- -- Subscribe to a signal:
137
- signal:Connect(function(msg)
138
- print("Got message:", msg)
139
- end)
140
-
141
- -- Dispatch an event:
142
- signal:Fire("Hello world!")
143
- ```
144
- ]=]
145
- local Signal = {}
146
- Signal.__index = Signal
147
-
148
- --[=[
149
- Constructs a new Signal
150
-
151
- @return Signal
152
- ]=]
153
- function Signal.new<T...>(): Signal<T...>
154
- local self = setmetatable({
155
- _handlerListHead = false,
156
- _proxyHandler = nil,
157
- _yieldedThreads = nil,
158
- }, Signal)
159
-
160
- return self
161
- end
162
-
163
- --[=[
164
- Constructs a new Signal that wraps around an RBXScriptSignal.
165
-
166
- @param rbxScriptSignal RBXScriptSignal -- Existing RBXScriptSignal to wrap
167
- @return Signal
168
-
169
- For example:
170
- ```lua
171
- local signal = Signal.Wrap(workspace.ChildAdded)
172
- signal:Connect(function(part) print(part.Name .. " added") end)
173
- Instance.new("Part").Parent = workspace
174
- ```
175
- ]=]
176
- function Signal.Wrap<T...>(rbxScriptSignal: RBXScriptSignal): Signal<T...>
177
- assert(
178
- typeof(rbxScriptSignal) == "RBXScriptSignal",
179
- "Argument #1 to Signal.Wrap must be a RBXScriptSignal; got " .. typeof(rbxScriptSignal)
180
- )
181
-
182
- local signal = Signal.new()
183
- signal._proxyHandler = rbxScriptSignal:Connect(function(...)
184
- signal:Fire(...)
185
- end)
186
-
187
- return signal
188
- end
189
-
190
- --[=[
191
- Checks if the given object is a Signal.
192
-
193
- @param obj any -- Object to check
194
- @return boolean -- `true` if the object is a Signal.
195
- ]=]
196
- function Signal.Is(obj: any): boolean
197
- return type(obj) == "table" and getmetatable(obj) == Signal
198
- end
199
-
200
- --[=[
201
- @param fn ConnectionFn
202
- @return SignalConnection
203
-
204
- Connects a function to the signal, which will be called anytime the signal is fired.
205
- ```lua
206
- signal:Connect(function(msg, num)
207
- print(msg, num)
208
- end)
209
-
210
- signal:Fire("Hello", 25)
211
- ```
212
- ]=]
213
- function Signal:Connect(fn)
214
- local connection = setmetatable({
215
- Connected = true,
216
- _signal = self,
217
- _fn = fn,
218
- _next = false,
219
- }, Connection)
220
-
221
- if self._handlerListHead then
222
- connection._next = self._handlerListHead
223
- self._handlerListHead = connection
224
- else
225
- self._handlerListHead = connection
226
- end
227
-
228
- return connection
229
- end
230
-
231
- --[=[
232
- @deprecated v1.3.0 -- Use `Signal:Once` instead.
233
- @param fn ConnectionFn
234
- @return SignalConnection
235
- ]=]
236
- function Signal:ConnectOnce(fn)
237
- return self:Once(fn)
238
- end
239
-
240
- --[=[
241
- @param fn ConnectionFn
242
- @return SignalConnection
243
-
244
- Connects a function to the signal, which will be called the next time the signal fires. Once
245
- the connection is triggered, it will disconnect itself.
246
- ```lua
247
- signal:Once(function(msg, num)
248
- print(msg, num)
249
- end)
250
-
251
- signal:Fire("Hello", 25)
252
- signal:Fire("This message will not go through", 10)
253
- ```
254
- ]=]
255
- function Signal:Once(fn)
256
- local connection
257
- local done = false
258
-
259
- connection = self:Connect(function(...)
260
- if done then
261
- return
262
- end
263
-
264
- done = true
265
- connection:Disconnect()
266
- fn(...)
267
- end)
268
-
269
- return connection
270
- end
271
-
272
- function Signal:GetConnections()
273
- local items = {}
274
-
275
- local item = self._handlerListHead
276
- while item do
277
- table.insert(items, item)
278
- item = item._next
279
- end
280
-
281
- return items
282
- end
283
-
284
- -- Disconnect all handlers. Since we use a linked list it suffices to clear the
285
- -- reference to the head handler.
286
- --[=[
287
- Disconnects all connections from the signal.
288
- ```lua
289
- signal:DisconnectAll()
290
- ```
291
- ]=]
292
- function Signal:DisconnectAll()
293
- local item = self._handlerListHead
294
- while item do
295
- item.Connected = false
296
- item = item._next
297
- end
298
- self._handlerListHead = false
299
-
300
- local yieldedThreads = rawget(self, "_yieldedThreads")
301
- if yieldedThreads then
302
- for thread in yieldedThreads do
303
- if coroutine.status(thread) == "suspended" then
304
- warn(debug.traceback(thread, "signal disconnected; yielded thread cancelled", 2))
305
- task.cancel(thread)
306
- end
307
- end
308
- table.clear(self._yieldedThreads)
309
- end
310
- end
311
-
312
- -- Signal:Fire(...) implemented by running the handler functions on the
313
- -- coRunnerThread, and any time the resulting thread yielded without returning
314
- -- to us, that means that it yielded to the Roblox scheduler and has been taken
315
- -- over by Roblox scheduling, meaning we have to make a new coroutine runner.
316
- --[=[
317
- @param ... any
318
-
319
- Fire the signal, which will call all of the connected functions with the given arguments.
320
- ```lua
321
- signal:Fire("Hello")
322
-
323
- -- Any number of arguments can be fired:
324
- signal:Fire("Hello", 32, {Test = "Test"}, true)
325
- ```
326
- ]=]
327
- function Signal:Fire(...)
328
- local item = self._handlerListHead
329
- while item do
330
- if item.Connected then
331
- if not freeRunnerThread then
332
- freeRunnerThread = coroutine.create(runEventHandlerInFreeThread)
333
- end
334
- task.spawn(freeRunnerThread, item._fn, ...)
335
- end
336
- item = item._next
337
- end
338
- end
339
-
340
- --[=[
341
- @param ... any
342
-
343
- Same as `Fire`, but uses `task.defer` internally & doesn't take advantage of thread reuse.
344
- ```lua
345
- signal:FireDeferred("Hello")
346
- ```
347
- ]=]
348
- function Signal:FireDeferred(...)
349
- local item = self._handlerListHead
350
- while item do
351
- local conn = item
352
- task.defer(function(...)
353
- if conn.Connected then
354
- conn._fn(...)
355
- end
356
- end, ...)
357
- item = item._next
358
- end
359
- end
360
-
361
- --[=[
362
- @return ... any
363
- @yields
364
-
365
- Yields the current thread until the signal is fired, and returns the arguments fired from the signal.
366
- Yielding the current thread is not always desirable. If the desire is to only capture the next event
367
- fired, using `Once` might be a better solution.
368
- ```lua
369
- task.spawn(function()
370
- local msg, num = signal:Wait()
371
- print(msg, num) --> "Hello", 32
372
- end)
373
- signal:Fire("Hello", 32)
374
- ```
375
- ]=]
376
- function Signal:Wait()
377
- local yieldedThreads = rawget(self, "_yieldedThreads")
378
- if not yieldedThreads then
379
- yieldedThreads = {}
380
- rawset(self, "_yieldedThreads", yieldedThreads)
381
- end
382
-
383
- local thread = coroutine.running()
384
- yieldedThreads[thread] = true
385
-
386
- self:Once(function(...)
387
- yieldedThreads[thread] = nil
388
-
389
- if coroutine.status(thread) == "suspended" then
390
- task.spawn(thread, ...)
391
- end
392
- end)
393
-
394
- return coroutine.yield()
395
- end
396
-
397
- --[=[
398
- Cleans up the signal.
399
-
400
- Technically, this is only necessary if the signal is created using
401
- `Signal.Wrap`. Connections should be properly GC'd once the signal
402
- is no longer referenced anywhere. However, it is still good practice
403
- to include ways to strictly clean up resources. Calling `Destroy`
404
- on a signal will also disconnect all connections immediately.
405
- ```lua
406
- signal:Destroy()
407
- ```
408
- ]=]
409
- function Signal:Destroy()
410
- self:DisconnectAll()
411
-
412
- local proxyHandler = rawget(self, "_proxyHandler")
413
- if proxyHandler then
414
- proxyHandler:Disconnect()
415
- end
416
- end
417
-
418
- -- Make signal strict
419
- setmetatable(Signal, {
420
- __index = function(_tb, key)
421
- error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2)
422
- end,
423
- __newindex = function(_tb, key, _value)
424
- error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2)
425
- end,
426
- })
427
-
428
- return table.freeze({
429
- new = Signal.new,
430
- Wrap = Signal.Wrap,
431
- Is = Signal.Is,
432
- })
1
+ -- -----------------------------------------------------------------------------
2
+ -- Batched Yield-Safe Signal Implementation --
3
+ -- This is a Signal class which has effectively identical behavior to a --
4
+ -- normal RBXScriptSignal, with the only difference being a couple extra --
5
+ -- stack frames at the bottom of the stack trace when an error is thrown. --
6
+ -- This implementation caches runner coroutines, so the ability to yield in --
7
+ -- the signal handlers comes at minimal extra cost over a naive signal --
8
+ -- implementation that either always or never spawns a thread. --
9
+ -- --
10
+ -- License: --
11
+ -- Licensed under the MIT license. --
12
+ -- --
13
+ -- Authors: --
14
+ -- stravant - July 31st, 2021 - Created the file. --
15
+ -- sleitnick - August 3rd, 2021 - Modified for Knit. --
16
+ -- -----------------------------------------------------------------------------
17
+
18
+ -- Signal types
19
+ export type Connection = {
20
+ Disconnect: (self: Connection) -> (),
21
+ Destroy: (self: Connection) -> (),
22
+ Connected: boolean,
23
+ }
24
+
25
+ export type Signal<T...> = {
26
+ Fire: (self: Signal<T...>, T...) -> (),
27
+ FireDeferred: (self: Signal<T...>, T...) -> (),
28
+ Connect: (self: Signal<T...>, fn: (T...) -> ()) -> Connection,
29
+ Once: (self: Signal<T...>, fn: (T...) -> ()) -> Connection,
30
+ DisconnectAll: (self: Signal<T...>) -> (),
31
+ GetConnections: (self: Signal<T...>) -> { Connection },
32
+ Destroy: (self: Signal<T...>) -> (),
33
+ Wait: (self: Signal<T...>) -> T...,
34
+ }
35
+
36
+ -- The currently idle thread to run the next handler on
37
+ local freeRunnerThread = nil
38
+
39
+ -- Function which acquires the currently idle handler runner thread, runs the
40
+ -- function fn on it, and then releases the thread, returning it to being the
41
+ -- currently idle one.
42
+ -- If there was a currently idle runner thread already, that's okay, that old
43
+ -- one will just get thrown and eventually GCed.
44
+ local function acquireRunnerThreadAndCallEventHandler(fn, ...)
45
+ local acquiredRunnerThread = freeRunnerThread
46
+ freeRunnerThread = nil
47
+ fn(...)
48
+ -- The handler finished running, this runner thread is free again.
49
+ freeRunnerThread = acquiredRunnerThread
50
+ end
51
+
52
+ -- Coroutine runner that we create coroutines of. The coroutine can be
53
+ -- repeatedly resumed with functions to run followed by the argument to run
54
+ -- them with.
55
+ local function runEventHandlerInFreeThread(...)
56
+ acquireRunnerThreadAndCallEventHandler(...)
57
+ while true do
58
+ acquireRunnerThreadAndCallEventHandler(coroutine.yield())
59
+ end
60
+ end
61
+
62
+ --[=[
63
+ @within Signal
64
+ @interface SignalConnection
65
+ .Connected boolean
66
+ .Disconnect (SignalConnection) -> ()
67
+
68
+ Represents a connection to a signal.
69
+ ```lua
70
+ local connection = signal:Connect(function() end)
71
+ print(connection.Connected) --> true
72
+ connection:Disconnect()
73
+ print(connection.Connected) --> false
74
+ ```
75
+ ]=]
76
+
77
+ -- Connection class
78
+ local Connection = {}
79
+ Connection.__index = Connection
80
+
81
+ function Connection:Disconnect()
82
+ if not self.Connected then
83
+ return
84
+ end
85
+ self.Connected = false
86
+
87
+ -- Unhook the node, but DON'T clear it. That way any fire calls that are
88
+ -- currently sitting on this node will be able to iterate forwards off of
89
+ -- it, but any subsequent fire calls will not hit it, and it will be GCed
90
+ -- when no more fire calls are sitting on it.
91
+ if self._signal._handlerListHead == self then
92
+ self._signal._handlerListHead = self._next
93
+ else
94
+ local prev = self._signal._handlerListHead
95
+ while prev and prev._next ~= self do
96
+ prev = prev._next
97
+ end
98
+ if prev then
99
+ prev._next = self._next
100
+ end
101
+ end
102
+ end
103
+
104
+ Connection.Destroy = Connection.Disconnect
105
+
106
+ -- Make Connection strict
107
+ setmetatable(Connection, {
108
+ __index = function(_tb, key)
109
+ error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2)
110
+ end,
111
+ __newindex = function(_tb, key, _value)
112
+ error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2)
113
+ end,
114
+ })
115
+
116
+ --[=[
117
+ @within Signal
118
+ @type ConnectionFn (...any) -> ()
119
+
120
+ A function connected to a signal.
121
+ ]=]
122
+
123
+ --[=[
124
+ @class Signal
125
+
126
+ A Signal is a data structure that allows events to be dispatched
127
+ and observed.
128
+
129
+ This implementation is a direct copy of the de facto standard, [GoodSignal](https://devforum.roblox.com/t/lua-signal-class-comparison-optimal-goodsignal-class/1387063),
130
+ with some added methods and typings.
131
+
132
+ For example:
133
+ ```lua
134
+ local signal = Signal.new()
135
+
136
+ -- Subscribe to a signal:
137
+ signal:Connect(function(msg)
138
+ print("Got message:", msg)
139
+ end)
140
+
141
+ -- Dispatch an event:
142
+ signal:Fire("Hello world!")
143
+ ```
144
+ ]=]
145
+ local Signal = {}
146
+ Signal.__index = Signal
147
+
148
+ --[=[
149
+ Constructs a new Signal
150
+
151
+ @return Signal
152
+ ]=]
153
+ function Signal.new<T...>(): Signal<T...>
154
+ local self = setmetatable({
155
+ _handlerListHead = false,
156
+ _proxyHandler = nil,
157
+ _yieldedThreads = nil,
158
+ }, Signal)
159
+
160
+ return self
161
+ end
162
+
163
+ --[=[
164
+ Constructs a new Signal that wraps around an RBXScriptSignal.
165
+
166
+ @param rbxScriptSignal RBXScriptSignal -- Existing RBXScriptSignal to wrap
167
+ @return Signal
168
+
169
+ For example:
170
+ ```lua
171
+ local signal = Signal.Wrap(workspace.ChildAdded)
172
+ signal:Connect(function(part) print(part.Name .. " added") end)
173
+ Instance.new("Part").Parent = workspace
174
+ ```
175
+ ]=]
176
+ function Signal.Wrap<T...>(rbxScriptSignal: RBXScriptSignal): Signal<T...>
177
+ assert(
178
+ typeof(rbxScriptSignal) == "RBXScriptSignal",
179
+ "Argument #1 to Signal.Wrap must be a RBXScriptSignal; got " .. typeof(rbxScriptSignal)
180
+ )
181
+
182
+ local signal = Signal.new()
183
+ signal._proxyHandler = rbxScriptSignal:Connect(function(...)
184
+ signal:Fire(...)
185
+ end)
186
+
187
+ return signal
188
+ end
189
+
190
+ --[=[
191
+ Checks if the given object is a Signal.
192
+
193
+ @param obj any -- Object to check
194
+ @return boolean -- `true` if the object is a Signal.
195
+ ]=]
196
+ function Signal.Is(obj: any): boolean
197
+ return type(obj) == "table" and getmetatable(obj) == Signal
198
+ end
199
+
200
+ --[=[
201
+ @param fn ConnectionFn
202
+ @return SignalConnection
203
+
204
+ Connects a function to the signal, which will be called anytime the signal is fired.
205
+ ```lua
206
+ signal:Connect(function(msg, num)
207
+ print(msg, num)
208
+ end)
209
+
210
+ signal:Fire("Hello", 25)
211
+ ```
212
+ ]=]
213
+ function Signal:Connect(fn)
214
+ local connection = setmetatable({
215
+ Connected = true,
216
+ _signal = self,
217
+ _fn = fn,
218
+ _next = false,
219
+ }, Connection)
220
+
221
+ if self._handlerListHead then
222
+ connection._next = self._handlerListHead
223
+ self._handlerListHead = connection
224
+ else
225
+ self._handlerListHead = connection
226
+ end
227
+
228
+ return connection
229
+ end
230
+
231
+ --[=[
232
+ @deprecated v1.3.0 -- Use `Signal:Once` instead.
233
+ @param fn ConnectionFn
234
+ @return SignalConnection
235
+ ]=]
236
+ function Signal:ConnectOnce(fn)
237
+ return self:Once(fn)
238
+ end
239
+
240
+ --[=[
241
+ @param fn ConnectionFn
242
+ @return SignalConnection
243
+
244
+ Connects a function to the signal, which will be called the next time the signal fires. Once
245
+ the connection is triggered, it will disconnect itself.
246
+ ```lua
247
+ signal:Once(function(msg, num)
248
+ print(msg, num)
249
+ end)
250
+
251
+ signal:Fire("Hello", 25)
252
+ signal:Fire("This message will not go through", 10)
253
+ ```
254
+ ]=]
255
+ function Signal:Once(fn)
256
+ local connection
257
+ local done = false
258
+
259
+ connection = self:Connect(function(...)
260
+ if done then
261
+ return
262
+ end
263
+
264
+ done = true
265
+ connection:Disconnect()
266
+ fn(...)
267
+ end)
268
+
269
+ return connection
270
+ end
271
+
272
+ function Signal:GetConnections()
273
+ local items = {}
274
+
275
+ local item = self._handlerListHead
276
+ while item do
277
+ table.insert(items, item)
278
+ item = item._next
279
+ end
280
+
281
+ return items
282
+ end
283
+
284
+ -- Disconnect all handlers. Since we use a linked list it suffices to clear the
285
+ -- reference to the head handler.
286
+ --[=[
287
+ Disconnects all connections from the signal.
288
+ ```lua
289
+ signal:DisconnectAll()
290
+ ```
291
+ ]=]
292
+ function Signal:DisconnectAll()
293
+ local item = self._handlerListHead
294
+ while item do
295
+ item.Connected = false
296
+ item = item._next
297
+ end
298
+ self._handlerListHead = false
299
+
300
+ local yieldedThreads = rawget(self, "_yieldedThreads")
301
+ if yieldedThreads then
302
+ for thread in yieldedThreads do
303
+ if coroutine.status(thread) == "suspended" then
304
+ warn(debug.traceback(thread, "signal disconnected; yielded thread cancelled", 2))
305
+ task.cancel(thread)
306
+ end
307
+ end
308
+ table.clear(self._yieldedThreads)
309
+ end
310
+ end
311
+
312
+ -- Signal:Fire(...) implemented by running the handler functions on the
313
+ -- coRunnerThread, and any time the resulting thread yielded without returning
314
+ -- to us, that means that it yielded to the Roblox scheduler and has been taken
315
+ -- over by Roblox scheduling, meaning we have to make a new coroutine runner.
316
+ --[=[
317
+ @param ... any
318
+
319
+ Fire the signal, which will call all of the connected functions with the given arguments.
320
+ ```lua
321
+ signal:Fire("Hello")
322
+
323
+ -- Any number of arguments can be fired:
324
+ signal:Fire("Hello", 32, {Test = "Test"}, true)
325
+ ```
326
+ ]=]
327
+ function Signal:Fire(...)
328
+ local item = self._handlerListHead
329
+ while item do
330
+ if item.Connected then
331
+ if not freeRunnerThread then
332
+ freeRunnerThread = coroutine.create(runEventHandlerInFreeThread)
333
+ end
334
+ task.spawn(freeRunnerThread, item._fn, ...)
335
+ end
336
+ item = item._next
337
+ end
338
+ end
339
+
340
+ --[=[
341
+ @param ... any
342
+
343
+ Same as `Fire`, but uses `task.defer` internally & doesn't take advantage of thread reuse.
344
+ ```lua
345
+ signal:FireDeferred("Hello")
346
+ ```
347
+ ]=]
348
+ function Signal:FireDeferred(...)
349
+ local item = self._handlerListHead
350
+ while item do
351
+ local conn = item
352
+ task.defer(function(...)
353
+ if conn.Connected then
354
+ conn._fn(...)
355
+ end
356
+ end, ...)
357
+ item = item._next
358
+ end
359
+ end
360
+
361
+ --[=[
362
+ @return ... any
363
+ @yields
364
+
365
+ Yields the current thread until the signal is fired, and returns the arguments fired from the signal.
366
+ Yielding the current thread is not always desirable. If the desire is to only capture the next event
367
+ fired, using `Once` might be a better solution.
368
+ ```lua
369
+ task.spawn(function()
370
+ local msg, num = signal:Wait()
371
+ print(msg, num) --> "Hello", 32
372
+ end)
373
+ signal:Fire("Hello", 32)
374
+ ```
375
+ ]=]
376
+ function Signal:Wait()
377
+ local yieldedThreads = rawget(self, "_yieldedThreads")
378
+ if not yieldedThreads then
379
+ yieldedThreads = {}
380
+ rawset(self, "_yieldedThreads", yieldedThreads)
381
+ end
382
+
383
+ local thread = coroutine.running()
384
+ yieldedThreads[thread] = true
385
+
386
+ self:Once(function(...)
387
+ yieldedThreads[thread] = nil
388
+
389
+ if coroutine.status(thread) == "suspended" then
390
+ task.spawn(thread, ...)
391
+ end
392
+ end)
393
+
394
+ return coroutine.yield()
395
+ end
396
+
397
+ --[=[
398
+ Cleans up the signal.
399
+
400
+ Technically, this is only necessary if the signal is created using
401
+ `Signal.Wrap`. Connections should be properly GC'd once the signal
402
+ is no longer referenced anywhere. However, it is still good practice
403
+ to include ways to strictly clean up resources. Calling `Destroy`
404
+ on a signal will also disconnect all connections immediately.
405
+ ```lua
406
+ signal:Destroy()
407
+ ```
408
+ ]=]
409
+ function Signal:Destroy()
410
+ self:DisconnectAll()
411
+
412
+ local proxyHandler = rawget(self, "_proxyHandler")
413
+ if proxyHandler then
414
+ proxyHandler:Disconnect()
415
+ end
416
+ end
417
+
418
+ -- Make signal strict
419
+ setmetatable(Signal, {
420
+ __index = function(_tb, key)
421
+ error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2)
422
+ end,
423
+ __newindex = function(_tb, key, _value)
424
+ error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2)
425
+ end,
426
+ })
427
+
428
+ return table.freeze({
429
+ new = Signal.new,
430
+ Wrap = Signal.Wrap,
431
+ Is = Signal.Is,
432
+ })