roblox-opencode 1.0.0

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 (248) hide show
  1. package/README.md +122 -0
  2. package/commands/setup-game.md +108 -0
  3. package/commands/sync-check.md +53 -0
  4. package/core/roblox-core.md +93 -0
  5. package/dist/server.js +167 -0
  6. package/package.json +35 -0
  7. package/skills/roblox-analytics/SKILL.md +277 -0
  8. package/skills/roblox-analytics/references/event-batcher.luau +75 -0
  9. package/skills/roblox-animation-vfx/SKILL.md +1325 -0
  10. package/skills/roblox-architecture/SKILL.md +863 -0
  11. package/skills/roblox-architecture/references/combat-systems.md +1381 -0
  12. package/skills/roblox-code-review/SKILL.md +687 -0
  13. package/skills/roblox-data/SKILL.md +889 -0
  14. package/skills/roblox-data/references/inventory-systems.md +1729 -0
  15. package/skills/roblox-debug/SKILL.md +99 -0
  16. package/skills/roblox-gui/SKILL.md +1103 -0
  17. package/skills/roblox-gui-fusion/SKILL.md +150 -0
  18. package/skills/roblox-gui-fusion/references/inventory.luau +427 -0
  19. package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -0
  20. package/skills/roblox-gui-fusion/references/shop.luau +411 -0
  21. package/skills/roblox-luau-mastery/SKILL.md +1519 -0
  22. package/skills/roblox-monetization/SKILL.md +1084 -0
  23. package/skills/roblox-monetization/references/process-receipt.luau +131 -0
  24. package/skills/roblox-networking/SKILL.md +669 -0
  25. package/skills/roblox-networking/references/remote-validator.luau +193 -0
  26. package/skills/roblox-publish-checklist/SKILL.md +128 -0
  27. package/skills/roblox-runtime/SKILL.md +753 -0
  28. package/skills/roblox-sharp-edges/SKILL.md +295 -0
  29. package/skills/roblox-sync/SKILL.md +126 -0
  30. package/skills/roblox-testing/SKILL.md +943 -0
  31. package/skills/roblox-tooling/SKILL.md +150 -0
  32. package/vendor/LICENSES/ProfileStore-LICENSE +201 -0
  33. package/vendor/LICENSES/RbxUtil-LICENSE +7 -0
  34. package/vendor/LICENSES/promise-LICENSE +21 -0
  35. package/vendor/LICENSES/t-LICENSE +21 -0
  36. package/vendor/LICENSES/testez-LICENSE +201 -0
  37. package/vendor/README.md +84 -0
  38. package/vendor/fusion/Animation/ExternalTime.luau +84 -0
  39. package/vendor/fusion/Animation/Spring.luau +322 -0
  40. package/vendor/fusion/Animation/Stopwatch.luau +128 -0
  41. package/vendor/fusion/Animation/Tween.luau +187 -0
  42. package/vendor/fusion/Animation/getTweenDuration.luau +27 -0
  43. package/vendor/fusion/Animation/getTweenRatio.luau +47 -0
  44. package/vendor/fusion/Animation/lerpType.luau +164 -0
  45. package/vendor/fusion/Animation/packType.luau +100 -0
  46. package/vendor/fusion/Animation/springCoefficients.luau +80 -0
  47. package/vendor/fusion/Animation/unpackType.luau +103 -0
  48. package/vendor/fusion/Colour/Oklab.luau +70 -0
  49. package/vendor/fusion/Colour/sRGB.luau +55 -0
  50. package/vendor/fusion/External.luau +168 -0
  51. package/vendor/fusion/ExternalDebug.luau +70 -0
  52. package/vendor/fusion/Graph/Observer.luau +114 -0
  53. package/vendor/fusion/Graph/castToGraph.luau +29 -0
  54. package/vendor/fusion/Graph/change.luau +81 -0
  55. package/vendor/fusion/Graph/depend.luau +33 -0
  56. package/vendor/fusion/Graph/evaluate.luau +56 -0
  57. package/vendor/fusion/Instances/Attribute.luau +58 -0
  58. package/vendor/fusion/Instances/AttributeChange.luau +47 -0
  59. package/vendor/fusion/Instances/AttributeOut.luau +63 -0
  60. package/vendor/fusion/Instances/Child.luau +21 -0
  61. package/vendor/fusion/Instances/Children.luau +148 -0
  62. package/vendor/fusion/Instances/Hydrate.luau +33 -0
  63. package/vendor/fusion/Instances/New.luau +53 -0
  64. package/vendor/fusion/Instances/OnChange.luau +50 -0
  65. package/vendor/fusion/Instances/OnEvent.luau +54 -0
  66. package/vendor/fusion/Instances/Out.luau +69 -0
  67. package/vendor/fusion/Instances/applyInstanceProps.luau +149 -0
  68. package/vendor/fusion/Instances/defaultProps.luau +194 -0
  69. package/vendor/fusion/LICENSE +21 -0
  70. package/vendor/fusion/Logging/formatError.luau +49 -0
  71. package/vendor/fusion/Logging/messages.luau +52 -0
  72. package/vendor/fusion/Logging/parseError.luau +25 -0
  73. package/vendor/fusion/Memory/checkLifetime.luau +134 -0
  74. package/vendor/fusion/Memory/deriveScope.luau +24 -0
  75. package/vendor/fusion/Memory/deriveScopeImpl.luau +45 -0
  76. package/vendor/fusion/Memory/doCleanup.luau +79 -0
  77. package/vendor/fusion/Memory/innerScope.luau +34 -0
  78. package/vendor/fusion/Memory/legacyCleanup.luau +18 -0
  79. package/vendor/fusion/Memory/needsDestruction.luau +17 -0
  80. package/vendor/fusion/Memory/poisonScope.luau +34 -0
  81. package/vendor/fusion/Memory/scopePool.luau +55 -0
  82. package/vendor/fusion/Memory/scoped.luau +27 -0
  83. package/vendor/fusion/Memory/whichLivesLonger.luau +75 -0
  84. package/vendor/fusion/RobloxExternal.luau +98 -0
  85. package/vendor/fusion/State/Computed.luau +139 -0
  86. package/vendor/fusion/State/For/Disassembly.luau +211 -0
  87. package/vendor/fusion/State/For/ForTypes.luau +30 -0
  88. package/vendor/fusion/State/For/init.luau +110 -0
  89. package/vendor/fusion/State/ForKeys.luau +94 -0
  90. package/vendor/fusion/State/ForPairs.luau +97 -0
  91. package/vendor/fusion/State/ForValues.luau +94 -0
  92. package/vendor/fusion/State/Value.luau +88 -0
  93. package/vendor/fusion/State/castToState.luau +26 -0
  94. package/vendor/fusion/State/peek.luau +31 -0
  95. package/vendor/fusion/State/updateAll.luau +1 -0
  96. package/vendor/fusion/Types.luau +314 -0
  97. package/vendor/fusion/Utility/Contextual.luau +91 -0
  98. package/vendor/fusion/Utility/Safe.luau +23 -0
  99. package/vendor/fusion/Utility/isSimilar.luau +29 -0
  100. package/vendor/fusion/Utility/merge.luau +35 -0
  101. package/vendor/fusion/Utility/nameOf.luau +35 -0
  102. package/vendor/fusion/Utility/never.luau +14 -0
  103. package/vendor/fusion/Utility/nicknames.luau +11 -0
  104. package/vendor/fusion/Utility/xtypeof.luau +27 -0
  105. package/vendor/fusion/init.luau +82 -0
  106. package/vendor/profilestore/init.luau +2243 -0
  107. package/vendor/promise/init.luau +1982 -0
  108. package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -0
  109. package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -0
  110. package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -0
  111. package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -0
  112. package/vendor/rbxutil/buffer-util/Types.luau +60 -0
  113. package/vendor/rbxutil/buffer-util/index.d.ts +153 -0
  114. package/vendor/rbxutil/buffer-util/init.luau +41 -0
  115. package/vendor/rbxutil/buffer-util/package.json +16 -0
  116. package/vendor/rbxutil/buffer-util/wally.toml +9 -0
  117. package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -0
  118. package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -0
  119. package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -0
  120. package/vendor/rbxutil/comm/Client/init.luau +135 -0
  121. package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -0
  122. package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -0
  123. package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -0
  124. package/vendor/rbxutil/comm/Server/init.luau +140 -0
  125. package/vendor/rbxutil/comm/Types.luau +18 -0
  126. package/vendor/rbxutil/comm/Util.luau +27 -0
  127. package/vendor/rbxutil/comm/init.luau +35 -0
  128. package/vendor/rbxutil/comm/wally.toml +13 -0
  129. package/vendor/rbxutil/component/init.luau +759 -0
  130. package/vendor/rbxutil/component/init.test.luau +311 -0
  131. package/vendor/rbxutil/component/wally.toml +14 -0
  132. package/vendor/rbxutil/concur/init.luau +542 -0
  133. package/vendor/rbxutil/concur/init.test.luau +364 -0
  134. package/vendor/rbxutil/concur/wally.toml +8 -0
  135. package/vendor/rbxutil/enum-list/init.luau +101 -0
  136. package/vendor/rbxutil/enum-list/init.test.luau +91 -0
  137. package/vendor/rbxutil/enum-list/wally.toml +8 -0
  138. package/vendor/rbxutil/find/index.d.ts +20 -0
  139. package/vendor/rbxutil/find/init.luau +44 -0
  140. package/vendor/rbxutil/find/package.json +17 -0
  141. package/vendor/rbxutil/find/wally.toml +8 -0
  142. package/vendor/rbxutil/input/Gamepad.luau +559 -0
  143. package/vendor/rbxutil/input/Keyboard.luau +124 -0
  144. package/vendor/rbxutil/input/Mouse.luau +278 -0
  145. package/vendor/rbxutil/input/PreferredInput.luau +91 -0
  146. package/vendor/rbxutil/input/Touch.luau +120 -0
  147. package/vendor/rbxutil/input/init.luau +33 -0
  148. package/vendor/rbxutil/input/wally.toml +12 -0
  149. package/vendor/rbxutil/loader/index.d.ts +15 -0
  150. package/vendor/rbxutil/loader/init.luau +137 -0
  151. package/vendor/rbxutil/loader/wally.toml +8 -0
  152. package/vendor/rbxutil/log/index.d.ts +38 -0
  153. package/vendor/rbxutil/log/init.luau +746 -0
  154. package/vendor/rbxutil/log/wally.toml +8 -0
  155. package/vendor/rbxutil/net/init.luau +190 -0
  156. package/vendor/rbxutil/net/wally.toml +8 -0
  157. package/vendor/rbxutil/option/index.d.ts +44 -0
  158. package/vendor/rbxutil/option/init.luau +489 -0
  159. package/vendor/rbxutil/option/init.test.luau +342 -0
  160. package/vendor/rbxutil/option/wally.toml +8 -0
  161. package/vendor/rbxutil/pid/index.d.ts +53 -0
  162. package/vendor/rbxutil/pid/init.luau +195 -0
  163. package/vendor/rbxutil/pid/package.json +16 -0
  164. package/vendor/rbxutil/pid/wally.toml +9 -0
  165. package/vendor/rbxutil/quaternion/index.d.ts +117 -0
  166. package/vendor/rbxutil/quaternion/init.luau +570 -0
  167. package/vendor/rbxutil/quaternion/package.json +16 -0
  168. package/vendor/rbxutil/quaternion/wally.toml +9 -0
  169. package/vendor/rbxutil/query/index.d.ts +43 -0
  170. package/vendor/rbxutil/query/init.luau +117 -0
  171. package/vendor/rbxutil/query/package.json +18 -0
  172. package/vendor/rbxutil/query/wally.toml +9 -0
  173. package/vendor/rbxutil/sequent/index.d.ts +28 -0
  174. package/vendor/rbxutil/sequent/init.luau +340 -0
  175. package/vendor/rbxutil/sequent/package.json +16 -0
  176. package/vendor/rbxutil/sequent/wally.toml +9 -0
  177. package/vendor/rbxutil/ser/init.luau +175 -0
  178. package/vendor/rbxutil/ser/init.test.luau +50 -0
  179. package/vendor/rbxutil/ser/wally.toml +11 -0
  180. package/vendor/rbxutil/shake/index.d.ts +36 -0
  181. package/vendor/rbxutil/shake/init.luau +532 -0
  182. package/vendor/rbxutil/shake/init.test.luau +267 -0
  183. package/vendor/rbxutil/shake/package.json +16 -0
  184. package/vendor/rbxutil/shake/wally.toml +9 -0
  185. package/vendor/rbxutil/signal/index.d.ts +100 -0
  186. package/vendor/rbxutil/signal/init.luau +432 -0
  187. package/vendor/rbxutil/signal/init.test.luau +190 -0
  188. package/vendor/rbxutil/signal/package.json +17 -0
  189. package/vendor/rbxutil/signal/wally.toml +9 -0
  190. package/vendor/rbxutil/silo/TableWatcher.luau +65 -0
  191. package/vendor/rbxutil/silo/Util.luau +55 -0
  192. package/vendor/rbxutil/silo/init.luau +338 -0
  193. package/vendor/rbxutil/silo/init.test.luau +215 -0
  194. package/vendor/rbxutil/silo/wally.toml +8 -0
  195. package/vendor/rbxutil/spring/index.d.ts +40 -0
  196. package/vendor/rbxutil/spring/init.luau +97 -0
  197. package/vendor/rbxutil/spring/package.json +17 -0
  198. package/vendor/rbxutil/spring/wally.toml +8 -0
  199. package/vendor/rbxutil/stream/index.d.ts +88 -0
  200. package/vendor/rbxutil/stream/init.luau +597 -0
  201. package/vendor/rbxutil/stream/package.json +18 -0
  202. package/vendor/rbxutil/stream/wally.toml +9 -0
  203. package/vendor/rbxutil/streamable/Streamable.luau +202 -0
  204. package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -0
  205. package/vendor/rbxutil/streamable/init.luau +8 -0
  206. package/vendor/rbxutil/streamable/wally.toml +12 -0
  207. package/vendor/rbxutil/symbol/init.luau +56 -0
  208. package/vendor/rbxutil/symbol/init.test.luau +37 -0
  209. package/vendor/rbxutil/symbol/wally.toml +8 -0
  210. package/vendor/rbxutil/table-util/init.luau +938 -0
  211. package/vendor/rbxutil/table-util/init.test.luau +439 -0
  212. package/vendor/rbxutil/table-util/wally.toml +8 -0
  213. package/vendor/rbxutil/task-queue/index.d.ts +27 -0
  214. package/vendor/rbxutil/task-queue/init.luau +97 -0
  215. package/vendor/rbxutil/task-queue/wally.toml +8 -0
  216. package/vendor/rbxutil/timer/index.d.ts +81 -0
  217. package/vendor/rbxutil/timer/init.luau +249 -0
  218. package/vendor/rbxutil/timer/init.test.luau +73 -0
  219. package/vendor/rbxutil/timer/wally.toml +11 -0
  220. package/vendor/rbxutil/tree/index.d.ts +15 -0
  221. package/vendor/rbxutil/tree/init.luau +137 -0
  222. package/vendor/rbxutil/tree/wally.toml +8 -0
  223. package/vendor/rbxutil/trove/index.d.ts +46 -0
  224. package/vendor/rbxutil/trove/init.luau +787 -0
  225. package/vendor/rbxutil/trove/init.test.luau +203 -0
  226. package/vendor/rbxutil/trove/wally.toml +8 -0
  227. package/vendor/rbxutil/typed-remote/init.luau +196 -0
  228. package/vendor/rbxutil/typed-remote/wally.toml +8 -0
  229. package/vendor/rbxutil/wait-for/index.d.ts +17 -0
  230. package/vendor/rbxutil/wait-for/init.luau +257 -0
  231. package/vendor/rbxutil/wait-for/init.test.luau +182 -0
  232. package/vendor/rbxutil/wait-for/wally.toml +11 -0
  233. package/vendor/t/t.lua +1350 -0
  234. package/vendor/testez/Context.lua +26 -0
  235. package/vendor/testez/Expectation.lua +311 -0
  236. package/vendor/testez/ExpectationContext.lua +38 -0
  237. package/vendor/testez/LifecycleHooks.lua +89 -0
  238. package/vendor/testez/Reporters/TeamCityReporter.lua +102 -0
  239. package/vendor/testez/Reporters/TextReporter.lua +106 -0
  240. package/vendor/testez/Reporters/TextReporterQuiet.lua +97 -0
  241. package/vendor/testez/TestBootstrap.lua +147 -0
  242. package/vendor/testez/TestEnum.lua +28 -0
  243. package/vendor/testez/TestPlan.lua +304 -0
  244. package/vendor/testez/TestPlanner.lua +40 -0
  245. package/vendor/testez/TestResults.lua +112 -0
  246. package/vendor/testez/TestRunner.lua +188 -0
  247. package/vendor/testez/TestSession.lua +243 -0
  248. package/vendor/testez/init.lua +40 -0
@@ -0,0 +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
+ })
@@ -0,0 +1,190 @@
1
+ local ServerScriptService = game:GetService("ServerScriptService")
2
+
3
+ local Test = require(ServerScriptService.TestRunner.Test)
4
+
5
+ local function AwaitCondition(predicate: () -> boolean, timeout: number?)
6
+ local start = os.clock()
7
+ timeout = (timeout or 10)
8
+ while true do
9
+ if predicate() then
10
+ return true
11
+ end
12
+ if (os.clock() - start) > timeout then
13
+ return false
14
+ end
15
+ task.wait()
16
+ end
17
+ end
18
+
19
+ return function(ctx: Test.TestContext)
20
+ local Signal = require(script.Parent)
21
+
22
+ local signal
23
+
24
+ local function NumConns(sig)
25
+ sig = sig or signal
26
+ return #sig:GetConnections()
27
+ end
28
+
29
+ ctx:BeforeEach(function()
30
+ signal = Signal.new()
31
+ end)
32
+
33
+ ctx:AfterEach(function()
34
+ signal:Destroy()
35
+ end)
36
+
37
+ ctx:Describe("Constructor", function()
38
+ ctx:Test("should create a new signal and fire it", function()
39
+ ctx:Expect(Signal.Is(signal)):ToBe(true)
40
+ task.defer(function()
41
+ signal:Fire(10, 20)
42
+ end)
43
+ local n1, n2 = signal:Wait()
44
+ ctx:Expect(n1):ToBe(10)
45
+ ctx:Expect(n2):ToBe(20)
46
+ end)
47
+
48
+ ctx:Test("should create a proxy signal and connect to it", function()
49
+ local signalWrap = Signal.Wrap(game:GetService("RunService").Heartbeat)
50
+ ctx:Expect(Signal.Is(signalWrap)):ToBe(true)
51
+ local fired = false
52
+ signalWrap:Connect(function()
53
+ fired = true
54
+ end)
55
+ ctx:Expect(AwaitCondition(function()
56
+ return fired
57
+ end, 2)):ToBe(true)
58
+ signalWrap:Destroy()
59
+ end)
60
+ end)
61
+
62
+ ctx:Describe("FireDeferred", function()
63
+ ctx:Test("should be able to fire primitive argument", function()
64
+ local send = 10
65
+ local value
66
+ signal:Connect(function(v)
67
+ value = v
68
+ end)
69
+ signal:FireDeferred(send)
70
+ ctx:Expect(AwaitCondition(function()
71
+ return (send == value)
72
+ end, 1)):ToBe(true)
73
+ end)
74
+
75
+ ctx:Test("should be able to fire a reference based argument", function()
76
+ local send = { 10, 20 }
77
+ local value
78
+ signal:Connect(function(v)
79
+ value = v
80
+ end)
81
+ signal:FireDeferred(send)
82
+ ctx:Expect(AwaitCondition(function()
83
+ return (send == value)
84
+ end, 1)):ToBe(true)
85
+ end)
86
+ end)
87
+
88
+ ctx:Describe("Fire", function()
89
+ ctx:Test("should be able to fire primitive argument", function()
90
+ local send = 10
91
+ local value
92
+ signal:Connect(function(v)
93
+ value = v
94
+ end)
95
+ signal:Fire(send)
96
+ ctx:Expect(value):ToBe(send)
97
+ end)
98
+
99
+ ctx:Test("should be able to fire a reference based argument", function()
100
+ local send = { 10, 20 }
101
+ local value
102
+ signal:Connect(function(v)
103
+ value = v
104
+ end)
105
+ signal:Fire(send)
106
+ ctx:Expect(value):ToBe(send)
107
+ end)
108
+ end)
109
+
110
+ ctx:Describe("ConnectOnce", function()
111
+ ctx:Test("should only capture first fire", function()
112
+ local value
113
+ local c = signal:ConnectOnce(function(v)
114
+ value = v
115
+ end)
116
+ ctx:Expect(c.Connected):ToBe(true)
117
+ signal:Fire(10)
118
+ ctx:Expect(c.Connected):ToBe(false)
119
+ signal:Fire(20)
120
+ ctx:Expect(value):ToBe(10)
121
+ end)
122
+ end)
123
+
124
+ ctx:Describe("Wait", function()
125
+ ctx:Test("should be able to wait for a signal to fire", function()
126
+ task.defer(function()
127
+ signal:Fire(10, 20, 30)
128
+ end)
129
+ local n1, n2, n3 = signal:Wait()
130
+ ctx:Expect(n1):ToBe(10)
131
+ ctx:Expect(n2):ToBe(20)
132
+ ctx:Expect(n3):ToBe(30)
133
+ end)
134
+ end)
135
+
136
+ ctx:Describe("DisconnectAll", function()
137
+ ctx:Test("should disconnect all connections", function()
138
+ signal:Connect(function() end)
139
+ signal:Connect(function() end)
140
+ ctx:Expect(NumConns()):ToBe(2)
141
+ signal:DisconnectAll()
142
+ ctx:Expect(NumConns()):ToBe(0)
143
+ end)
144
+ end)
145
+
146
+ ctx:Describe("Disconnect", function()
147
+ ctx:Test("should disconnect connection", function()
148
+ local con = signal:Connect(function() end)
149
+ ctx:Expect(NumConns()):ToBe(1)
150
+ con:Disconnect()
151
+ ctx:Expect(NumConns()):ToBe(0)
152
+ end)
153
+
154
+ ctx:Test("should still work if connections disconnected while firing", function()
155
+ local a = 0
156
+ local c
157
+ signal:Connect(function()
158
+ a += 1
159
+ end)
160
+ c = signal:Connect(function()
161
+ c:Disconnect()
162
+ a += 1
163
+ end)
164
+ signal:Connect(function()
165
+ a += 1
166
+ end)
167
+ signal:Fire()
168
+ ctx:Expect(a):ToBe(3)
169
+ end)
170
+
171
+ ctx:Test("should still work if connections disconnected while firing deferred", function()
172
+ local a = 0
173
+ local c
174
+ signal:Connect(function()
175
+ a += 1
176
+ end)
177
+ c = signal:Connect(function()
178
+ c:Disconnect()
179
+ a += 1
180
+ end)
181
+ signal:Connect(function()
182
+ a += 1
183
+ end)
184
+ signal:FireDeferred()
185
+ ctx:Expect(AwaitCondition(function()
186
+ return a == 3
187
+ end)):ToBe(true)
188
+ end)
189
+ end)
190
+ end
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@rbxutil/signal",
3
+ "version": "2.0.3",
4
+ "main": "init.luau",
5
+ "repository": "github:Sleitnick/RbxUtil",
6
+ "license": "MIT",
7
+ "types": "index.d.ts",
8
+ "files": [
9
+ "./*",
10
+ "!*.toml",
11
+ "!*.json",
12
+ "!*.test.luau"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ }
17
+ }
@@ -0,0 +1,9 @@
1
+ [package]
2
+ name = "sleitnick/signal"
3
+ description = "Signal class"
4
+ version = "2.0.3"
5
+ license = "MIT"
6
+ authors = ["Stephen Leitnick"]
7
+ registry = "https://github.com/UpliftGames/wally-index"
8
+ realm = "shared"
9
+ exclude = ["node_modules", "package.json", "**/*.ts"]
@@ -0,0 +1,65 @@
1
+ --!strict
2
+
3
+ -- TableWatcher
4
+ -- Stephen Leitnick
5
+ -- April 29, 2022
6
+
7
+ type AnyTable = { [any]: any }
8
+
9
+ type Watcher = {
10
+ Changes: AnyTable,
11
+ Tbl: AnyTable,
12
+ }
13
+
14
+ local Util = require(script.Parent.Util)
15
+
16
+ local watchers: { [TableWatcher]: Watcher } = {}
17
+ setmetatable(watchers, { __mode = "k" })
18
+
19
+ local WatcherMt = {}
20
+
21
+ function WatcherMt:__index(index)
22
+ local w = watchers[self]
23
+ local c = w.Changes[index]
24
+ if c ~= nil then
25
+ if c == Util.None then
26
+ return nil
27
+ else
28
+ return c
29
+ end
30
+ end
31
+ return w.Tbl[index]
32
+ end
33
+
34
+ function WatcherMt:__newindex(index, value)
35
+ local w = watchers[self]
36
+ if w.Tbl[index] == value then
37
+ return
38
+ end
39
+ if value == nil then
40
+ w.Changes[index] = Util.None
41
+ else
42
+ w.Changes[index] = value
43
+ end
44
+ end
45
+
46
+ function WatcherMt:__call()
47
+ local w = watchers[self]
48
+ if next(w.Changes) == nil then
49
+ return w.Tbl
50
+ end
51
+ return Util.Extend(w.Tbl, w.Changes)
52
+ end
53
+
54
+ local function TableWatcher(t: AnyTable): TableWatcher
55
+ local watcher = setmetatable({}, WatcherMt)
56
+ watchers[watcher] = {
57
+ Changes = {},
58
+ Tbl = t,
59
+ }
60
+ return watcher
61
+ end
62
+
63
+ type TableWatcher = typeof(setmetatable({}, WatcherMt))
64
+
65
+ return TableWatcher