roblox-opencode 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/README.md +112 -122
  2. package/commands/setup-game.md +108 -108
  3. package/commands/sync-check.md +53 -53
  4. package/core/roblox-core.md +93 -93
  5. package/dist/server.js +189 -167
  6. package/package.json +35 -35
  7. package/skills/roblox-analytics/SKILL.md +277 -277
  8. package/skills/roblox-analytics/references/event-batcher.luau +75 -75
  9. package/skills/roblox-animation-vfx/SKILL.md +1325 -1325
  10. package/skills/roblox-architecture/SKILL.md +863 -863
  11. package/skills/roblox-architecture/references/combat-systems.md +1381 -1381
  12. package/skills/roblox-code-review/SKILL.md +686 -686
  13. package/skills/roblox-data/SKILL.md +889 -889
  14. package/skills/roblox-data/references/inventory-systems.md +1729 -1729
  15. package/skills/roblox-debug/SKILL.md +98 -98
  16. package/skills/roblox-gui/SKILL.md +1103 -1103
  17. package/skills/roblox-gui-fusion/SKILL.md +150 -150
  18. package/skills/roblox-gui-fusion/references/inventory.luau +427 -427
  19. package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -579
  20. package/skills/roblox-gui-fusion/references/shop.luau +411 -411
  21. package/skills/roblox-luau-mastery/SKILL.md +1519 -1519
  22. package/skills/roblox-monetization/SKILL.md +1084 -1084
  23. package/skills/roblox-monetization/references/process-receipt.luau +131 -131
  24. package/skills/roblox-networking/SKILL.md +669 -669
  25. package/skills/roblox-networking/references/remote-validator.luau +193 -193
  26. package/skills/roblox-publish-checklist/SKILL.md +127 -127
  27. package/skills/roblox-runtime/SKILL.md +753 -753
  28. package/skills/roblox-sharp-edges/SKILL.md +294 -294
  29. package/skills/roblox-sync/SKILL.md +126 -126
  30. package/skills/roblox-testing/SKILL.md +943 -943
  31. package/skills/roblox-tooling/SKILL.md +149 -149
  32. package/vendor/LICENSES/ProfileStore-LICENSE +201 -201
  33. package/vendor/LICENSES/RbxUtil-LICENSE +7 -7
  34. package/vendor/LICENSES/promise-LICENSE +20 -20
  35. package/vendor/LICENSES/t-LICENSE +21 -21
  36. package/vendor/LICENSES/testez-LICENSE +200 -200
  37. package/vendor/README.md +83 -83
  38. package/vendor/fusion/Animation/ExternalTime.luau +83 -83
  39. package/vendor/fusion/Animation/Spring.luau +321 -321
  40. package/vendor/fusion/Animation/Stopwatch.luau +127 -127
  41. package/vendor/fusion/Animation/Tween.luau +187 -187
  42. package/vendor/fusion/Animation/getTweenDuration.luau +27 -27
  43. package/vendor/fusion/Animation/getTweenRatio.luau +47 -47
  44. package/vendor/fusion/Animation/lerpType.luau +163 -163
  45. package/vendor/fusion/Animation/packType.luau +99 -99
  46. package/vendor/fusion/Animation/springCoefficients.luau +80 -80
  47. package/vendor/fusion/Animation/unpackType.luau +102 -102
  48. package/vendor/fusion/Colour/Oklab.luau +70 -70
  49. package/vendor/fusion/Colour/sRGB.luau +54 -54
  50. package/vendor/fusion/External.luau +167 -167
  51. package/vendor/fusion/ExternalDebug.luau +69 -69
  52. package/vendor/fusion/Graph/Observer.luau +113 -113
  53. package/vendor/fusion/Graph/castToGraph.luau +28 -28
  54. package/vendor/fusion/Graph/change.luau +80 -80
  55. package/vendor/fusion/Graph/depend.luau +32 -32
  56. package/vendor/fusion/Graph/evaluate.luau +55 -55
  57. package/vendor/fusion/Instances/Attribute.luau +57 -57
  58. package/vendor/fusion/Instances/AttributeChange.luau +46 -46
  59. package/vendor/fusion/Instances/AttributeOut.luau +63 -63
  60. package/vendor/fusion/Instances/Child.luau +21 -21
  61. package/vendor/fusion/Instances/Children.luau +147 -147
  62. package/vendor/fusion/Instances/Hydrate.luau +32 -32
  63. package/vendor/fusion/Instances/New.luau +52 -52
  64. package/vendor/fusion/Instances/OnChange.luau +49 -49
  65. package/vendor/fusion/Instances/OnEvent.luau +53 -53
  66. package/vendor/fusion/Instances/Out.luau +69 -69
  67. package/vendor/fusion/Instances/applyInstanceProps.luau +148 -148
  68. package/vendor/fusion/Instances/defaultProps.luau +194 -194
  69. package/vendor/fusion/LICENSE +21 -21
  70. package/vendor/fusion/Logging/formatError.luau +48 -48
  71. package/vendor/fusion/Logging/messages.luau +51 -51
  72. package/vendor/fusion/Logging/parseError.luau +24 -24
  73. package/vendor/fusion/Memory/checkLifetime.luau +133 -133
  74. package/vendor/fusion/Memory/deriveScope.luau +23 -23
  75. package/vendor/fusion/Memory/deriveScopeImpl.luau +44 -44
  76. package/vendor/fusion/Memory/doCleanup.luau +78 -78
  77. package/vendor/fusion/Memory/innerScope.luau +33 -33
  78. package/vendor/fusion/Memory/legacyCleanup.luau +17 -17
  79. package/vendor/fusion/Memory/needsDestruction.luau +16 -16
  80. package/vendor/fusion/Memory/poisonScope.luau +33 -33
  81. package/vendor/fusion/Memory/scopePool.luau +54 -54
  82. package/vendor/fusion/Memory/scoped.luau +26 -26
  83. package/vendor/fusion/Memory/whichLivesLonger.luau +74 -74
  84. package/vendor/fusion/RobloxExternal.luau +97 -97
  85. package/vendor/fusion/State/Computed.luau +138 -138
  86. package/vendor/fusion/State/For/Disassembly.luau +210 -210
  87. package/vendor/fusion/State/For/ForTypes.luau +30 -30
  88. package/vendor/fusion/State/For/init.luau +109 -109
  89. package/vendor/fusion/State/ForKeys.luau +93 -93
  90. package/vendor/fusion/State/ForPairs.luau +96 -96
  91. package/vendor/fusion/State/ForValues.luau +93 -93
  92. package/vendor/fusion/State/Value.luau +87 -87
  93. package/vendor/fusion/State/castToState.luau +25 -25
  94. package/vendor/fusion/State/peek.luau +30 -30
  95. package/vendor/fusion/Types.luau +314 -314
  96. package/vendor/fusion/Utility/Contextual.luau +90 -90
  97. package/vendor/fusion/Utility/Safe.luau +22 -22
  98. package/vendor/fusion/Utility/isSimilar.luau +29 -29
  99. package/vendor/fusion/Utility/merge.luau +35 -35
  100. package/vendor/fusion/Utility/nameOf.luau +34 -34
  101. package/vendor/fusion/Utility/never.luau +13 -13
  102. package/vendor/fusion/Utility/nicknames.luau +10 -10
  103. package/vendor/fusion/Utility/xtypeof.luau +26 -26
  104. package/vendor/fusion/init.luau +82 -82
  105. package/vendor/profilestore/init.luau +2242 -2242
  106. package/vendor/promise/init.luau +1982 -1982
  107. package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -25
  108. package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -228
  109. package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -269
  110. package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -223
  111. package/vendor/rbxutil/buffer-util/Types.luau +60 -60
  112. package/vendor/rbxutil/buffer-util/index.d.ts +153 -153
  113. package/vendor/rbxutil/buffer-util/init.luau +41 -41
  114. package/vendor/rbxutil/buffer-util/package.json +16 -16
  115. package/vendor/rbxutil/buffer-util/wally.toml +9 -9
  116. package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -232
  117. package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -156
  118. package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -109
  119. package/vendor/rbxutil/comm/Client/init.luau +135 -135
  120. package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -295
  121. package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -211
  122. package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -211
  123. package/vendor/rbxutil/comm/Server/init.luau +140 -140
  124. package/vendor/rbxutil/comm/Types.luau +18 -18
  125. package/vendor/rbxutil/comm/Util.luau +27 -27
  126. package/vendor/rbxutil/comm/init.luau +35 -35
  127. package/vendor/rbxutil/comm/wally.toml +13 -13
  128. package/vendor/rbxutil/component/init.luau +759 -759
  129. package/vendor/rbxutil/component/init.test.luau +311 -311
  130. package/vendor/rbxutil/component/wally.toml +14 -14
  131. package/vendor/rbxutil/concur/init.luau +542 -542
  132. package/vendor/rbxutil/concur/init.test.luau +364 -364
  133. package/vendor/rbxutil/concur/wally.toml +8 -8
  134. package/vendor/rbxutil/enum-list/init.luau +101 -101
  135. package/vendor/rbxutil/enum-list/init.test.luau +91 -91
  136. package/vendor/rbxutil/enum-list/wally.toml +8 -8
  137. package/vendor/rbxutil/find/index.d.ts +20 -20
  138. package/vendor/rbxutil/find/init.luau +44 -44
  139. package/vendor/rbxutil/find/package.json +17 -17
  140. package/vendor/rbxutil/find/wally.toml +8 -8
  141. package/vendor/rbxutil/input/Gamepad.luau +559 -559
  142. package/vendor/rbxutil/input/Keyboard.luau +124 -124
  143. package/vendor/rbxutil/input/Mouse.luau +278 -278
  144. package/vendor/rbxutil/input/PreferredInput.luau +91 -91
  145. package/vendor/rbxutil/input/Touch.luau +120 -120
  146. package/vendor/rbxutil/input/init.luau +33 -33
  147. package/vendor/rbxutil/input/wally.toml +12 -12
  148. package/vendor/rbxutil/loader/index.d.ts +15 -15
  149. package/vendor/rbxutil/loader/init.luau +137 -137
  150. package/vendor/rbxutil/loader/wally.toml +8 -8
  151. package/vendor/rbxutil/log/index.d.ts +38 -38
  152. package/vendor/rbxutil/log/init.luau +746 -746
  153. package/vendor/rbxutil/log/wally.toml +8 -8
  154. package/vendor/rbxutil/net/init.luau +190 -190
  155. package/vendor/rbxutil/net/wally.toml +8 -8
  156. package/vendor/rbxutil/option/index.d.ts +44 -44
  157. package/vendor/rbxutil/option/init.luau +489 -489
  158. package/vendor/rbxutil/option/init.test.luau +342 -342
  159. package/vendor/rbxutil/option/wally.toml +8 -8
  160. package/vendor/rbxutil/pid/index.d.ts +53 -53
  161. package/vendor/rbxutil/pid/init.luau +195 -195
  162. package/vendor/rbxutil/pid/package.json +16 -16
  163. package/vendor/rbxutil/pid/wally.toml +9 -9
  164. package/vendor/rbxutil/quaternion/index.d.ts +117 -117
  165. package/vendor/rbxutil/quaternion/init.luau +570 -570
  166. package/vendor/rbxutil/quaternion/package.json +16 -16
  167. package/vendor/rbxutil/quaternion/wally.toml +9 -9
  168. package/vendor/rbxutil/query/index.d.ts +43 -43
  169. package/vendor/rbxutil/query/init.luau +117 -117
  170. package/vendor/rbxutil/query/package.json +18 -18
  171. package/vendor/rbxutil/query/wally.toml +9 -9
  172. package/vendor/rbxutil/sequent/index.d.ts +28 -28
  173. package/vendor/rbxutil/sequent/init.luau +340 -340
  174. package/vendor/rbxutil/sequent/package.json +16 -16
  175. package/vendor/rbxutil/sequent/wally.toml +9 -9
  176. package/vendor/rbxutil/ser/init.luau +175 -175
  177. package/vendor/rbxutil/ser/init.test.luau +50 -50
  178. package/vendor/rbxutil/ser/wally.toml +11 -11
  179. package/vendor/rbxutil/shake/index.d.ts +36 -36
  180. package/vendor/rbxutil/shake/init.luau +532 -532
  181. package/vendor/rbxutil/shake/init.test.luau +267 -267
  182. package/vendor/rbxutil/shake/package.json +16 -16
  183. package/vendor/rbxutil/shake/wally.toml +9 -9
  184. package/vendor/rbxutil/signal/index.d.ts +100 -100
  185. package/vendor/rbxutil/signal/init.luau +432 -432
  186. package/vendor/rbxutil/signal/init.test.luau +190 -190
  187. package/vendor/rbxutil/signal/package.json +17 -17
  188. package/vendor/rbxutil/signal/wally.toml +9 -9
  189. package/vendor/rbxutil/silo/TableWatcher.luau +65 -65
  190. package/vendor/rbxutil/silo/Util.luau +55 -55
  191. package/vendor/rbxutil/silo/init.luau +338 -338
  192. package/vendor/rbxutil/silo/init.test.luau +215 -215
  193. package/vendor/rbxutil/silo/wally.toml +8 -8
  194. package/vendor/rbxutil/spring/index.d.ts +40 -40
  195. package/vendor/rbxutil/spring/init.luau +97 -97
  196. package/vendor/rbxutil/spring/package.json +17 -17
  197. package/vendor/rbxutil/spring/wally.toml +8 -8
  198. package/vendor/rbxutil/stream/index.d.ts +88 -88
  199. package/vendor/rbxutil/stream/init.luau +597 -597
  200. package/vendor/rbxutil/stream/package.json +18 -18
  201. package/vendor/rbxutil/stream/wally.toml +9 -9
  202. package/vendor/rbxutil/streamable/Streamable.luau +202 -202
  203. package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -80
  204. package/vendor/rbxutil/streamable/init.luau +8 -8
  205. package/vendor/rbxutil/streamable/wally.toml +12 -12
  206. package/vendor/rbxutil/symbol/init.luau +56 -56
  207. package/vendor/rbxutil/symbol/init.test.luau +37 -37
  208. package/vendor/rbxutil/symbol/wally.toml +8 -8
  209. package/vendor/rbxutil/table-util/init.luau +938 -938
  210. package/vendor/rbxutil/table-util/init.test.luau +439 -439
  211. package/vendor/rbxutil/task-queue/index.d.ts +27 -27
  212. package/vendor/rbxutil/task-queue/init.luau +97 -97
  213. package/vendor/rbxutil/task-queue/wally.toml +8 -8
  214. package/vendor/rbxutil/timer/index.d.ts +81 -81
  215. package/vendor/rbxutil/timer/init.luau +249 -249
  216. package/vendor/rbxutil/timer/init.test.luau +73 -73
  217. package/vendor/rbxutil/timer/wally.toml +11 -11
  218. package/vendor/rbxutil/tree/index.d.ts +15 -15
  219. package/vendor/rbxutil/tree/init.luau +137 -137
  220. package/vendor/rbxutil/tree/wally.toml +8 -8
  221. package/vendor/rbxutil/trove/index.d.ts +46 -46
  222. package/vendor/rbxutil/trove/init.luau +787 -787
  223. package/vendor/rbxutil/trove/init.test.luau +203 -203
  224. package/vendor/rbxutil/trove/wally.toml +8 -8
  225. package/vendor/rbxutil/typed-remote/init.luau +196 -196
  226. package/vendor/rbxutil/typed-remote/wally.toml +8 -8
  227. package/vendor/rbxutil/wait-for/index.d.ts +17 -17
  228. package/vendor/rbxutil/wait-for/init.luau +257 -257
  229. package/vendor/rbxutil/wait-for/init.test.luau +182 -182
  230. package/vendor/rbxutil/wait-for/wally.toml +11 -11
  231. package/vendor/t/t.lua +1350 -1350
  232. package/vendor/testez/Context.lua +26 -26
  233. package/vendor/testez/Expectation.lua +311 -311
  234. package/vendor/testez/ExpectationContext.lua +38 -38
  235. package/vendor/testez/LifecycleHooks.lua +89 -89
  236. package/vendor/testez/Reporters/TeamCityReporter.lua +101 -101
  237. package/vendor/testez/Reporters/TextReporter.lua +105 -105
  238. package/vendor/testez/Reporters/TextReporterQuiet.lua +96 -96
  239. package/vendor/testez/TestBootstrap.lua +146 -146
  240. package/vendor/testez/TestEnum.lua +27 -27
  241. package/vendor/testez/TestPlan.lua +304 -304
  242. package/vendor/testez/TestPlanner.lua +39 -39
  243. package/vendor/testez/TestResults.lua +111 -111
  244. package/vendor/testez/TestRunner.lua +188 -188
  245. package/vendor/testez/TestSession.lua +243 -243
  246. package/vendor/testez/init.lua +39 -39
@@ -1,338 +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
- }
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
+ }