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,787 +1,787 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
|
|
3
|
-
local RunService = game:GetService("RunService")
|
|
4
|
-
|
|
5
|
-
export type Trove = {
|
|
6
|
-
Extend: (self: Trove) -> Trove,
|
|
7
|
-
Clone: <T>(self: Trove, instance: T & Instance) -> T,
|
|
8
|
-
Construct: <T, A...>(self: Trove, class: Constructable<T, A...>, A...) -> T,
|
|
9
|
-
Connect: (
|
|
10
|
-
self: Trove,
|
|
11
|
-
signal: SignalLike | SignalLikeMetatable | RBXScriptSignal,
|
|
12
|
-
fn: (...any) -> ...any
|
|
13
|
-
) -> ConnectionLike | ConnectionLikeMetatable,
|
|
14
|
-
BindToRenderStep: (self: Trove, name: string, priority: number, fn: (dt: number) -> ()) -> (),
|
|
15
|
-
AddPromise: <T>(self: Trove, promise: (T & PromiseLike) | (T & PromiseLikeMetatable)) -> T,
|
|
16
|
-
Add: <T>(self: Trove, object: T & Trackable, cleanupMethod: string?) -> T,
|
|
17
|
-
Remove: <T>(self: Trove, object: T & Trackable) -> boolean,
|
|
18
|
-
Pop: <T>(self: Trove, object: T & Trackable) -> boolean,
|
|
19
|
-
Clean: (self: Trove) -> (),
|
|
20
|
-
WrapClean: (self: Trove) -> () -> (),
|
|
21
|
-
AttachToInstance: (self: Trove, instance: Instance) -> RBXScriptConnection,
|
|
22
|
-
Destroy: (self: Trove) -> (),
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
type TroveInternal = Trove & {
|
|
26
|
-
_objects: { any },
|
|
27
|
-
_cleaning: boolean,
|
|
28
|
-
_findAndRemoveFromObjects: (self: TroveInternal, object: any, cleanup: boolean) -> boolean,
|
|
29
|
-
_cleanupObject: (self: TroveInternal, object: any, cleanupMethod: string?) -> (),
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
--[=[
|
|
33
|
-
@within Trove
|
|
34
|
-
@type Trackable Instance | RBXScriptConnection | ConnectionLike | ConnectLikeMetatable | PromiseLike | PromiseLikeMetatable | thread | ((...any) -> ...any) | Destroyable | DestroyableMetatable | DestroyableLowercase | DestroyableLowercaseMetatable | Disconnectable | DisconnectableMetatable | DisconnectableLowercase | DisconnectableLowercaseMetatable | SignalLike | SignalLikeMetatable
|
|
35
|
-
Represents all trackable objects by Trove.
|
|
36
|
-
]=]
|
|
37
|
-
export type Trackable =
|
|
38
|
-
| Instance
|
|
39
|
-
| RBXScriptConnection
|
|
40
|
-
| ConnectionLike
|
|
41
|
-
| ConnectionLikeMetatable
|
|
42
|
-
| PromiseLike
|
|
43
|
-
| PromiseLikeMetatable
|
|
44
|
-
| thread
|
|
45
|
-
| ((...any) -> ...any)
|
|
46
|
-
| Destroyable
|
|
47
|
-
| DestroyableMetatable
|
|
48
|
-
| DestroyableLowercase
|
|
49
|
-
| DestroyableLowercaseMetatable
|
|
50
|
-
| Disconnectable
|
|
51
|
-
| DisconectableMetatable
|
|
52
|
-
| DisconnectableLowercase
|
|
53
|
-
| DisconnectableLowercaseMetatable
|
|
54
|
-
| SignalLike
|
|
55
|
-
| SignalLikeMetatable
|
|
56
|
-
|
|
57
|
-
--[=[
|
|
58
|
-
@within Trove
|
|
59
|
-
@interface ConnectionLike
|
|
60
|
-
.Connected boolean
|
|
61
|
-
.Disconnect (self) -> ()
|
|
62
|
-
]=]
|
|
63
|
-
type ConnectionLike = {
|
|
64
|
-
Connected: boolean,
|
|
65
|
-
Disconnect: (self: ConnectionLike) -> (),
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
--[=[
|
|
69
|
-
@within Trove
|
|
70
|
-
@interface ConnectionLikeMetatable
|
|
71
|
-
.Connected boolean
|
|
72
|
-
.Disconnect (self) -> ()
|
|
73
|
-
@tag Metatable
|
|
74
|
-
]=]
|
|
75
|
-
type ConnectionLikeMetatable = typeof(setmetatable(
|
|
76
|
-
{},
|
|
77
|
-
{} :: { Connected: boolean, Disconnect: (self: ConnectionLikeMetatable) -> () }
|
|
78
|
-
))
|
|
79
|
-
|
|
80
|
-
--[=[
|
|
81
|
-
@within Trove
|
|
82
|
-
@interface SignalLike
|
|
83
|
-
.Connect (self, callback: (...any) -> ...any) -> ConnectionLike
|
|
84
|
-
.Once (self, callback: (...any) -> ...any) -> ConnectionLike
|
|
85
|
-
]=]
|
|
86
|
-
type SignalLike = {
|
|
87
|
-
Connect: (self: SignalLike, callback: (...any) -> ...any) -> ConnectionLike | ConnectionLikeMetatable,
|
|
88
|
-
Once: (self: SignalLike, callback: (...any) -> ...any) -> ConnectionLike | ConnectionLikeMetatable,
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
--[=[
|
|
92
|
-
@within Trove
|
|
93
|
-
@interface SignalLikeMetatable
|
|
94
|
-
.Connect (self, callback: (...any) -> ...any) -> ConnectionLike
|
|
95
|
-
.Once (self, callback: (...any) -> ...any) -> ConnectionLike
|
|
96
|
-
@tag Metatable
|
|
97
|
-
]=]
|
|
98
|
-
type SignalLikeMetatable = typeof(setmetatable(
|
|
99
|
-
{},
|
|
100
|
-
{} :: {
|
|
101
|
-
Connect: (self: SignalLikeMetatable, callback: (...any) -> ...any) -> ConnectionLike | ConnectionLikeMetatable,
|
|
102
|
-
Once: (self: SignalLikeMetatable, callback: (...any) -> ...any) -> ConnectionLike | ConnectionLikeMetatable,
|
|
103
|
-
}
|
|
104
|
-
))
|
|
105
|
-
|
|
106
|
-
--[=[
|
|
107
|
-
@within Trove
|
|
108
|
-
@interface PromiseLike
|
|
109
|
-
.getStatus (self) -> string
|
|
110
|
-
.finally (self, callback: (...any) -> ...any) -> PromiseLike
|
|
111
|
-
.cancel (self) -> ()
|
|
112
|
-
]=]
|
|
113
|
-
type PromiseLike = {
|
|
114
|
-
getStatus: (self: PromiseLike) -> string,
|
|
115
|
-
finally: (self: PromiseLike, callback: (...any) -> ...any) -> PromiseLike | PromiseLikeMetatable,
|
|
116
|
-
cancel: (self: PromiseLike) -> (),
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
--[=[
|
|
120
|
-
@within Trove
|
|
121
|
-
@interface PromiseLikeMetatable
|
|
122
|
-
.getStatus (self) -> string
|
|
123
|
-
.finally (self, callback: (...any) -> ...any) -> PromiseLike
|
|
124
|
-
.cancel (self) -> ()
|
|
125
|
-
@tag Metatable
|
|
126
|
-
]=]
|
|
127
|
-
type PromiseLikeMetatable = typeof(setmetatable(
|
|
128
|
-
{},
|
|
129
|
-
{} :: {
|
|
130
|
-
getStatus: (self: any) -> string,
|
|
131
|
-
finally: (self: PromiseLikeMetatable, callback: (...any) -> ...any) -> PromiseLike | PromiseLikeMetatable,
|
|
132
|
-
cancel: (self: PromiseLikeMetatable) -> (),
|
|
133
|
-
}
|
|
134
|
-
))
|
|
135
|
-
|
|
136
|
-
--[=[
|
|
137
|
-
@within Trove
|
|
138
|
-
@type Constructable { new: (A...) -> T } | (A...) -> T
|
|
139
|
-
]=]
|
|
140
|
-
type Constructable<T, A...> = { new: (A...) -> T } | (A...) -> T
|
|
141
|
-
|
|
142
|
-
--[=[
|
|
143
|
-
@within Trove
|
|
144
|
-
@interface Destroyable
|
|
145
|
-
.Destroy (self) -> ()
|
|
146
|
-
]=]
|
|
147
|
-
type Destroyable = {
|
|
148
|
-
Destroy: (self: Destroyable) -> (),
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
--[=[
|
|
152
|
-
@within Trove
|
|
153
|
-
@interface DestroyableMetatable
|
|
154
|
-
.Destroy (self) -> ()
|
|
155
|
-
@tag Metatable
|
|
156
|
-
]=]
|
|
157
|
-
type DestroyableMetatable = typeof(setmetatable({}, {} :: { Destroy: (self: DestroyableMetatable) -> () }))
|
|
158
|
-
|
|
159
|
-
--[=[
|
|
160
|
-
@within Trove
|
|
161
|
-
@interface DestroyableLowercase
|
|
162
|
-
.destroy (self) -> ()
|
|
163
|
-
]=]
|
|
164
|
-
type DestroyableLowercase = {
|
|
165
|
-
destroy: (self: DestroyableLowercase) -> (),
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
--[=[
|
|
169
|
-
@within Trove
|
|
170
|
-
@interface DestroyableLowercaseMetatable
|
|
171
|
-
.destroy (self) -> ()
|
|
172
|
-
@tag Metatable
|
|
173
|
-
]=]
|
|
174
|
-
type DestroyableLowercaseMetatable = typeof(setmetatable(
|
|
175
|
-
{},
|
|
176
|
-
{} :: { destroy: (self: DestroyableLowercaseMetatable) -> () }
|
|
177
|
-
))
|
|
178
|
-
|
|
179
|
-
--[=[
|
|
180
|
-
@within Trove
|
|
181
|
-
@interface Disconnectable
|
|
182
|
-
.Disconnect (self) -> ()
|
|
183
|
-
]=]
|
|
184
|
-
type Disconnectable = {
|
|
185
|
-
Disconnect: (self: Disconnectable) -> (),
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
--[=[
|
|
189
|
-
@within Trove
|
|
190
|
-
@interface DisconectableMetatable
|
|
191
|
-
.Disconnect (self) -> ()
|
|
192
|
-
@tag Metatable
|
|
193
|
-
]=]
|
|
194
|
-
type DisconectableMetatable = typeof(setmetatable({}, {} :: { Disconnect: (self: DisconectableMetatable) -> () }))
|
|
195
|
-
|
|
196
|
-
--[=[
|
|
197
|
-
@within Trove
|
|
198
|
-
@interface DisconnectableLowercase
|
|
199
|
-
.disconnect (self) -> ()
|
|
200
|
-
]=]
|
|
201
|
-
type DisconnectableLowercase = {
|
|
202
|
-
disconnect: (self: DisconnectableLowercase) -> (),
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
--[=[
|
|
206
|
-
@within Trove
|
|
207
|
-
@interface DisconnectableLowercaseMetatable
|
|
208
|
-
.disconnect (self) -> ()
|
|
209
|
-
@tag Metatable
|
|
210
|
-
]=]
|
|
211
|
-
type DisconnectableLowercaseMetatable = typeof(setmetatable(
|
|
212
|
-
{},
|
|
213
|
-
{} :: { disconnect: (self: DisconnectableLowercaseMetatable) -> () }
|
|
214
|
-
))
|
|
215
|
-
|
|
216
|
-
local FN_MARKER = newproxy()
|
|
217
|
-
local THREAD_MARKER = newproxy()
|
|
218
|
-
local GENERIC_OBJECT_CLEANUP_METHODS = table.freeze({ "Destroy", "Disconnect", "destroy", "disconnect" })
|
|
219
|
-
|
|
220
|
-
local function getObjectCleanupFunction(object: any, cleanupMethod: string?)
|
|
221
|
-
local t = typeof(object)
|
|
222
|
-
|
|
223
|
-
if t == "function" then
|
|
224
|
-
return FN_MARKER
|
|
225
|
-
elseif t == "thread" then
|
|
226
|
-
return THREAD_MARKER
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
if cleanupMethod then
|
|
230
|
-
return cleanupMethod
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
if t == "Instance" then
|
|
234
|
-
return "Destroy"
|
|
235
|
-
elseif t == "RBXScriptConnection" then
|
|
236
|
-
return "Disconnect"
|
|
237
|
-
elseif t == "table" then
|
|
238
|
-
for _, genericCleanupMethod in GENERIC_OBJECT_CLEANUP_METHODS do
|
|
239
|
-
if typeof(object[genericCleanupMethod]) == "function" then
|
|
240
|
-
return genericCleanupMethod
|
|
241
|
-
end
|
|
242
|
-
end
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
error(`failed to get cleanup function for object {t}: {object}`, 3)
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
local function assertPromiseLike(object: any)
|
|
249
|
-
if
|
|
250
|
-
typeof(object) ~= "table"
|
|
251
|
-
or typeof(object.getStatus) ~= "function"
|
|
252
|
-
or typeof(object.finally) ~= "function"
|
|
253
|
-
or typeof(object.cancel) ~= "function"
|
|
254
|
-
then
|
|
255
|
-
error("did not receive a promise as an argument", 3)
|
|
256
|
-
end
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
local function assertSignalLike(object: any)
|
|
260
|
-
if
|
|
261
|
-
typeof(object) ~= "RBXScriptSignal"
|
|
262
|
-
and (typeof(object) ~= "table" or typeof(object.Connect) ~= "function" or typeof(object.Once) ~= "function")
|
|
263
|
-
then
|
|
264
|
-
error("did not receive a signal as an argument", 3)
|
|
265
|
-
end
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
--[=[
|
|
269
|
-
@class Trove
|
|
270
|
-
A Trove is helpful for tracking any sort of object during
|
|
271
|
-
runtime that needs to get cleaned up at some point.
|
|
272
|
-
]=]
|
|
273
|
-
local Trove = {}
|
|
274
|
-
Trove.__index = Trove
|
|
275
|
-
|
|
276
|
-
--[=[
|
|
277
|
-
@return Trove
|
|
278
|
-
Constructs a Trove object.
|
|
279
|
-
|
|
280
|
-
```lua
|
|
281
|
-
local trove = Trove.new()
|
|
282
|
-
```
|
|
283
|
-
]=]
|
|
284
|
-
function Trove.new(): Trove
|
|
285
|
-
local self = setmetatable({}, Trove)
|
|
286
|
-
|
|
287
|
-
self._objects = {}
|
|
288
|
-
self._cleaning = false
|
|
289
|
-
|
|
290
|
-
return (self :: any) :: Trove
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
--[=[
|
|
294
|
-
@method Add
|
|
295
|
-
@within Trove
|
|
296
|
-
@param object any -- Object to track
|
|
297
|
-
@param cleanupMethod string? -- Optional cleanup name override
|
|
298
|
-
@return object: any
|
|
299
|
-
Adds an object to the trove. Once the trove is cleaned or
|
|
300
|
-
destroyed, the object will also be cleaned up.
|
|
301
|
-
|
|
302
|
-
The following types are accepted (e.g. `typeof(object)`):
|
|
303
|
-
|
|
304
|
-
| Type | Cleanup |
|
|
305
|
-
| ---- | ------- |
|
|
306
|
-
| `Instance` | `object:Destroy()` |
|
|
307
|
-
| `RBXScriptConnection` | `object:Disconnect()` |
|
|
308
|
-
| `function` | `object()` |
|
|
309
|
-
| `thread` | `task.cancel(object)` |
|
|
310
|
-
| `table` | `object:Destroy()` _or_ `object:Disconnect()` _or_ `object:destroy()` _or_ `object:disconnect()` |
|
|
311
|
-
| `table` with `cleanupMethod` | `object:<cleanupMethod>()` |
|
|
312
|
-
|
|
313
|
-
Returns the object added.
|
|
314
|
-
|
|
315
|
-
```lua
|
|
316
|
-
-- Add a part to the trove, then destroy the trove,
|
|
317
|
-
-- which will also destroy the part:
|
|
318
|
-
local part = Instance.new("Part")
|
|
319
|
-
trove:Add(part)
|
|
320
|
-
trove:Destroy()
|
|
321
|
-
|
|
322
|
-
-- Add a function to the trove:
|
|
323
|
-
trove:Add(function()
|
|
324
|
-
print("Cleanup!")
|
|
325
|
-
end)
|
|
326
|
-
trove:Destroy()
|
|
327
|
-
|
|
328
|
-
-- Standard cleanup from table:
|
|
329
|
-
local tbl = {}
|
|
330
|
-
function tbl:Destroy()
|
|
331
|
-
print("Cleanup")
|
|
332
|
-
end
|
|
333
|
-
trove:Add(tbl)
|
|
334
|
-
|
|
335
|
-
-- Custom cleanup from table:
|
|
336
|
-
local tbl = {}
|
|
337
|
-
function tbl:DoSomething()
|
|
338
|
-
print("Do something on cleanup")
|
|
339
|
-
end
|
|
340
|
-
trove:Add(tbl, "DoSomething")
|
|
341
|
-
```
|
|
342
|
-
]=]
|
|
343
|
-
function Trove.Add(self: TroveInternal, object: Trackable, cleanupMethod: string?): any
|
|
344
|
-
if self._cleaning then
|
|
345
|
-
error("cannot call trove:Add() while cleaning", 2)
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
local cleanup = getObjectCleanupFunction(object, cleanupMethod)
|
|
349
|
-
table.insert(self._objects, { object, cleanup })
|
|
350
|
-
|
|
351
|
-
return object
|
|
352
|
-
end
|
|
353
|
-
|
|
354
|
-
--[=[
|
|
355
|
-
@method Clone
|
|
356
|
-
@within Trove
|
|
357
|
-
@return Instance
|
|
358
|
-
Clones the given instance and adds it to the trove. Shorthand for
|
|
359
|
-
`trove:Add(instance:Clone())`.
|
|
360
|
-
|
|
361
|
-
```lua
|
|
362
|
-
local clonedPart = trove:Clone(somePart)
|
|
363
|
-
```
|
|
364
|
-
]=]
|
|
365
|
-
function Trove.Clone(self: TroveInternal, instance: Instance): Instance
|
|
366
|
-
if self._cleaning then
|
|
367
|
-
error("cannot call trove:Clone() while cleaning", 2)
|
|
368
|
-
end
|
|
369
|
-
|
|
370
|
-
return self:Add(instance:Clone())
|
|
371
|
-
end
|
|
372
|
-
|
|
373
|
-
--[=[
|
|
374
|
-
@method Construct
|
|
375
|
-
@within Trove
|
|
376
|
-
@param class { new(Args...) -> T } | (Args...) -> T
|
|
377
|
-
@param ... Args...
|
|
378
|
-
@return T
|
|
379
|
-
Constructs a new object from either the
|
|
380
|
-
table or function given.
|
|
381
|
-
|
|
382
|
-
If a table is given, the table's `new`
|
|
383
|
-
function will be called with the given
|
|
384
|
-
arguments.
|
|
385
|
-
|
|
386
|
-
If a function is given, the function will
|
|
387
|
-
be called with the given arguments.
|
|
388
|
-
|
|
389
|
-
The result from either of the two options
|
|
390
|
-
will be added to the trove.
|
|
391
|
-
|
|
392
|
-
This is shorthand for `trove:Add(SomeClass.new(...))`
|
|
393
|
-
and `trove:Add(SomeFunction(...))`.
|
|
394
|
-
|
|
395
|
-
```lua
|
|
396
|
-
local Signal = require(somewhere.Signal)
|
|
397
|
-
|
|
398
|
-
-- All of these are identical:
|
|
399
|
-
local s = trove:Construct(Signal)
|
|
400
|
-
local s = trove:Construct(Signal.new)
|
|
401
|
-
local s = trove:Construct(function() return Signal.new() end)
|
|
402
|
-
local s = trove:Add(Signal.new())
|
|
403
|
-
|
|
404
|
-
-- Even Roblox instances can be created:
|
|
405
|
-
local part = trove:Construct(Instance, "Part")
|
|
406
|
-
```
|
|
407
|
-
]=]
|
|
408
|
-
function Trove.Construct<T, A...>(self: TroveInternal, class: Constructable<T, A...>, ...: A...)
|
|
409
|
-
if self._cleaning then
|
|
410
|
-
error("Cannot call trove:Construct() while cleaning", 2)
|
|
411
|
-
end
|
|
412
|
-
|
|
413
|
-
local object = nil
|
|
414
|
-
local t = type(class)
|
|
415
|
-
if t == "table" then
|
|
416
|
-
object = (class :: any).new(...)
|
|
417
|
-
elseif t == "function" then
|
|
418
|
-
object = (class :: any)(...)
|
|
419
|
-
end
|
|
420
|
-
|
|
421
|
-
return self:Add(object)
|
|
422
|
-
end
|
|
423
|
-
|
|
424
|
-
--[=[
|
|
425
|
-
@method Connect
|
|
426
|
-
@within Trove
|
|
427
|
-
@param signal RBXScriptSignal
|
|
428
|
-
@param fn (...: any) -> ()
|
|
429
|
-
@return RBXScriptConnection
|
|
430
|
-
Connects the function to the signal, adds the connection
|
|
431
|
-
to the trove, and then returns the connection.
|
|
432
|
-
|
|
433
|
-
This is shorthand for `trove:Add(signal:Connect(fn))`.
|
|
434
|
-
|
|
435
|
-
```lua
|
|
436
|
-
trove:Connect(workspace.ChildAdded, function(instance)
|
|
437
|
-
print(instance.Name .. " added to workspace")
|
|
438
|
-
end)
|
|
439
|
-
```
|
|
440
|
-
]=]
|
|
441
|
-
function Trove.Connect(
|
|
442
|
-
self: TroveInternal,
|
|
443
|
-
signal: SignalLike | SignalLikeMetatable | RBXScriptSignal,
|
|
444
|
-
fn: (...any) -> ...any
|
|
445
|
-
)
|
|
446
|
-
if self._cleaning then
|
|
447
|
-
error("Cannot call trove:Connect() while cleaning", 2)
|
|
448
|
-
end
|
|
449
|
-
assertSignalLike(signal)
|
|
450
|
-
|
|
451
|
-
local confirmedSignal = signal :: SignalLike
|
|
452
|
-
|
|
453
|
-
return self:Add(confirmedSignal:Connect(fn))
|
|
454
|
-
end
|
|
455
|
-
|
|
456
|
-
--[=[
|
|
457
|
-
@method Once
|
|
458
|
-
@within Trove
|
|
459
|
-
@param signal RBXScriptSignal
|
|
460
|
-
@param fn (...: any) -> ()
|
|
461
|
-
@return RBXScriptConnection
|
|
462
|
-
Connects the function to the signal using the `Once`
|
|
463
|
-
method, adds the connection to the trove, and then
|
|
464
|
-
returns the connection.
|
|
465
|
-
|
|
466
|
-
This is shorthand for `trove:Add(signal:Once(fn))`.
|
|
467
|
-
|
|
468
|
-
```lua
|
|
469
|
-
trove:Connect(workspace.ChildAdded, function(instance)
|
|
470
|
-
print(instance.Name .. " added to workspace")
|
|
471
|
-
end)
|
|
472
|
-
```
|
|
473
|
-
]=]
|
|
474
|
-
function Trove.Once(
|
|
475
|
-
self: TroveInternal,
|
|
476
|
-
signal: SignalLike | SignalLikeMetatable | RBXScriptSignal,
|
|
477
|
-
fn: (...any) -> ...any
|
|
478
|
-
)
|
|
479
|
-
if self._cleaning then
|
|
480
|
-
error("Cannot call trove:Connect() while cleaning", 2)
|
|
481
|
-
end
|
|
482
|
-
assertSignalLike(signal)
|
|
483
|
-
|
|
484
|
-
local confirmedSignal = signal :: SignalLike
|
|
485
|
-
|
|
486
|
-
local conn
|
|
487
|
-
conn = confirmedSignal:Once(function(...)
|
|
488
|
-
fn(...)
|
|
489
|
-
self:Pop(conn)
|
|
490
|
-
end)
|
|
491
|
-
|
|
492
|
-
return self:Add(conn)
|
|
493
|
-
end
|
|
494
|
-
|
|
495
|
-
--[=[
|
|
496
|
-
@method BindToRenderStep
|
|
497
|
-
@within Trove
|
|
498
|
-
@param name string
|
|
499
|
-
@param priority number
|
|
500
|
-
@param fn (dt: number) -> ()
|
|
501
|
-
Calls `RunService:BindToRenderStep` and registers a function in the
|
|
502
|
-
trove that will call `RunService:UnbindFromRenderStep` on cleanup.
|
|
503
|
-
|
|
504
|
-
```lua
|
|
505
|
-
trove:BindToRenderStep("Test", Enum.RenderPriority.Last.Value, function(dt)
|
|
506
|
-
-- Do something
|
|
507
|
-
end)
|
|
508
|
-
```
|
|
509
|
-
]=]
|
|
510
|
-
function Trove.BindToRenderStep(self: TroveInternal, name: string, priority: number, fn: (dt: number) -> ())
|
|
511
|
-
if self._cleaning then
|
|
512
|
-
error("cannot call trove:BindToRenderStep() while cleaning", 2)
|
|
513
|
-
end
|
|
514
|
-
|
|
515
|
-
RunService:BindToRenderStep(name, priority, fn)
|
|
516
|
-
|
|
517
|
-
self:Add(function()
|
|
518
|
-
RunService:UnbindFromRenderStep(name)
|
|
519
|
-
end)
|
|
520
|
-
end
|
|
521
|
-
|
|
522
|
-
--[=[
|
|
523
|
-
@method AddPromise
|
|
524
|
-
@within Trove
|
|
525
|
-
@param promise Promise
|
|
526
|
-
@return Promise
|
|
527
|
-
Gives the promise to the trove, which will cancel the promise if the trove is cleaned up or if the promise
|
|
528
|
-
is removed. The exact promise is returned, thus allowing chaining.
|
|
529
|
-
|
|
530
|
-
```lua
|
|
531
|
-
trove:AddPromise(doSomethingThatReturnsAPromise())
|
|
532
|
-
:andThen(function()
|
|
533
|
-
print("Done")
|
|
534
|
-
end)
|
|
535
|
-
-- Will cancel the above promise (assuming it didn't resolve immediately)
|
|
536
|
-
trove:Clean()
|
|
537
|
-
|
|
538
|
-
local p = trove:AddPromise(doSomethingThatReturnsAPromise())
|
|
539
|
-
-- Will also cancel the promise
|
|
540
|
-
trove:Remove(p)
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
:::caution Promise v4 Only
|
|
544
|
-
This is only compatible with the [roblox-lua-promise](https://eryn.io/roblox-lua-promise/) library, version 4.
|
|
545
|
-
:::
|
|
546
|
-
]=]
|
|
547
|
-
function Trove.AddPromise(self: TroveInternal, promise: PromiseLike | PromiseLikeMetatable)
|
|
548
|
-
if self._cleaning then
|
|
549
|
-
error("cannot call trove:AddPromise() while cleaning", 2)
|
|
550
|
-
end
|
|
551
|
-
assertPromiseLike(promise)
|
|
552
|
-
local confirmedPromise = promise :: PromiseLike
|
|
553
|
-
|
|
554
|
-
if confirmedPromise:getStatus() == "Started" then
|
|
555
|
-
confirmedPromise:finally(function()
|
|
556
|
-
if self._cleaning then
|
|
557
|
-
return
|
|
558
|
-
end
|
|
559
|
-
self:_findAndRemoveFromObjects(confirmedPromise, false)
|
|
560
|
-
end)
|
|
561
|
-
|
|
562
|
-
self:Add(confirmedPromise, "cancel")
|
|
563
|
-
end
|
|
564
|
-
|
|
565
|
-
return confirmedPromise
|
|
566
|
-
end
|
|
567
|
-
|
|
568
|
-
--[=[
|
|
569
|
-
@method Remove
|
|
570
|
-
@within Trove
|
|
571
|
-
@param object any
|
|
572
|
-
Removes the object from the Trove and cleans it up. To remove without
|
|
573
|
-
cleaning, use the `Pop` method instead.
|
|
574
|
-
|
|
575
|
-
```lua
|
|
576
|
-
local part = Instance.new("Part")
|
|
577
|
-
trove:Add(part)
|
|
578
|
-
trove:Remove(part) -- Part is destroyed
|
|
579
|
-
```
|
|
580
|
-
]=]
|
|
581
|
-
function Trove.Remove(self: TroveInternal, object: Trackable): boolean
|
|
582
|
-
if self._cleaning then
|
|
583
|
-
error("cannot call trove:Remove() while cleaning", 2)
|
|
584
|
-
end
|
|
585
|
-
|
|
586
|
-
return self:_findAndRemoveFromObjects(object, true)
|
|
587
|
-
end
|
|
588
|
-
|
|
589
|
-
--[=[
|
|
590
|
-
@method Pop
|
|
591
|
-
@within Trove
|
|
592
|
-
@param object any
|
|
593
|
-
Removes the object from the Trove, but does _not_ clean it up. To
|
|
594
|
-
clean up the object on removal, use the `Remove` method instead.
|
|
595
|
-
|
|
596
|
-
```lua
|
|
597
|
-
local part = Instance.new("Part")
|
|
598
|
-
trove:Add(part)
|
|
599
|
-
trove:Pop(part)
|
|
600
|
-
trove:Clean() -- Part is NOT destroyed
|
|
601
|
-
```
|
|
602
|
-
]=]
|
|
603
|
-
function Trove.Pop(self: TroveInternal, object: Trackable): boolean
|
|
604
|
-
if self._cleaning then
|
|
605
|
-
error("cannot call trove:Pop() while cleaning", 2)
|
|
606
|
-
end
|
|
607
|
-
|
|
608
|
-
return self:_findAndRemoveFromObjects(object, false)
|
|
609
|
-
end
|
|
610
|
-
|
|
611
|
-
--[=[
|
|
612
|
-
@method Extend
|
|
613
|
-
@within Trove
|
|
614
|
-
@return Trove
|
|
615
|
-
Creates and adds another trove to itself. This is just shorthand
|
|
616
|
-
for `trove:Construct(Trove)`. This is useful for contexts where
|
|
617
|
-
the trove object is present, but the class itself isn't.
|
|
618
|
-
|
|
619
|
-
:::note
|
|
620
|
-
This does _not_ clone the trove. In other words, the objects in the
|
|
621
|
-
trove are not given to the new constructed trove. This is simply to
|
|
622
|
-
construct a new Trove and add it as an object to track.
|
|
623
|
-
:::
|
|
624
|
-
|
|
625
|
-
```lua
|
|
626
|
-
local trove = Trove.new()
|
|
627
|
-
local subTrove = trove:Extend()
|
|
628
|
-
|
|
629
|
-
trove:Clean() -- Cleans up the subTrove too
|
|
630
|
-
```
|
|
631
|
-
]=]
|
|
632
|
-
function Trove.Extend(self: TroveInternal)
|
|
633
|
-
if self._cleaning then
|
|
634
|
-
error("cannot call trove:Extend() while cleaning", 2)
|
|
635
|
-
end
|
|
636
|
-
|
|
637
|
-
return self:Construct(Trove)
|
|
638
|
-
end
|
|
639
|
-
|
|
640
|
-
--[=[
|
|
641
|
-
@method Clean
|
|
642
|
-
@within Trove
|
|
643
|
-
Cleans up all objects in the trove. This is
|
|
644
|
-
similar to calling `Remove` on each object
|
|
645
|
-
within the trove. The ordering of the objects
|
|
646
|
-
removed is _not_ guaranteed.
|
|
647
|
-
|
|
648
|
-
```lua
|
|
649
|
-
trove:Clean()
|
|
650
|
-
```
|
|
651
|
-
]=]
|
|
652
|
-
function Trove.Clean(self: TroveInternal)
|
|
653
|
-
if self._cleaning then
|
|
654
|
-
return
|
|
655
|
-
end
|
|
656
|
-
|
|
657
|
-
self._cleaning = true
|
|
658
|
-
|
|
659
|
-
for _, obj in self._objects do
|
|
660
|
-
self:_cleanupObject(obj[1], obj[2])
|
|
661
|
-
end
|
|
662
|
-
|
|
663
|
-
table.clear(self._objects)
|
|
664
|
-
self._cleaning = false
|
|
665
|
-
end
|
|
666
|
-
|
|
667
|
-
--[=[
|
|
668
|
-
@method WrapClean
|
|
669
|
-
@within Trove
|
|
670
|
-
Returns a function that wraps the trove's `Clean()`
|
|
671
|
-
method. Calling the returned function will clean up
|
|
672
|
-
the trove.
|
|
673
|
-
|
|
674
|
-
This is often useful in contexts where functions
|
|
675
|
-
are the primary mode for cleaning up an environment,
|
|
676
|
-
such as in many "observer" patterns.
|
|
677
|
-
|
|
678
|
-
```lua
|
|
679
|
-
local cleanup = trove:WrapClean()
|
|
680
|
-
|
|
681
|
-
-- Sometime later...
|
|
682
|
-
cleanup()
|
|
683
|
-
```
|
|
684
|
-
|
|
685
|
-
```lua
|
|
686
|
-
-- Common observer pattern example:
|
|
687
|
-
someObserver(function()
|
|
688
|
-
local trove = Trove.new()
|
|
689
|
-
-- Foo
|
|
690
|
-
return trove:WrapClean()
|
|
691
|
-
end)
|
|
692
|
-
```
|
|
693
|
-
]=]
|
|
694
|
-
function Trove.WrapClean(self: TroveInternal)
|
|
695
|
-
return function()
|
|
696
|
-
self:Clean()
|
|
697
|
-
end
|
|
698
|
-
end
|
|
699
|
-
|
|
700
|
-
function Trove._findAndRemoveFromObjects(self: TroveInternal, object: any, cleanup: boolean): boolean
|
|
701
|
-
local objects = self._objects
|
|
702
|
-
|
|
703
|
-
for i, obj in objects do
|
|
704
|
-
if obj[1] == object then
|
|
705
|
-
local n = #objects
|
|
706
|
-
objects[i] = objects[n]
|
|
707
|
-
objects[n] = nil
|
|
708
|
-
|
|
709
|
-
if cleanup then
|
|
710
|
-
self:_cleanupObject(obj[1], obj[2])
|
|
711
|
-
end
|
|
712
|
-
|
|
713
|
-
return true
|
|
714
|
-
end
|
|
715
|
-
end
|
|
716
|
-
|
|
717
|
-
return false
|
|
718
|
-
end
|
|
719
|
-
|
|
720
|
-
function Trove._cleanupObject(_self: TroveInternal, object: any, cleanupMethod: string?)
|
|
721
|
-
if cleanupMethod == FN_MARKER then
|
|
722
|
-
task.spawn(object)
|
|
723
|
-
elseif cleanupMethod == THREAD_MARKER then
|
|
724
|
-
pcall(task.cancel, object)
|
|
725
|
-
else
|
|
726
|
-
object[cleanupMethod](object)
|
|
727
|
-
end
|
|
728
|
-
end
|
|
729
|
-
|
|
730
|
-
--[=[
|
|
731
|
-
@method AttachToInstance
|
|
732
|
-
@within Trove
|
|
733
|
-
@param instance Instance
|
|
734
|
-
@return RBXScriptConnection
|
|
735
|
-
Attaches the trove to a Roblox instance. Once this
|
|
736
|
-
instance is removed from the game (parent or ancestor's
|
|
737
|
-
parent set to `nil`), the trove will automatically
|
|
738
|
-
clean up.
|
|
739
|
-
|
|
740
|
-
This inverses the ownership of the Trove object, and should
|
|
741
|
-
only be used when necessary. In other words, the attached
|
|
742
|
-
instance dictates when the trove is cleaned up, rather than
|
|
743
|
-
the trove dictating the cleanup of the instance.
|
|
744
|
-
|
|
745
|
-
:::caution
|
|
746
|
-
Will throw an error if `instance` is not a descendant
|
|
747
|
-
of the game hierarchy.
|
|
748
|
-
:::
|
|
749
|
-
|
|
750
|
-
```lua
|
|
751
|
-
trove:AttachToInstance(somePart)
|
|
752
|
-
trove:Add(function()
|
|
753
|
-
print("Cleaned")
|
|
754
|
-
end)
|
|
755
|
-
|
|
756
|
-
-- Destroying the part will cause the trove to clean up, thus "Cleaned" printed:
|
|
757
|
-
somePart:Destroy()
|
|
758
|
-
```
|
|
759
|
-
]=]
|
|
760
|
-
function Trove.AttachToInstance(self: TroveInternal, instance: Instance)
|
|
761
|
-
if self._cleaning then
|
|
762
|
-
error("cannot call trove:AttachToInstance() while cleaning", 2)
|
|
763
|
-
elseif not instance:IsDescendantOf(game) then
|
|
764
|
-
error("instance is not a descendant of the game hierarchy", 2)
|
|
765
|
-
end
|
|
766
|
-
|
|
767
|
-
return self:Connect(instance.Destroying, function()
|
|
768
|
-
self:Destroy()
|
|
769
|
-
end)
|
|
770
|
-
end
|
|
771
|
-
|
|
772
|
-
--[=[
|
|
773
|
-
@method Destroy
|
|
774
|
-
@within Trove
|
|
775
|
-
Alias for `trove:Clean()`.
|
|
776
|
-
|
|
777
|
-
```lua
|
|
778
|
-
trove:Destroy()
|
|
779
|
-
```
|
|
780
|
-
]=]
|
|
781
|
-
function Trove.Destroy(self: TroveInternal)
|
|
782
|
-
self:Clean()
|
|
783
|
-
end
|
|
784
|
-
|
|
785
|
-
return {
|
|
786
|
-
new = Trove.new,
|
|
787
|
-
}
|
|
1
|
+
--!strict
|
|
2
|
+
|
|
3
|
+
local RunService = game:GetService("RunService")
|
|
4
|
+
|
|
5
|
+
export type Trove = {
|
|
6
|
+
Extend: (self: Trove) -> Trove,
|
|
7
|
+
Clone: <T>(self: Trove, instance: T & Instance) -> T,
|
|
8
|
+
Construct: <T, A...>(self: Trove, class: Constructable<T, A...>, A...) -> T,
|
|
9
|
+
Connect: (
|
|
10
|
+
self: Trove,
|
|
11
|
+
signal: SignalLike | SignalLikeMetatable | RBXScriptSignal,
|
|
12
|
+
fn: (...any) -> ...any
|
|
13
|
+
) -> ConnectionLike | ConnectionLikeMetatable,
|
|
14
|
+
BindToRenderStep: (self: Trove, name: string, priority: number, fn: (dt: number) -> ()) -> (),
|
|
15
|
+
AddPromise: <T>(self: Trove, promise: (T & PromiseLike) | (T & PromiseLikeMetatable)) -> T,
|
|
16
|
+
Add: <T>(self: Trove, object: T & Trackable, cleanupMethod: string?) -> T,
|
|
17
|
+
Remove: <T>(self: Trove, object: T & Trackable) -> boolean,
|
|
18
|
+
Pop: <T>(self: Trove, object: T & Trackable) -> boolean,
|
|
19
|
+
Clean: (self: Trove) -> (),
|
|
20
|
+
WrapClean: (self: Trove) -> () -> (),
|
|
21
|
+
AttachToInstance: (self: Trove, instance: Instance) -> RBXScriptConnection,
|
|
22
|
+
Destroy: (self: Trove) -> (),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type TroveInternal = Trove & {
|
|
26
|
+
_objects: { any },
|
|
27
|
+
_cleaning: boolean,
|
|
28
|
+
_findAndRemoveFromObjects: (self: TroveInternal, object: any, cleanup: boolean) -> boolean,
|
|
29
|
+
_cleanupObject: (self: TroveInternal, object: any, cleanupMethod: string?) -> (),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
--[=[
|
|
33
|
+
@within Trove
|
|
34
|
+
@type Trackable Instance | RBXScriptConnection | ConnectionLike | ConnectLikeMetatable | PromiseLike | PromiseLikeMetatable | thread | ((...any) -> ...any) | Destroyable | DestroyableMetatable | DestroyableLowercase | DestroyableLowercaseMetatable | Disconnectable | DisconnectableMetatable | DisconnectableLowercase | DisconnectableLowercaseMetatable | SignalLike | SignalLikeMetatable
|
|
35
|
+
Represents all trackable objects by Trove.
|
|
36
|
+
]=]
|
|
37
|
+
export type Trackable =
|
|
38
|
+
| Instance
|
|
39
|
+
| RBXScriptConnection
|
|
40
|
+
| ConnectionLike
|
|
41
|
+
| ConnectionLikeMetatable
|
|
42
|
+
| PromiseLike
|
|
43
|
+
| PromiseLikeMetatable
|
|
44
|
+
| thread
|
|
45
|
+
| ((...any) -> ...any)
|
|
46
|
+
| Destroyable
|
|
47
|
+
| DestroyableMetatable
|
|
48
|
+
| DestroyableLowercase
|
|
49
|
+
| DestroyableLowercaseMetatable
|
|
50
|
+
| Disconnectable
|
|
51
|
+
| DisconectableMetatable
|
|
52
|
+
| DisconnectableLowercase
|
|
53
|
+
| DisconnectableLowercaseMetatable
|
|
54
|
+
| SignalLike
|
|
55
|
+
| SignalLikeMetatable
|
|
56
|
+
|
|
57
|
+
--[=[
|
|
58
|
+
@within Trove
|
|
59
|
+
@interface ConnectionLike
|
|
60
|
+
.Connected boolean
|
|
61
|
+
.Disconnect (self) -> ()
|
|
62
|
+
]=]
|
|
63
|
+
type ConnectionLike = {
|
|
64
|
+
Connected: boolean,
|
|
65
|
+
Disconnect: (self: ConnectionLike) -> (),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
--[=[
|
|
69
|
+
@within Trove
|
|
70
|
+
@interface ConnectionLikeMetatable
|
|
71
|
+
.Connected boolean
|
|
72
|
+
.Disconnect (self) -> ()
|
|
73
|
+
@tag Metatable
|
|
74
|
+
]=]
|
|
75
|
+
type ConnectionLikeMetatable = typeof(setmetatable(
|
|
76
|
+
{},
|
|
77
|
+
{} :: { Connected: boolean, Disconnect: (self: ConnectionLikeMetatable) -> () }
|
|
78
|
+
))
|
|
79
|
+
|
|
80
|
+
--[=[
|
|
81
|
+
@within Trove
|
|
82
|
+
@interface SignalLike
|
|
83
|
+
.Connect (self, callback: (...any) -> ...any) -> ConnectionLike
|
|
84
|
+
.Once (self, callback: (...any) -> ...any) -> ConnectionLike
|
|
85
|
+
]=]
|
|
86
|
+
type SignalLike = {
|
|
87
|
+
Connect: (self: SignalLike, callback: (...any) -> ...any) -> ConnectionLike | ConnectionLikeMetatable,
|
|
88
|
+
Once: (self: SignalLike, callback: (...any) -> ...any) -> ConnectionLike | ConnectionLikeMetatable,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
--[=[
|
|
92
|
+
@within Trove
|
|
93
|
+
@interface SignalLikeMetatable
|
|
94
|
+
.Connect (self, callback: (...any) -> ...any) -> ConnectionLike
|
|
95
|
+
.Once (self, callback: (...any) -> ...any) -> ConnectionLike
|
|
96
|
+
@tag Metatable
|
|
97
|
+
]=]
|
|
98
|
+
type SignalLikeMetatable = typeof(setmetatable(
|
|
99
|
+
{},
|
|
100
|
+
{} :: {
|
|
101
|
+
Connect: (self: SignalLikeMetatable, callback: (...any) -> ...any) -> ConnectionLike | ConnectionLikeMetatable,
|
|
102
|
+
Once: (self: SignalLikeMetatable, callback: (...any) -> ...any) -> ConnectionLike | ConnectionLikeMetatable,
|
|
103
|
+
}
|
|
104
|
+
))
|
|
105
|
+
|
|
106
|
+
--[=[
|
|
107
|
+
@within Trove
|
|
108
|
+
@interface PromiseLike
|
|
109
|
+
.getStatus (self) -> string
|
|
110
|
+
.finally (self, callback: (...any) -> ...any) -> PromiseLike
|
|
111
|
+
.cancel (self) -> ()
|
|
112
|
+
]=]
|
|
113
|
+
type PromiseLike = {
|
|
114
|
+
getStatus: (self: PromiseLike) -> string,
|
|
115
|
+
finally: (self: PromiseLike, callback: (...any) -> ...any) -> PromiseLike | PromiseLikeMetatable,
|
|
116
|
+
cancel: (self: PromiseLike) -> (),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
--[=[
|
|
120
|
+
@within Trove
|
|
121
|
+
@interface PromiseLikeMetatable
|
|
122
|
+
.getStatus (self) -> string
|
|
123
|
+
.finally (self, callback: (...any) -> ...any) -> PromiseLike
|
|
124
|
+
.cancel (self) -> ()
|
|
125
|
+
@tag Metatable
|
|
126
|
+
]=]
|
|
127
|
+
type PromiseLikeMetatable = typeof(setmetatable(
|
|
128
|
+
{},
|
|
129
|
+
{} :: {
|
|
130
|
+
getStatus: (self: any) -> string,
|
|
131
|
+
finally: (self: PromiseLikeMetatable, callback: (...any) -> ...any) -> PromiseLike | PromiseLikeMetatable,
|
|
132
|
+
cancel: (self: PromiseLikeMetatable) -> (),
|
|
133
|
+
}
|
|
134
|
+
))
|
|
135
|
+
|
|
136
|
+
--[=[
|
|
137
|
+
@within Trove
|
|
138
|
+
@type Constructable { new: (A...) -> T } | (A...) -> T
|
|
139
|
+
]=]
|
|
140
|
+
type Constructable<T, A...> = { new: (A...) -> T } | (A...) -> T
|
|
141
|
+
|
|
142
|
+
--[=[
|
|
143
|
+
@within Trove
|
|
144
|
+
@interface Destroyable
|
|
145
|
+
.Destroy (self) -> ()
|
|
146
|
+
]=]
|
|
147
|
+
type Destroyable = {
|
|
148
|
+
Destroy: (self: Destroyable) -> (),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
--[=[
|
|
152
|
+
@within Trove
|
|
153
|
+
@interface DestroyableMetatable
|
|
154
|
+
.Destroy (self) -> ()
|
|
155
|
+
@tag Metatable
|
|
156
|
+
]=]
|
|
157
|
+
type DestroyableMetatable = typeof(setmetatable({}, {} :: { Destroy: (self: DestroyableMetatable) -> () }))
|
|
158
|
+
|
|
159
|
+
--[=[
|
|
160
|
+
@within Trove
|
|
161
|
+
@interface DestroyableLowercase
|
|
162
|
+
.destroy (self) -> ()
|
|
163
|
+
]=]
|
|
164
|
+
type DestroyableLowercase = {
|
|
165
|
+
destroy: (self: DestroyableLowercase) -> (),
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
--[=[
|
|
169
|
+
@within Trove
|
|
170
|
+
@interface DestroyableLowercaseMetatable
|
|
171
|
+
.destroy (self) -> ()
|
|
172
|
+
@tag Metatable
|
|
173
|
+
]=]
|
|
174
|
+
type DestroyableLowercaseMetatable = typeof(setmetatable(
|
|
175
|
+
{},
|
|
176
|
+
{} :: { destroy: (self: DestroyableLowercaseMetatable) -> () }
|
|
177
|
+
))
|
|
178
|
+
|
|
179
|
+
--[=[
|
|
180
|
+
@within Trove
|
|
181
|
+
@interface Disconnectable
|
|
182
|
+
.Disconnect (self) -> ()
|
|
183
|
+
]=]
|
|
184
|
+
type Disconnectable = {
|
|
185
|
+
Disconnect: (self: Disconnectable) -> (),
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
--[=[
|
|
189
|
+
@within Trove
|
|
190
|
+
@interface DisconectableMetatable
|
|
191
|
+
.Disconnect (self) -> ()
|
|
192
|
+
@tag Metatable
|
|
193
|
+
]=]
|
|
194
|
+
type DisconectableMetatable = typeof(setmetatable({}, {} :: { Disconnect: (self: DisconectableMetatable) -> () }))
|
|
195
|
+
|
|
196
|
+
--[=[
|
|
197
|
+
@within Trove
|
|
198
|
+
@interface DisconnectableLowercase
|
|
199
|
+
.disconnect (self) -> ()
|
|
200
|
+
]=]
|
|
201
|
+
type DisconnectableLowercase = {
|
|
202
|
+
disconnect: (self: DisconnectableLowercase) -> (),
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
--[=[
|
|
206
|
+
@within Trove
|
|
207
|
+
@interface DisconnectableLowercaseMetatable
|
|
208
|
+
.disconnect (self) -> ()
|
|
209
|
+
@tag Metatable
|
|
210
|
+
]=]
|
|
211
|
+
type DisconnectableLowercaseMetatable = typeof(setmetatable(
|
|
212
|
+
{},
|
|
213
|
+
{} :: { disconnect: (self: DisconnectableLowercaseMetatable) -> () }
|
|
214
|
+
))
|
|
215
|
+
|
|
216
|
+
local FN_MARKER = newproxy()
|
|
217
|
+
local THREAD_MARKER = newproxy()
|
|
218
|
+
local GENERIC_OBJECT_CLEANUP_METHODS = table.freeze({ "Destroy", "Disconnect", "destroy", "disconnect" })
|
|
219
|
+
|
|
220
|
+
local function getObjectCleanupFunction(object: any, cleanupMethod: string?)
|
|
221
|
+
local t = typeof(object)
|
|
222
|
+
|
|
223
|
+
if t == "function" then
|
|
224
|
+
return FN_MARKER
|
|
225
|
+
elseif t == "thread" then
|
|
226
|
+
return THREAD_MARKER
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
if cleanupMethod then
|
|
230
|
+
return cleanupMethod
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
if t == "Instance" then
|
|
234
|
+
return "Destroy"
|
|
235
|
+
elseif t == "RBXScriptConnection" then
|
|
236
|
+
return "Disconnect"
|
|
237
|
+
elseif t == "table" then
|
|
238
|
+
for _, genericCleanupMethod in GENERIC_OBJECT_CLEANUP_METHODS do
|
|
239
|
+
if typeof(object[genericCleanupMethod]) == "function" then
|
|
240
|
+
return genericCleanupMethod
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
error(`failed to get cleanup function for object {t}: {object}`, 3)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
local function assertPromiseLike(object: any)
|
|
249
|
+
if
|
|
250
|
+
typeof(object) ~= "table"
|
|
251
|
+
or typeof(object.getStatus) ~= "function"
|
|
252
|
+
or typeof(object.finally) ~= "function"
|
|
253
|
+
or typeof(object.cancel) ~= "function"
|
|
254
|
+
then
|
|
255
|
+
error("did not receive a promise as an argument", 3)
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
local function assertSignalLike(object: any)
|
|
260
|
+
if
|
|
261
|
+
typeof(object) ~= "RBXScriptSignal"
|
|
262
|
+
and (typeof(object) ~= "table" or typeof(object.Connect) ~= "function" or typeof(object.Once) ~= "function")
|
|
263
|
+
then
|
|
264
|
+
error("did not receive a signal as an argument", 3)
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
--[=[
|
|
269
|
+
@class Trove
|
|
270
|
+
A Trove is helpful for tracking any sort of object during
|
|
271
|
+
runtime that needs to get cleaned up at some point.
|
|
272
|
+
]=]
|
|
273
|
+
local Trove = {}
|
|
274
|
+
Trove.__index = Trove
|
|
275
|
+
|
|
276
|
+
--[=[
|
|
277
|
+
@return Trove
|
|
278
|
+
Constructs a Trove object.
|
|
279
|
+
|
|
280
|
+
```lua
|
|
281
|
+
local trove = Trove.new()
|
|
282
|
+
```
|
|
283
|
+
]=]
|
|
284
|
+
function Trove.new(): Trove
|
|
285
|
+
local self = setmetatable({}, Trove)
|
|
286
|
+
|
|
287
|
+
self._objects = {}
|
|
288
|
+
self._cleaning = false
|
|
289
|
+
|
|
290
|
+
return (self :: any) :: Trove
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
--[=[
|
|
294
|
+
@method Add
|
|
295
|
+
@within Trove
|
|
296
|
+
@param object any -- Object to track
|
|
297
|
+
@param cleanupMethod string? -- Optional cleanup name override
|
|
298
|
+
@return object: any
|
|
299
|
+
Adds an object to the trove. Once the trove is cleaned or
|
|
300
|
+
destroyed, the object will also be cleaned up.
|
|
301
|
+
|
|
302
|
+
The following types are accepted (e.g. `typeof(object)`):
|
|
303
|
+
|
|
304
|
+
| Type | Cleanup |
|
|
305
|
+
| ---- | ------- |
|
|
306
|
+
| `Instance` | `object:Destroy()` |
|
|
307
|
+
| `RBXScriptConnection` | `object:Disconnect()` |
|
|
308
|
+
| `function` | `object()` |
|
|
309
|
+
| `thread` | `task.cancel(object)` |
|
|
310
|
+
| `table` | `object:Destroy()` _or_ `object:Disconnect()` _or_ `object:destroy()` _or_ `object:disconnect()` |
|
|
311
|
+
| `table` with `cleanupMethod` | `object:<cleanupMethod>()` |
|
|
312
|
+
|
|
313
|
+
Returns the object added.
|
|
314
|
+
|
|
315
|
+
```lua
|
|
316
|
+
-- Add a part to the trove, then destroy the trove,
|
|
317
|
+
-- which will also destroy the part:
|
|
318
|
+
local part = Instance.new("Part")
|
|
319
|
+
trove:Add(part)
|
|
320
|
+
trove:Destroy()
|
|
321
|
+
|
|
322
|
+
-- Add a function to the trove:
|
|
323
|
+
trove:Add(function()
|
|
324
|
+
print("Cleanup!")
|
|
325
|
+
end)
|
|
326
|
+
trove:Destroy()
|
|
327
|
+
|
|
328
|
+
-- Standard cleanup from table:
|
|
329
|
+
local tbl = {}
|
|
330
|
+
function tbl:Destroy()
|
|
331
|
+
print("Cleanup")
|
|
332
|
+
end
|
|
333
|
+
trove:Add(tbl)
|
|
334
|
+
|
|
335
|
+
-- Custom cleanup from table:
|
|
336
|
+
local tbl = {}
|
|
337
|
+
function tbl:DoSomething()
|
|
338
|
+
print("Do something on cleanup")
|
|
339
|
+
end
|
|
340
|
+
trove:Add(tbl, "DoSomething")
|
|
341
|
+
```
|
|
342
|
+
]=]
|
|
343
|
+
function Trove.Add(self: TroveInternal, object: Trackable, cleanupMethod: string?): any
|
|
344
|
+
if self._cleaning then
|
|
345
|
+
error("cannot call trove:Add() while cleaning", 2)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
local cleanup = getObjectCleanupFunction(object, cleanupMethod)
|
|
349
|
+
table.insert(self._objects, { object, cleanup })
|
|
350
|
+
|
|
351
|
+
return object
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
--[=[
|
|
355
|
+
@method Clone
|
|
356
|
+
@within Trove
|
|
357
|
+
@return Instance
|
|
358
|
+
Clones the given instance and adds it to the trove. Shorthand for
|
|
359
|
+
`trove:Add(instance:Clone())`.
|
|
360
|
+
|
|
361
|
+
```lua
|
|
362
|
+
local clonedPart = trove:Clone(somePart)
|
|
363
|
+
```
|
|
364
|
+
]=]
|
|
365
|
+
function Trove.Clone(self: TroveInternal, instance: Instance): Instance
|
|
366
|
+
if self._cleaning then
|
|
367
|
+
error("cannot call trove:Clone() while cleaning", 2)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
return self:Add(instance:Clone())
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
--[=[
|
|
374
|
+
@method Construct
|
|
375
|
+
@within Trove
|
|
376
|
+
@param class { new(Args...) -> T } | (Args...) -> T
|
|
377
|
+
@param ... Args...
|
|
378
|
+
@return T
|
|
379
|
+
Constructs a new object from either the
|
|
380
|
+
table or function given.
|
|
381
|
+
|
|
382
|
+
If a table is given, the table's `new`
|
|
383
|
+
function will be called with the given
|
|
384
|
+
arguments.
|
|
385
|
+
|
|
386
|
+
If a function is given, the function will
|
|
387
|
+
be called with the given arguments.
|
|
388
|
+
|
|
389
|
+
The result from either of the two options
|
|
390
|
+
will be added to the trove.
|
|
391
|
+
|
|
392
|
+
This is shorthand for `trove:Add(SomeClass.new(...))`
|
|
393
|
+
and `trove:Add(SomeFunction(...))`.
|
|
394
|
+
|
|
395
|
+
```lua
|
|
396
|
+
local Signal = require(somewhere.Signal)
|
|
397
|
+
|
|
398
|
+
-- All of these are identical:
|
|
399
|
+
local s = trove:Construct(Signal)
|
|
400
|
+
local s = trove:Construct(Signal.new)
|
|
401
|
+
local s = trove:Construct(function() return Signal.new() end)
|
|
402
|
+
local s = trove:Add(Signal.new())
|
|
403
|
+
|
|
404
|
+
-- Even Roblox instances can be created:
|
|
405
|
+
local part = trove:Construct(Instance, "Part")
|
|
406
|
+
```
|
|
407
|
+
]=]
|
|
408
|
+
function Trove.Construct<T, A...>(self: TroveInternal, class: Constructable<T, A...>, ...: A...)
|
|
409
|
+
if self._cleaning then
|
|
410
|
+
error("Cannot call trove:Construct() while cleaning", 2)
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
local object = nil
|
|
414
|
+
local t = type(class)
|
|
415
|
+
if t == "table" then
|
|
416
|
+
object = (class :: any).new(...)
|
|
417
|
+
elseif t == "function" then
|
|
418
|
+
object = (class :: any)(...)
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
return self:Add(object)
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
--[=[
|
|
425
|
+
@method Connect
|
|
426
|
+
@within Trove
|
|
427
|
+
@param signal RBXScriptSignal
|
|
428
|
+
@param fn (...: any) -> ()
|
|
429
|
+
@return RBXScriptConnection
|
|
430
|
+
Connects the function to the signal, adds the connection
|
|
431
|
+
to the trove, and then returns the connection.
|
|
432
|
+
|
|
433
|
+
This is shorthand for `trove:Add(signal:Connect(fn))`.
|
|
434
|
+
|
|
435
|
+
```lua
|
|
436
|
+
trove:Connect(workspace.ChildAdded, function(instance)
|
|
437
|
+
print(instance.Name .. " added to workspace")
|
|
438
|
+
end)
|
|
439
|
+
```
|
|
440
|
+
]=]
|
|
441
|
+
function Trove.Connect(
|
|
442
|
+
self: TroveInternal,
|
|
443
|
+
signal: SignalLike | SignalLikeMetatable | RBXScriptSignal,
|
|
444
|
+
fn: (...any) -> ...any
|
|
445
|
+
)
|
|
446
|
+
if self._cleaning then
|
|
447
|
+
error("Cannot call trove:Connect() while cleaning", 2)
|
|
448
|
+
end
|
|
449
|
+
assertSignalLike(signal)
|
|
450
|
+
|
|
451
|
+
local confirmedSignal = signal :: SignalLike
|
|
452
|
+
|
|
453
|
+
return self:Add(confirmedSignal:Connect(fn))
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
--[=[
|
|
457
|
+
@method Once
|
|
458
|
+
@within Trove
|
|
459
|
+
@param signal RBXScriptSignal
|
|
460
|
+
@param fn (...: any) -> ()
|
|
461
|
+
@return RBXScriptConnection
|
|
462
|
+
Connects the function to the signal using the `Once`
|
|
463
|
+
method, adds the connection to the trove, and then
|
|
464
|
+
returns the connection.
|
|
465
|
+
|
|
466
|
+
This is shorthand for `trove:Add(signal:Once(fn))`.
|
|
467
|
+
|
|
468
|
+
```lua
|
|
469
|
+
trove:Connect(workspace.ChildAdded, function(instance)
|
|
470
|
+
print(instance.Name .. " added to workspace")
|
|
471
|
+
end)
|
|
472
|
+
```
|
|
473
|
+
]=]
|
|
474
|
+
function Trove.Once(
|
|
475
|
+
self: TroveInternal,
|
|
476
|
+
signal: SignalLike | SignalLikeMetatable | RBXScriptSignal,
|
|
477
|
+
fn: (...any) -> ...any
|
|
478
|
+
)
|
|
479
|
+
if self._cleaning then
|
|
480
|
+
error("Cannot call trove:Connect() while cleaning", 2)
|
|
481
|
+
end
|
|
482
|
+
assertSignalLike(signal)
|
|
483
|
+
|
|
484
|
+
local confirmedSignal = signal :: SignalLike
|
|
485
|
+
|
|
486
|
+
local conn
|
|
487
|
+
conn = confirmedSignal:Once(function(...)
|
|
488
|
+
fn(...)
|
|
489
|
+
self:Pop(conn)
|
|
490
|
+
end)
|
|
491
|
+
|
|
492
|
+
return self:Add(conn)
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
--[=[
|
|
496
|
+
@method BindToRenderStep
|
|
497
|
+
@within Trove
|
|
498
|
+
@param name string
|
|
499
|
+
@param priority number
|
|
500
|
+
@param fn (dt: number) -> ()
|
|
501
|
+
Calls `RunService:BindToRenderStep` and registers a function in the
|
|
502
|
+
trove that will call `RunService:UnbindFromRenderStep` on cleanup.
|
|
503
|
+
|
|
504
|
+
```lua
|
|
505
|
+
trove:BindToRenderStep("Test", Enum.RenderPriority.Last.Value, function(dt)
|
|
506
|
+
-- Do something
|
|
507
|
+
end)
|
|
508
|
+
```
|
|
509
|
+
]=]
|
|
510
|
+
function Trove.BindToRenderStep(self: TroveInternal, name: string, priority: number, fn: (dt: number) -> ())
|
|
511
|
+
if self._cleaning then
|
|
512
|
+
error("cannot call trove:BindToRenderStep() while cleaning", 2)
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
RunService:BindToRenderStep(name, priority, fn)
|
|
516
|
+
|
|
517
|
+
self:Add(function()
|
|
518
|
+
RunService:UnbindFromRenderStep(name)
|
|
519
|
+
end)
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
--[=[
|
|
523
|
+
@method AddPromise
|
|
524
|
+
@within Trove
|
|
525
|
+
@param promise Promise
|
|
526
|
+
@return Promise
|
|
527
|
+
Gives the promise to the trove, which will cancel the promise if the trove is cleaned up or if the promise
|
|
528
|
+
is removed. The exact promise is returned, thus allowing chaining.
|
|
529
|
+
|
|
530
|
+
```lua
|
|
531
|
+
trove:AddPromise(doSomethingThatReturnsAPromise())
|
|
532
|
+
:andThen(function()
|
|
533
|
+
print("Done")
|
|
534
|
+
end)
|
|
535
|
+
-- Will cancel the above promise (assuming it didn't resolve immediately)
|
|
536
|
+
trove:Clean()
|
|
537
|
+
|
|
538
|
+
local p = trove:AddPromise(doSomethingThatReturnsAPromise())
|
|
539
|
+
-- Will also cancel the promise
|
|
540
|
+
trove:Remove(p)
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
:::caution Promise v4 Only
|
|
544
|
+
This is only compatible with the [roblox-lua-promise](https://eryn.io/roblox-lua-promise/) library, version 4.
|
|
545
|
+
:::
|
|
546
|
+
]=]
|
|
547
|
+
function Trove.AddPromise(self: TroveInternal, promise: PromiseLike | PromiseLikeMetatable)
|
|
548
|
+
if self._cleaning then
|
|
549
|
+
error("cannot call trove:AddPromise() while cleaning", 2)
|
|
550
|
+
end
|
|
551
|
+
assertPromiseLike(promise)
|
|
552
|
+
local confirmedPromise = promise :: PromiseLike
|
|
553
|
+
|
|
554
|
+
if confirmedPromise:getStatus() == "Started" then
|
|
555
|
+
confirmedPromise:finally(function()
|
|
556
|
+
if self._cleaning then
|
|
557
|
+
return
|
|
558
|
+
end
|
|
559
|
+
self:_findAndRemoveFromObjects(confirmedPromise, false)
|
|
560
|
+
end)
|
|
561
|
+
|
|
562
|
+
self:Add(confirmedPromise, "cancel")
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
return confirmedPromise
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
--[=[
|
|
569
|
+
@method Remove
|
|
570
|
+
@within Trove
|
|
571
|
+
@param object any
|
|
572
|
+
Removes the object from the Trove and cleans it up. To remove without
|
|
573
|
+
cleaning, use the `Pop` method instead.
|
|
574
|
+
|
|
575
|
+
```lua
|
|
576
|
+
local part = Instance.new("Part")
|
|
577
|
+
trove:Add(part)
|
|
578
|
+
trove:Remove(part) -- Part is destroyed
|
|
579
|
+
```
|
|
580
|
+
]=]
|
|
581
|
+
function Trove.Remove(self: TroveInternal, object: Trackable): boolean
|
|
582
|
+
if self._cleaning then
|
|
583
|
+
error("cannot call trove:Remove() while cleaning", 2)
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
return self:_findAndRemoveFromObjects(object, true)
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
--[=[
|
|
590
|
+
@method Pop
|
|
591
|
+
@within Trove
|
|
592
|
+
@param object any
|
|
593
|
+
Removes the object from the Trove, but does _not_ clean it up. To
|
|
594
|
+
clean up the object on removal, use the `Remove` method instead.
|
|
595
|
+
|
|
596
|
+
```lua
|
|
597
|
+
local part = Instance.new("Part")
|
|
598
|
+
trove:Add(part)
|
|
599
|
+
trove:Pop(part)
|
|
600
|
+
trove:Clean() -- Part is NOT destroyed
|
|
601
|
+
```
|
|
602
|
+
]=]
|
|
603
|
+
function Trove.Pop(self: TroveInternal, object: Trackable): boolean
|
|
604
|
+
if self._cleaning then
|
|
605
|
+
error("cannot call trove:Pop() while cleaning", 2)
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
return self:_findAndRemoveFromObjects(object, false)
|
|
609
|
+
end
|
|
610
|
+
|
|
611
|
+
--[=[
|
|
612
|
+
@method Extend
|
|
613
|
+
@within Trove
|
|
614
|
+
@return Trove
|
|
615
|
+
Creates and adds another trove to itself. This is just shorthand
|
|
616
|
+
for `trove:Construct(Trove)`. This is useful for contexts where
|
|
617
|
+
the trove object is present, but the class itself isn't.
|
|
618
|
+
|
|
619
|
+
:::note
|
|
620
|
+
This does _not_ clone the trove. In other words, the objects in the
|
|
621
|
+
trove are not given to the new constructed trove. This is simply to
|
|
622
|
+
construct a new Trove and add it as an object to track.
|
|
623
|
+
:::
|
|
624
|
+
|
|
625
|
+
```lua
|
|
626
|
+
local trove = Trove.new()
|
|
627
|
+
local subTrove = trove:Extend()
|
|
628
|
+
|
|
629
|
+
trove:Clean() -- Cleans up the subTrove too
|
|
630
|
+
```
|
|
631
|
+
]=]
|
|
632
|
+
function Trove.Extend(self: TroveInternal)
|
|
633
|
+
if self._cleaning then
|
|
634
|
+
error("cannot call trove:Extend() while cleaning", 2)
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
return self:Construct(Trove)
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
--[=[
|
|
641
|
+
@method Clean
|
|
642
|
+
@within Trove
|
|
643
|
+
Cleans up all objects in the trove. This is
|
|
644
|
+
similar to calling `Remove` on each object
|
|
645
|
+
within the trove. The ordering of the objects
|
|
646
|
+
removed is _not_ guaranteed.
|
|
647
|
+
|
|
648
|
+
```lua
|
|
649
|
+
trove:Clean()
|
|
650
|
+
```
|
|
651
|
+
]=]
|
|
652
|
+
function Trove.Clean(self: TroveInternal)
|
|
653
|
+
if self._cleaning then
|
|
654
|
+
return
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
self._cleaning = true
|
|
658
|
+
|
|
659
|
+
for _, obj in self._objects do
|
|
660
|
+
self:_cleanupObject(obj[1], obj[2])
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
table.clear(self._objects)
|
|
664
|
+
self._cleaning = false
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
--[=[
|
|
668
|
+
@method WrapClean
|
|
669
|
+
@within Trove
|
|
670
|
+
Returns a function that wraps the trove's `Clean()`
|
|
671
|
+
method. Calling the returned function will clean up
|
|
672
|
+
the trove.
|
|
673
|
+
|
|
674
|
+
This is often useful in contexts where functions
|
|
675
|
+
are the primary mode for cleaning up an environment,
|
|
676
|
+
such as in many "observer" patterns.
|
|
677
|
+
|
|
678
|
+
```lua
|
|
679
|
+
local cleanup = trove:WrapClean()
|
|
680
|
+
|
|
681
|
+
-- Sometime later...
|
|
682
|
+
cleanup()
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
```lua
|
|
686
|
+
-- Common observer pattern example:
|
|
687
|
+
someObserver(function()
|
|
688
|
+
local trove = Trove.new()
|
|
689
|
+
-- Foo
|
|
690
|
+
return trove:WrapClean()
|
|
691
|
+
end)
|
|
692
|
+
```
|
|
693
|
+
]=]
|
|
694
|
+
function Trove.WrapClean(self: TroveInternal)
|
|
695
|
+
return function()
|
|
696
|
+
self:Clean()
|
|
697
|
+
end
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
function Trove._findAndRemoveFromObjects(self: TroveInternal, object: any, cleanup: boolean): boolean
|
|
701
|
+
local objects = self._objects
|
|
702
|
+
|
|
703
|
+
for i, obj in objects do
|
|
704
|
+
if obj[1] == object then
|
|
705
|
+
local n = #objects
|
|
706
|
+
objects[i] = objects[n]
|
|
707
|
+
objects[n] = nil
|
|
708
|
+
|
|
709
|
+
if cleanup then
|
|
710
|
+
self:_cleanupObject(obj[1], obj[2])
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
return true
|
|
714
|
+
end
|
|
715
|
+
end
|
|
716
|
+
|
|
717
|
+
return false
|
|
718
|
+
end
|
|
719
|
+
|
|
720
|
+
function Trove._cleanupObject(_self: TroveInternal, object: any, cleanupMethod: string?)
|
|
721
|
+
if cleanupMethod == FN_MARKER then
|
|
722
|
+
task.spawn(object)
|
|
723
|
+
elseif cleanupMethod == THREAD_MARKER then
|
|
724
|
+
pcall(task.cancel, object)
|
|
725
|
+
else
|
|
726
|
+
object[cleanupMethod](object)
|
|
727
|
+
end
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
--[=[
|
|
731
|
+
@method AttachToInstance
|
|
732
|
+
@within Trove
|
|
733
|
+
@param instance Instance
|
|
734
|
+
@return RBXScriptConnection
|
|
735
|
+
Attaches the trove to a Roblox instance. Once this
|
|
736
|
+
instance is removed from the game (parent or ancestor's
|
|
737
|
+
parent set to `nil`), the trove will automatically
|
|
738
|
+
clean up.
|
|
739
|
+
|
|
740
|
+
This inverses the ownership of the Trove object, and should
|
|
741
|
+
only be used when necessary. In other words, the attached
|
|
742
|
+
instance dictates when the trove is cleaned up, rather than
|
|
743
|
+
the trove dictating the cleanup of the instance.
|
|
744
|
+
|
|
745
|
+
:::caution
|
|
746
|
+
Will throw an error if `instance` is not a descendant
|
|
747
|
+
of the game hierarchy.
|
|
748
|
+
:::
|
|
749
|
+
|
|
750
|
+
```lua
|
|
751
|
+
trove:AttachToInstance(somePart)
|
|
752
|
+
trove:Add(function()
|
|
753
|
+
print("Cleaned")
|
|
754
|
+
end)
|
|
755
|
+
|
|
756
|
+
-- Destroying the part will cause the trove to clean up, thus "Cleaned" printed:
|
|
757
|
+
somePart:Destroy()
|
|
758
|
+
```
|
|
759
|
+
]=]
|
|
760
|
+
function Trove.AttachToInstance(self: TroveInternal, instance: Instance)
|
|
761
|
+
if self._cleaning then
|
|
762
|
+
error("cannot call trove:AttachToInstance() while cleaning", 2)
|
|
763
|
+
elseif not instance:IsDescendantOf(game) then
|
|
764
|
+
error("instance is not a descendant of the game hierarchy", 2)
|
|
765
|
+
end
|
|
766
|
+
|
|
767
|
+
return self:Connect(instance.Destroying, function()
|
|
768
|
+
self:Destroy()
|
|
769
|
+
end)
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
--[=[
|
|
773
|
+
@method Destroy
|
|
774
|
+
@within Trove
|
|
775
|
+
Alias for `trove:Clean()`.
|
|
776
|
+
|
|
777
|
+
```lua
|
|
778
|
+
trove:Destroy()
|
|
779
|
+
```
|
|
780
|
+
]=]
|
|
781
|
+
function Trove.Destroy(self: TroveInternal)
|
|
782
|
+
self:Clean()
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
return {
|
|
786
|
+
new = Trove.new,
|
|
787
|
+
}
|