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,203 @@
1
+ local ServerScriptService = game:GetService("ServerScriptService")
2
+
3
+ local Test = require(ServerScriptService.TestRunner.Test)
4
+
5
+ return function(ctx: Test.TestContext)
6
+ local Trove = require(script.Parent)
7
+
8
+ ctx:Describe("Trove", function()
9
+ local trove
10
+
11
+ ctx:BeforeEach(function()
12
+ trove = Trove.new()
13
+ end)
14
+
15
+ ctx:AfterEach(function()
16
+ if trove then
17
+ trove:Destroy()
18
+ trove = nil
19
+ end
20
+ end)
21
+
22
+ ctx:Test("should add and clean up roblox instance", function()
23
+ local part = Instance.new("Part")
24
+ part.Parent = workspace
25
+ trove:Add(part)
26
+ trove:Destroy()
27
+ ctx:Expect(part.Parent):ToBeNil()
28
+ end)
29
+
30
+ ctx:Test("should add and clean up roblox connection", function()
31
+ local connection = workspace.Changed:Connect(function() end)
32
+ trove:Add(connection)
33
+ trove:Destroy()
34
+ ctx:Expect(connection.Connected):ToBe(false)
35
+ end)
36
+
37
+ ctx:Test("should add and clean up a table with a destroy method", function()
38
+ local tbl = { Destroyed = false }
39
+ function tbl:Destroy()
40
+ self.Destroyed = true
41
+ end
42
+ trove:Add(tbl)
43
+ trove:Destroy()
44
+ ctx:Expect(tbl.Destroyed):ToBe(true)
45
+ end)
46
+
47
+ ctx:Test("should add and clean up a table with a disconnect method", function()
48
+ local tbl = { Connected = true }
49
+ function tbl:Disconnect()
50
+ self.Connected = false
51
+ end
52
+ trove:Add(tbl)
53
+ trove:Destroy()
54
+ ctx:Expect(tbl.Connected):ToBe(false)
55
+ end)
56
+
57
+ ctx:Test("should add and clean up a function", function()
58
+ local fired = false
59
+ trove:Add(function()
60
+ fired = true
61
+ end)
62
+ trove:Destroy()
63
+ ctx:Expect(fired):ToBe(true)
64
+ end)
65
+
66
+ ctx:Test("should allow a custom cleanup method", function()
67
+ local tbl = { Cleaned = false }
68
+ function tbl:Cleanup()
69
+ self.Cleaned = true
70
+ end
71
+ trove:Add(tbl, "Cleanup")
72
+ trove:Destroy()
73
+ ctx:Expect(tbl.Cleaned):ToBe(true)
74
+ end)
75
+
76
+ ctx:Test("should return the object passed to add", function()
77
+ local part = Instance.new("Part")
78
+ local part2 = trove:Add(part)
79
+ ctx:Expect(part):ToBe(part2)
80
+ trove:Destroy()
81
+ end)
82
+
83
+ ctx:Test("should fail to add object without proper cleanup method", function()
84
+ local tbl = {}
85
+ ctx:Expect(function()
86
+ trove:Add(tbl)
87
+ end):ToThrow()
88
+ end)
89
+
90
+ ctx:Test("should construct an object and add it", function()
91
+ local class = {}
92
+ class.__index = class
93
+ function class.new(msg)
94
+ local self = setmetatable({}, class)
95
+ self._msg = msg
96
+ self._destroyed = false
97
+ return self
98
+ end
99
+ function class:Destroy()
100
+ self._destroyed = true
101
+ end
102
+ local msg = "abc"
103
+ local obj = trove:Construct(class, msg)
104
+ ctx:Expect(typeof(obj)):ToBe("table")
105
+ ctx:Expect(getmetatable(obj)):ToBe(class)
106
+ ctx:Expect(obj._msg):ToBe(msg)
107
+ ctx:Expect(obj._destroyed):ToBe(false)
108
+ trove:Destroy()
109
+ ctx:Expect(obj._destroyed):ToBe(true)
110
+ end)
111
+
112
+ ctx:Test("should connect to a signal", function()
113
+ local connection = trove:Connect(workspace.Changed, function() end)
114
+ ctx:Expect(typeof(connection)):ToBe("RBXScriptConnection")
115
+ ctx:Expect(connection.Connected):ToBe(true)
116
+ trove:Destroy()
117
+ ctx:Expect(connection.Connected):ToBe(false)
118
+ end)
119
+
120
+ ctx:Test("should remove an object", function()
121
+ local connection = trove:Connect(workspace.Changed, function() end)
122
+ ctx:Expect(trove:Remove(connection)):ToBe(true)
123
+ ctx:Expect(connection.Connected):ToBe(false)
124
+ end)
125
+
126
+ ctx:Test("should not remove an object not in the trove", function()
127
+ local connection = workspace.Changed:Connect(function() end)
128
+ ctx:Expect(trove:Remove(connection)):ToBe(false)
129
+ ctx:Expect(connection.Connected):ToBe(true)
130
+ connection:Disconnect()
131
+ end)
132
+
133
+ ctx:Test("should attach to instance", function()
134
+ local part = Instance.new("Part")
135
+ part.Parent = workspace
136
+ local connection = trove:AttachToInstance(part)
137
+ ctx:Expect(connection.Connected):ToBe(true)
138
+ part:Destroy()
139
+ ctx:Expect(connection.Connected):ToBe(false)
140
+ end)
141
+
142
+ ctx:Test("should fail to attach to instance not in hierarchy", function()
143
+ local part = Instance.new("Part")
144
+ ctx:Expect(function()
145
+ trove:AttachToInstance(part)
146
+ end):ToThrow()
147
+ end)
148
+
149
+ ctx:Test("should extend itself", function()
150
+ local subTrove = trove:Extend()
151
+ local called = false
152
+ subTrove:Add(function()
153
+ called = true
154
+ end)
155
+ ctx:Expect(typeof(subTrove)):ToBe("table")
156
+ ctx:Expect(getmetatable(subTrove)):ToBe(getmetatable(trove))
157
+ trove:Clean()
158
+ ctx:Expect(called):ToBe(true)
159
+ end)
160
+
161
+ ctx:Test("should clone an instance", function()
162
+ local name = "TroveCloneTest"
163
+ local p1 = trove:Construct(Instance.new, "Part")
164
+ p1.Name = name
165
+ local p2 = trove:Clone(p1)
166
+ ctx:Expect(typeof(p2)):ToBe("Instance")
167
+ ctx:Expect(p2):Not():ToBe(p1)
168
+ ctx:Expect(p2.Name):ToBe(name)
169
+ ctx:Expect(p1.Name):ToBe(p2.Name)
170
+ end)
171
+
172
+ ctx:Test("should clean up a thread", function()
173
+ local co = coroutine.create(function() end)
174
+ trove:Add(co)
175
+ ctx:Expect(coroutine.status(co)):ToBe("suspended")
176
+ trove:Clean()
177
+ ctx:Expect(coroutine.status(co)):ToBe("dead")
178
+ end)
179
+
180
+ ctx:Test("should not allow objects added during cleanup", function()
181
+ local added = false
182
+ trove:Add(function()
183
+ trove:Add(function() end)
184
+ added = true
185
+ end)
186
+ trove:Clean()
187
+
188
+ ctx:Expect(added):ToBe(false)
189
+ end)
190
+
191
+ ctx:Test("should not allow objects to be removed during cleanup", function()
192
+ local f = function() end
193
+ local removed = false
194
+ trove:Add(f)
195
+ trove:Add(function()
196
+ trove:Remove(f)
197
+ removed = true
198
+ end)
199
+
200
+ ctx:Expect(removed):ToBe(false)
201
+ end)
202
+ end)
203
+ end
@@ -0,0 +1,8 @@
1
+ [package]
2
+ name = "sleitnick/trove"
3
+ description = "Trove class for tracking and cleaning up objects"
4
+ version = "1.8.0"
5
+ license = "MIT"
6
+ authors = ["Stephen Leitnick"]
7
+ registry = "https://github.com/UpliftGames/wally-index"
8
+ realm = "shared"
@@ -0,0 +1,196 @@
1
+ --!strict
2
+
3
+ type Signal<T...> = {
4
+ Connect: (self: Signal<T...>, fn: (T...) -> ()) -> RBXScriptConnection,
5
+ ConnectParallel: (self: Signal<T...>, fn: (T...) -> ()) -> RBXScriptConnection,
6
+ Once: (self: Signal<T...>, fn: (T...) -> ()) -> RBXScriptConnection,
7
+ Wait: (self: Signal<T...>) -> T...,
8
+ }
9
+
10
+ type PlayerSignal<T...> = {
11
+ Connect: (self: PlayerSignal<T...>, fn: (player: Player, T...) -> ()) -> RBXScriptConnection,
12
+ ConnectParallel: (self: PlayerSignal<T...>, fn: (player: Player, T...) -> ()) -> RBXScriptConnection,
13
+ Once: (self: PlayerSignal<T...>, fn: (player: Player, T...) -> ()) -> RBXScriptConnection,
14
+ Wait: (self: PlayerSignal<T...>) -> (Player, T...),
15
+ }
16
+
17
+ --[=[
18
+ @within TypedRemote
19
+ @interface Event<T...>
20
+ .OnClientEvent Signal<T...>,
21
+ .OnServerEvent PlayerSignal<T...>,
22
+ .FireClient (self: Event<T...>, player: Player, T...) -> (),
23
+ .FireAllClients (self: Event<T...>, T...) -> (),
24
+ .FireServer (self: Event<T...>, T...) -> (),
25
+ ]=]
26
+ export type Event<T...> = Instance & {
27
+ OnClientEvent: Signal<T...>,
28
+ OnServerEvent: PlayerSignal<T...>,
29
+ FireClient: (self: Event<T...>, player: Player, T...) -> (),
30
+ FireAllClients: (self: Event<T...>, T...) -> (),
31
+ FireServer: (self: Event<T...>, T...) -> (),
32
+ }
33
+
34
+ --[=[
35
+ @within TypedRemote
36
+ @interface UnreliableEvent<T...>
37
+ .OnClientEvent Signal<T...>,
38
+ .OnServerEvent PlayerSignal<T...>,
39
+ .FireClient (self: Event<T...>, player: Player, T...) -> (),
40
+ .FireAllClients (self: Event<T...>, T...) -> (),
41
+ .FireServer (self: Event<T...>, T...) -> (),
42
+ ]=]
43
+ export type UnreliableEvent<T...> = Event<T...>
44
+
45
+ --[=[
46
+ @within TypedRemote
47
+ @interface Function<T..., R...>
48
+ .InvokeServer (self: Function<T..., R...>, T...) -> R...,
49
+ .OnServerInvoke (player: Player, T...) -> R...,
50
+ ]=]
51
+ export type Function<T..., R...> = Instance & {
52
+ InvokeServer: (self: Function<T..., R...>, T...) -> R...,
53
+ OnServerInvoke: (player: Player, T...) -> R...,
54
+ }
55
+
56
+ type CreatorFn<T> = (name: string) -> T
57
+
58
+ local IS_SERVER = game:GetService("RunService"):IsServer()
59
+
60
+ --[=[
61
+ @class TypedRemote
62
+
63
+ Simple networking package that helps create typed RemoteEvents and RemoteFunctions.
64
+
65
+ ```lua
66
+ -- ReplicatedStorage.Network (ModuleScript)
67
+
68
+ local TypedRemote = require(ReplicatedStorage.Packages.TypedRemote)
69
+
70
+ -- Get the RF, RE and URE instance creators, which create RemoteFunctions/RemoteEvents/UnreliableRemoteEvents
71
+ -- within the given parent (the script by default):
72
+ local RF, RE, URE = TypedRemote.parent()
73
+
74
+ -- Redeclare the TypedRemote types for simplicity:
75
+ type RF<T..., R...> = TypedRemote.Function<T..., R...>
76
+ type RE<T...> = TypedRemote.Event<T...>
77
+ type URE<T...> = TypedRemote.UnreliableEvent<T...>
78
+
79
+ -- Define network table:
80
+ return {
81
+ -- RemoteFunction that takes two arguments (boolean, string) and returns a number:
82
+ MyFunc = RF("MyFunc") :: RF<(boolean, string), (number)>,
83
+
84
+ -- RemoteEvent that takes two arguments - a string and a number:
85
+ MyEvent = RE("MyEvent") :: RE<string, number>,
86
+
87
+ -- UnreliableRemoteEvent that takes two arguments - a string and a number:
88
+ MyUnreliableEvent = URE("MyUnreliableEvent") :: URE<string, number>,
89
+ }
90
+ ```
91
+
92
+ ```lua
93
+ -- Example usage of the above Network module:
94
+
95
+ local Network = require(ReplicatedStorage.Network)
96
+
97
+ -- If you type this out, intellisense will help with what the function signature should be:
98
+ Network.MyEvent.OnClientEvent:Connect(function(player, str, num)
99
+ -- Foo
100
+ end)
101
+ ```
102
+
103
+ In most cases, the `TypedRemote.parent()` function will be used to create the memoized
104
+ RemoteFunction and RemoteEvent builder functions. From there, call the given functions
105
+ with the desired name per remote.
106
+
107
+ The `TypedRemote.func` and `TypedRemote.event` functions can also be used, but the
108
+ parent must be supplied to each call, hence the helpful `parent()` memoizer.
109
+ ]=]
110
+ local TypedRemote = {}
111
+
112
+ --[=[
113
+ @return ((name: string) -> RemoteFunction, (name: string) -> RemoteEvent)
114
+
115
+ Creates a memoized version of the `func` and `event` functions that include the `parent`
116
+ in each call.
117
+
118
+ ```lua
119
+ -- Create RF and RE functions that use the current script as the instance parent:
120
+ local RF, RE = TypedRemote.parent(script)
121
+
122
+ local remoteFunc = RF("RemoteFunc")
123
+ ```
124
+ ]=]
125
+ function TypedRemote.parent(
126
+ parent: Instance?
127
+ ): (CreatorFn<RemoteFunction>, CreatorFn<RemoteEvent>, CreatorFn<UnreliableRemoteEvent>)
128
+ return function(name: string)
129
+ return TypedRemote.func(name, parent)
130
+ end, function(name: string)
131
+ return TypedRemote.event(name, parent)
132
+ end, function(name: string)
133
+ return TypedRemote.unreliableEvent(name, parent)
134
+ end
135
+ end
136
+
137
+ --[=[
138
+ Creates a RemoteFunction with `name` and parents it inside of `parent`.
139
+
140
+ If the `parent` argument is not included or is `nil`, then it defaults to the parent of
141
+ this TypedRemote ModuleScript.
142
+ ]=]
143
+ function TypedRemote.func(name: string, parent: Instance?): RemoteFunction
144
+ local rf: RemoteFunction
145
+ if IS_SERVER then
146
+ rf = Instance.new("RemoteFunction")
147
+ rf.Name = name
148
+ rf.Parent = if parent then parent else script
149
+ else
150
+ rf = (if parent then parent else script):WaitForChild(name)
151
+ assert(rf:IsA("RemoteFunction"), "expected remote function")
152
+ end
153
+ return rf
154
+ end
155
+
156
+ --[=[
157
+ Creates a RemoteEvent with `name` and parents it inside of `parent`.
158
+
159
+ If the `parent` argument is not included or is `nil`, then it defaults to the parent of
160
+ this TypedRemote ModuleScript.
161
+ ]=]
162
+ function TypedRemote.event(name: string, parent: Instance?): RemoteEvent
163
+ local re: RemoteEvent
164
+ if IS_SERVER then
165
+ re = Instance.new("RemoteEvent")
166
+ re.Name = name
167
+ re.Parent = if parent then parent else script
168
+ else
169
+ re = (if parent then parent else script):WaitForChild(name)
170
+ assert(re:IsA("RemoteEvent"), "expected remote event")
171
+ end
172
+ return re
173
+ end
174
+
175
+ --[=[
176
+ Creates an UnreliableRemoteEvent with `name` and parents it inside of `parent`.
177
+
178
+ If the `parent` argument is not included or is `nil`, then it defaults to the parent of
179
+ this TypedRemote ModuleScript.
180
+ ]=]
181
+ function TypedRemote.unreliableEvent(name: string, parent: Instance?): UnreliableRemoteEvent
182
+ local ure: UnreliableRemoteEvent
183
+ if IS_SERVER then
184
+ ure = Instance.new("UnreliableRemoteEvent")
185
+ ure.Name = name
186
+ ure.Parent = if parent then parent else script
187
+ else
188
+ ure = (if parent then parent else script):WaitForChild(name)
189
+ assert(ure:IsA("UnreliableRemoteEvent"), "expected unreliable remote event")
190
+ end
191
+ return ure
192
+ end
193
+
194
+ table.freeze(TypedRemote)
195
+
196
+ return TypedRemote
@@ -0,0 +1,8 @@
1
+ [package]
2
+ name = "sleitnick/typed-remote"
3
+ description = "Simple networking package for typed RemoteEvents and RemoteFunctions"
4
+ version = "0.3.0"
5
+ license = "MIT"
6
+ authors = ["Stephen Leitnick"]
7
+ registry = "https://github.com/UpliftGames/wally-index"
8
+ realm = "shared"
@@ -0,0 +1,17 @@
1
+ declare namespace WaitFor {
2
+ interface Constructor {
3
+ readonly Error: { Unparented: "Unparented"; ParentChanged: "ParentChanged" };
4
+
5
+ Child: (parent: Instance, childName: string, timeout?: number) => Promise<Instance>;
6
+ Children: (parent: Instance, childrenNames: string[], timeout?: number) => Promise<Instance[]>;
7
+ Descendant: (parent: Instance, descendantName: string, timeout?: number) => Promise<Instance>;
8
+ Descendants: (parent: Instance, descendantNames: string[], timeout?: number) => Promise<Instance[]>;
9
+ PrimaryPart: (model: Model, timeout?: number) => Promise<BasePart>;
10
+ ObjectValue: (objectValue: ObjectValue, timeout?: number) => Promise<Instance>;
11
+ Custom: <T>(predicate: () => T | undefined, timeout?: number) => Promise<T>;
12
+ }
13
+ }
14
+
15
+ declare const WaitFor: WaitFor.Constructor;
16
+
17
+ export = WaitFor;
@@ -0,0 +1,257 @@
1
+ -- WaitFor
2
+ -- Stephen Leitnick
3
+ -- January 17, 2022
4
+
5
+ local RunService = game:GetService("RunService")
6
+
7
+ local Promise = require(script.Parent.Promise)
8
+
9
+ local DEFAULT_TIMEOUT = 60
10
+
11
+ --[=[
12
+ @class WaitFor
13
+ Utility class for awaiting the existence of instances.
14
+
15
+ By default, all promises timeout after 60 seconds, unless the `timeout`
16
+ argument is specified.
17
+
18
+ :::note
19
+ Promises will be rejected if the parent (or any ancestor) is unparented
20
+ from the game.
21
+ :::
22
+
23
+ :::caution Set name before parent
24
+ When waiting for instances based on name (e.g. `WaitFor.Child`), the `WaitFor`
25
+ system is listening to events to capture these instances being added. This
26
+ means that the name must be set _before_ being parented into the object.
27
+ :::
28
+ ]=]
29
+ local WaitFor = {}
30
+
31
+ --[=[
32
+ @within WaitFor
33
+ @prop Error {Unparented: string, ParentChanged: string}
34
+ ]=]
35
+ WaitFor.Error = {
36
+ Unparented = "Unparented",
37
+ ParentChanged = "ParentChanged",
38
+ }
39
+
40
+ local function PromiseWatchAncestry(instance: Instance, promise)
41
+ return Promise.race({
42
+ promise,
43
+ Promise.fromEvent(instance.AncestryChanged, function(_, newParent)
44
+ return newParent == nil
45
+ end):andThen(function()
46
+ return Promise.reject(WaitFor.Error.Unparented)
47
+ end),
48
+ })
49
+ end
50
+
51
+ --[=[
52
+ @return Promise<Instance>
53
+ Wait for a child to exist within a given parent based on the child name.
54
+
55
+ ```lua
56
+ WaitFor.Child(parent, "SomeObject"):andThen(function(someObject)
57
+ print(someObject, "now exists")
58
+ end):catch(warn)
59
+ ```
60
+ ]=]
61
+ function WaitFor.Child(parent: Instance, childName: string, timeout: number?)
62
+ local child = parent:FindFirstChild(childName)
63
+ if child then
64
+ return Promise.resolve(child)
65
+ end
66
+ return PromiseWatchAncestry(
67
+ parent,
68
+ Promise.fromEvent(parent.ChildAdded, function(c)
69
+ return c.Name == childName
70
+ end):timeout(timeout or DEFAULT_TIMEOUT)
71
+ )
72
+ end
73
+
74
+ --[=[
75
+ @return Promise<{Instance}>
76
+ Wait for all children to exist within the given parent.
77
+
78
+ ```lua
79
+ WaitFor.Children(parent, {"SomeObject01", "SomeObject02"}):andThen(function(children)
80
+ local someObject01, someObject02 = table.unpack(children)
81
+ end)
82
+ ```
83
+
84
+ :::note
85
+ Once all children are found, a second check is made to ensure that all children
86
+ are still directly parented to the given `parent` (since one child's parent
87
+ might have changed before another child was found). A rejected promise with the
88
+ `WaitFor.Error.ParentChanged` error will be thrown if any parents of the children
89
+ no longer match the given `parent`.
90
+ :::
91
+ ]=]
92
+ function WaitFor.Children(parent: Instance, childrenNames: { string }, timeout: number?)
93
+ local all = table.create(#childrenNames)
94
+ for i, childName in ipairs(childrenNames) do
95
+ all[i] = WaitFor.Child(parent, childName, timeout)
96
+ end
97
+ return Promise.all(all):andThen(function(children)
98
+ -- Check that all are still parented
99
+ for _, child in ipairs(children) do
100
+ if child.Parent ~= parent then
101
+ return Promise.reject(WaitFor.Error.ParentChanged)
102
+ end
103
+ end
104
+ return children
105
+ end)
106
+ end
107
+
108
+ --[=[
109
+ @return Promise<Instance>
110
+ Wait for a descendant to exist within a given parent. This is similar to
111
+ `WaitFor.Child`, except it looks for all descendants instead of immediate
112
+ children.
113
+
114
+ ```lua
115
+ WaitFor.Descendant(parent, "SomeDescendant"):andThen(function(someDescendant)
116
+ print("SomeDescendant now exists")
117
+ end)
118
+ ```
119
+ ]=]
120
+ function WaitFor.Descendant(parent: Instance, descendantName: string, timeout: number?)
121
+ local descendant = parent:FindFirstChild(descendantName, true)
122
+ if descendant then
123
+ return Promise.resolve(descendant)
124
+ end
125
+ return PromiseWatchAncestry(
126
+ parent,
127
+ Promise.fromEvent(parent.DescendantAdded, function(d)
128
+ return d.Name == descendantName
129
+ end):timeout(timeout or DEFAULT_TIMEOUT)
130
+ )
131
+ end
132
+
133
+ --[=[
134
+ @return Promise<{Instance}>
135
+ Wait for all descendants to exist within a given parent.
136
+
137
+ ```lua
138
+ WaitFor.Descendants(parent, {"SomeDescendant01", "SomeDescendant02"}):andThen(function(descendants)
139
+ local someDescendant01, someDescendant02 = table.unpack(descendants)
140
+ end)
141
+ ```
142
+
143
+ :::note
144
+ Once all descendants are found, a second check is made to ensure that none of the
145
+ instances have moved outside of the parent (since one instance might change before
146
+ another instance is found). A rejected promise with the `WaitFor.Error.ParentChanged`
147
+ error will be thrown if any of the instances are no longer descendants of the given
148
+ `parent`.
149
+ :::
150
+ ]=]
151
+ function WaitFor.Descendants(parent: Instance, descendantNames: { string }, timeout: number?)
152
+ local all = table.create(#descendantNames)
153
+ for i, descendantName in ipairs(descendantNames) do
154
+ all[i] = WaitFor.Descendant(parent, descendantName, timeout)
155
+ end
156
+ return Promise.all(all):andThen(function(descendants)
157
+ -- Check that all are still parented
158
+ for _, descendant in ipairs(descendants) do
159
+ if not descendant:IsDescendantOf(parent) then
160
+ return Promise.reject(WaitFor.Error.ParentChanged)
161
+ end
162
+ end
163
+ return descendants
164
+ end)
165
+ end
166
+
167
+ --[=[
168
+ @return Promise<Instance>
169
+ Wait for the PrimaryPart of a model to exist.
170
+
171
+ ```lua
172
+ WaitFor.PrimaryPart(model):andThen(function(primaryPart)
173
+ print(primaryPart == model.PrimaryPart)
174
+ end)
175
+ ```
176
+ ]=]
177
+ function WaitFor.PrimaryPart(model: Model, timeout: number?)
178
+ local primary = model.PrimaryPart
179
+ if primary then
180
+ return Promise.resolve(primary)
181
+ end
182
+ return PromiseWatchAncestry(
183
+ model,
184
+ Promise.fromEvent(model:GetPropertyChangedSignal("PrimaryPart"), function()
185
+ primary = model.PrimaryPart
186
+ return primary ~= nil
187
+ end)
188
+ :andThen(function()
189
+ return primary
190
+ end)
191
+ :timeout(timeout or DEFAULT_TIMEOUT)
192
+ )
193
+ end
194
+
195
+ --[=[
196
+ @return Promise<Instance>
197
+ Wait for the Value of an ObjectValue to exist.
198
+
199
+ ```lua
200
+ WaitFor.ObjectValue(someObjectValue):andThen(function(value)
201
+ print("someObjectValue's value is", value)
202
+ end)
203
+ ```
204
+ ]=]
205
+ function WaitFor.ObjectValue(objectValue: ObjectValue, timeout: number?)
206
+ local value = objectValue.Value
207
+ if value then
208
+ return Promise.resolve(value)
209
+ end
210
+ return PromiseWatchAncestry(
211
+ objectValue,
212
+ Promise.fromEvent(objectValue.Changed, function(v)
213
+ value = v
214
+ return value ~= nil
215
+ end)
216
+ :andThen(function()
217
+ return value
218
+ end)
219
+ :timeout(timeout or DEFAULT_TIMEOUT)
220
+ )
221
+ end
222
+
223
+ --[=[
224
+ @return Promise<T>
225
+ Wait for the given predicate function to return a non-nil value of
226
+ of type `T`. The predicate is fired every RunService Heartbeat step.
227
+
228
+ ```lua
229
+ -- Example, waiting for some property to be set:
230
+ WaitFor.Custom(function() return vectorForce.Attachment0 end):andThen(function(a0)
231
+ print(a0)
232
+ end)
233
+ ```
234
+ ]=]
235
+ function WaitFor.Custom<T>(predicate: () -> T?, timeout: number?)
236
+ local value = predicate()
237
+ if value ~= nil then
238
+ return Promise.resolve(value)
239
+ end
240
+ return Promise.new(function(resolve, _reject, onCancel)
241
+ local heartbeat
242
+ local function OnDone()
243
+ heartbeat:Disconnect()
244
+ end
245
+ local function Update()
246
+ local v = predicate()
247
+ if v ~= nil then
248
+ OnDone()
249
+ resolve(v)
250
+ end
251
+ end
252
+ heartbeat = RunService.Heartbeat:Connect(Update)
253
+ onCancel(OnDone)
254
+ end):timeout(timeout or DEFAULT_TIMEOUT)
255
+ end
256
+
257
+ return WaitFor