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.
- package/README.md +122 -0
- package/commands/setup-game.md +108 -0
- package/commands/sync-check.md +53 -0
- package/core/roblox-core.md +93 -0
- package/dist/server.js +167 -0
- package/package.json +35 -0
- package/skills/roblox-analytics/SKILL.md +277 -0
- package/skills/roblox-analytics/references/event-batcher.luau +75 -0
- package/skills/roblox-animation-vfx/SKILL.md +1325 -0
- package/skills/roblox-architecture/SKILL.md +863 -0
- package/skills/roblox-architecture/references/combat-systems.md +1381 -0
- package/skills/roblox-code-review/SKILL.md +687 -0
- package/skills/roblox-data/SKILL.md +889 -0
- package/skills/roblox-data/references/inventory-systems.md +1729 -0
- package/skills/roblox-debug/SKILL.md +99 -0
- package/skills/roblox-gui/SKILL.md +1103 -0
- package/skills/roblox-gui-fusion/SKILL.md +150 -0
- package/skills/roblox-gui-fusion/references/inventory.luau +427 -0
- package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -0
- package/skills/roblox-gui-fusion/references/shop.luau +411 -0
- package/skills/roblox-luau-mastery/SKILL.md +1519 -0
- package/skills/roblox-monetization/SKILL.md +1084 -0
- package/skills/roblox-monetization/references/process-receipt.luau +131 -0
- package/skills/roblox-networking/SKILL.md +669 -0
- package/skills/roblox-networking/references/remote-validator.luau +193 -0
- package/skills/roblox-publish-checklist/SKILL.md +128 -0
- package/skills/roblox-runtime/SKILL.md +753 -0
- package/skills/roblox-sharp-edges/SKILL.md +295 -0
- package/skills/roblox-sync/SKILL.md +126 -0
- package/skills/roblox-testing/SKILL.md +943 -0
- package/skills/roblox-tooling/SKILL.md +150 -0
- package/vendor/LICENSES/ProfileStore-LICENSE +201 -0
- package/vendor/LICENSES/RbxUtil-LICENSE +7 -0
- package/vendor/LICENSES/promise-LICENSE +21 -0
- package/vendor/LICENSES/t-LICENSE +21 -0
- package/vendor/LICENSES/testez-LICENSE +201 -0
- package/vendor/README.md +84 -0
- package/vendor/fusion/Animation/ExternalTime.luau +84 -0
- package/vendor/fusion/Animation/Spring.luau +322 -0
- package/vendor/fusion/Animation/Stopwatch.luau +128 -0
- package/vendor/fusion/Animation/Tween.luau +187 -0
- package/vendor/fusion/Animation/getTweenDuration.luau +27 -0
- package/vendor/fusion/Animation/getTweenRatio.luau +47 -0
- package/vendor/fusion/Animation/lerpType.luau +164 -0
- package/vendor/fusion/Animation/packType.luau +100 -0
- package/vendor/fusion/Animation/springCoefficients.luau +80 -0
- package/vendor/fusion/Animation/unpackType.luau +103 -0
- package/vendor/fusion/Colour/Oklab.luau +70 -0
- package/vendor/fusion/Colour/sRGB.luau +55 -0
- package/vendor/fusion/External.luau +168 -0
- package/vendor/fusion/ExternalDebug.luau +70 -0
- package/vendor/fusion/Graph/Observer.luau +114 -0
- package/vendor/fusion/Graph/castToGraph.luau +29 -0
- package/vendor/fusion/Graph/change.luau +81 -0
- package/vendor/fusion/Graph/depend.luau +33 -0
- package/vendor/fusion/Graph/evaluate.luau +56 -0
- package/vendor/fusion/Instances/Attribute.luau +58 -0
- package/vendor/fusion/Instances/AttributeChange.luau +47 -0
- package/vendor/fusion/Instances/AttributeOut.luau +63 -0
- package/vendor/fusion/Instances/Child.luau +21 -0
- package/vendor/fusion/Instances/Children.luau +148 -0
- package/vendor/fusion/Instances/Hydrate.luau +33 -0
- package/vendor/fusion/Instances/New.luau +53 -0
- package/vendor/fusion/Instances/OnChange.luau +50 -0
- package/vendor/fusion/Instances/OnEvent.luau +54 -0
- package/vendor/fusion/Instances/Out.luau +69 -0
- package/vendor/fusion/Instances/applyInstanceProps.luau +149 -0
- package/vendor/fusion/Instances/defaultProps.luau +194 -0
- package/vendor/fusion/LICENSE +21 -0
- package/vendor/fusion/Logging/formatError.luau +49 -0
- package/vendor/fusion/Logging/messages.luau +52 -0
- package/vendor/fusion/Logging/parseError.luau +25 -0
- package/vendor/fusion/Memory/checkLifetime.luau +134 -0
- package/vendor/fusion/Memory/deriveScope.luau +24 -0
- package/vendor/fusion/Memory/deriveScopeImpl.luau +45 -0
- package/vendor/fusion/Memory/doCleanup.luau +79 -0
- package/vendor/fusion/Memory/innerScope.luau +34 -0
- package/vendor/fusion/Memory/legacyCleanup.luau +18 -0
- package/vendor/fusion/Memory/needsDestruction.luau +17 -0
- package/vendor/fusion/Memory/poisonScope.luau +34 -0
- package/vendor/fusion/Memory/scopePool.luau +55 -0
- package/vendor/fusion/Memory/scoped.luau +27 -0
- package/vendor/fusion/Memory/whichLivesLonger.luau +75 -0
- package/vendor/fusion/RobloxExternal.luau +98 -0
- package/vendor/fusion/State/Computed.luau +139 -0
- package/vendor/fusion/State/For/Disassembly.luau +211 -0
- package/vendor/fusion/State/For/ForTypes.luau +30 -0
- package/vendor/fusion/State/For/init.luau +110 -0
- package/vendor/fusion/State/ForKeys.luau +94 -0
- package/vendor/fusion/State/ForPairs.luau +97 -0
- package/vendor/fusion/State/ForValues.luau +94 -0
- package/vendor/fusion/State/Value.luau +88 -0
- package/vendor/fusion/State/castToState.luau +26 -0
- package/vendor/fusion/State/peek.luau +31 -0
- package/vendor/fusion/State/updateAll.luau +1 -0
- package/vendor/fusion/Types.luau +314 -0
- package/vendor/fusion/Utility/Contextual.luau +91 -0
- package/vendor/fusion/Utility/Safe.luau +23 -0
- package/vendor/fusion/Utility/isSimilar.luau +29 -0
- package/vendor/fusion/Utility/merge.luau +35 -0
- package/vendor/fusion/Utility/nameOf.luau +35 -0
- package/vendor/fusion/Utility/never.luau +14 -0
- package/vendor/fusion/Utility/nicknames.luau +11 -0
- package/vendor/fusion/Utility/xtypeof.luau +27 -0
- package/vendor/fusion/init.luau +82 -0
- package/vendor/profilestore/init.luau +2243 -0
- package/vendor/promise/init.luau +1982 -0
- package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -0
- package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -0
- package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -0
- package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -0
- package/vendor/rbxutil/buffer-util/Types.luau +60 -0
- package/vendor/rbxutil/buffer-util/index.d.ts +153 -0
- package/vendor/rbxutil/buffer-util/init.luau +41 -0
- package/vendor/rbxutil/buffer-util/package.json +16 -0
- package/vendor/rbxutil/buffer-util/wally.toml +9 -0
- package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -0
- package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -0
- package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -0
- package/vendor/rbxutil/comm/Client/init.luau +135 -0
- package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -0
- package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -0
- package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -0
- package/vendor/rbxutil/comm/Server/init.luau +140 -0
- package/vendor/rbxutil/comm/Types.luau +18 -0
- package/vendor/rbxutil/comm/Util.luau +27 -0
- package/vendor/rbxutil/comm/init.luau +35 -0
- package/vendor/rbxutil/comm/wally.toml +13 -0
- package/vendor/rbxutil/component/init.luau +759 -0
- package/vendor/rbxutil/component/init.test.luau +311 -0
- package/vendor/rbxutil/component/wally.toml +14 -0
- package/vendor/rbxutil/concur/init.luau +542 -0
- package/vendor/rbxutil/concur/init.test.luau +364 -0
- package/vendor/rbxutil/concur/wally.toml +8 -0
- package/vendor/rbxutil/enum-list/init.luau +101 -0
- package/vendor/rbxutil/enum-list/init.test.luau +91 -0
- package/vendor/rbxutil/enum-list/wally.toml +8 -0
- package/vendor/rbxutil/find/index.d.ts +20 -0
- package/vendor/rbxutil/find/init.luau +44 -0
- package/vendor/rbxutil/find/package.json +17 -0
- package/vendor/rbxutil/find/wally.toml +8 -0
- package/vendor/rbxutil/input/Gamepad.luau +559 -0
- package/vendor/rbxutil/input/Keyboard.luau +124 -0
- package/vendor/rbxutil/input/Mouse.luau +278 -0
- package/vendor/rbxutil/input/PreferredInput.luau +91 -0
- package/vendor/rbxutil/input/Touch.luau +120 -0
- package/vendor/rbxutil/input/init.luau +33 -0
- package/vendor/rbxutil/input/wally.toml +12 -0
- package/vendor/rbxutil/loader/index.d.ts +15 -0
- package/vendor/rbxutil/loader/init.luau +137 -0
- package/vendor/rbxutil/loader/wally.toml +8 -0
- package/vendor/rbxutil/log/index.d.ts +38 -0
- package/vendor/rbxutil/log/init.luau +746 -0
- package/vendor/rbxutil/log/wally.toml +8 -0
- package/vendor/rbxutil/net/init.luau +190 -0
- package/vendor/rbxutil/net/wally.toml +8 -0
- package/vendor/rbxutil/option/index.d.ts +44 -0
- package/vendor/rbxutil/option/init.luau +489 -0
- package/vendor/rbxutil/option/init.test.luau +342 -0
- package/vendor/rbxutil/option/wally.toml +8 -0
- package/vendor/rbxutil/pid/index.d.ts +53 -0
- package/vendor/rbxutil/pid/init.luau +195 -0
- package/vendor/rbxutil/pid/package.json +16 -0
- package/vendor/rbxutil/pid/wally.toml +9 -0
- package/vendor/rbxutil/quaternion/index.d.ts +117 -0
- package/vendor/rbxutil/quaternion/init.luau +570 -0
- package/vendor/rbxutil/quaternion/package.json +16 -0
- package/vendor/rbxutil/quaternion/wally.toml +9 -0
- package/vendor/rbxutil/query/index.d.ts +43 -0
- package/vendor/rbxutil/query/init.luau +117 -0
- package/vendor/rbxutil/query/package.json +18 -0
- package/vendor/rbxutil/query/wally.toml +9 -0
- package/vendor/rbxutil/sequent/index.d.ts +28 -0
- package/vendor/rbxutil/sequent/init.luau +340 -0
- package/vendor/rbxutil/sequent/package.json +16 -0
- package/vendor/rbxutil/sequent/wally.toml +9 -0
- package/vendor/rbxutil/ser/init.luau +175 -0
- package/vendor/rbxutil/ser/init.test.luau +50 -0
- package/vendor/rbxutil/ser/wally.toml +11 -0
- package/vendor/rbxutil/shake/index.d.ts +36 -0
- package/vendor/rbxutil/shake/init.luau +532 -0
- package/vendor/rbxutil/shake/init.test.luau +267 -0
- package/vendor/rbxutil/shake/package.json +16 -0
- package/vendor/rbxutil/shake/wally.toml +9 -0
- package/vendor/rbxutil/signal/index.d.ts +100 -0
- package/vendor/rbxutil/signal/init.luau +432 -0
- package/vendor/rbxutil/signal/init.test.luau +190 -0
- package/vendor/rbxutil/signal/package.json +17 -0
- package/vendor/rbxutil/signal/wally.toml +9 -0
- package/vendor/rbxutil/silo/TableWatcher.luau +65 -0
- package/vendor/rbxutil/silo/Util.luau +55 -0
- package/vendor/rbxutil/silo/init.luau +338 -0
- package/vendor/rbxutil/silo/init.test.luau +215 -0
- package/vendor/rbxutil/silo/wally.toml +8 -0
- package/vendor/rbxutil/spring/index.d.ts +40 -0
- package/vendor/rbxutil/spring/init.luau +97 -0
- package/vendor/rbxutil/spring/package.json +17 -0
- package/vendor/rbxutil/spring/wally.toml +8 -0
- package/vendor/rbxutil/stream/index.d.ts +88 -0
- package/vendor/rbxutil/stream/init.luau +597 -0
- package/vendor/rbxutil/stream/package.json +18 -0
- package/vendor/rbxutil/stream/wally.toml +9 -0
- package/vendor/rbxutil/streamable/Streamable.luau +202 -0
- package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -0
- package/vendor/rbxutil/streamable/init.luau +8 -0
- package/vendor/rbxutil/streamable/wally.toml +12 -0
- package/vendor/rbxutil/symbol/init.luau +56 -0
- package/vendor/rbxutil/symbol/init.test.luau +37 -0
- package/vendor/rbxutil/symbol/wally.toml +8 -0
- package/vendor/rbxutil/table-util/init.luau +938 -0
- package/vendor/rbxutil/table-util/init.test.luau +439 -0
- package/vendor/rbxutil/table-util/wally.toml +8 -0
- package/vendor/rbxutil/task-queue/index.d.ts +27 -0
- package/vendor/rbxutil/task-queue/init.luau +97 -0
- package/vendor/rbxutil/task-queue/wally.toml +8 -0
- package/vendor/rbxutil/timer/index.d.ts +81 -0
- package/vendor/rbxutil/timer/init.luau +249 -0
- package/vendor/rbxutil/timer/init.test.luau +73 -0
- package/vendor/rbxutil/timer/wally.toml +11 -0
- package/vendor/rbxutil/tree/index.d.ts +15 -0
- package/vendor/rbxutil/tree/init.luau +137 -0
- package/vendor/rbxutil/tree/wally.toml +8 -0
- package/vendor/rbxutil/trove/index.d.ts +46 -0
- package/vendor/rbxutil/trove/init.luau +787 -0
- package/vendor/rbxutil/trove/init.test.luau +203 -0
- package/vendor/rbxutil/trove/wally.toml +8 -0
- package/vendor/rbxutil/typed-remote/init.luau +196 -0
- package/vendor/rbxutil/typed-remote/wally.toml +8 -0
- package/vendor/rbxutil/wait-for/index.d.ts +17 -0
- package/vendor/rbxutil/wait-for/init.luau +257 -0
- package/vendor/rbxutil/wait-for/init.test.luau +182 -0
- package/vendor/rbxutil/wait-for/wally.toml +11 -0
- package/vendor/t/t.lua +1350 -0
- package/vendor/testez/Context.lua +26 -0
- package/vendor/testez/Expectation.lua +311 -0
- package/vendor/testez/ExpectationContext.lua +38 -0
- package/vendor/testez/LifecycleHooks.lua +89 -0
- package/vendor/testez/Reporters/TeamCityReporter.lua +102 -0
- package/vendor/testez/Reporters/TextReporter.lua +106 -0
- package/vendor/testez/Reporters/TextReporterQuiet.lua +97 -0
- package/vendor/testez/TestBootstrap.lua +147 -0
- package/vendor/testez/TestEnum.lua +28 -0
- package/vendor/testez/TestPlan.lua +304 -0
- package/vendor/testez/TestPlanner.lua +40 -0
- package/vendor/testez/TestResults.lua +112 -0
- package/vendor/testez/TestRunner.lua +188 -0
- package/vendor/testez/TestSession.lua +243 -0
- 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,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;
|