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,55 @@
1
+ --!strict
2
+
3
+ -- Util
4
+ -- Stephen Leitnick
5
+ -- April 29, 2022
6
+
7
+ type AnyTable = { [any]: any }
8
+
9
+ local Util = {}
10
+
11
+ Util.None = newproxy()
12
+
13
+ -- Recursive table freeze.
14
+ function Util.DeepFreeze<T>(tbl: AnyTable): AnyTable
15
+ table.freeze(tbl)
16
+ for _, v in tbl do
17
+ if type(v) == "table" then
18
+ Util.DeepFreeze(v)
19
+ end
20
+ end
21
+ return tbl
22
+ end
23
+
24
+ -- Recursive table copy.
25
+ function Util.DeepCopy<T>(tbl: AnyTable): AnyTable
26
+ local newTbl = table.clone(tbl)
27
+ for k, v in newTbl do
28
+ if type(v) == "table" then
29
+ newTbl[k] = Util.DeepCopy(v)
30
+ end
31
+ end
32
+ return newTbl
33
+ end
34
+
35
+ -- Extends one table with another.
36
+ -- Similar to the spread operator in JavaScript.
37
+ function Util.Extend(original: AnyTable, extension: AnyTable): AnyTable
38
+ local t = Util.DeepCopy(original)
39
+ for k, v in extension do
40
+ if type(v) == "table" then
41
+ if type(original[k]) == "table" then
42
+ t[k] = Util.Extend(original[k], v)
43
+ else
44
+ t[k] = Util.DeepCopy(v)
45
+ end
46
+ elseif v == Util.None then
47
+ t[k] = nil
48
+ else
49
+ t[k] = v
50
+ end
51
+ end
52
+ return t
53
+ end
54
+
55
+ return Util
@@ -0,0 +1,338 @@
1
+ -- Silo
2
+ -- Stephen Leitnick
3
+ -- April 29, 2022
4
+
5
+ --[=[
6
+ @within Silo
7
+ @type State<S> {[string]: any}
8
+ Represents state.
9
+ ]=]
10
+ export type State<S> = S & { [string]: any }
11
+
12
+ --[=[
13
+ @within Silo
14
+ @type Modifier<S> (State<S>, any) -> ()
15
+ A function that modifies state.
16
+ ]=]
17
+ export type Modifier<S> = (State<S>, any) -> ()
18
+
19
+ --[=[
20
+ @within Silo
21
+ @interface Action<A>
22
+ .Name string
23
+ .Payload A
24
+ Actions are passed to `Dispatch`. However, typically actions are
25
+ never constructed by hand. Use a silo's Actions table to generate
26
+ these actions.
27
+ ]=]
28
+ type Action<A> = {
29
+ Name: string,
30
+ Payload: A,
31
+ }
32
+
33
+ export type Silo<S, A> = {
34
+ Actions: { [string]: <A>(value: A) -> () },
35
+
36
+ GetState: (self: Silo<S, A>) -> State<S>,
37
+ Dispatch: (self: Silo<S, A>, action: Action<A>) -> (),
38
+ ResetToDefaultState: (self: Silo<S, A>) -> (),
39
+ Subscribe: (self: Silo<S, A>, subscriber: (newState: State<S>, oldState: State<S>) -> ()) -> () -> (),
40
+ Watch: <T>(self: Silo<S, A>, selector: (State<S>) -> T, onChange: (T) -> ()) -> () -> (),
41
+ }
42
+
43
+ local TableWatcher = require(script.TableWatcher)
44
+ local Util = require(script.Util)
45
+
46
+ --[=[
47
+ @class Silo
48
+ A Silo is a state container, inspired by Redux slices and
49
+ designed for Roblox developers.
50
+ ]=]
51
+ local Silo = {}
52
+ Silo.__index = Silo
53
+
54
+ --[=[
55
+ @return Silo
56
+ Create a Silo.
57
+
58
+ ```lua
59
+ local statsSilo = Silo.new({
60
+ -- Initial state:
61
+ Kills = 0,
62
+ Deaths = 0,
63
+ Points = 0,
64
+ }, {
65
+ -- Modifiers are functions that modify the state:
66
+ SetKills = function(state, kills)
67
+ state.Kills = kills
68
+ end,
69
+ AddPoints = function(state, points)
70
+ state.Points += points
71
+ end,
72
+ })
73
+
74
+ -- Use Actions to modify the state:
75
+ statsSilo:Dispatch(statsSilo.Actions.SetKills(10))
76
+
77
+ -- Use GetState to get the current state:
78
+ print("Kills", statsSilo:GetState().Kills)
79
+ ```
80
+
81
+ From the above example, note how the modifier functions were transformed
82
+ into functions that can be called from `Actions` with just the single
83
+ payload (no need to pass state). The `SetKills` modifier is then used
84
+ as the `SetKills` action to be dispatched.
85
+ ]=]
86
+ function Silo.new<S, A>(defaultState: State<S>, modifiers: { [string]: Modifier<S> }?): Silo<S, A>
87
+ local self = setmetatable({}, Silo)
88
+
89
+ self._DefaultState = Util.DeepFreeze(Util.DeepCopy(defaultState))
90
+ self._State = Util.DeepFreeze(Util.DeepCopy(defaultState))
91
+ self._Modifiers = {} :: { [string]: any }
92
+ self._Dispatching = false
93
+ self._Parent = self
94
+ self._Subscribers = {}
95
+
96
+ self.Actions = {}
97
+
98
+ -- Create modifiers and action creators:
99
+ if modifiers then
100
+ for actionName, modifier in modifiers do
101
+ self._Modifiers[actionName] = function(state: State<S>, payload: any)
102
+ -- Create a watcher to virtually watch for state mutations:
103
+ local watcher = TableWatcher(state)
104
+ modifier(watcher :: any, payload)
105
+ -- Apply state mutations into new state table:
106
+ return watcher()
107
+ end
108
+
109
+ self.Actions[actionName] = function(payload)
110
+ return {
111
+ Name = actionName,
112
+ Payload = payload,
113
+ }
114
+ end
115
+ end
116
+ end
117
+
118
+ return self
119
+ end
120
+
121
+ --[=[
122
+ @param silos {Silo}
123
+ @return Silo
124
+ Constructs a new silo as a combination of other silos.
125
+ ]=]
126
+ function Silo.combine<S, A>(silos: { [string]: Silo<unknown, unknown> }, initialState: State<S>?): Silo<S, A>
127
+ -- Combine state:
128
+ local state = {}
129
+ for name, silo in silos do
130
+ if silo._Dispatching then
131
+ error("cannot combine silos from a modifier", 2)
132
+ end
133
+ state[name] = silo:GetState()
134
+ end
135
+
136
+ local combinedSilo = Silo.new(Util.Extend(state, initialState or {}))
137
+
138
+ -- Combine modifiers and actions:
139
+ for name, silo in silos do
140
+ silo._Parent = combinedSilo
141
+ for actionName, modifier in silo._Modifiers do
142
+ -- Prefix action name to keep it unique:
143
+ local fullActionName = `{name}/{actionName}`
144
+ combinedSilo._Modifiers[fullActionName] = function(s, payload)
145
+ -- Extend the top-level state from the sub-silo state modification:
146
+ return Util.Extend(s, {
147
+ [name] = modifier((s :: { [string]: any })[name], payload),
148
+ })
149
+ end
150
+ end
151
+ for actionName in silo.Actions do
152
+ if combinedSilo.Actions[actionName] ~= nil then
153
+ error(`duplicate action name {actionName} found when combining silos`, 2)
154
+ end
155
+ -- Update the action creator to include the correct prefixed action name:
156
+ local fullActionName = `{name}/{actionName}`
157
+ silo.Actions[actionName] = function(p)
158
+ return {
159
+ Name = fullActionName,
160
+ Payload = p,
161
+ }
162
+ end
163
+ combinedSilo.Actions[actionName] = silo.Actions[actionName]
164
+ end
165
+ end
166
+
167
+ return combinedSilo
168
+ end
169
+
170
+ --[=[
171
+ Get the current state.
172
+
173
+ ```lua
174
+ local state = silo:GetState()
175
+ ```
176
+ ]=]
177
+ function Silo:GetState<S>(): State<S>
178
+ if self._Parent ~= self then
179
+ error("can only get state from top-level silo", 2)
180
+ end
181
+ return self._State
182
+ end
183
+
184
+ --[=[
185
+ Dispatch an action.
186
+
187
+ ```lua
188
+ silo:Dispatch(silo.Actions.DoSomething("something"))
189
+ ```
190
+ ]=]
191
+ function Silo:Dispatch<A>(action: Action<A>)
192
+ if self._Dispatching then
193
+ error("cannot dispatch from a modifier", 2)
194
+ end
195
+ if self._Parent ~= self then
196
+ error("can only dispatch from top-level silo", 2)
197
+ end
198
+
199
+ -- Find and invoke the modifier to modify current state:
200
+ self._Dispatching = true
201
+ local oldState = self._State
202
+ local newState = oldState
203
+ local modifier = self._Modifiers[action.Name]
204
+ if modifier then
205
+ newState = modifier(newState, action.Payload)
206
+ end
207
+ self._Dispatching = false
208
+
209
+ -- State changed:
210
+ if newState ~= oldState then
211
+ self._State = Util.DeepFreeze(newState)
212
+
213
+ -- Notify subscribers of state change:
214
+ for _, subscriber in self._Subscribers do
215
+ subscriber(newState, oldState)
216
+ end
217
+ end
218
+ end
219
+
220
+ --[=[
221
+ Subscribe a function to receive all state updates, including
222
+ initial state (subscriber is called immediately).
223
+
224
+ Returns an unsubscribe function. Call the function to unsubscribe.
225
+
226
+ ```lua
227
+ local unsubscribe = silo:Subscribe(function(newState, oldState)
228
+ -- Do something
229
+ end)
230
+
231
+ -- Later on, if desired, disconnect the subscription by calling unsubscribe:
232
+ unsubscribe()
233
+ ```
234
+ ]=]
235
+ function Silo:Subscribe<S>(subscriber: (newState: State<S>, oldState: State<S>) -> ()): () -> ()
236
+ if self._Dispatching then
237
+ error("cannot subscribe from within a modifier", 2)
238
+ end
239
+ if self._Parent ~= self then
240
+ error("can only subscribe on top-level silo", 2)
241
+ end
242
+ if table.find(self._Subscribers, subscriber) then
243
+ error("cannot subscribe same function more than once", 2)
244
+ end
245
+
246
+ table.insert(self._Subscribers, subscriber)
247
+
248
+ -- Unsubscribe:
249
+ return function()
250
+ local index = table.find(self._Subscribers, subscriber)
251
+ if not index then
252
+ return
253
+ end
254
+ table.remove(self._Subscribers, index)
255
+ end
256
+ end
257
+
258
+ --[=[
259
+ Watch a specific value within the state, which is selected by the
260
+ `selector` function. The initial value, and any subsequent changes
261
+ grabbed by the selector, will be passed to the `onChange` function.
262
+
263
+ Just like `Subscribe`, a function is returned that can be used
264
+ to unsubscribe (i.e. stop watching).
265
+
266
+ ```lua
267
+ local function SelectPoints(state)
268
+ return state.Statistics.Points
269
+ end
270
+
271
+ local unsubscribe = silo:Watch(SelectPoints, function(points)
272
+ print("Points", points)
273
+ end)
274
+ ```
275
+ ]=]
276
+ function Silo:Watch<S, T>(selector: (State<S>) -> T, onChange: (T) -> ()): () -> ()
277
+ local value = selector(self:GetState())
278
+
279
+ local unsubscribe = self:Subscribe(function(state)
280
+ local newValue = selector(state)
281
+ if newValue == value then
282
+ return
283
+ end
284
+ value = newValue
285
+ onChange(value)
286
+ end)
287
+
288
+ -- Call initial onChange after subscription to verify subscription didn't fail:
289
+ onChange(value)
290
+
291
+ return unsubscribe
292
+ end
293
+
294
+ --[=[
295
+ Reset the state to the default state that was given in the constructor.
296
+
297
+ ```lua
298
+ local silo = Silo.new({
299
+ Points = 0,
300
+ }, {
301
+ SetPoints = function(state, points)
302
+ state.Points = points
303
+ end
304
+ })
305
+
306
+ silo:Dispatch(silo.Actions.SetPoints(10))
307
+
308
+ print(silo:GetState().Points) -- 10
309
+
310
+ silo:ResetToDefaultState()
311
+
312
+ print(silo:GetState().Points) -- 0
313
+ ```
314
+ ]=]
315
+ function Silo:ResetToDefaultState()
316
+ if self._Dispatching then
317
+ error("cannot reset state from within a modifier", 2)
318
+ end
319
+
320
+ if self._Parent ~= self then
321
+ error("can only reset state on top-level silo", 2)
322
+ end
323
+
324
+ local oldState = self._State
325
+
326
+ if self._DefaultState ~= oldState then
327
+ self._State = Util.DeepFreeze(Util.DeepCopy(self._DefaultState))
328
+
329
+ for _, subscriber in self._Subscribers do
330
+ subscriber(self._State, oldState)
331
+ end
332
+ end
333
+ end
334
+
335
+ return {
336
+ new = Silo.new,
337
+ combine = Silo.combine,
338
+ }
@@ -0,0 +1,215 @@
1
+ local ServerScriptService = game:GetService("ServerScriptService")
2
+
3
+ local Test = require(ServerScriptService.TestRunner.Test)
4
+
5
+ return function(ctx: Test.TestContext)
6
+ local Silo = require(script.Parent)
7
+
8
+ local silo1, silo2, rootSilo
9
+
10
+ ctx:BeforeEach(function()
11
+ silo1 = Silo.new({
12
+ Kills = 0,
13
+ Deaths = 0,
14
+ }, {
15
+ SetKills = function(state, kills)
16
+ state.Kills = kills
17
+ end,
18
+ IncrementDeaths = function(state, deaths)
19
+ state.Deaths += deaths
20
+ end,
21
+ })
22
+ silo2 = Silo.new({
23
+ Money = 0,
24
+ }, {
25
+ AddMoney = function(state, money)
26
+ state.Money += money
27
+ end,
28
+ })
29
+ rootSilo = Silo.combine({
30
+ Stats = silo1,
31
+ Econ = silo2,
32
+ })
33
+ end)
34
+
35
+ ctx:Describe("State", function()
36
+ ctx:Test("should get state properly", function()
37
+ local silo = Silo.new({
38
+ ABC = 10,
39
+ })
40
+ local state = silo:GetState()
41
+ ctx:Expect(state):ToBeA("table")
42
+ ctx:Expect(state.ABC):ToBe(10)
43
+ end)
44
+
45
+ ctx:Test("should get state from combined silos", function()
46
+ local state = rootSilo:GetState()
47
+ ctx:Expect(state):ToBeA("table")
48
+ ctx:Expect(state.Stats):ToBeA("table")
49
+ ctx:Expect(state.Econ):ToBeA("table")
50
+ ctx:Expect(state.Stats.Kills):ToBeA("number")
51
+ ctx:Expect(state.Stats.Deaths):ToBeA("number")
52
+ ctx:Expect(state.Econ.Money):ToBeA("number")
53
+ end)
54
+
55
+ ctx:Test("should not allow getting state from sub-silo", function()
56
+ ctx:Expect(function()
57
+ silo1:GetState()
58
+ end):ToThrow()
59
+ ctx:Expect(function()
60
+ silo2:GetState()
61
+ end):ToThrow()
62
+ end)
63
+
64
+ ctx:Test("should throw error if attempting to modify state directly", function()
65
+ ctx:Expect(function()
66
+ rootSilo:GetState().Stats.Kills = 10
67
+ end):ToThrow()
68
+ ctx:Expect(function()
69
+ rootSilo:GetState().Stats.SomethingNew = 100
70
+ end):ToThrow()
71
+ ctx:Expect(function()
72
+ rootSilo:GetState().Stats = {}
73
+ end):ToThrow()
74
+ ctx:Expect(function()
75
+ rootSilo:GetState().SomethingElse = {}
76
+ end):ToThrow()
77
+ end)
78
+ end)
79
+
80
+ ctx:Describe("Dispatch", function()
81
+ ctx:Test("should dispatch", function()
82
+ ctx:Expect(rootSilo:GetState().Stats.Kills):ToBe(0)
83
+ rootSilo:Dispatch(silo1.Actions.SetKills(10))
84
+ ctx:Expect(rootSilo:GetState().Stats.Kills):ToBe(10)
85
+ rootSilo:Dispatch(silo2.Actions.AddMoney(10))
86
+ rootSilo:Dispatch(silo2.Actions.AddMoney(20))
87
+ ctx:Expect(rootSilo:GetState().Econ.Money):ToBe(30)
88
+ end)
89
+
90
+ ctx:Test("should not allow dispatching from a sub-silo", function()
91
+ ctx:Expect(function()
92
+ silo1:Dispatch(silo1.Action.SetKills(0))
93
+ end):ToThrow()
94
+ ctx:Expect(function()
95
+ silo2:Dispatch(silo2.Action.AddMoney(0))
96
+ end):ToThrow()
97
+ end)
98
+
99
+ ctx:Test("should not allow dispatching from within a modifier", function()
100
+ ctx:Expect(function()
101
+ local silo
102
+ silo = Silo.new({
103
+ Data = 0,
104
+ }, {
105
+ SetData = function(state, newData)
106
+ state.Data = newData
107
+ silo:Dispatch({ Name = "", Payload = 0 })
108
+ end,
109
+ })
110
+ silo:Dispatch(silo.Actions.SetData(0))
111
+ end):ToThrow()
112
+ end)
113
+ end)
114
+
115
+ ctx:Describe("Subscribe", function()
116
+ ctx:Test("should subscribe to a silo", function()
117
+ local new, old
118
+ local n = 0
119
+ local unsubscribe = rootSilo:Subscribe(function(newState, oldState)
120
+ n += 1
121
+ new, old = newState, oldState
122
+ end)
123
+ ctx:Expect(n):ToBe(0)
124
+ rootSilo:Dispatch(silo1.Actions.SetKills(10))
125
+ ctx:Expect(n):ToBe(1)
126
+ ctx:Expect(new):ToBeA("table")
127
+ ctx:Expect(old):ToBeA("table")
128
+ ctx:Expect(new.Stats.Kills):ToBe(10)
129
+ ctx:Expect(old.Stats.Kills):ToBe(0)
130
+ rootSilo:Dispatch(silo1.Actions.SetKills(20))
131
+ ctx:Expect(n):ToBe(2)
132
+ ctx:Expect(new.Stats.Kills):ToBe(20)
133
+ ctx:Expect(old.Stats.Kills):ToBe(10)
134
+ unsubscribe()
135
+ rootSilo:Dispatch(silo1.Actions.SetKills(30))
136
+ ctx:Expect(n):ToBe(2)
137
+ end)
138
+
139
+ ctx:Test("should not allow subscribing same function more than once", function()
140
+ local function sub() end
141
+ ctx:Expect(function()
142
+ rootSilo:Subscribe(sub)
143
+ end)
144
+ :Not()
145
+ :ToThrow()
146
+ ctx:Expect(function()
147
+ rootSilo:Subscribe(sub)
148
+ end):ToThrow()
149
+ end)
150
+
151
+ ctx:Test("should not allow subscribing to a sub-silo", function()
152
+ ctx:Expect(function()
153
+ silo1:Subscribe(function() end)
154
+ end):ToThrow()
155
+ end)
156
+
157
+ ctx:Test("should not allow subscribing from within a modifier", function()
158
+ ctx:Expect(function()
159
+ local silo
160
+ silo = Silo.new({
161
+ Data = 0,
162
+ }, {
163
+ SetData = function(state, newData)
164
+ state.Data = newData
165
+ silo:Subscribe(function() end)
166
+ end,
167
+ })
168
+ silo:Dispatch(silo.Actions.SetData(0))
169
+ end):ToThrow()
170
+ end)
171
+ end)
172
+
173
+ ctx:Describe("Watch", function()
174
+ ctx:Test("should watch value changes", function()
175
+ local function SelectMoney(state)
176
+ return state.Econ.Money
177
+ end
178
+ local changes = 0
179
+ local currentMoney = 0
180
+ local unsubscribeWatch = rootSilo:Watch(SelectMoney, function(money)
181
+ changes += 1
182
+ currentMoney = money
183
+ end)
184
+ ctx:Expect(changes):ToBe(1)
185
+ rootSilo:Dispatch(silo2.Actions.AddMoney(10))
186
+ ctx:Expect(changes):ToBe(2)
187
+ ctx:Expect(currentMoney):ToBe(10)
188
+ rootSilo:Dispatch(silo2.Actions.AddMoney(20))
189
+ ctx:Expect(changes):ToBe(3)
190
+ ctx:Expect(currentMoney):ToBe(30)
191
+ rootSilo:Dispatch(silo2.Actions.AddMoney(0))
192
+ ctx:Expect(changes):ToBe(3)
193
+ ctx:Expect(currentMoney):ToBe(30)
194
+ rootSilo:Dispatch(silo1.Actions.SetKills(10))
195
+ ctx:Expect(changes):ToBe(3)
196
+ ctx:Expect(currentMoney):ToBe(30)
197
+ unsubscribeWatch()
198
+ rootSilo:Dispatch(silo2.Actions.AddMoney(10))
199
+ ctx:Expect(changes):ToBe(3)
200
+ ctx:Expect(currentMoney):ToBe(30)
201
+ end)
202
+ end)
203
+
204
+ ctx:Describe("ResetToDefaultState", function()
205
+ ctx:Test("should reset the silo to it's default state", function()
206
+ rootSilo:Dispatch(silo1.Actions.SetKills(10))
207
+ rootSilo:Dispatch(silo2.Actions.AddMoney(30))
208
+ ctx:Expect(rootSilo:GetState().Stats.Kills):ToBe(10)
209
+ ctx:Expect(rootSilo:GetState().Econ.Money):ToBe(30)
210
+ rootSilo:ResetToDefaultState()
211
+ ctx:Expect(rootSilo:GetState().Stats.Kills):ToBe(0)
212
+ ctx:Expect(rootSilo:GetState().Econ.Money):ToBe(0)
213
+ end)
214
+ end)
215
+ end
@@ -0,0 +1,8 @@
1
+ [package]
2
+ name = "sleitnick/silo"
3
+ description = "State container class"
4
+ version = "0.2.0"
5
+ license = "MIT"
6
+ authors = ["Stephen Leitnick"]
7
+ registry = "https://github.com/UpliftGames/wally-index"
8
+ realm = "shared"
@@ -0,0 +1,40 @@
1
+ declare namespace Spring {
2
+ interface Constructor {
3
+ /** Constructs a new spring. */
4
+ new <T extends Vector2 | Vector3 | CFrame | number>(
5
+ initial: T,
6
+ smoothTime: number,
7
+ maxSpeed?: number,
8
+ ): Spring<T>;
9
+ }
10
+ }
11
+
12
+ interface Spring<T extends Vector2 | Vector3 | CFrame | number> {
13
+ /** The current value of the spring. */
14
+ Current: T;
15
+
16
+ /** The target value of the spring. */
17
+ Target: T;
18
+
19
+ /** The spring's current velocity. */
20
+ Velocity: T;
21
+
22
+ /** Approximately how many seconds it will take to reach the target. */
23
+ SmoothTime: number;
24
+
25
+ /** Maximum allowed speed of the spring. */
26
+ MaxSpeed: number;
27
+
28
+ /** Updates the spring. */
29
+ Update(deltaTime: number): T;
30
+
31
+ /** Impulses the spring. */
32
+ Impulse(force: T): void;
33
+
34
+ /** Resets the spring. */
35
+ Reset(): void;
36
+ }
37
+
38
+ declare const Spring: Spring.Constructor;
39
+
40
+ export = Spring;