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.
Files changed (248) hide show
  1. package/README.md +122 -0
  2. package/commands/setup-game.md +108 -0
  3. package/commands/sync-check.md +53 -0
  4. package/core/roblox-core.md +93 -0
  5. package/dist/server.js +167 -0
  6. package/package.json +35 -0
  7. package/skills/roblox-analytics/SKILL.md +277 -0
  8. package/skills/roblox-analytics/references/event-batcher.luau +75 -0
  9. package/skills/roblox-animation-vfx/SKILL.md +1325 -0
  10. package/skills/roblox-architecture/SKILL.md +863 -0
  11. package/skills/roblox-architecture/references/combat-systems.md +1381 -0
  12. package/skills/roblox-code-review/SKILL.md +687 -0
  13. package/skills/roblox-data/SKILL.md +889 -0
  14. package/skills/roblox-data/references/inventory-systems.md +1729 -0
  15. package/skills/roblox-debug/SKILL.md +99 -0
  16. package/skills/roblox-gui/SKILL.md +1103 -0
  17. package/skills/roblox-gui-fusion/SKILL.md +150 -0
  18. package/skills/roblox-gui-fusion/references/inventory.luau +427 -0
  19. package/skills/roblox-gui-fusion/references/settings-menu.luau +579 -0
  20. package/skills/roblox-gui-fusion/references/shop.luau +411 -0
  21. package/skills/roblox-luau-mastery/SKILL.md +1519 -0
  22. package/skills/roblox-monetization/SKILL.md +1084 -0
  23. package/skills/roblox-monetization/references/process-receipt.luau +131 -0
  24. package/skills/roblox-networking/SKILL.md +669 -0
  25. package/skills/roblox-networking/references/remote-validator.luau +193 -0
  26. package/skills/roblox-publish-checklist/SKILL.md +128 -0
  27. package/skills/roblox-runtime/SKILL.md +753 -0
  28. package/skills/roblox-sharp-edges/SKILL.md +295 -0
  29. package/skills/roblox-sync/SKILL.md +126 -0
  30. package/skills/roblox-testing/SKILL.md +943 -0
  31. package/skills/roblox-tooling/SKILL.md +150 -0
  32. package/vendor/LICENSES/ProfileStore-LICENSE +201 -0
  33. package/vendor/LICENSES/RbxUtil-LICENSE +7 -0
  34. package/vendor/LICENSES/promise-LICENSE +21 -0
  35. package/vendor/LICENSES/t-LICENSE +21 -0
  36. package/vendor/LICENSES/testez-LICENSE +201 -0
  37. package/vendor/README.md +84 -0
  38. package/vendor/fusion/Animation/ExternalTime.luau +84 -0
  39. package/vendor/fusion/Animation/Spring.luau +322 -0
  40. package/vendor/fusion/Animation/Stopwatch.luau +128 -0
  41. package/vendor/fusion/Animation/Tween.luau +187 -0
  42. package/vendor/fusion/Animation/getTweenDuration.luau +27 -0
  43. package/vendor/fusion/Animation/getTweenRatio.luau +47 -0
  44. package/vendor/fusion/Animation/lerpType.luau +164 -0
  45. package/vendor/fusion/Animation/packType.luau +100 -0
  46. package/vendor/fusion/Animation/springCoefficients.luau +80 -0
  47. package/vendor/fusion/Animation/unpackType.luau +103 -0
  48. package/vendor/fusion/Colour/Oklab.luau +70 -0
  49. package/vendor/fusion/Colour/sRGB.luau +55 -0
  50. package/vendor/fusion/External.luau +168 -0
  51. package/vendor/fusion/ExternalDebug.luau +70 -0
  52. package/vendor/fusion/Graph/Observer.luau +114 -0
  53. package/vendor/fusion/Graph/castToGraph.luau +29 -0
  54. package/vendor/fusion/Graph/change.luau +81 -0
  55. package/vendor/fusion/Graph/depend.luau +33 -0
  56. package/vendor/fusion/Graph/evaluate.luau +56 -0
  57. package/vendor/fusion/Instances/Attribute.luau +58 -0
  58. package/vendor/fusion/Instances/AttributeChange.luau +47 -0
  59. package/vendor/fusion/Instances/AttributeOut.luau +63 -0
  60. package/vendor/fusion/Instances/Child.luau +21 -0
  61. package/vendor/fusion/Instances/Children.luau +148 -0
  62. package/vendor/fusion/Instances/Hydrate.luau +33 -0
  63. package/vendor/fusion/Instances/New.luau +53 -0
  64. package/vendor/fusion/Instances/OnChange.luau +50 -0
  65. package/vendor/fusion/Instances/OnEvent.luau +54 -0
  66. package/vendor/fusion/Instances/Out.luau +69 -0
  67. package/vendor/fusion/Instances/applyInstanceProps.luau +149 -0
  68. package/vendor/fusion/Instances/defaultProps.luau +194 -0
  69. package/vendor/fusion/LICENSE +21 -0
  70. package/vendor/fusion/Logging/formatError.luau +49 -0
  71. package/vendor/fusion/Logging/messages.luau +52 -0
  72. package/vendor/fusion/Logging/parseError.luau +25 -0
  73. package/vendor/fusion/Memory/checkLifetime.luau +134 -0
  74. package/vendor/fusion/Memory/deriveScope.luau +24 -0
  75. package/vendor/fusion/Memory/deriveScopeImpl.luau +45 -0
  76. package/vendor/fusion/Memory/doCleanup.luau +79 -0
  77. package/vendor/fusion/Memory/innerScope.luau +34 -0
  78. package/vendor/fusion/Memory/legacyCleanup.luau +18 -0
  79. package/vendor/fusion/Memory/needsDestruction.luau +17 -0
  80. package/vendor/fusion/Memory/poisonScope.luau +34 -0
  81. package/vendor/fusion/Memory/scopePool.luau +55 -0
  82. package/vendor/fusion/Memory/scoped.luau +27 -0
  83. package/vendor/fusion/Memory/whichLivesLonger.luau +75 -0
  84. package/vendor/fusion/RobloxExternal.luau +98 -0
  85. package/vendor/fusion/State/Computed.luau +139 -0
  86. package/vendor/fusion/State/For/Disassembly.luau +211 -0
  87. package/vendor/fusion/State/For/ForTypes.luau +30 -0
  88. package/vendor/fusion/State/For/init.luau +110 -0
  89. package/vendor/fusion/State/ForKeys.luau +94 -0
  90. package/vendor/fusion/State/ForPairs.luau +97 -0
  91. package/vendor/fusion/State/ForValues.luau +94 -0
  92. package/vendor/fusion/State/Value.luau +88 -0
  93. package/vendor/fusion/State/castToState.luau +26 -0
  94. package/vendor/fusion/State/peek.luau +31 -0
  95. package/vendor/fusion/State/updateAll.luau +1 -0
  96. package/vendor/fusion/Types.luau +314 -0
  97. package/vendor/fusion/Utility/Contextual.luau +91 -0
  98. package/vendor/fusion/Utility/Safe.luau +23 -0
  99. package/vendor/fusion/Utility/isSimilar.luau +29 -0
  100. package/vendor/fusion/Utility/merge.luau +35 -0
  101. package/vendor/fusion/Utility/nameOf.luau +35 -0
  102. package/vendor/fusion/Utility/never.luau +14 -0
  103. package/vendor/fusion/Utility/nicknames.luau +11 -0
  104. package/vendor/fusion/Utility/xtypeof.luau +27 -0
  105. package/vendor/fusion/init.luau +82 -0
  106. package/vendor/profilestore/init.luau +2243 -0
  107. package/vendor/promise/init.luau +1982 -0
  108. package/vendor/rbxutil/buffer-util/Buffer.test.luau +25 -0
  109. package/vendor/rbxutil/buffer-util/BufferReader.luau +228 -0
  110. package/vendor/rbxutil/buffer-util/BufferWriter.luau +269 -0
  111. package/vendor/rbxutil/buffer-util/DataTypeBuffer.luau +223 -0
  112. package/vendor/rbxutil/buffer-util/Types.luau +60 -0
  113. package/vendor/rbxutil/buffer-util/index.d.ts +153 -0
  114. package/vendor/rbxutil/buffer-util/init.luau +41 -0
  115. package/vendor/rbxutil/buffer-util/package.json +16 -0
  116. package/vendor/rbxutil/buffer-util/wally.toml +9 -0
  117. package/vendor/rbxutil/comm/Client/ClientComm.luau +232 -0
  118. package/vendor/rbxutil/comm/Client/ClientRemoteProperty.luau +156 -0
  119. package/vendor/rbxutil/comm/Client/ClientRemoteSignal.luau +109 -0
  120. package/vendor/rbxutil/comm/Client/init.luau +135 -0
  121. package/vendor/rbxutil/comm/Server/RemoteProperty.luau +295 -0
  122. package/vendor/rbxutil/comm/Server/RemoteSignal.luau +211 -0
  123. package/vendor/rbxutil/comm/Server/ServerComm.luau +211 -0
  124. package/vendor/rbxutil/comm/Server/init.luau +140 -0
  125. package/vendor/rbxutil/comm/Types.luau +18 -0
  126. package/vendor/rbxutil/comm/Util.luau +27 -0
  127. package/vendor/rbxutil/comm/init.luau +35 -0
  128. package/vendor/rbxutil/comm/wally.toml +13 -0
  129. package/vendor/rbxutil/component/init.luau +759 -0
  130. package/vendor/rbxutil/component/init.test.luau +311 -0
  131. package/vendor/rbxutil/component/wally.toml +14 -0
  132. package/vendor/rbxutil/concur/init.luau +542 -0
  133. package/vendor/rbxutil/concur/init.test.luau +364 -0
  134. package/vendor/rbxutil/concur/wally.toml +8 -0
  135. package/vendor/rbxutil/enum-list/init.luau +101 -0
  136. package/vendor/rbxutil/enum-list/init.test.luau +91 -0
  137. package/vendor/rbxutil/enum-list/wally.toml +8 -0
  138. package/vendor/rbxutil/find/index.d.ts +20 -0
  139. package/vendor/rbxutil/find/init.luau +44 -0
  140. package/vendor/rbxutil/find/package.json +17 -0
  141. package/vendor/rbxutil/find/wally.toml +8 -0
  142. package/vendor/rbxutil/input/Gamepad.luau +559 -0
  143. package/vendor/rbxutil/input/Keyboard.luau +124 -0
  144. package/vendor/rbxutil/input/Mouse.luau +278 -0
  145. package/vendor/rbxutil/input/PreferredInput.luau +91 -0
  146. package/vendor/rbxutil/input/Touch.luau +120 -0
  147. package/vendor/rbxutil/input/init.luau +33 -0
  148. package/vendor/rbxutil/input/wally.toml +12 -0
  149. package/vendor/rbxutil/loader/index.d.ts +15 -0
  150. package/vendor/rbxutil/loader/init.luau +137 -0
  151. package/vendor/rbxutil/loader/wally.toml +8 -0
  152. package/vendor/rbxutil/log/index.d.ts +38 -0
  153. package/vendor/rbxutil/log/init.luau +746 -0
  154. package/vendor/rbxutil/log/wally.toml +8 -0
  155. package/vendor/rbxutil/net/init.luau +190 -0
  156. package/vendor/rbxutil/net/wally.toml +8 -0
  157. package/vendor/rbxutil/option/index.d.ts +44 -0
  158. package/vendor/rbxutil/option/init.luau +489 -0
  159. package/vendor/rbxutil/option/init.test.luau +342 -0
  160. package/vendor/rbxutil/option/wally.toml +8 -0
  161. package/vendor/rbxutil/pid/index.d.ts +53 -0
  162. package/vendor/rbxutil/pid/init.luau +195 -0
  163. package/vendor/rbxutil/pid/package.json +16 -0
  164. package/vendor/rbxutil/pid/wally.toml +9 -0
  165. package/vendor/rbxutil/quaternion/index.d.ts +117 -0
  166. package/vendor/rbxutil/quaternion/init.luau +570 -0
  167. package/vendor/rbxutil/quaternion/package.json +16 -0
  168. package/vendor/rbxutil/quaternion/wally.toml +9 -0
  169. package/vendor/rbxutil/query/index.d.ts +43 -0
  170. package/vendor/rbxutil/query/init.luau +117 -0
  171. package/vendor/rbxutil/query/package.json +18 -0
  172. package/vendor/rbxutil/query/wally.toml +9 -0
  173. package/vendor/rbxutil/sequent/index.d.ts +28 -0
  174. package/vendor/rbxutil/sequent/init.luau +340 -0
  175. package/vendor/rbxutil/sequent/package.json +16 -0
  176. package/vendor/rbxutil/sequent/wally.toml +9 -0
  177. package/vendor/rbxutil/ser/init.luau +175 -0
  178. package/vendor/rbxutil/ser/init.test.luau +50 -0
  179. package/vendor/rbxutil/ser/wally.toml +11 -0
  180. package/vendor/rbxutil/shake/index.d.ts +36 -0
  181. package/vendor/rbxutil/shake/init.luau +532 -0
  182. package/vendor/rbxutil/shake/init.test.luau +267 -0
  183. package/vendor/rbxutil/shake/package.json +16 -0
  184. package/vendor/rbxutil/shake/wally.toml +9 -0
  185. package/vendor/rbxutil/signal/index.d.ts +100 -0
  186. package/vendor/rbxutil/signal/init.luau +432 -0
  187. package/vendor/rbxutil/signal/init.test.luau +190 -0
  188. package/vendor/rbxutil/signal/package.json +17 -0
  189. package/vendor/rbxutil/signal/wally.toml +9 -0
  190. package/vendor/rbxutil/silo/TableWatcher.luau +65 -0
  191. package/vendor/rbxutil/silo/Util.luau +55 -0
  192. package/vendor/rbxutil/silo/init.luau +338 -0
  193. package/vendor/rbxutil/silo/init.test.luau +215 -0
  194. package/vendor/rbxutil/silo/wally.toml +8 -0
  195. package/vendor/rbxutil/spring/index.d.ts +40 -0
  196. package/vendor/rbxutil/spring/init.luau +97 -0
  197. package/vendor/rbxutil/spring/package.json +17 -0
  198. package/vendor/rbxutil/spring/wally.toml +8 -0
  199. package/vendor/rbxutil/stream/index.d.ts +88 -0
  200. package/vendor/rbxutil/stream/init.luau +597 -0
  201. package/vendor/rbxutil/stream/package.json +18 -0
  202. package/vendor/rbxutil/stream/wally.toml +9 -0
  203. package/vendor/rbxutil/streamable/Streamable.luau +202 -0
  204. package/vendor/rbxutil/streamable/StreamableUtil.luau +80 -0
  205. package/vendor/rbxutil/streamable/init.luau +8 -0
  206. package/vendor/rbxutil/streamable/wally.toml +12 -0
  207. package/vendor/rbxutil/symbol/init.luau +56 -0
  208. package/vendor/rbxutil/symbol/init.test.luau +37 -0
  209. package/vendor/rbxutil/symbol/wally.toml +8 -0
  210. package/vendor/rbxutil/table-util/init.luau +938 -0
  211. package/vendor/rbxutil/table-util/init.test.luau +439 -0
  212. package/vendor/rbxutil/table-util/wally.toml +8 -0
  213. package/vendor/rbxutil/task-queue/index.d.ts +27 -0
  214. package/vendor/rbxutil/task-queue/init.luau +97 -0
  215. package/vendor/rbxutil/task-queue/wally.toml +8 -0
  216. package/vendor/rbxutil/timer/index.d.ts +81 -0
  217. package/vendor/rbxutil/timer/init.luau +249 -0
  218. package/vendor/rbxutil/timer/init.test.luau +73 -0
  219. package/vendor/rbxutil/timer/wally.toml +11 -0
  220. package/vendor/rbxutil/tree/index.d.ts +15 -0
  221. package/vendor/rbxutil/tree/init.luau +137 -0
  222. package/vendor/rbxutil/tree/wally.toml +8 -0
  223. package/vendor/rbxutil/trove/index.d.ts +46 -0
  224. package/vendor/rbxutil/trove/init.luau +787 -0
  225. package/vendor/rbxutil/trove/init.test.luau +203 -0
  226. package/vendor/rbxutil/trove/wally.toml +8 -0
  227. package/vendor/rbxutil/typed-remote/init.luau +196 -0
  228. package/vendor/rbxutil/typed-remote/wally.toml +8 -0
  229. package/vendor/rbxutil/wait-for/index.d.ts +17 -0
  230. package/vendor/rbxutil/wait-for/init.luau +257 -0
  231. package/vendor/rbxutil/wait-for/init.test.luau +182 -0
  232. package/vendor/rbxutil/wait-for/wally.toml +11 -0
  233. package/vendor/t/t.lua +1350 -0
  234. package/vendor/testez/Context.lua +26 -0
  235. package/vendor/testez/Expectation.lua +311 -0
  236. package/vendor/testez/ExpectationContext.lua +38 -0
  237. package/vendor/testez/LifecycleHooks.lua +89 -0
  238. package/vendor/testez/Reporters/TeamCityReporter.lua +102 -0
  239. package/vendor/testez/Reporters/TextReporter.lua +106 -0
  240. package/vendor/testez/Reporters/TextReporterQuiet.lua +97 -0
  241. package/vendor/testez/TestBootstrap.lua +147 -0
  242. package/vendor/testez/TestEnum.lua +28 -0
  243. package/vendor/testez/TestPlan.lua +304 -0
  244. package/vendor/testez/TestPlanner.lua +40 -0
  245. package/vendor/testez/TestResults.lua +112 -0
  246. package/vendor/testez/TestRunner.lua +188 -0
  247. package/vendor/testez/TestSession.lua +243 -0
  248. package/vendor/testez/init.lua +40 -0
@@ -0,0 +1,70 @@
1
+ --!strict
2
+ --!nolint LocalUnused
3
+ --!nolint LocalShadow
4
+ local task = nil -- Disable usage of Roblox's task scheduler
5
+
6
+ --[[
7
+ Provides functions for converting Color3s into Oklab space, for more
8
+ perceptually uniform colour blending.
9
+
10
+ See: https://bottosson.github.io/posts/oklab/
11
+ ]]
12
+
13
+ local sRGB = require(script.Parent.sRGB)
14
+
15
+ local Oklab = {}
16
+
17
+ -- Converts a Color3 in linear RGB space to a Vector3 in Oklab space.
18
+ function Oklab.fromLinear(rgb: Color3): Vector3
19
+
20
+ local l = rgb.R * 0.4122214708 + rgb.G * 0.5363325363 + rgb.B * 0.0514459929
21
+ local m = rgb.R * 0.2119034982 + rgb.G * 0.6806995451 + rgb.B * 0.1073969566
22
+ local s = rgb.R * 0.0883024619 + rgb.G * 0.2817188376 + rgb.B * 0.6299787005
23
+
24
+ local lRoot = l ^ (1/3)
25
+ local mRoot = m ^ (1/3)
26
+ local sRoot = s ^ (1/3)
27
+
28
+ return Vector3.new(
29
+ lRoot * 0.2104542553 + mRoot * 0.7936177850 - sRoot * 0.0040720468,
30
+ lRoot * 1.9779984951 - mRoot * 2.4285922050 + sRoot * 0.4505937099,
31
+ lRoot * 0.0259040371 + mRoot * 0.7827717662 - sRoot * 0.8086757660
32
+ )
33
+ end
34
+
35
+ -- Converts a Color3 in sRGB space to a Vector3 in Oklab space.
36
+ function Oklab.fromSRGB(srgb: Color3): Vector3
37
+ return Oklab.fromLinear(sRGB.toLinear(srgb))
38
+ end
39
+
40
+ -- Converts a Vector3 in Oklab space to a Color3 in linear RGB space.
41
+ -- The Color3 will be clamped by default unless specified otherwise.
42
+ function Oklab.toLinear(lab: Vector3, unclamped: boolean?): Color3
43
+ local lRoot = lab.X + lab.Y * 0.3963377774 + lab.Z * 0.2158037573
44
+ local mRoot = lab.X - lab.Y * 0.1055613458 - lab.Z * 0.0638541728
45
+ local sRoot = lab.X - lab.Y * 0.0894841775 - lab.Z * 1.2914855480
46
+
47
+ local l = lRoot ^ 3
48
+ local m = mRoot ^ 3
49
+ local s = sRoot ^ 3
50
+
51
+ local red = l * 4.0767416621 - m * 3.3077115913 + s * 0.2309699292
52
+ local green = l * -1.2684380046 + m * 2.6097574011 - s * 0.3413193965
53
+ local blue = l * -0.0041960863 - m * 0.7034186147 + s * 1.7076147010
54
+
55
+ if not unclamped then
56
+ red = math.clamp(red, 0, 1)
57
+ green = math.clamp(green, 0, 1)
58
+ blue = math.clamp(blue, 0, 1)
59
+ end
60
+
61
+ return Color3.new(red, green, blue)
62
+ end
63
+
64
+ -- Converts a Vector3 in Oklab space to a Color3 in sRGB space.
65
+ -- The Color3 will be clamped by default unless specified otherwise.
66
+ function Oklab.toSRGB(lab: Vector3, unclamped: boolean?): Color3
67
+ return sRGB.fromLinear(Oklab.toLinear(lab, unclamped))
68
+ end
69
+
70
+ return Oklab
@@ -0,0 +1,55 @@
1
+ --!strict
2
+ --!nolint LocalUnused
3
+ --!nolint LocalShadow
4
+ local task = nil -- Disable usage of Roblox's task scheduler
5
+
6
+ --[[
7
+ Provides transformation functions for converting linear RGB values
8
+ into sRGB values.
9
+
10
+ RGB color channel transformations are outlined here:
11
+ https://bottosson.github.io/posts/colorwrong/#what-can-we-do%3F
12
+ ]]
13
+
14
+ local sRGB = {}
15
+
16
+ -- Equivalent to f_inv. Takes a linear sRGB channel and returns
17
+ -- the sRGB channel
18
+ local function transform(channel: number): number
19
+ if channel >= 0.04045 then
20
+ return ((channel + 0.055)/(1 + 0.055))^2.4
21
+ else
22
+ return channel / 12.92
23
+ end
24
+ end
25
+
26
+ -- Equivalent to f. Takes an sRGB channel and returns
27
+ -- the linear sRGB channel
28
+ local function inverse(channel: number): number
29
+ if channel >= 0.0031308 then
30
+ return (1.055) * channel^(1.0/2.4) - 0.055
31
+ else
32
+ return 12.92 * channel
33
+ end
34
+ end
35
+
36
+ -- Uses a tranformation to convert linear RGB into sRGB.
37
+ function sRGB.fromLinear(rgb: Color3): Color3
38
+ return Color3.new(
39
+ transform(rgb.R),
40
+ transform(rgb.G),
41
+ transform(rgb.B)
42
+ )
43
+ end
44
+
45
+ -- Converts an sRGB into linear RGB using a
46
+ -- (The inverse of sRGB.fromLinear).
47
+ function sRGB.toLinear(srgb: Color3): Color3
48
+ return Color3.new(
49
+ inverse(srgb.R),
50
+ inverse(srgb.G),
51
+ inverse(srgb.B)
52
+ )
53
+ end
54
+
55
+ return sRGB
@@ -0,0 +1,168 @@
1
+ --!strict
2
+ --!nolint LocalUnused
3
+ --!nolint LocalShadow
4
+ local task = nil -- Disable usage of Roblox's task scheduler
5
+
6
+ --[[
7
+ Abstraction layer between Fusion internals and external environments,
8
+ allowing for flexible integration with schedulers and test mocks.
9
+ ]]
10
+
11
+ local Package = script.Parent
12
+ local formatError = require(Package.Logging.formatError)
13
+ local Types = require(Package.Types)
14
+
15
+ local ERROR_INFO_URL = "https://elttob.uk/Fusion/0.3/api-reference/general/errors/#"
16
+
17
+ local External = {}
18
+
19
+ -- Indicates that a highly time-critical passage of code is running. During
20
+ -- critical periods of a program, Fusion might decide to change some of its
21
+ -- internal behaviour to be more performance friendly.
22
+ local timeCritical = false
23
+
24
+ -- Multiplier for running-time safety checks across the Fusion codebase. Used to
25
+ -- stricten tests on infinite loop detection during unit testing.
26
+ External.safetyTimerMultiplier = 1
27
+
28
+ local updateStepCallbacks = {}
29
+ local currentProvider: Types.ExternalProvider? = nil
30
+ local lastUpdateStep = 0
31
+
32
+ --[[
33
+ Swaps to a new provider for external operations.
34
+ Returns the old provider, so it can be used again later.
35
+ ]]
36
+ function External.setExternalProvider(
37
+ newProvider: Types.ExternalProvider?
38
+ ): Types.ExternalProvider?
39
+ local oldProvider = currentProvider
40
+ if oldProvider ~= nil then
41
+ oldProvider.stopScheduler()
42
+ end
43
+ currentProvider = newProvider
44
+ if newProvider ~= nil then
45
+ newProvider.startScheduler()
46
+ end
47
+ return oldProvider
48
+ end
49
+
50
+ --[[
51
+ Returns true if a highly time-critical passage of code is running.
52
+ ]]
53
+ function External.isTimeCritical(): boolean
54
+ return timeCritical
55
+ end
56
+
57
+ --[[
58
+ Sends an immediate task to the external provider. Throws if none is set.
59
+ ]]
60
+ function External.doTaskImmediate(
61
+ resume: () -> ()
62
+ )
63
+ if currentProvider == nil then
64
+ External.logError("noTaskScheduler")
65
+ else
66
+ currentProvider.doTaskImmediate(resume)
67
+ end
68
+ end
69
+
70
+ --[[
71
+ Sends a deferred task to the external provider. Throws if none is set.
72
+ ]]
73
+ function External.doTaskDeferred(
74
+ resume: () -> ()
75
+ )
76
+ if currentProvider == nil then
77
+ External.logError("noTaskScheduler")
78
+ else
79
+ currentProvider.doTaskDeferred(resume)
80
+ end
81
+ end
82
+
83
+ --[[
84
+ Errors in the current thread and halts execution.
85
+ ]]
86
+ function External.logError(
87
+ messageID: string,
88
+ errObj: Types.Error?,
89
+ ...: unknown
90
+ ): never
91
+ error(formatError(currentProvider, messageID, errObj, ...), 0)
92
+ end
93
+
94
+ --[[
95
+ Errors in a different thread to preserve the flow of execution.
96
+ ]]
97
+ function External.logErrorNonFatal(
98
+ messageID: string,
99
+ errObj: Types.Error?,
100
+ ...: unknown
101
+ ): ()
102
+ local errorString = formatError(currentProvider, messageID, errObj, ...)
103
+ if currentProvider ~= nil then
104
+ currentProvider.logErrorNonFatal(errorString)
105
+ else
106
+ print(errorString)
107
+ end
108
+ end
109
+
110
+ --[[
111
+ Shows a warning message in the output.
112
+ ]]
113
+ function External.logWarn(
114
+ messageID: string,
115
+ ...: unknown
116
+ ): ()
117
+ local errorString = formatError(currentProvider, messageID, debug.traceback(nil, 2), ...)
118
+ if currentProvider ~= nil then
119
+ currentProvider.logWarn(errorString)
120
+ else
121
+ print(errorString)
122
+ end
123
+ end
124
+
125
+ --[[
126
+ Registers a callback to the update step of the external provider.
127
+ Returns a function that can be used to disconnect later.
128
+
129
+ Callbacks are given the current number of seconds since an arbitrary epoch.
130
+
131
+ TODO: This epoch may change between providers. We could investigate ways
132
+ of allowing providers to co-operate to keep the epoch the same, so that
133
+ monotonicity can be better preserved.
134
+ ]]
135
+ function External.bindToUpdateStep(
136
+ callback: (
137
+ now: number
138
+ ) -> ()
139
+ ): () -> ()
140
+ local uniqueIdentifier = {}
141
+ updateStepCallbacks[uniqueIdentifier] = callback
142
+ return function()
143
+ updateStepCallbacks[uniqueIdentifier] = nil
144
+ end
145
+ end
146
+
147
+ --[[
148
+ Steps time-dependent systems with the current number of seconds since an
149
+ arbitrary epoch. This should be called as early as possible in the external
150
+ provider's update cycle.
151
+ ]]
152
+ function External.performUpdateStep(
153
+ now: number
154
+ )
155
+ lastUpdateStep = now
156
+ for _, callback in updateStepCallbacks do
157
+ callback(now)
158
+ end
159
+ end
160
+
161
+ --[[
162
+ Returns the timestamp of the last update step.
163
+ ]]
164
+ function External.lastUpdateStep()
165
+ return lastUpdateStep
166
+ end
167
+
168
+ return External
@@ -0,0 +1,70 @@
1
+ --!strict
2
+ --!nolint LocalUnused
3
+ --!nolint LocalShadow
4
+ local task = nil -- Disable usage of Roblox's task scheduler
5
+
6
+ --[[
7
+ Abstraction layer between Fusion internals and external debuggers, allowing
8
+ for deep introspection using function hooks.
9
+
10
+ Unlike `External`, attaching a debugger is optional, and all debugger
11
+ functions are expected to be infallible and non-blocking.
12
+ ]]
13
+
14
+ local Package = script.Parent
15
+ local Types = require(Package.Types)
16
+
17
+ local currentProvider: Types.ExternalDebugger? = nil
18
+ local lastUpdateStep = 0
19
+
20
+ local Debugger = {}
21
+
22
+ --[[
23
+ Swaps to a new debugger.
24
+ Returns the old debugger, so it can be used again later.
25
+ ]]
26
+ function Debugger.setDebugger(
27
+ newProvider: Types.ExternalDebugger?
28
+ ): Types.ExternalDebugger?
29
+ local oldProvider = currentProvider
30
+ if oldProvider ~= nil then
31
+ oldProvider.stopDebugging()
32
+ end
33
+ currentProvider = newProvider
34
+ if newProvider ~= nil then
35
+ newProvider.startDebugging()
36
+ end
37
+ return oldProvider
38
+ end
39
+
40
+ --[[
41
+ Called at the earliest moment after a scope is created or removed from the
42
+ scope pool, but not before the scope has finished being prepared by the
43
+ library, so that debuggers can register its existence and track changes
44
+ to the scope over time.
45
+ ]]
46
+ function Debugger.trackScope(
47
+ scope: Types.Scope<unknown>
48
+ ): ()
49
+ if currentProvider == nil then
50
+ return
51
+ end
52
+ currentProvider.trackScope(scope)
53
+ end
54
+
55
+ --[[
56
+ Called at the final moment before a scope is poisoned or added to the scope
57
+ pool, after all cleanup tasks have completed, so that debuggers can erase
58
+ the scope from internal trackers. Note that, due to scope pooling and user
59
+ code, never assume that this correlates with garbage collection events.
60
+ ]]
61
+ function Debugger.untrackScope(
62
+ scope: Types.Scope<unknown>
63
+ ): ()
64
+ if currentProvider == nil then
65
+ return
66
+ end
67
+ currentProvider.trackScope(scope)
68
+ end
69
+
70
+ return Debugger
@@ -0,0 +1,114 @@
1
+ --!strict
2
+ --!nolint LocalUnused
3
+ --!nolint LocalShadow
4
+ local task = nil -- Disable usage of Roblox's task scheduler
5
+
6
+ --[[
7
+ A graph object that runs user code when it's updated by the reactive graph.
8
+
9
+ http://elttob.uk/Fusion/0.3/api-reference/state/types/observer/
10
+ ]]
11
+
12
+ local Package = script.Parent.Parent
13
+ local Types = require(Package.Types)
14
+ local External = require(Package.External)
15
+ -- Memory
16
+ local checkLifetime = require(Package.Memory.checkLifetime)
17
+ -- Graph
18
+ local castToGraph = require(Package.Graph.castToGraph)
19
+ local depend = require(Package.Graph.depend)
20
+ local evaluate = require(Package.Graph.evaluate)
21
+ -- Utility
22
+ local nicknames = require(Package.Utility.nicknames)
23
+
24
+ type Self = Types.Observer & {
25
+ _watchingGraph: Types.GraphObject?,
26
+ _changeListeners: {[unknown]: () -> ()}
27
+ }
28
+
29
+ local class = {}
30
+ class.type = "Observer"
31
+ class.timeliness = "eager"
32
+ class.dependentSet = table.freeze {}
33
+
34
+ local METATABLE = table.freeze {__index = class}
35
+
36
+ local function Observer(
37
+ scope: Types.Scope<unknown>,
38
+ watching: unknown
39
+ ): Types.Observer
40
+ local createdAt = os.clock()
41
+ if watching == nil then
42
+ External.logError("scopeMissing", nil, "Observers", "myScope:Observer(watching)")
43
+ end
44
+
45
+ local self: Self = setmetatable(
46
+ {
47
+ scope = scope,
48
+ createdAt = createdAt,
49
+ dependencySet = {},
50
+ lastChange = nil,
51
+ validity = "invalid",
52
+ _watchingGraph = castToGraph(watching),
53
+ _changeListeners = {}
54
+ },
55
+ METATABLE
56
+ ) :: any
57
+ local destroy = function()
58
+ self.scope = nil
59
+ for dependency in pairs(self.dependencySet) do
60
+ dependency.dependentSet[self] = nil
61
+ end
62
+ end
63
+ self.oldestTask = destroy
64
+ nicknames[self.oldestTask] = "Observer"
65
+ table.insert(scope, destroy)
66
+
67
+ if self._watchingGraph ~= nil then
68
+ checkLifetime.bOutlivesA(
69
+ scope, self.oldestTask,
70
+ self._watchingGraph.scope, self._watchingGraph.oldestTask,
71
+ checkLifetime.formatters.observer
72
+ )
73
+ end
74
+
75
+ -- Eagerly evaluated objects need to evaluate themselves so that they're
76
+ -- valid at all times.
77
+ evaluate(self, true)
78
+
79
+ return self
80
+ end
81
+
82
+ function class.onBind(
83
+ self: Self,
84
+ callback: () -> ()
85
+ ): () -> ()
86
+ External.doTaskImmediate(callback)
87
+ return self:onChange(callback)
88
+ end
89
+
90
+ function class.onChange(
91
+ self: Self,
92
+ callback: () -> ()
93
+ ): () -> ()
94
+ local uniqueIdentifier = table.freeze {}
95
+ self._changeListeners[uniqueIdentifier] = callback
96
+ return function()
97
+ self._changeListeners[uniqueIdentifier] = nil
98
+ end
99
+ end
100
+
101
+ function class._evaluate(
102
+ self: Self
103
+ ): ()
104
+ if self._watchingGraph ~= nil then
105
+ depend(self, self._watchingGraph)
106
+ end
107
+ for _, callback in self._changeListeners do
108
+ External.doTaskImmediate(callback)
109
+ end
110
+ return true
111
+ end
112
+
113
+ table.freeze(class)
114
+ return Observer :: Types.ObserverConstructor
@@ -0,0 +1,29 @@
1
+ --!strict
2
+ --!nolint LocalUnused
3
+ --!nolint LocalShadow
4
+ local task = nil -- Disable usage of Roblox's task scheduler
5
+
6
+ --[[
7
+ Returns the input *only* if it is a graph object.
8
+ ]]
9
+
10
+ local Package = script.Parent.Parent
11
+ local Types = require(Package.Types)
12
+
13
+ local function castToGraph(
14
+ target: any
15
+ ): Types.GraphObject?
16
+ if
17
+ typeof(target) == "table" and
18
+ typeof(target.validity) == "string" and
19
+ typeof(target.timeliness) == "string" and
20
+ typeof(target.dependencySet) == "table" and
21
+ typeof(target.dependentSet) == "table"
22
+ then
23
+ return target
24
+ else
25
+ return nil
26
+ end
27
+ end
28
+
29
+ return castToGraph
@@ -0,0 +1,81 @@
1
+ --!strict
2
+ --!nolint LocalUnused
3
+ --!nolint LocalShadow
4
+ local task = nil -- Disable usage of Roblox's task scheduler
5
+
6
+ --[[
7
+ Prompts a graph object to re-evaluate its own value. If it meaningfully
8
+ changes, then dependents will have to re-evaluate their own values in the
9
+ future.
10
+
11
+ https://fluff.blog/2024/04/16/monotonic-painting.html
12
+ ]]
13
+
14
+ local Package = script.Parent.Parent
15
+ local Types = require(Package.Types)
16
+ local External = require(Package.External)
17
+ local evaluate = require(Package.Graph.evaluate)
18
+
19
+ -- How long should this function run before it's considered to be in an infinite
20
+ -- cycle and error out?
21
+ local TERMINATION_TIME = 1
22
+
23
+ local function change(
24
+ target: Types.GraphObject
25
+ ): ()
26
+ if target.validity == "busy" then
27
+ return External.logError("infiniteLoop")
28
+ end
29
+
30
+ local meaningfullyChanged = evaluate(target, true)
31
+ if not meaningfullyChanged then
32
+ return
33
+ end
34
+
35
+ local searchInNow: {Types.GraphObject} = {}
36
+ local searchInNext: {Types.GraphObject} = {}
37
+ local invalidateList: {Types.GraphObject} = {}
38
+
39
+ searchInNow[1] = target
40
+ local terminateBy = os.clock() + TERMINATION_TIME * External.safetyTimerMultiplier
41
+ repeat
42
+ if os.clock() > terminateBy then
43
+ return External.logError("infiniteLoop")
44
+ end
45
+ local done = true
46
+ for _, searchTarget in searchInNow do
47
+ for dependent in searchTarget.dependentSet do
48
+ if dependent.validity == "valid" then
49
+ done = false
50
+ table.insert(invalidateList, dependent)
51
+ table.insert(searchInNext, dependent)
52
+ elseif dependent.validity == "busy" then
53
+ return External.logError("infiniteLoop")
54
+ end
55
+ end
56
+ end
57
+ searchInNow, searchInNext = searchInNext, searchInNow
58
+ table.clear(searchInNext)
59
+ until done
60
+
61
+ local eagerList: {Types.GraphObject} = {}
62
+
63
+ for _, invalidateTarget in invalidateList do
64
+ invalidateTarget.validity = "invalid"
65
+ if invalidateTarget.timeliness == "eager" then
66
+ table.insert(eagerList, invalidateTarget)
67
+ end
68
+ end
69
+ -- If objects are not executed in order of creations, then dynamic graphs
70
+ -- may experience 'glitches' where nested graph objects see intermediate
71
+ -- values before being destroyed.
72
+ -- https://fluff.blog/2024/07/14/glitches-in-dynamic-reactive-graphs.html
73
+ table.sort(eagerList, function(a, b)
74
+ return a.createdAt < b.createdAt
75
+ end)
76
+ for _, eagerTarget in eagerList do
77
+ evaluate(eagerTarget, false)
78
+ end
79
+ end
80
+
81
+ return change
@@ -0,0 +1,33 @@
1
+ --!strict
2
+ --!nolint LocalUnused
3
+ --!nolint LocalShadow
4
+ local task = nil -- Disable usage of Roblox's task scheduler
5
+
6
+ --[[
7
+ Forms a dependency on a graph object.
8
+ ]]
9
+
10
+ local Package = script.Parent.Parent
11
+ local Types = require(Package.Types)
12
+ local External = require(Package.External)
13
+ local evaluate = require(Package.Graph.evaluate)
14
+ local nameOf = require(Package.Utility.nameOf)
15
+
16
+ local function depend<T>(
17
+ dependent: Types.GraphObject,
18
+ dependency: Types.GraphObject
19
+ ): ()
20
+ -- Ensure dependencies are evaluated and up-to-date
21
+ -- when they are depended on. Also, newly created objects
22
+ -- might not have any transitive dependencies captured yet,
23
+ -- so ensure that they're present.
24
+ evaluate(dependency, false)
25
+
26
+ if table.isfrozen(dependent.dependencySet) or table.isfrozen(dependency.dependentSet) then
27
+ External.logError("cannotDepend", nil, nameOf(dependent, "Dependent"), nameOf(dependency, "dependency"))
28
+ end
29
+ dependency.dependentSet[dependent] = true
30
+ dependent.dependencySet[dependency] = true
31
+ end
32
+
33
+ return depend
@@ -0,0 +1,56 @@
1
+ --!strict
2
+ --!nolint LocalUnused
3
+ --!nolint LocalShadow
4
+ local task = nil -- Disable usage of Roblox's task scheduler
5
+
6
+ --[[
7
+ Evaluates the graph object if necessary, so that it is up to date.
8
+ Returns true if it meaningfully changed.
9
+
10
+ https://fluff.blog/2024/04/16/monotonic-painting.html
11
+ ]]
12
+
13
+ local Package = script.Parent.Parent
14
+ local Types = require(Package.Types)
15
+ local External = require(Package.External)
16
+
17
+ local function evaluate(
18
+ target: Types.GraphObject,
19
+ forceComputation: boolean
20
+ ): boolean
21
+ if target.validity == "busy" then
22
+ return External.logError("infiniteLoop")
23
+ end
24
+ local firstEvaluation = target.lastChange == nil
25
+ local isInvalid = target.validity == "invalid"
26
+ if firstEvaluation or isInvalid or forceComputation then
27
+ local needsComputation = firstEvaluation or forceComputation
28
+ if not needsComputation then
29
+ for dependency in target.dependencySet do
30
+ evaluate(dependency, false)
31
+ if dependency.lastChange > target.lastChange then
32
+ needsComputation = true
33
+ break
34
+ end
35
+ end
36
+ end
37
+ local targetMeaningfullyChanged = false
38
+ if needsComputation then
39
+ for dependency in target.dependencySet do
40
+ dependency.dependentSet[target] = nil
41
+ target.dependencySet[dependency] = nil
42
+ end
43
+ target.validity = "busy"
44
+ targetMeaningfullyChanged = target:_evaluate() or firstEvaluation
45
+ end
46
+ if targetMeaningfullyChanged then
47
+ target.lastChange = os.clock()
48
+ end
49
+ target.validity = "valid"
50
+ return targetMeaningfullyChanged
51
+ else
52
+ return false
53
+ end
54
+ end
55
+
56
+ return evaluate