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
package/vendor/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Vendor Libraries
|
|
2
|
+
|
|
3
|
+
AI routing table for vendored libraries. The harness ships these so studio-native users
|
|
4
|
+
don't need Wally. Two tiers:
|
|
5
|
+
|
|
6
|
+
## Core (auto-placed with mention)
|
|
7
|
+
|
|
8
|
+
These are placed into the project automatically when relevant. The agent mentions the choice;
|
|
9
|
+
user can veto. Context-mode queries check for existing equivalents before auto-placing.
|
|
10
|
+
|
|
11
|
+
| Library | Path | Source | License | Use Instead Of |
|
|
12
|
+
|---------|------|--------|---------|----------------|
|
|
13
|
+
| **ProfileStore** | `profilestore/init.luau` | loleris/MadStudioRoblox | Apache 2.0 | Raw DataStoreService |
|
|
14
|
+
| **Trove** | `rbxutil/trove/` | Sleitnick/RbxUtil | MIT | Manual connection tracking |
|
|
15
|
+
| **Signal** | `rbxutil/signal/` | Sleitnick/RbxUtil | MIT | BindableEvent for module-to-module |
|
|
16
|
+
| **Promise** | `promise/init.luau` | evaera/roblox-lua-promise | MIT | Raw coroutines for async |
|
|
17
|
+
| **Comm** | `rbxutil/comm/` | Sleitnick/RbxUtil | MIT | Raw RemoteEvent/RemoteFunction |
|
|
18
|
+
| **Component** | `rbxutil/component/` | Sleitnick/RbxUtil | MIT | Manual CollectionService tag listeners |
|
|
19
|
+
|
|
20
|
+
## Recommended (not auto-placed, suggest when relevant)
|
|
21
|
+
|
|
22
|
+
These require user buy-in. Recommend when the task calls for them.
|
|
23
|
+
|
|
24
|
+
| Library | Path | Source | License | Use For |
|
|
25
|
+
|---------|------|--------|---------|---------|
|
|
26
|
+
| **t** | `t/t.lua` | osyrisrblx/t v3.1.1 | MIT | Runtime type checking, RemoteEvent validation, function args |
|
|
27
|
+
| **TestEZ** | `testez/` | Roblox/testez v0.4.2 | Apache 2.0 | BDD testing (.spec files) |
|
|
28
|
+
|
|
29
|
+
## Available (use when specifically needed)
|
|
30
|
+
|
|
31
|
+
Additional RbxUtil packages. Don't recommend proactively - use when the task specifically
|
|
32
|
+
calls for their functionality.
|
|
33
|
+
|
|
34
|
+
| Library | Path | Purpose |
|
|
35
|
+
|---------|------|---------|
|
|
36
|
+
| **Streamable** | `rbxutil/streamable/` | Safe instance access with StreamingEnabled |
|
|
37
|
+
| **Net** | `rbxutil/net/` | Typed networking wrapper |
|
|
38
|
+
| **Timer** | `rbxutil/timer/` | Repeating/countdown timers with pause/resume |
|
|
39
|
+
| **Shake** | `rbxutil/shake/` | Camera/UI shake effects |
|
|
40
|
+
| **Spring** | `rbxutil/spring/` | Physics-based spring animations |
|
|
41
|
+
| **Input** | `rbxutil/input/` | Gamepad/keyboard/mouse/touch abstraction |
|
|
42
|
+
| **TableUtil** | `rbxutil/table-util/` | Table manipulation utilities (deep copy, merge, etc.) |
|
|
43
|
+
| **Option** | `rbxutil/option/` | Rust-style Option type for nil safety |
|
|
44
|
+
| **Concur** | `rbxutil/concur/` | Structured concurrency primitives |
|
|
45
|
+
| **Silo** | `rbxutil/silo/` | State management (Redux-like) |
|
|
46
|
+
| **Log** | `rbxutil/log/` | Structured logging with levels |
|
|
47
|
+
| **Loader** | `rbxutil/loader/` | Module loader with Init/Start lifecycle |
|
|
48
|
+
| **Query** | `rbxutil/query/` | Instance query builder |
|
|
49
|
+
| **Find** | `rbxutil/find/` | Safe instance finding with type narrowing |
|
|
50
|
+
| **WaitFor** | `rbxutil/wait-for/` | Promise-based WaitForChild |
|
|
51
|
+
| **BufferUtil** | `rbxutil/buffer-util/` | Binary buffer read/write |
|
|
52
|
+
| **Quaternion** | `rbxutil/quaternion/` | Quaternion math for rotations |
|
|
53
|
+
| **PID** | `rbxutil/pid/` | PID controller for smooth following |
|
|
54
|
+
| **Stream** | `rbxutil/stream/` | Reactive data streams |
|
|
55
|
+
| **Sequent** | `rbxutil/sequent/` | Sequential task execution |
|
|
56
|
+
| **TaskQueue** | `rbxutil/task-queue/` | Deferred task batching |
|
|
57
|
+
| **EnumList** | `rbxutil/enum-list/` | Custom enum definitions |
|
|
58
|
+
| **Symbol** | `rbxutil/symbol/` | Unique symbol identifiers |
|
|
59
|
+
| **Ser** | `rbxutil/ser/` | Instance serialization |
|
|
60
|
+
| **Tree** | `rbxutil/tree/` | Instance tree traversal utilities |
|
|
61
|
+
| **TypedRemote** | `rbxutil/typed-remote/` | Type-safe remote events |
|
|
62
|
+
|
|
63
|
+
## Licenses
|
|
64
|
+
|
|
65
|
+
All license files are in `LICENSES/`. Every vendored library is MIT or Apache 2.0.
|
|
66
|
+
|
|
67
|
+
## Require Paths
|
|
68
|
+
|
|
69
|
+
When placing vendored libraries into a project, use ReplicatedStorage for shared modules:
|
|
70
|
+
|
|
71
|
+
```luau
|
|
72
|
+
-- Core libraries (auto-placed into ReplicatedStorage.Packages/)
|
|
73
|
+
local Trove = require(game.ReplicatedStorage.Packages.Trove)
|
|
74
|
+
local Signal = require(game.ReplicatedStorage.Packages.Signal)
|
|
75
|
+
local Promise = require(game.ReplicatedStorage.Packages.Promise)
|
|
76
|
+
local Comm = require(game.ReplicatedStorage.Packages.Comm)
|
|
77
|
+
local Component = require(game.ReplicatedStorage.Packages.Component)
|
|
78
|
+
|
|
79
|
+
-- ProfileStore (server-only, placed in ServerScriptService)
|
|
80
|
+
local ProfileStore = require(game.ServerScriptService.Packages.ProfileStore) -- profilestore/init.luau
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Note: Vendor source lives in `.opencode/vendor/` on disk. The require paths above
|
|
84
|
+
reference where Script Sync maps them in the DataModel - not the filesystem path.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!nolint LocalUnused
|
|
3
|
+
--!nolint LocalShadow
|
|
4
|
+
local task = nil -- Disable usage of Roblox's task scheduler
|
|
5
|
+
|
|
6
|
+
--[[
|
|
7
|
+
Outputs the current external time as a state object.
|
|
8
|
+
]]
|
|
9
|
+
|
|
10
|
+
local Package = script.Parent.Parent
|
|
11
|
+
local Types = require(Package.Types)
|
|
12
|
+
local External = require(Package.External)
|
|
13
|
+
-- Graph
|
|
14
|
+
local change = require(Package.Graph.change)
|
|
15
|
+
-- Utility
|
|
16
|
+
local nicknames = require(Package.Utility.nicknames)
|
|
17
|
+
|
|
18
|
+
type ExternalTime = Types.StateObject<number>
|
|
19
|
+
|
|
20
|
+
type Self = ExternalTime
|
|
21
|
+
|
|
22
|
+
local class = {}
|
|
23
|
+
class.type = "State"
|
|
24
|
+
class.kind = "ExternalTime"
|
|
25
|
+
class.timeliness = "lazy"
|
|
26
|
+
class.dependencySet = table.freeze {}
|
|
27
|
+
class._EXTREMELY_DANGEROUS_usedAsValue = External.lastUpdateStep()
|
|
28
|
+
|
|
29
|
+
local METATABLE = table.freeze {__index = class}
|
|
30
|
+
|
|
31
|
+
local allTimers: {Self} = {}
|
|
32
|
+
|
|
33
|
+
local function ExternalTime(
|
|
34
|
+
scope: Types.Scope<unknown>
|
|
35
|
+
): ExternalTime
|
|
36
|
+
local createdAt = os.clock()
|
|
37
|
+
local self: Self = setmetatable(
|
|
38
|
+
{
|
|
39
|
+
createdAt = createdAt,
|
|
40
|
+
dependentSet = {},
|
|
41
|
+
lastChange = nil,
|
|
42
|
+
scope = scope,
|
|
43
|
+
validity = "invalid"
|
|
44
|
+
},
|
|
45
|
+
METATABLE
|
|
46
|
+
) :: any
|
|
47
|
+
local destroy = function()
|
|
48
|
+
self.scope = nil
|
|
49
|
+
local index = table.find(allTimers, self)
|
|
50
|
+
if index ~= nil then
|
|
51
|
+
table.remove(allTimers, index)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
self.oldestTask = destroy
|
|
55
|
+
nicknames[self.oldestTask] = "ExternalTime"
|
|
56
|
+
table.insert(scope, destroy)
|
|
57
|
+
table.insert(allTimers, self)
|
|
58
|
+
return self
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
function class._evaluate(
|
|
62
|
+
self: Self
|
|
63
|
+
): boolean
|
|
64
|
+
-- While someone else could call `change()` on this object, it wouldn't be
|
|
65
|
+
-- idiomatic. So, since the only idiomatic time this function runs is when
|
|
66
|
+
-- the external update step runs, it's safe enough to assume that the result
|
|
67
|
+
-- has always meaningfully changed. The worst that can happen is unexpected
|
|
68
|
+
-- refreshing for people doing unorthodox shenanigans, which is an OK trade.
|
|
69
|
+
return true
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
External.bindToUpdateStep(function(
|
|
73
|
+
externalNow: number
|
|
74
|
+
): ()
|
|
75
|
+
class._EXTREMELY_DANGEROUS_usedAsValue = External.lastUpdateStep()
|
|
76
|
+
for _, timer in allTimers do
|
|
77
|
+
change(timer)
|
|
78
|
+
end
|
|
79
|
+
end)
|
|
80
|
+
|
|
81
|
+
-- Do *not* freeze the class table, because it stores the shared value of all
|
|
82
|
+
-- external time objects, and is updated every frame because of that.
|
|
83
|
+
-- table.freeze(class)
|
|
84
|
+
return ExternalTime
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!nolint LocalUnused
|
|
3
|
+
--!nolint LocalShadow
|
|
4
|
+
local task = nil -- Disable usage of Roblox's task scheduler
|
|
5
|
+
|
|
6
|
+
--[[
|
|
7
|
+
A specialised state object for following a goal state smoothly over time,
|
|
8
|
+
using physics to shape the motion.
|
|
9
|
+
|
|
10
|
+
https://elttob.uk/Fusion/0.3/api-reference/animation/types/spring/
|
|
11
|
+
]]
|
|
12
|
+
|
|
13
|
+
local Package = script.Parent.Parent
|
|
14
|
+
local Types = require(Package.Types)
|
|
15
|
+
local External = require(Package.External)
|
|
16
|
+
-- Memory
|
|
17
|
+
local checkLifetime = require(Package.Memory.checkLifetime)
|
|
18
|
+
-- Graph
|
|
19
|
+
local depend = require(Package.Graph.depend)
|
|
20
|
+
local change = require(Package.Graph.change)
|
|
21
|
+
local evaluate = require(Package.Graph.evaluate)
|
|
22
|
+
-- State
|
|
23
|
+
local castToState = require(Package.State.castToState)
|
|
24
|
+
local peek = require(Package.State.peek)
|
|
25
|
+
-- Animation
|
|
26
|
+
local ExternalTime = require(Package.Animation.ExternalTime)
|
|
27
|
+
local Stopwatch = require(Package.Animation.Stopwatch)
|
|
28
|
+
local packType = require(Package.Animation.packType)
|
|
29
|
+
local unpackType = require(Package.Animation.unpackType)
|
|
30
|
+
local springCoefficients = require(Package.Animation.springCoefficients)
|
|
31
|
+
-- Utility
|
|
32
|
+
local nicknames = require(Package.Utility.nicknames)
|
|
33
|
+
|
|
34
|
+
local EPSILON = 0.00001
|
|
35
|
+
|
|
36
|
+
type Self<T> = Types.Spring<T> & {
|
|
37
|
+
_activeDamping: number,
|
|
38
|
+
_activeGoal: T,
|
|
39
|
+
_activeLatestP: {number},
|
|
40
|
+
_activeLatestV: {number},
|
|
41
|
+
_activeNumSprings: number,
|
|
42
|
+
_activeSpeed: number,
|
|
43
|
+
_activeStartP: {number},
|
|
44
|
+
_activeStartV: {number},
|
|
45
|
+
_activeTargetP: {number},
|
|
46
|
+
_activeType: string,
|
|
47
|
+
_speed: Types.UsedAs<number>,
|
|
48
|
+
_damping: Types.UsedAs<number>,
|
|
49
|
+
_goal: Types.UsedAs<T>,
|
|
50
|
+
_stopwatch: Stopwatch.Stopwatch
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
local class = {}
|
|
54
|
+
class.type = "State"
|
|
55
|
+
class.kind = "Spring"
|
|
56
|
+
class.timeliness = "eager"
|
|
57
|
+
|
|
58
|
+
local METATABLE = table.freeze {__index = class}
|
|
59
|
+
|
|
60
|
+
local function Spring<T>(
|
|
61
|
+
scope: Types.Scope<unknown>,
|
|
62
|
+
goal: Types.UsedAs<T>,
|
|
63
|
+
speed: Types.UsedAs<number>?,
|
|
64
|
+
damping: Types.UsedAs<number>?
|
|
65
|
+
): Types.Spring<T>
|
|
66
|
+
local createdAt = os.clock()
|
|
67
|
+
if typeof(scope) ~= "table" or castToState(scope) ~= nil then
|
|
68
|
+
External.logError("scopeMissing", nil, "Springs", "myScope:Spring(goalState, speed, damping)")
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
local goalState = castToState(goal)
|
|
72
|
+
local stopwatch = nil
|
|
73
|
+
if goalState ~= nil then
|
|
74
|
+
stopwatch = Stopwatch(scope, ExternalTime(scope))
|
|
75
|
+
stopwatch:unpause()
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
local speed = speed or 10
|
|
79
|
+
local damping = damping or 1
|
|
80
|
+
|
|
81
|
+
local self: Self<T> = setmetatable(
|
|
82
|
+
{
|
|
83
|
+
createdAt = createdAt,
|
|
84
|
+
dependencySet = {},
|
|
85
|
+
dependentSet = {},
|
|
86
|
+
lastChange = nil,
|
|
87
|
+
scope = scope,
|
|
88
|
+
validity = "invalid",
|
|
89
|
+
_activeDamping = -1,
|
|
90
|
+
_activeGoal = nil,
|
|
91
|
+
_activeLatestP = {},
|
|
92
|
+
_activeLatestV = {},
|
|
93
|
+
_activeNumSprings = 0,
|
|
94
|
+
_activeSpeed = -1,
|
|
95
|
+
_activeStartP = {},
|
|
96
|
+
_activeStartV = {},
|
|
97
|
+
_activeTargetP = {},
|
|
98
|
+
_activeType = "",
|
|
99
|
+
_damping = damping,
|
|
100
|
+
_EXTREMELY_DANGEROUS_usedAsValue = peek(goal),
|
|
101
|
+
_goal = goal,
|
|
102
|
+
_speed = speed,
|
|
103
|
+
_stopwatch = stopwatch
|
|
104
|
+
},
|
|
105
|
+
METATABLE
|
|
106
|
+
) :: any
|
|
107
|
+
local destroy = function()
|
|
108
|
+
self.scope = nil
|
|
109
|
+
for dependency in pairs(self.dependencySet) do
|
|
110
|
+
dependency.dependentSet[self] = nil
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
self.oldestTask = destroy
|
|
114
|
+
nicknames[self.oldestTask] = "Spring"
|
|
115
|
+
table.insert(scope, destroy)
|
|
116
|
+
|
|
117
|
+
if goalState ~= nil then
|
|
118
|
+
checkLifetime.bOutlivesA(
|
|
119
|
+
scope, self.oldestTask,
|
|
120
|
+
goalState.scope, goalState.oldestTask,
|
|
121
|
+
checkLifetime.formatters.animationGoal
|
|
122
|
+
)
|
|
123
|
+
end
|
|
124
|
+
local speedState = castToState(speed)
|
|
125
|
+
if speedState ~= nil then
|
|
126
|
+
checkLifetime.bOutlivesA(
|
|
127
|
+
scope, self.oldestTask,
|
|
128
|
+
speedState.scope, speedState.oldestTask,
|
|
129
|
+
checkLifetime.formatters.parameter, "speed"
|
|
130
|
+
)
|
|
131
|
+
end
|
|
132
|
+
local dampingState = castToState(damping)
|
|
133
|
+
if dampingState ~= nil then
|
|
134
|
+
checkLifetime.bOutlivesA(
|
|
135
|
+
scope, self.oldestTask,
|
|
136
|
+
dampingState.scope, dampingState.oldestTask,
|
|
137
|
+
checkLifetime.formatters.parameter, "damping"
|
|
138
|
+
)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
-- Eagerly evaluated objects need to evaluate themselves so that they're
|
|
142
|
+
-- valid at all times.
|
|
143
|
+
evaluate(self, true)
|
|
144
|
+
|
|
145
|
+
return self
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
function class.addVelocity<T>(
|
|
149
|
+
self: Self<T>,
|
|
150
|
+
deltaValue: T
|
|
151
|
+
): ()
|
|
152
|
+
evaluate(self, false) -- ensure the _active params are up to date
|
|
153
|
+
local deltaType = typeof(deltaValue)
|
|
154
|
+
if deltaType ~= self._activeType then
|
|
155
|
+
External.logError("springTypeMismatch", nil, deltaType, self._activeType)
|
|
156
|
+
end
|
|
157
|
+
local newStartV = unpackType(deltaValue, deltaType)
|
|
158
|
+
for index, velocity in self._activeLatestV do
|
|
159
|
+
newStartV[index] += velocity
|
|
160
|
+
end
|
|
161
|
+
self._activeStartP = table.clone(self._activeLatestP)
|
|
162
|
+
self._activeStartV = newStartV
|
|
163
|
+
self._stopwatch:zero()
|
|
164
|
+
self._stopwatch:unpause()
|
|
165
|
+
change(self)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
function class.get<T>(
|
|
169
|
+
self: Self<T>
|
|
170
|
+
): never
|
|
171
|
+
return External.logError("stateGetWasRemoved")
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
function class.setPosition<T>(
|
|
175
|
+
self: Self<T>,
|
|
176
|
+
newValue: T
|
|
177
|
+
): ()
|
|
178
|
+
evaluate(self, false) -- ensure the _active params are up to date
|
|
179
|
+
local newType = typeof(newValue)
|
|
180
|
+
if newType ~= self._activeType then
|
|
181
|
+
External.logError("springTypeMismatch", nil, newType, self._activeType)
|
|
182
|
+
end
|
|
183
|
+
self._activeStartP = unpackType(newValue, newType)
|
|
184
|
+
self._activeStartV = table.clone(self._activeLatestV)
|
|
185
|
+
self._stopwatch:zero()
|
|
186
|
+
self._stopwatch:unpause()
|
|
187
|
+
change(self)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
function class.setVelocity<T>(
|
|
191
|
+
self: Self<T>,
|
|
192
|
+
newValue: T
|
|
193
|
+
): ()
|
|
194
|
+
evaluate(self, false) -- ensure the _active params are up to date
|
|
195
|
+
local newType = typeof(newValue)
|
|
196
|
+
if newType ~= self._activeType then
|
|
197
|
+
External.logError("springTypeMismatch", nil, newType, self._activeType)
|
|
198
|
+
end
|
|
199
|
+
self._activeStartP = table.clone(self._activeLatestP)
|
|
200
|
+
self._activeStartV = unpackType(newValue, newType)
|
|
201
|
+
self._stopwatch:zero()
|
|
202
|
+
self._stopwatch:unpause()
|
|
203
|
+
change(self)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
function class._evaluate<T>(
|
|
207
|
+
self: Self<T>
|
|
208
|
+
): boolean
|
|
209
|
+
local goal = castToState(self._goal)
|
|
210
|
+
-- Allow non-state goals to pass through transparently.
|
|
211
|
+
if goal == nil then
|
|
212
|
+
self._EXTREMELY_DANGEROUS_usedAsValue = self._goal :: T
|
|
213
|
+
return false
|
|
214
|
+
end
|
|
215
|
+
-- depend(self, goal)
|
|
216
|
+
local nextFrameGoal = peek(goal)
|
|
217
|
+
-- Protect against NaN goals.
|
|
218
|
+
if nextFrameGoal ~= nextFrameGoal then
|
|
219
|
+
External.logWarn("springNanGoal")
|
|
220
|
+
return false
|
|
221
|
+
end
|
|
222
|
+
local nextFrameGoalType = typeof(nextFrameGoal)
|
|
223
|
+
local discontinuous = nextFrameGoalType ~= self._activeType
|
|
224
|
+
|
|
225
|
+
local stopwatch = self._stopwatch :: Stopwatch.Stopwatch
|
|
226
|
+
local elapsed = peek(stopwatch)
|
|
227
|
+
depend(self, stopwatch)
|
|
228
|
+
|
|
229
|
+
local oldValue = self._EXTREMELY_DANGEROUS_usedAsValue
|
|
230
|
+
local newValue: T
|
|
231
|
+
|
|
232
|
+
if discontinuous then
|
|
233
|
+
-- Propagate changes in type instantly throughout the whole reactive
|
|
234
|
+
-- graph, even if simulation is logically one frame behind, because it
|
|
235
|
+
-- makes the whole graph behave more consistently.
|
|
236
|
+
newValue = nextFrameGoal
|
|
237
|
+
elseif elapsed <= 0 then
|
|
238
|
+
newValue = oldValue
|
|
239
|
+
else
|
|
240
|
+
-- Calculate spring motion.
|
|
241
|
+
-- IMPORTANT: use the parameters from last frame, not this frame. We're
|
|
242
|
+
-- integrating the motion that happened over the last frame, after all.
|
|
243
|
+
-- The stopwatch will have captured the length of time needed correctly.
|
|
244
|
+
local posPos, posVel, velPos, velVel = springCoefficients(
|
|
245
|
+
elapsed,
|
|
246
|
+
self._activeDamping,
|
|
247
|
+
self._activeSpeed
|
|
248
|
+
)
|
|
249
|
+
local isMoving = false
|
|
250
|
+
for index = 1, self._activeNumSprings do
|
|
251
|
+
local startP = self._activeStartP[index]
|
|
252
|
+
local targetP = self._activeTargetP[index]
|
|
253
|
+
local startV = self._activeStartV[index]
|
|
254
|
+
local startD = startP - targetP
|
|
255
|
+
local latestD = startD * posPos + startV * posVel
|
|
256
|
+
local latestV = startD * velPos + startV * velVel
|
|
257
|
+
if latestD ~= latestD or latestV ~= latestV then
|
|
258
|
+
External.logWarn("springNanMotion")
|
|
259
|
+
latestD, latestV = 0, 0
|
|
260
|
+
end
|
|
261
|
+
if math.abs(latestD) > EPSILON or math.abs(latestV) > EPSILON then
|
|
262
|
+
isMoving = true
|
|
263
|
+
end
|
|
264
|
+
local latestP = latestD + targetP
|
|
265
|
+
self._activeLatestP[index] = latestP
|
|
266
|
+
self._activeLatestV[index] = latestV
|
|
267
|
+
end
|
|
268
|
+
-- Sleep and snap to goal if the motion has decayed to a negligible amount.
|
|
269
|
+
if not isMoving then
|
|
270
|
+
for index = 1, self._activeNumSprings do
|
|
271
|
+
self._activeLatestP[index] = self._activeTargetP[index]
|
|
272
|
+
end
|
|
273
|
+
-- TODO: figure out how to do sleeping correctly for single frame
|
|
274
|
+
-- changes
|
|
275
|
+
-- stopwatch:pause()
|
|
276
|
+
-- stopwatch:zero()
|
|
277
|
+
end
|
|
278
|
+
-- Pack springs into final value.
|
|
279
|
+
newValue = packType(self._activeLatestP, self._activeType) :: any
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
-- Reconfigure spring when any of its parameters are changed.
|
|
283
|
+
-- This should happen after integrating the last frame's motion.
|
|
284
|
+
-- NOTE: don't need to add a dependency on these objects! they do not cause
|
|
285
|
+
-- a spring to wake from sleep, so the stopwatch dependency is sufficient.
|
|
286
|
+
local nextFrameSpeed = peek(self._speed) :: number
|
|
287
|
+
local nextFrameDamping = peek(self._damping) :: number
|
|
288
|
+
if
|
|
289
|
+
discontinuous or
|
|
290
|
+
nextFrameGoal ~= self._activeGoal or
|
|
291
|
+
nextFrameSpeed ~= self._activeSpeed or
|
|
292
|
+
nextFrameDamping ~= self._activeDamping
|
|
293
|
+
then
|
|
294
|
+
self._activeTargetP = unpackType(nextFrameGoal, nextFrameGoalType)
|
|
295
|
+
self._activeNumSprings = #self._activeTargetP
|
|
296
|
+
if discontinuous then
|
|
297
|
+
self._activeStartP = table.clone(self._activeTargetP)
|
|
298
|
+
self._activeLatestP = table.clone(self._activeTargetP)
|
|
299
|
+
self._activeStartV = table.create(self._activeNumSprings, 0)
|
|
300
|
+
self._activeLatestV = table.create(self._activeNumSprings, 0)
|
|
301
|
+
else
|
|
302
|
+
self._activeStartP = table.clone(self._activeLatestP)
|
|
303
|
+
self._activeStartV = table.clone(self._activeLatestV)
|
|
304
|
+
end
|
|
305
|
+
self._activeType = nextFrameGoalType
|
|
306
|
+
self._activeGoal = nextFrameGoal
|
|
307
|
+
self._activeDamping = nextFrameDamping
|
|
308
|
+
self._activeSpeed = nextFrameSpeed
|
|
309
|
+
stopwatch:zero()
|
|
310
|
+
stopwatch:unpause()
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
-- Push update and check for similarity.
|
|
314
|
+
-- Don't need to use the similarity test here because this code doesn't
|
|
315
|
+
-- deal with tables, and NaN is already guarded against, so the similarity
|
|
316
|
+
-- test doesn't actually add any new safety here.
|
|
317
|
+
self._EXTREMELY_DANGEROUS_usedAsValue = newValue
|
|
318
|
+
return oldValue ~= newValue
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
table.freeze(class)
|
|
322
|
+
return Spring :: Types.SpringConstructor
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!nolint LocalUnused
|
|
3
|
+
--!nolint LocalShadow
|
|
4
|
+
local task = nil -- Disable usage of Roblox's task scheduler
|
|
5
|
+
|
|
6
|
+
--[[
|
|
7
|
+
State object for measuring time since an event using a reference timer.
|
|
8
|
+
|
|
9
|
+
TODO: this should not be exposed to users until it has a proper reactive API
|
|
10
|
+
surface
|
|
11
|
+
]]
|
|
12
|
+
|
|
13
|
+
local Package = script.Parent.Parent
|
|
14
|
+
local Types = require(Package.Types)
|
|
15
|
+
-- Memory
|
|
16
|
+
local checkLifetime = require(Package.Memory.checkLifetime)
|
|
17
|
+
-- Graph
|
|
18
|
+
local depend = require(Package.Graph.depend)
|
|
19
|
+
local change = require(Package.Graph.change)
|
|
20
|
+
-- State
|
|
21
|
+
local peek = require(Package.State.peek)
|
|
22
|
+
-- Utility
|
|
23
|
+
local nicknames = require(Package.Utility.nicknames)
|
|
24
|
+
|
|
25
|
+
export type Stopwatch = Types.StateObject<number> & {
|
|
26
|
+
zero: (Stopwatch) -> (),
|
|
27
|
+
pause: (Stopwatch) -> (),
|
|
28
|
+
unpause: (Stopwatch) -> ()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type Self = Stopwatch & {
|
|
32
|
+
_measureTimeSince: number,
|
|
33
|
+
_playing: boolean,
|
|
34
|
+
_timer: Types.StateObject<number>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
local class = {}
|
|
38
|
+
class.type = "State"
|
|
39
|
+
class.kind = "Stopwatch"
|
|
40
|
+
class.timeliness = "lazy"
|
|
41
|
+
|
|
42
|
+
local METATABLE = table.freeze {__index = class}
|
|
43
|
+
|
|
44
|
+
local function Stopwatch(
|
|
45
|
+
scope: Types.Scope<unknown>,
|
|
46
|
+
timer: Types.StateObject<number>
|
|
47
|
+
): Stopwatch
|
|
48
|
+
local createdAt = os.clock()
|
|
49
|
+
local self: Self = setmetatable(
|
|
50
|
+
{
|
|
51
|
+
awake = true,
|
|
52
|
+
createdAt = createdAt,
|
|
53
|
+
dependencySet = {},
|
|
54
|
+
dependentSet = {},
|
|
55
|
+
lastChange = nil,
|
|
56
|
+
scope = scope,
|
|
57
|
+
validity = "invalid",
|
|
58
|
+
_EXTREMELY_DANGEROUS_usedAsValue = 0,
|
|
59
|
+
_measureTimeSince = 0, -- this should be set on unpause
|
|
60
|
+
_playing = false,
|
|
61
|
+
_timer = timer
|
|
62
|
+
},
|
|
63
|
+
METATABLE
|
|
64
|
+
) :: any
|
|
65
|
+
local destroy = function()
|
|
66
|
+
self.scope = nil
|
|
67
|
+
end
|
|
68
|
+
self.oldestTask = destroy
|
|
69
|
+
nicknames[self.oldestTask] = "Stopwatch"
|
|
70
|
+
table.insert(scope, destroy)
|
|
71
|
+
|
|
72
|
+
checkLifetime.bOutlivesA(
|
|
73
|
+
scope, self.oldestTask,
|
|
74
|
+
timer.scope, timer.oldestTask,
|
|
75
|
+
checkLifetime.formatters.parameter, "timer"
|
|
76
|
+
)
|
|
77
|
+
depend(self, timer)
|
|
78
|
+
return self
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
function class.zero(
|
|
82
|
+
self: Self
|
|
83
|
+
): ()
|
|
84
|
+
local newTimepoint = peek(self._timer)
|
|
85
|
+
if newTimepoint ~= self._measureTimeSince then
|
|
86
|
+
self._measureTimeSince = newTimepoint
|
|
87
|
+
self._EXTREMELY_DANGEROUS_usedAsValue = 0
|
|
88
|
+
change(self)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
function class.pause(
|
|
93
|
+
self: Self
|
|
94
|
+
): ()
|
|
95
|
+
if self._playing == true then
|
|
96
|
+
self._playing = false
|
|
97
|
+
change(self)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
function class.unpause(
|
|
102
|
+
self: Self
|
|
103
|
+
): ()
|
|
104
|
+
if self._playing == false then
|
|
105
|
+
self._playing = true
|
|
106
|
+
self._measureTimeSince = peek(self._timer) - self._EXTREMELY_DANGEROUS_usedAsValue
|
|
107
|
+
change(self)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
function class._evaluate(
|
|
112
|
+
self: Self
|
|
113
|
+
): boolean
|
|
114
|
+
if self._playing then
|
|
115
|
+
depend(self, self._timer)
|
|
116
|
+
local currentTime = peek(self._timer)
|
|
117
|
+
local oldValue = self._EXTREMELY_DANGEROUS_usedAsValue
|
|
118
|
+
local newValue = currentTime - self._measureTimeSince
|
|
119
|
+
self._EXTREMELY_DANGEROUS_usedAsValue = newValue
|
|
120
|
+
return oldValue ~= newValue
|
|
121
|
+
else
|
|
122
|
+
return false
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
table.freeze(class)
|
|
128
|
+
return Stopwatch
|