roblox-opencode 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -122
- package/commands/setup-game.md +108 -108
- package/commands/sync-check.md +53 -53
- package/core/roblox-core.md +93 -93
- package/dist/server.js +189 -167
- package/package.json +35 -35
- package/skills/roblox-analytics/SKILL.md +277 -277
- package/skills/roblox-analytics/references/event-batcher.luau +75 -75
- package/skills/roblox-animation-vfx/SKILL.md +1325 -1325
- package/skills/roblox-architecture/SKILL.md +877 -863
- package/skills/roblox-architecture/references/combat-systems.md +1381 -1381
- package/skills/roblox-code-review/SKILL.md +686 -686
- package/skills/roblox-data/SKILL.md +889 -889
- package/skills/roblox-data/references/inventory-systems.md +1729 -1729
- package/skills/roblox-debug/SKILL.md +98 -98
- package/skills/roblox-gui/SKILL.md +1103 -1103
- package/skills/roblox-gui-fusion/SKILL.md +150 -150
- package/skills/roblox-gui-fusion/references/inventory.luau +427 -427
- package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -579
- package/skills/roblox-gui-fusion/references/shop.luau +411 -411
- package/skills/roblox-luau-mastery/SKILL.md +1618 -1519
- package/skills/roblox-monetization/SKILL.md +1084 -1084
- package/skills/roblox-monetization/references/process-receipt.luau +131 -131
- package/skills/roblox-networking/SKILL.md +669 -669
- package/skills/roblox-networking/references/remote-validator.luau +193 -193
- package/skills/roblox-publish-checklist/SKILL.md +127 -127
- package/skills/roblox-runtime/SKILL.md +753 -753
- package/skills/roblox-sharp-edges/SKILL.md +294 -294
- package/skills/roblox-sync/SKILL.md +126 -126
- package/skills/roblox-testing/SKILL.md +943 -943
- package/skills/roblox-tooling/SKILL.md +149 -149
- package/vendor/LICENSES/ProfileStore-LICENSE +201 -201
- package/vendor/LICENSES/RbxUtil-LICENSE +7 -7
- package/vendor/LICENSES/promise-LICENSE +20 -20
- package/vendor/LICENSES/t-LICENSE +21 -21
- package/vendor/LICENSES/testez-LICENSE +200 -200
- package/vendor/README.md +83 -83
- package/vendor/fusion/Animation/ExternalTime.luau +83 -83
- package/vendor/fusion/Animation/Spring.luau +321 -321
- package/vendor/fusion/Animation/Stopwatch.luau +127 -127
- package/vendor/fusion/Animation/Tween.luau +187 -187
- package/vendor/fusion/Animation/getTweenDuration.luau +27 -27
- package/vendor/fusion/Animation/getTweenRatio.luau +47 -47
- package/vendor/fusion/Animation/lerpType.luau +163 -163
- package/vendor/fusion/Animation/packType.luau +99 -99
- package/vendor/fusion/Animation/springCoefficients.luau +80 -80
- package/vendor/fusion/Animation/unpackType.luau +102 -102
- package/vendor/fusion/Colour/Oklab.luau +70 -70
- package/vendor/fusion/Colour/sRGB.luau +54 -54
- package/vendor/fusion/External.luau +167 -167
- package/vendor/fusion/ExternalDebug.luau +69 -69
- package/vendor/fusion/Graph/Observer.luau +113 -113
- package/vendor/fusion/Graph/castToGraph.luau +28 -28
- package/vendor/fusion/Graph/change.luau +80 -80
- package/vendor/fusion/Graph/depend.luau +32 -32
- package/vendor/fusion/Graph/evaluate.luau +55 -55
- package/vendor/fusion/Instances/Attribute.luau +57 -57
- package/vendor/fusion/Instances/AttributeChange.luau +46 -46
- package/vendor/fusion/Instances/AttributeOut.luau +63 -63
- package/vendor/fusion/Instances/Child.luau +21 -21
- package/vendor/fusion/Instances/Children.luau +147 -147
- package/vendor/fusion/Instances/Hydrate.luau +32 -32
- package/vendor/fusion/Instances/New.luau +52 -52
- package/vendor/fusion/Instances/OnChange.luau +49 -49
- package/vendor/fusion/Instances/OnEvent.luau +53 -53
- package/vendor/fusion/Instances/Out.luau +69 -69
- package/vendor/fusion/Instances/applyInstanceProps.luau +148 -148
- package/vendor/fusion/Instances/defaultProps.luau +194 -194
- package/vendor/fusion/LICENSE +21 -21
- package/vendor/fusion/Logging/formatError.luau +48 -48
- package/vendor/fusion/Logging/messages.luau +51 -51
- package/vendor/fusion/Logging/parseError.luau +24 -24
- package/vendor/fusion/Memory/checkLifetime.luau +133 -133
- package/vendor/fusion/Memory/deriveScope.luau +23 -23
- package/vendor/fusion/Memory/deriveScopeImpl.luau +44 -44
- package/vendor/fusion/Memory/doCleanup.luau +78 -78
- package/vendor/fusion/Memory/innerScope.luau +33 -33
- package/vendor/fusion/Memory/legacyCleanup.luau +17 -17
- package/vendor/fusion/Memory/needsDestruction.luau +16 -16
- package/vendor/fusion/Memory/poisonScope.luau +33 -33
- package/vendor/fusion/Memory/scopePool.luau +54 -54
- package/vendor/fusion/Memory/scoped.luau +26 -26
- package/vendor/fusion/Memory/whichLivesLonger.luau +74 -74
- package/vendor/fusion/RobloxExternal.luau +97 -97
- package/vendor/fusion/State/Computed.luau +138 -138
- package/vendor/fusion/State/For/Disassembly.luau +210 -210
- package/vendor/fusion/State/For/ForTypes.luau +30 -30
- package/vendor/fusion/State/For/init.luau +109 -109
- package/vendor/fusion/State/ForKeys.luau +93 -93
- package/vendor/fusion/State/ForPairs.luau +96 -96
- package/vendor/fusion/State/ForValues.luau +93 -93
- package/vendor/fusion/State/Value.luau +87 -87
- package/vendor/fusion/State/castToState.luau +25 -25
- package/vendor/fusion/State/peek.luau +30 -30
- package/vendor/fusion/Types.luau +314 -314
- package/vendor/fusion/Utility/Contextual.luau +90 -90
- package/vendor/fusion/Utility/Safe.luau +22 -22
- package/vendor/fusion/Utility/isSimilar.luau +29 -29
- package/vendor/fusion/Utility/merge.luau +35 -35
- package/vendor/fusion/Utility/nameOf.luau +34 -34
- package/vendor/fusion/Utility/never.luau +13 -13
- package/vendor/fusion/Utility/nicknames.luau +10 -10
- package/vendor/fusion/Utility/xtypeof.luau +26 -26
- package/vendor/fusion/init.luau +82 -82
- package/vendor/profilestore/init.luau +2242 -2242
- package/vendor/promise/init.luau +1982 -1982
- package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -25
- package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -228
- package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -269
- package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -223
- package/vendor/rbxutil/buffer-util/Types.luau +60 -60
- package/vendor/rbxutil/buffer-util/index.d.ts +153 -153
- package/vendor/rbxutil/buffer-util/init.luau +41 -41
- package/vendor/rbxutil/buffer-util/package.json +16 -16
- package/vendor/rbxutil/buffer-util/wally.toml +9 -9
- package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -232
- package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -156
- package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -109
- package/vendor/rbxutil/comm/Client/init.luau +135 -135
- package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -295
- package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -211
- package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -211
- package/vendor/rbxutil/comm/Server/init.luau +140 -140
- package/vendor/rbxutil/comm/Types.luau +18 -18
- package/vendor/rbxutil/comm/Util.luau +27 -27
- package/vendor/rbxutil/comm/init.luau +35 -35
- package/vendor/rbxutil/comm/wally.toml +13 -13
- package/vendor/rbxutil/component/init.luau +759 -759
- package/vendor/rbxutil/component/init.test.luau +311 -311
- package/vendor/rbxutil/component/wally.toml +14 -14
- package/vendor/rbxutil/concur/init.luau +542 -542
- package/vendor/rbxutil/concur/init.test.luau +364 -364
- package/vendor/rbxutil/concur/wally.toml +8 -8
- package/vendor/rbxutil/enum-list/init.luau +101 -101
- package/vendor/rbxutil/enum-list/init.test.luau +91 -91
- package/vendor/rbxutil/enum-list/wally.toml +8 -8
- package/vendor/rbxutil/find/index.d.ts +20 -20
- package/vendor/rbxutil/find/init.luau +44 -44
- package/vendor/rbxutil/find/package.json +17 -17
- package/vendor/rbxutil/find/wally.toml +8 -8
- package/vendor/rbxutil/input/Gamepad.luau +559 -559
- package/vendor/rbxutil/input/Keyboard.luau +124 -124
- package/vendor/rbxutil/input/Mouse.luau +278 -278
- package/vendor/rbxutil/input/PreferredInput.luau +91 -91
- package/vendor/rbxutil/input/Touch.luau +120 -120
- package/vendor/rbxutil/input/init.luau +33 -33
- package/vendor/rbxutil/input/wally.toml +12 -12
- package/vendor/rbxutil/loader/index.d.ts +15 -15
- package/vendor/rbxutil/loader/init.luau +137 -137
- package/vendor/rbxutil/loader/wally.toml +8 -8
- package/vendor/rbxutil/log/index.d.ts +38 -38
- package/vendor/rbxutil/log/init.luau +746 -746
- package/vendor/rbxutil/log/wally.toml +8 -8
- package/vendor/rbxutil/net/init.luau +190 -190
- package/vendor/rbxutil/net/wally.toml +8 -8
- package/vendor/rbxutil/option/index.d.ts +44 -44
- package/vendor/rbxutil/option/init.luau +489 -489
- package/vendor/rbxutil/option/init.test.luau +342 -342
- package/vendor/rbxutil/option/wally.toml +8 -8
- package/vendor/rbxutil/pid/index.d.ts +53 -53
- package/vendor/rbxutil/pid/init.luau +195 -195
- package/vendor/rbxutil/pid/package.json +16 -16
- package/vendor/rbxutil/pid/wally.toml +9 -9
- package/vendor/rbxutil/quaternion/index.d.ts +117 -117
- package/vendor/rbxutil/quaternion/init.luau +570 -570
- package/vendor/rbxutil/quaternion/package.json +16 -16
- package/vendor/rbxutil/quaternion/wally.toml +9 -9
- package/vendor/rbxutil/query/index.d.ts +43 -43
- package/vendor/rbxutil/query/init.luau +117 -117
- package/vendor/rbxutil/query/package.json +18 -18
- package/vendor/rbxutil/query/wally.toml +9 -9
- package/vendor/rbxutil/sequent/index.d.ts +28 -28
- package/vendor/rbxutil/sequent/init.luau +340 -340
- package/vendor/rbxutil/sequent/package.json +16 -16
- package/vendor/rbxutil/sequent/wally.toml +9 -9
- package/vendor/rbxutil/ser/init.luau +175 -175
- package/vendor/rbxutil/ser/init.test.luau +50 -50
- package/vendor/rbxutil/ser/wally.toml +11 -11
- package/vendor/rbxutil/shake/index.d.ts +36 -36
- package/vendor/rbxutil/shake/init.luau +532 -532
- package/vendor/rbxutil/shake/init.test.luau +267 -267
- package/vendor/rbxutil/shake/package.json +16 -16
- package/vendor/rbxutil/shake/wally.toml +9 -9
- package/vendor/rbxutil/signal/index.d.ts +100 -100
- package/vendor/rbxutil/signal/init.luau +432 -432
- package/vendor/rbxutil/signal/init.test.luau +190 -190
- package/vendor/rbxutil/signal/package.json +17 -17
- package/vendor/rbxutil/signal/wally.toml +9 -9
- package/vendor/rbxutil/silo/TableWatcher.luau +65 -65
- package/vendor/rbxutil/silo/Util.luau +55 -55
- package/vendor/rbxutil/silo/init.luau +338 -338
- package/vendor/rbxutil/silo/init.test.luau +215 -215
- package/vendor/rbxutil/silo/wally.toml +8 -8
- package/vendor/rbxutil/spring/index.d.ts +40 -40
- package/vendor/rbxutil/spring/init.luau +97 -97
- package/vendor/rbxutil/spring/package.json +17 -17
- package/vendor/rbxutil/spring/wally.toml +8 -8
- package/vendor/rbxutil/stream/index.d.ts +88 -88
- package/vendor/rbxutil/stream/init.luau +597 -597
- package/vendor/rbxutil/stream/package.json +18 -18
- package/vendor/rbxutil/stream/wally.toml +9 -9
- package/vendor/rbxutil/streamable/Streamable.luau +202 -202
- package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -80
- package/vendor/rbxutil/streamable/init.luau +8 -8
- package/vendor/rbxutil/streamable/wally.toml +12 -12
- package/vendor/rbxutil/symbol/init.luau +56 -56
- package/vendor/rbxutil/symbol/init.test.luau +37 -37
- package/vendor/rbxutil/symbol/wally.toml +8 -8
- package/vendor/rbxutil/table-util/init.luau +938 -938
- package/vendor/rbxutil/table-util/init.test.luau +439 -439
- package/vendor/rbxutil/task-queue/index.d.ts +27 -27
- package/vendor/rbxutil/task-queue/init.luau +97 -97
- package/vendor/rbxutil/task-queue/wally.toml +8 -8
- package/vendor/rbxutil/timer/index.d.ts +81 -81
- package/vendor/rbxutil/timer/init.luau +249 -249
- package/vendor/rbxutil/timer/init.test.luau +73 -73
- package/vendor/rbxutil/timer/wally.toml +11 -11
- package/vendor/rbxutil/tree/index.d.ts +15 -15
- package/vendor/rbxutil/tree/init.luau +137 -137
- package/vendor/rbxutil/tree/wally.toml +8 -8
- package/vendor/rbxutil/trove/index.d.ts +46 -46
- package/vendor/rbxutil/trove/init.luau +787 -787
- package/vendor/rbxutil/trove/init.test.luau +203 -203
- package/vendor/rbxutil/trove/wally.toml +8 -8
- package/vendor/rbxutil/typed-remote/init.luau +196 -196
- package/vendor/rbxutil/typed-remote/wally.toml +8 -8
- package/vendor/rbxutil/wait-for/index.d.ts +17 -17
- package/vendor/rbxutil/wait-for/init.luau +257 -257
- package/vendor/rbxutil/wait-for/init.test.luau +182 -182
- package/vendor/rbxutil/wait-for/wally.toml +11 -11
- package/vendor/t/t.lua +1350 -1350
- package/vendor/testez/Context.lua +26 -26
- package/vendor/testez/Expectation.lua +311 -311
- package/vendor/testez/ExpectationContext.lua +38 -38
- package/vendor/testez/LifecycleHooks.lua +89 -89
- package/vendor/testez/Reporters/TeamCityReporter.lua +101 -101
- package/vendor/testez/Reporters/TextReporter.lua +105 -105
- package/vendor/testez/Reporters/TextReporterQuiet.lua +96 -96
- package/vendor/testez/TestBootstrap.lua +146 -146
- package/vendor/testez/TestEnum.lua +27 -27
- package/vendor/testez/TestPlan.lua +304 -304
- package/vendor/testez/TestPlanner.lua +39 -39
- package/vendor/testez/TestResults.lua +111 -111
- package/vendor/testez/TestRunner.lua +188 -188
- package/vendor/testez/TestSession.lua +243 -243
- 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
|
+
}
|