From 6c4014428e5c9fe9941588a46e0043f3288241b7 Mon Sep 17 00:00:00 2001 From: regginator Date: Thu, 26 Jan 2023 21:53:29 -0600 Subject: [PATCH] Remove `examples`, add automated packing workflow --- .maui.json | 3 + build.sh | 3 + examples/Baseplate_2023-01-06_14-00-53.lua | 32 - examples/EnvTest_2023-01-06_13-24-49.lua | 78 - .../MauiFusionDemo_2023-01-06_13-24-23.lua | 4067 ------------ examples/Maui_2023-01-06_13-25-04.lua | 5641 ----------------- examples/reggie_2023-01-07_15-10-46.lua | 527 -- serve.project.json | 4 + 8 files changed, 10 insertions(+), 10345 deletions(-) delete mode 100644 examples/Baseplate_2023-01-06_14-00-53.lua delete mode 100644 examples/EnvTest_2023-01-06_13-24-49.lua delete mode 100644 examples/MauiFusionDemo_2023-01-06_13-24-23.lua delete mode 100644 examples/Maui_2023-01-06_13-25-04.lua delete mode 100644 examples/reggie_2023-01-07_15-10-46.lua diff --git a/.maui.json b/.maui.json index 6d70599..d3b65fd 100644 --- a/.maui.json +++ b/.maui.json @@ -2,6 +2,9 @@ "FormatVersion": 1, "Output": { + "Directory": "return script.Parent.Parent.Builds[script.Parent.Version.Value]", + "ScriptName": "Maui-Packed", + "MinifyTable": true, "UseMinifiedLoader": true } diff --git a/build.sh b/build.sh index 954305e..a5f7bd8 100755 --- a/build.sh +++ b/build.sh @@ -28,6 +28,9 @@ mkdir -p "$build_path" cp LICENSE.txt "$build_path/LICENSE.txt" log "Copied LICENSE.txt to $build_path/LICENSE.txt" +# Create base file for 2-way-sync thing +touch "$build_path/Maui-Packed.lua" + # Minifiy the `LoadModule.lua` in the codegen, this is for building prod specifically log "Minifying \`LoadModule.lua.txt\` codegen src.. (Renames the file for Darklua, then renames back)" cp -f src/Codegen/LoadModuleCode/LoadModule.lua.txt src/Codegen/LoadModuleCode/LoadModule.lua diff --git a/examples/Baseplate_2023-01-06_14-00-53.lua b/examples/Baseplate_2023-01-06_14-00-53.lua deleted file mode 100644 index d812b75..0000000 --- a/examples/Baseplate_2023-01-06_14-00-53.lua +++ /dev/null @@ -1,32 +0,0 @@ --- This script was automatically @generated by Maui, it is not intended for manual editing. - -local ModuleRoot = { - { - Children = { - { - Properties = { - Transparency = 0.800000011920929, - StudsPerTileV = 8, - Texture = "rbxassetid://6372755229", - Face = Enum.NormalId.Top, - Color3 = Color3.new(0, 0, 0), - StudsPerTileU = 8 - }, - Reference = 2, - ClassName = "Texture" - } - }, - Properties = { - BottomSurface = Enum.SurfaceType.Smooth, - Name = "Baseplate", - Locked = true, - CFrame = CFrame.new(0, -8, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1), - Anchored = true, - TopSurface = Enum.SurfaceType.Smooth - }, - Reference = 1, - ClassName = "Part" - } -} - -do local a,b='0.1.0',game:GetService'RunService'local c,d,e,f,g,h,i,j,k=b:IsServer(),b:IsClient(),getfenv(0),{},{},{},{},{},{}local function l(m)local n,o=pcall(Instance.new,m.ClassName)if not n then return end f[m.Reference]=o if m.Closure then h[o]=m.Closure if o:IsA'BaseScript'then table.insert(j,o)end end if m.Properties then for p,q in next,m.Properties do pcall(function()o[p]=q end)end end if m.RefProperties then for p,q in next,m.RefProperties do table.insert(g,{InstanceObject=o,Property=p,ReferenceId=q})end end if m.Attributes then for p,q in next,m.Attributes do pcall(o.SetAttribute,o,p,q)end end if m.Children then for p,q in next,m.Children do local r=l(q)if r then r.Parent=o end end end return o end local m={}do for n,o in next,ModuleRoot do table.insert(m,l(o))end end local function n(o)local p=i[o]if o.ClassName=='ModuleScript'and p then return unpack(p)end local q=h[o]if not q then return end do local r local s={['maui']=table.freeze{Version=a,GetScript=function()return script end,GetShared=function()return k end},['script']=o,['require']=function(s,...)if s and s.ClassName=='ModuleScript'and h[s]then return n(s)end return require(s,...)end,['getfenv']=function(s,...)if type(s)=='number'and s>=0 then if s==0 then return r else local t,u=pcall(getfenv,s)if t and u==e then return r end end end return getfenv(s,...)end,['setfenv']=function(s,t,...)if type(s)=='number'and s>=0 then if s==0 then return setfenv(r,t)else local u,v=pcall(getfenv,s)if u and v==e then return setfenv(r,t)end end end return setfenv(s,t,...)end}r=setmetatable({},{__index=function(t,u)local v=rawget(r,u)if v~=nil then return v end local w=s[u]if w~=nil then return w end return e[u]end,__newindex=function(t,u,v)rawset(r,u,v)end})setfenv(q,r)end local r=coroutine.wrap(q)if o:IsA'BaseScript'then local s,t=if o.Enabled then task.defer(r)else nil t=o:GetPropertyChangedSignal'Enabled':Connect(function(u)t:Disconnect()if u==true then n(o)else pcall(task.cancel,s)end end)return else local s={r()}i[o]=s return unpack(s)end end for o,p in next,g do pcall(function()p.InstanceObject[p.Property]=f[p.ReferenceId]end)end for q,r in next,j do if(c and r.ClassName=='Script')or(d and r.ClassName=='LocalScript')then n(r)end end end diff --git a/examples/EnvTest_2023-01-06_13-24-49.lua b/examples/EnvTest_2023-01-06_13-24-49.lua deleted file mode 100644 index 7f3f505..0000000 --- a/examples/EnvTest_2023-01-06_13-24-49.lua +++ /dev/null @@ -1,78 +0,0 @@ --- This script was automatically @generated by Maui, it is not intended for manual editing. - -local ModuleRoot = { - { - Children = { - { - Closure = function() print("test 1: " .. tostring(getfenv(0)) .. ", " .. tostring(getfenv(1))) end, - Properties = { - Name = "test1" - }, - Reference = 4, - ClassName = "LocalScript" - }, - { - Closure = function() local Ok, FunctionEnvironment = pcall(getfenv, 2) -print("`getfenv(2)` status (should be an error): " .. tostring(FunctionEnvironment)) end, - Properties = { - Name = "no_getfenv_lvl2" - }, - Reference = 2, - ClassName = "LocalScript" - }, - { - Closure = function() end, - Properties = { - Name = "setfenv_test" - }, - Reference = 3, - ClassName = "LocalScript" - }, - { - Closure = function() print("test 2: " .. tostring(getfenv(0)) .. ", " .. tostring(getfenv(1))) end, - Properties = { - Name = "test2" - }, - Reference = 5, - ClassName = "LocalScript" - }, - { - Closure = function() local function f() - print("test 3: " .. tostring(getfenv(0)) .. ", " .. tostring(getfenv(1))) -end - -f() - end, - Properties = { - Name = "test3" - }, - Reference = 6, - ClassName = "LocalScript" - }, - { - Closure = function() local function f() - local function f2() - print("test 4: " .. tostring(getfenv(0)) .. ", " .. tostring(getfenv(1))) - end - - f2() -end - -f() - end, - Properties = { - Name = "test4" - }, - Reference = 7, - ClassName = "LocalScript" - } - }, - Properties = { - Name = "EnvTest" - }, - Reference = 1, - ClassName = "Folder" - } -} - -do local a,b='0.1.0',game:GetService'RunService'local c,d,e,f,g,h,i,j,k=b:IsServer(),b:IsClient(),getfenv(0),{},{},{},{},{},{}local function l(m)local n,o=pcall(Instance.new,m.ClassName)if not n then return end f[m.Reference]=o if m.Closure then h[o]=m.Closure if o:IsA'BaseScript'then table.insert(j,o)end end if m.Properties then for p,q in next,m.Properties do pcall(function()o[p]=q end)end end if m.RefProperties then for p,q in next,m.RefProperties do table.insert(g,{InstanceObject=o,Property=p,ReferenceId=q})end end if m.Attributes then for p,q in next,m.Attributes do pcall(o.SetAttribute,o,p,q)end end if m.Children then for p,q in next,m.Children do local r=l(q)if r then r.Parent=o end end end return o end local m={}do for n,o in next,ModuleRoot do table.insert(m,l(o))end end local function n(o)local p=i[o]if o.ClassName=='ModuleScript'and p then return unpack(p)end local q=h[o]if not q then return end do local r local s={['maui']=table.freeze{Version=a,GetScript=function()return script end,GetShared=function()return k end},['script']=o,['require']=function(s,...)if s and s.ClassName=='ModuleScript'and h[s]then return n(s)end return require(s,...)end,['getfenv']=function(s,...)if type(s)=='number'and s>=0 then if s==0 then return r else local t,u=pcall(getfenv,s)if t and u==e then return r end end end return getfenv(s,...)end,['setfenv']=function(s,t,...)if type(s)=='number'and s>=0 then if s==0 then return setfenv(r,t)else local u,v=pcall(getfenv,s)if u and v==e then return setfenv(r,t)end end end return setfenv(s,t,...)end}r=setmetatable({},{__index=function(t,u)local v=rawget(r,u)if v~=nil then return v end local w=s[u]if w~=nil then return w end return e[u]end,__newindex=function(t,u,v)rawset(r,u,v)end})setfenv(q,r)end local r=coroutine.wrap(q)if o:IsA'BaseScript'then local s,t=if o.Enabled then task.defer(r)else nil t=o:GetPropertyChangedSignal'Enabled':Connect(function(u)t:Disconnect()if u==true then n(o)else pcall(task.cancel,s)end end)return else local s={r()}i[o]=s return unpack(s)end end for o,p in next,g do pcall(function()p.InstanceObject[p.Property]=f[p.ReferenceId]end)end for q,r in next,j do if(c and r.ClassName=='Script')or(d and r.ClassName=='LocalScript')then n(r)end end end diff --git a/examples/MauiFusionDemo_2023-01-06_13-24-23.lua b/examples/MauiFusionDemo_2023-01-06_13-24-23.lua deleted file mode 100644 index 62bfc2f..0000000 --- a/examples/MauiFusionDemo_2023-01-06_13-24-23.lua +++ /dev/null @@ -1,4067 +0,0 @@ --- This script was automatically @generated by Maui, it is not intended for manual editing. - -local ModuleRoot = { - { - ClassName = "LocalScript", - Closure = function() ---@diagnostic disable: undefined-global --- Maui demo using Fusion with an exploit env - -local CoreGui = game:GetService("CoreGui") - -local Fusion = require(script.Fusion) - -local New = Fusion.New -local Children = Fusion.Children -local Value = Fusion.Value -local Computed = Fusion.Computed - -local ExecutorName = identifyexecutor() -local ValueChanges = Value(0) - -task.spawn(function() - for _ = 1, 50 do - ValueChanges:set(ValueChanges:get() + 1) - task.wait(1) - end -end) - -New "ScreenGui" { - Name = "MauiWithFusion", - Parent = CoreGui, - - DisplayOrder = 100, - ResetOnSpawn = false, - - [Children] = { - New "TextLabel" { - Name = "Hello", - - AutomaticSize = Enum.AutomaticSize.XY, - AnchorPoint = Vector2.new(0.5, 0.5), - BackgroundColor3 = Color3.fromRGB(44, 56, 63), - - Position = UDim2.fromScale(0.5, 0.5), - - Font = Enum.Font.GothamMedium, - Text = Computed(function() - return "Hello, Maui & Fusion!\nRunning on: " .. ExecutorName .. "\n\n" .. "Testing.. Testing... " .. ValueChanges:get() - end), - TextColor3 = Color3.fromRGB(218, 223, 228), - TextSize = 16, - TextWrapped = true, - - [Children] = { - New "UIPadding" { - PaddingTop = UDim.new(0, 16), - PaddingBottom = UDim.new(0, 16), - PaddingLeft = UDim.new(0, 16), - PaddingRight = UDim.new(0, 16), - }, - - New "UICorner" { - CornerRadius = UDim.new(0, 4) - }, - - New "UIStroke" { - ApplyStrokeMode = Enum.ApplyStrokeMode.Border, - Color = Color3.fromRGB(45, 51, 58) - } - } - } - } -} - end, - Properties = { - Name = "MauiFusionDemo" - }, - Reference = 1, - Children = { - { - ClassName = "ModuleScript", - Closure = function() --!strict - ---[[ - The entry point for the Fusion library. -]] - -local PubTypes = require(script.PubTypes) -local restrictRead = require(script.Utility.restrictRead) - -export type StateObject = PubTypes.StateObject -export type CanBeState = PubTypes.CanBeState -export type Symbol = PubTypes.Symbol -export type Value = PubTypes.Value -export type Computed = PubTypes.Computed -export type ForPairs = PubTypes.ForPairs -export type ForKeys = PubTypes.ForKeys -export type ForValues = PubTypes.ForKeys -export type Observer = PubTypes.Observer -export type Tween = PubTypes.Tween -export type Spring = PubTypes.Spring - -type Fusion = { - version: PubTypes.Version, - - New: (className: string) -> ((propertyTable: PubTypes.PropertyTable) -> Instance), - Hydrate: (target: Instance) -> ((propertyTable: PubTypes.PropertyTable) -> Instance), - Ref: PubTypes.SpecialKey, - Cleanup: PubTypes.SpecialKey, - Children: PubTypes.SpecialKey, - Out: PubTypes.SpecialKey, - OnEvent: (eventName: string) -> PubTypes.SpecialKey, - OnChange: (propertyName: string) -> PubTypes.SpecialKey, - - Value: (initialValue: T) -> Value, - Computed: (callback: () -> T, destructor: (T) -> ()?) -> Computed, - ForPairs: (inputTable: CanBeState<{[KI]: VI}>, processor: (KI, VI) -> (KO, VO, M?), destructor: (KO, VO, M?) -> ()?) -> ForPairs, - ForKeys: (inputTable: CanBeState<{[KI]: any}>, processor: (KI) -> (KO, M?), destructor: (KO, M?) -> ()?) -> ForKeys, - ForValues: (inputTable: CanBeState<{[any]: VI}>, processor: (VI) -> (VO, M?), destructor: (VO, M?) -> ()?) -> ForValues, - Observer: (watchedState: StateObject) -> Observer, - - Tween: (goalState: StateObject, tweenInfo: TweenInfo?) -> Tween, - Spring: (goalState: StateObject, speed: number?, damping: number?) -> Spring, - - cleanup: (...any) -> (), - doNothing: (...any) -> () -} - -return restrictRead("Fusion", { - version = {major = 0, minor = 2, isRelease = false}, - - New = require(script.Instances.New), - Hydrate = require(script.Instances.Hydrate), - Ref = require(script.Instances.Ref), - Out = require(script.Instances.Out), - Cleanup = require(script.Instances.Cleanup), - Children = require(script.Instances.Children), - OnEvent = require(script.Instances.OnEvent), - OnChange = require(script.Instances.OnChange), - - Value = require(script.State.Value), - Computed = require(script.State.Computed), - ForPairs = require(script.State.ForPairs), - ForKeys = require(script.State.ForKeys), - ForValues = require(script.State.ForValues), - Observer = require(script.State.Observer), - - Tween = require(script.Animation.Tween), - Spring = require(script.Animation.Spring), - - cleanup = require(script.Utility.cleanup), - doNothing = require(script.Utility.doNothing) -}) :: Fusion - end, - Properties = { - Name = "Fusion" - }, - Reference = 2, - Children = { - { - Closure = function() --!strict - ---[[ - Stores common public-facing type information for Fusion APIs. -]] - -type Set = {[T]: any} - ---[[ - General use types -]] - --- A unique symbolic value. -export type Symbol = { - type: string, -- replace with "Symbol" when Luau supports singleton types - name: string -} - --- Types that can be expressed as vectors of numbers, and so can be animated. -export type Animatable = - number | - CFrame | - Color3 | - ColorSequenceKeypoint | - DateTime | - NumberRange | - NumberSequenceKeypoint | - PhysicalProperties | - Ray | - Rect | - Region3 | - Region3int16 | - UDim | - UDim2 | - Vector2 | - Vector2int16 | - Vector3 | - Vector3int16 - --- A task which can be accepted for cleanup. -export type Task = - Instance | - RBXScriptConnection | - () -> () | - {destroy: (any) -> ()} | - {Destroy: (any) -> ()} | - {Task} - --- Script-readable version information. -export type Version = { - major: number, - minor: number, - isRelease: boolean -} ---[[ - Generic reactive graph types -]] - --- A graph object which can have dependents. -export type Dependency = { - dependentSet: Set -} - --- A graph object which can have dependencies. -export type Dependent = { - update: (Dependent) -> boolean, - dependencySet: Set -} - --- An object which stores a piece of reactive state. -export type StateObject = Dependency & { - type: string, -- replace with "State" when Luau supports singleton types - kind: string, - get: (StateObject, asDependency: boolean?) -> T -} - --- Either a constant value of type T, or a state object containing type T. -export type CanBeState = StateObject | T - ---[[ - Specific reactive graph types -]] - --- A state object whose value can be set at any time by the user. -export type Value = StateObject & { - -- kind: "State" (add this when Luau supports singleton types) - set: (Value, newValue: any, force: boolean?) -> () -} - --- A state object whose value is derived from other objects using a callback. -export type Computed = StateObject & Dependent & { - -- kind: "Computed" (add this when Luau supports singleton types) -} - --- A state object whose value is derived from other objects using a callback. -export type ForPairs = StateObject<{ [KO]: VO }> & Dependent & { - -- kind: "ForPairs" (add this when Luau supports singleton types) -} --- A state object whose value is derived from other objects using a callback. -export type ForKeys = StateObject<{ [KO]: V }> & Dependent & { - -- kind: "ForKeys" (add this when Luau supports singleton types) -} --- A state object whose value is derived from other objects using a callback. -export type ForValues = StateObject<{ [K]: VO }> & Dependent & { - -- kind: "ForKeys" (add this when Luau supports singleton types) -} - --- A state object which follows another state object using tweens. -export type Tween = StateObject & Dependent & { - -- kind: "Tween" (add this when Luau supports singleton types) -} - --- A state object which follows another state object using spring simulation. -export type Spring = StateObject & Dependent & { - -- kind: "Spring" (add this when Luau supports singleton types) - -- Uncomment when ENABLE_PARAM_SETTERS is enabled - -- setPosition: (Spring, newValue: Animatable) -> (), - -- setVelocity: (Spring, newValue: Animatable) -> (), - -- addVelocity: (Spring, deltaValue: Animatable) -> () -} - --- An object which can listen for updates on another state object. -export type Observer = Dependent & { - -- kind: "Observer" (add this when Luau supports singleton types) - onChange: (Observer, callback: () -> ()) -> (() -> ()) -} - ---[[ - Instance related types -]] - --- Denotes children instances in an instance or component's property table. -export type SpecialKey = { - type: string, -- replace with "SpecialKey" when Luau supports singleton types - kind: string, - stage: string, -- replace with "self" | "descendants" | "ancestor" | "observer" when Luau supports singleton types - apply: (SpecialKey, value: any, applyTo: Instance, cleanupTasks: {Task}) -> () -} - --- A collection of instances that may be parented to another instance. -export type Children = Instance | StateObject | {[any]: Children} - --- A table that defines an instance's properties, handlers and children. -export type PropertyTable = {[string | SpecialKey]: any} - -return nil end, - Properties = { - Name = "PubTypes" - }, - Reference = 38, - ClassName = "ModuleScript" - }, - { - Children = { - { - Closure = function() --!strict - ---[[ - An xpcall() error handler to collect and parse useful information about - errors, such as clean messages and stack traces. - - TODO: this should have a 'type' field for runtime type checking! -]] - -local Package = script.Parent.Parent -local Types = require(Package.Types) - -local function parseError(err: string): Types.Error - return { - type = "Error", - raw = err, - message = err:gsub("^.+:%d+:%s*", ""), - trace = debug.traceback(nil, 2) - } -end - -return parseError end, - Properties = { - Name = "parseError" - }, - Reference = 37, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Utility function to log a Fusion-specific warning. -]] - -local Package = script.Parent.Parent -local messages = require(Package.Logging.messages) - -local function logWarn(messageID, ...) - local formatString: string - - if messages[messageID] ~= nil then - formatString = messages[messageID] - else - messageID = "unknownMessage" - formatString = messages[messageID] - end - - warn(string.format("[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")", ...)) -end - -return logWarn end, - Properties = { - Name = "logWarn" - }, - Reference = 35, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Utility function to log a Fusion-specific error, without halting execution. -]] - -local Package = script.Parent.Parent -local Types = require(Package.Types) -local messages = require(Package.Logging.messages) - -local function logErrorNonFatal(messageID: string, errObj: Types.Error?, ...) - local formatString: string - - if messages[messageID] ~= nil then - formatString = messages[messageID] - else - messageID = "unknownMessage" - formatString = messages[messageID] - end - - local errorString - if errObj == nil then - errorString = string.format("[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")", ...) - else - formatString = formatString:gsub("ERROR_MESSAGE", errObj.message) - errorString = string.format("[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")\n---- Stack trace ----\n" .. errObj.trace, ...) - end - - task.spawn(function(...) - error(errorString:gsub("\n", "\n "), 0) - end, ...) -end - -return logErrorNonFatal end, - Properties = { - Name = "logErrorNonFatal" - }, - Reference = 34, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Utility function to log a Fusion-specific error. -]] - -local Package = script.Parent.Parent -local Types = require(Package.Types) -local messages = require(Package.Logging.messages) - -local function logError(messageID: string, errObj: Types.Error?, ...) - local formatString: string - - if messages[messageID] ~= nil then - formatString = messages[messageID] - else - messageID = "unknownMessage" - formatString = messages[messageID] - end - - local errorString - if errObj == nil then - errorString = string.format("[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")", ...) - else - formatString = formatString:gsub("ERROR_MESSAGE", errObj.message) - errorString = string.format("[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")\n---- Stack trace ----\n" .. errObj.trace, ...) - end - - error(errorString:gsub("\n", "\n "), 0) -end - -return logError end, - Properties = { - Name = "logError" - }, - Reference = 33, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Stores templates for different kinds of logging messages. -]] - -return { - cannotAssignProperty = "The class type '%s' has no assignable property '%s'.", - cannotConnectChange = "The %s class doesn't have a property called '%s'.", - cannotConnectEvent = "The %s class doesn't have an event called '%s'.", - cannotCreateClass = "Can't create a new instance of class '%s'.", - computedCallbackError = "Computed callback error: ERROR_MESSAGE", - destructorNeededValue = "To save instances into Values, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", - destructorNeededComputed = "To return instances from Computeds, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", - multiReturnComputed = "Returning multiple values from Computeds is discouraged, as behaviour will change soon - see discussion #189 on GitHub.", - destructorNeededForKeys = "To return instances from ForKeys, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", - destructorNeededForValues = "To return instances from ForValues, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", - destructorNeededForPairs = "To return instances from ForPairs, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", - duplicatePropertyKey = "", - forKeysProcessorError = "ForKeys callback error: ERROR_MESSAGE", - forKeysKeyCollision = "ForKeys should only write to output key '%s' once when processing key changes, but it wrote to it twice. Previously input key: '%s'; New input key: '%s'", - forKeysDestructorError = "ForKeys destructor error: ERROR_MESSAGE", - forPairsDestructorError = "ForPairs destructor error: ERROR_MESSAGE", - forPairsKeyCollision = "ForPairs should only write to output key '%s' once when processing key changes, but it wrote to it twice. Previous input pair: '[%s] = %s'; New input pair: '[%s] = %s'", - forPairsProcessorError = "ForPairs callback error: ERROR_MESSAGE", - forValuesProcessorError = "ForValues callback error: ERROR_MESSAGE", - forValuesDestructorError = "ForValues destructor error: ERROR_MESSAGE", - invalidChangeHandler = "The change handler for the '%s' property must be a function.", - invalidEventHandler = "The handler for the '%s' event must be a function.", - invalidPropertyType = "'%s.%s' expected a '%s' type, but got a '%s' type.", - invalidRefType = "Instance refs must be Value objects.", - invalidOutType = "[Out] properties must be given Value objects.", - invalidOutProperty = "The %s class doesn't have a property called '%s'.", - invalidSpringDamping = "The damping ratio for a spring must be >= 0. (damping was %.2f)", - invalidSpringSpeed = "The speed of a spring must be >= 0. (speed was %.2f)", - mistypedSpringDamping = "The damping ratio for a spring must be a number. (got a %s)", - mistypedSpringSpeed = "The speed of a spring must be a number. (got a %s)", - mistypedTweenInfo = "The tween info of a tween must be a TweenInfo. (got a %s)", - springTypeMismatch = "The type '%s' doesn't match the spring's type '%s'.", - strictReadError = "'%s' is not a valid member of '%s'.", - unknownMessage = "Unknown error: ERROR_MESSAGE", - unrecognisedChildType = "'%s' type children aren't accepted by `[Children]`.", - unrecognisedPropertyKey = "'%s' keys aren't accepted in property tables.", - unrecognisedPropertyStage = "'%s' isn't a valid stage for a special key to be applied at." -} end, - Properties = { - Name = "messages" - }, - Reference = 36, - ClassName = "ModuleScript" - } - }, - Properties = { - Name = "Logging" - }, - Reference = 32, - ClassName = "Folder" - }, - { - Children = { - { - Closure = function() --!nonstrict - ---[[ - Constructs and returns objects which can be used to model derived reactive - state. -]] - -local Package = script.Parent.Parent -local Types = require(Package.Types) -local captureDependencies = require(Package.Dependencies.captureDependencies) -local initDependency = require(Package.Dependencies.initDependency) -local useDependency = require(Package.Dependencies.useDependency) -local logErrorNonFatal = require(Package.Logging.logErrorNonFatal) -local logWarn = require(Package.Logging.logWarn) -local isSimilar = require(Package.Utility.isSimilar) -local needsDestruction = require(Package.Utility.needsDestruction) - -local class = {} - -local CLASS_METATABLE = {__index = class} -local WEAK_KEYS_METATABLE = {__mode = "k"} - ---[[ - Returns the last cached value calculated by this Computed object. - The computed object will be registered as a dependency unless `asDependency` - is false. -]] -function class:get(asDependency: boolean?): any - if asDependency ~= false then - useDependency(self) - end - return self._value -end - ---[[ - Recalculates this Computed's cached value and dependencies. - Returns true if it changed, or false if it's identical. -]] -function class:update(): boolean - -- remove this object from its dependencies' dependent sets - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = nil - end - - -- we need to create a new, empty dependency set to capture dependencies - -- into, but in case there's an error, we want to restore our old set of - -- dependencies. by using this table-swapping solution, we can avoid the - -- overhead of allocating new tables each update. - self._oldDependencySet, self.dependencySet = self.dependencySet, self._oldDependencySet - table.clear(self.dependencySet) - - local ok, newValue, newMetaValue = captureDependencies(self.dependencySet, self._processor) - - if ok then - if self._destructor == nil and needsDestruction(newValue) then - logWarn("destructorNeededComputed") - end - - if newMetaValue ~= nil then - logWarn("multiReturnComputed") - end - - local oldValue = self._value - local similar = isSimilar(oldValue, newValue) - if self._destructor ~= nil then - self._destructor(oldValue) - end - self._value = newValue - - -- add this object to the dependencies' dependent sets - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = true - end - - return not similar - else - -- this needs to be non-fatal, because otherwise it'd disrupt the - -- update process - logErrorNonFatal("computedCallbackError", newValue) - - -- restore old dependencies, because the new dependencies may be corrupt - self._oldDependencySet, self.dependencySet = self.dependencySet, self._oldDependencySet - - -- restore this object in the dependencies' dependent sets - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = true - end - - return false - end -end - -local function Computed(processor: () -> T, destructor: ((T) -> ())?): Types.Computed - local self = setmetatable({ - type = "State", - kind = "Computed", - dependencySet = {}, - -- if we held strong references to the dependents, then they wouldn't be - -- able to get garbage collected when they fall out of scope - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _oldDependencySet = {}, - _processor = processor, - _destructor = destructor, - _value = nil, - }, CLASS_METATABLE) - - initDependency(self) - self:update() - - return self -end - -return Computed end, - Properties = { - Name = "Computed" - }, - Reference = 40, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - A common interface for accessing the values of state objects or constants. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local xtypeof = require(Package.Utility.xtypeof) - -local function unwrap(item: PubTypes.CanBeState, useDependency: boolean?): T - return if xtypeof(item) == "State" then (item :: PubTypes.StateObject):get(useDependency) else (item :: T) -end - -return unwrap end, - Properties = { - Name = "unwrap" - }, - Reference = 46, - ClassName = "ModuleScript" - }, - { - Closure = function() --!nonstrict - ---[[ - Constructs and returns objects which can be used to model independent - reactive state. -]] - -local Package = script.Parent.Parent -local Types = require(Package.Types) -local useDependency = require(Package.Dependencies.useDependency) -local initDependency = require(Package.Dependencies.initDependency) -local updateAll = require(Package.Dependencies.updateAll) -local isSimilar = require(Package.Utility.isSimilar) - -local class = {} - -local CLASS_METATABLE = {__index = class} -local WEAK_KEYS_METATABLE = {__mode = "k"} - ---[[ - Returns the value currently stored in this State object. - The state object will be registered as a dependency unless `asDependency` is - false. -]] -function class:get(asDependency: boolean?): any - if asDependency ~= false then - useDependency(self) - end - return self._value -end - ---[[ - Updates the value stored in this State object. - - If `force` is enabled, this will skip equality checks and always update the - state object and any dependents - use this with care as this can lead to - unnecessary updates. -]] -function class:set(newValue: any, force: boolean?) - local oldValue = self._value - if force or not isSimilar(oldValue, newValue) then - self._value = newValue - updateAll(self) - end -end - -local function Value(initialValue: T): Types.State - local self = setmetatable({ - type = "State", - kind = "Value", - -- if we held strong references to the dependents, then they wouldn't be - -- able to get garbage collected when they fall out of scope - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _value = initialValue - }, CLASS_METATABLE) - - initDependency(self) - - return self -end - -return Value end, - Properties = { - Name = "Value" - }, - Reference = 45, - ClassName = "ModuleScript" - }, - { - Closure = function() --!nonstrict - ---[[ - Constructs a new ForKeys state object which maps keys of an array using - a `processor` function. - - Optionally, a `destructor` function can be specified for cleaning up - calculated keys. If omitted, the default cleanup function will be used instead. - - Optionally, a `meta` value can be returned in the processor function as the - second value to pass data from the processor to the destructor. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Types = require(Package.Types) -local captureDependencies = require(Package.Dependencies.captureDependencies) -local initDependency = require(Package.Dependencies.initDependency) -local useDependency = require(Package.Dependencies.useDependency) -local parseError = require(Package.Logging.parseError) -local logErrorNonFatal = require(Package.Logging.logErrorNonFatal) -local logError = require(Package.Logging.logError) -local logWarn = require(Package.Logging.logWarn) -local cleanup = require(Package.Utility.cleanup) -local needsDestruction = require(Package.Utility.needsDestruction) - -local class = {} - -local CLASS_METATABLE = { __index = class } -local WEAK_KEYS_METATABLE = { __mode = "k" } - ---[[ - Returns the current value of this ForKeys object. - The object will be registered as a dependency unless `asDependency` is false. -]] -function class:get(asDependency: boolean?): any - if asDependency ~= false then - useDependency(self) - end - return self._outputTable -end - - ---[[ - Called when the original table is changed. - - This will firstly find any keys meeting any of the following criteria: - - - they were not previously present - - a dependency used during generation of this value has changed - - It will recalculate those key pairs, storing information about any - dependencies used in the processor callback during output key generation, - and save the new key to the output array with the same value. If it is - overwriting an older value, that older value will be passed to the - destructor for cleanup. - - Finally, this function will find keys that are no longer present, and remove - their output keys from the output table and pass them to the destructor. -]] - -function class:update(): boolean - local inputIsState = self._inputIsState - local newInputTable = if inputIsState then self._inputTable:get(false) else self._inputTable - local oldInputTable = self._oldInputTable - local outputTable = self._outputTable - - local keyOIMap = self._keyOIMap - local keyIOMap = self._keyIOMap - local meta = self._meta - - local didChange = false - - - -- clean out main dependency set - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = nil - end - - self._oldDependencySet, self.dependencySet = self.dependencySet, self._oldDependencySet - table.clear(self.dependencySet) - - -- if the input table is a state object, add it as a dependency - if inputIsState then - self._inputTable.dependentSet[self] = true - self.dependencySet[self._inputTable] = true - end - - - -- STEP 1: find keys that changed or were not previously present - for newInKey, value in pairs(newInputTable) do - -- get or create key data - local keyData = self._keyData[newInKey] - - if keyData == nil then - keyData = { - dependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - oldDependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - dependencyValues = setmetatable({}, WEAK_KEYS_METATABLE), - } - self._keyData[newInKey] = keyData - end - - -- check if the key is new - local shouldRecalculate = oldInputTable[newInKey] == nil - - -- check if the key's dependencies have changed - if shouldRecalculate == false then - for dependency, oldValue in pairs(keyData.dependencyValues) do - if oldValue ~= dependency:get(false) then - shouldRecalculate = true - break - end - end - end - - - -- recalculate the output key if necessary - if shouldRecalculate then - keyData.oldDependencySet, keyData.dependencySet = keyData.dependencySet, keyData.oldDependencySet - table.clear(keyData.dependencySet) - - local processOK, newOutKey, newMetaValue = captureDependencies( - keyData.dependencySet, - self._processor, - newInKey - ) - - if processOK then - if self._destructor == nil and (needsDestruction(newOutKey) or needsDestruction(newMetaValue)) then - logWarn("destructorNeededForKeys") - end - - local oldInKey = keyOIMap[newOutKey] - local oldOutKey = keyIOMap[newInKey] - - -- check for key collision - if oldInKey ~= newInKey and newInputTable[oldInKey] ~= nil then - logError("forKeysKeyCollision", nil, tostring(newOutKey), tostring(oldInKey), tostring(newOutKey)) - end - - -- check for a changed output key - if oldOutKey ~= newOutKey and keyOIMap[oldOutKey] == newInKey then - -- clean up the old calculated value - local oldMetaValue = meta[oldOutKey] - - local destructOK, err = xpcall(self._destructor or cleanup, parseError, oldOutKey, oldMetaValue) - if not destructOK then - logErrorNonFatal("forKeysDestructorError", err) - end - - keyOIMap[oldOutKey] = nil - outputTable[oldOutKey] = nil - meta[oldOutKey] = nil - end - - -- update the stored data for this key - oldInputTable[newInKey] = value - meta[newOutKey] = newMetaValue - keyOIMap[newOutKey] = newInKey - keyIOMap[newInKey] = newOutKey - outputTable[newOutKey] = value - - -- if we had to recalculate the output, then we did change - didChange = true - else - -- restore old dependencies, because the new dependencies may be corrupt - keyData.oldDependencySet, keyData.dependencySet = keyData.dependencySet, keyData.oldDependencySet - - logErrorNonFatal("forKeysProcessorError", newOutKey) - end - end - - - -- save dependency values and add to main dependency set - for dependency in pairs(keyData.dependencySet) do - keyData.dependencyValues[dependency] = dependency:get(false) - - self.dependencySet[dependency] = true - dependency.dependentSet[self] = true - end - end - - - -- STEP 2: find keys that were removed - for outputKey, inputKey in pairs(keyOIMap) do - if newInputTable[inputKey] == nil then - -- clean up the old calculated value - local oldMetaValue = meta[outputKey] - - local destructOK, err = xpcall(self._destructor or cleanup, parseError, outputKey, oldMetaValue) - if not destructOK then - logErrorNonFatal("forKeysDestructorError", err) - end - - -- remove data - oldInputTable[inputKey] = nil - meta[outputKey] = nil - keyOIMap[outputKey] = nil - keyIOMap[inputKey] = nil - outputTable[outputKey] = nil - self._keyData[inputKey] = nil - - -- if we removed a key, then the table/state changed - didChange = true - end - end - - return didChange -end - -local function ForKeys( - inputTable: PubTypes.CanBeState<{ [KI]: any }>, - processor: (KI) -> (KO, M?), - destructor: (KO, M?) -> ()? -): Types.ForKeys - - local inputIsState = inputTable.type == "State" and typeof(inputTable.get) == "function" - - local self = setmetatable({ - type = "State", - kind = "ForKeys", - dependencySet = {}, - -- if we held strong references to the dependents, then they wouldn't be - -- able to get garbage collected when they fall out of scope - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _oldDependencySet = {}, - - _processor = processor, - _destructor = destructor, - _inputIsState = inputIsState, - - _inputTable = inputTable, - _oldInputTable = {}, - _outputTable = {}, - _keyOIMap = {}, - _keyIOMap = {}, - _keyData = {}, - _meta = {}, - }, CLASS_METATABLE) - - initDependency(self) - self:update() - - return self -end - -return ForKeys end, - Properties = { - Name = "ForKeys" - }, - Reference = 41, - ClassName = "ModuleScript" - }, - { - Closure = function() --!nonstrict - ---[[ - Constructs a new ForPairs object which maps pairs of a table using - a `processor` function. - - Optionally, a `destructor` function can be specified for cleaning up values. - If omitted, the default cleanup function will be used instead. - - Additionally, a `meta` table/value can optionally be returned to pass data created - when running the processor to the destructor when the created object is cleaned up. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Types = require(Package.Types) -local captureDependencies = require(Package.Dependencies.captureDependencies) -local initDependency = require(Package.Dependencies.initDependency) -local useDependency = require(Package.Dependencies.useDependency) -local parseError = require(Package.Logging.parseError) -local logErrorNonFatal = require(Package.Logging.logErrorNonFatal) -local logError = require(Package.Logging.logError) -local logWarn = require(Package.Logging.logWarn) -local cleanup = require(Package.Utility.cleanup) -local needsDestruction = require(Package.Utility.needsDestruction) - -local class = {} - -local CLASS_METATABLE = { __index = class } -local WEAK_KEYS_METATABLE = { __mode = "k" } - ---[[ - Returns the current value of this ForPairs object. - The object will be registered as a dependency unless `asDependency` is false. -]] -function class:get(asDependency: boolean?): any - if asDependency ~= false then - useDependency(self) - end - return self._outputTable -end - ---[[ - Called when the original table is changed. - - This will firstly find any keys meeting any of the following criteria: - - - they were not previously present - - their associated value has changed - - a dependency used during generation of this value has changed - - It will recalculate those key/value pairs, storing information about any - dependencies used in the processor callback during value generation, and - save the new key/value pair to the output array. If it is overwriting an - older key/value pair, that older pair will be passed to the destructor - for cleanup. - - Finally, this function will find keys that are no longer present, and remove - their key/value pairs from the output table and pass them to the destructor. -]] -function class:update(): boolean - local inputIsState = self._inputIsState - local newInputTable = if inputIsState then self._inputTable:get(false) else self._inputTable - local oldInputTable = self._oldInputTable - - local keyIOMap = self._keyIOMap - local meta = self._meta - - local didChange = false - - - -- clean out main dependency set - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = nil - end - - self._oldDependencySet, self.dependencySet = self.dependencySet, self._oldDependencySet - table.clear(self.dependencySet) - - -- if the input table is a state object, add it as a dependency - if inputIsState then - self._inputTable.dependentSet[self] = true - self.dependencySet[self._inputTable] = true - end - - -- clean out output table - self._oldOutputTable, self._outputTable = self._outputTable, self._oldOutputTable - - local oldOutputTable = self._oldOutputTable - local newOutputTable = self._outputTable - table.clear(newOutputTable) - - -- Step 1: find key/value pairs that changed or were not previously present - - for newInKey, newInValue in pairs(newInputTable) do - -- get or create key data - local keyData = self._keyData[newInKey] - - if keyData == nil then - keyData = { - dependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - oldDependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - dependencyValues = setmetatable({}, WEAK_KEYS_METATABLE), - } - self._keyData[newInKey] = keyData - end - - - -- check if the pair is new or changed - local shouldRecalculate = oldInputTable[newInKey] ~= newInValue - - -- check if the pair's dependencies have changed - if shouldRecalculate == false then - for dependency, oldValue in pairs(keyData.dependencyValues) do - if oldValue ~= dependency:get(false) then - shouldRecalculate = true - break - end - end - end - - - -- recalculate the output pair if necessary - if shouldRecalculate then - keyData.oldDependencySet, keyData.dependencySet = keyData.dependencySet, keyData.oldDependencySet - table.clear(keyData.dependencySet) - - local processOK, newOutKey, newOutValue, newMetaValue = captureDependencies( - keyData.dependencySet, - self._processor, - newInKey, - newInValue - ) - - if processOK then - if self._destructor == nil and (needsDestruction(newOutKey) or needsDestruction(newOutValue) or needsDestruction(newMetaValue)) then - logWarn("destructorNeededForPairs") - end - - -- if this key was already written to on this run-through, throw a fatal error. - if newOutputTable[newOutKey] ~= nil then - -- figure out which key/value pair previously wrote to this key - local previousNewKey, previousNewValue - for inKey, outKey in pairs(keyIOMap) do - if outKey == newOutKey then - previousNewValue = newInputTable[inKey] - if previousNewValue ~= nil then - previousNewKey = inKey - break - end - end - end - - if previousNewKey ~= nil then - logError( - "forPairsKeyCollision", - nil, - tostring(newOutKey), - tostring(previousNewKey), - tostring(previousNewValue), - tostring(newInKey), - tostring(newInValue) - ) - end - end - - local oldOutValue = oldOutputTable[newOutKey] - - if oldOutValue ~= newOutValue then - local oldMetaValue = meta[newOutKey] - if oldOutValue ~= nil then - local destructOK, err = xpcall(self._destructor or cleanup, parseError, newOutKey, oldOutValue, oldMetaValue) - if not destructOK then - logErrorNonFatal("forPairsDestructorError", err) - end - end - - oldOutputTable[newOutKey] = nil - end - - -- update the stored data for this key/value pair - oldInputTable[newInKey] = newInValue - keyIOMap[newInKey] = newOutKey - meta[newOutKey] = newMetaValue - newOutputTable[newOutKey] = newOutValue - - -- if we had to recalculate the output, then we did change - didChange = true - else - -- restore old dependencies, because the new dependencies may be corrupt - keyData.oldDependencySet, keyData.dependencySet = keyData.dependencySet, keyData.oldDependencySet - - logErrorNonFatal("forPairsProcessorError", newOutKey) - end - else - local storedOutKey = keyIOMap[newInKey] - - -- check for key collision - if newOutputTable[storedOutKey] ~= nil then - -- figure out which key/value pair previously wrote to this key - local previousNewKey, previousNewValue - for inKey, outKey in pairs(keyIOMap) do - if storedOutKey == outKey then - previousNewValue = newInputTable[inKey] - - if previousNewValue ~= nil then - previousNewKey = inKey - break - end - end - end - - if previousNewKey ~= nil then - logError( - "forPairsKeyCollision", - nil, - tostring(storedOutKey), - tostring(previousNewKey), - tostring(previousNewValue), - tostring(newInKey), - tostring(newInValue) - ) - end - end - - -- copy the stored key/value pair into the new output table - newOutputTable[storedOutKey] = oldOutputTable[storedOutKey] - end - - - -- save dependency values and add to main dependency set - for dependency in pairs(keyData.dependencySet) do - keyData.dependencyValues[dependency] = dependency:get(false) - - self.dependencySet[dependency] = true - dependency.dependentSet[self] = true - end - end - - -- STEP 2: find keys that were removed - for oldOutKey, oldOutValue in pairs(oldOutputTable) do - -- check if this key/value pair is in the new output table - if newOutputTable[oldOutKey] ~= oldOutValue then - -- clean up the old output pair - local oldMetaValue = meta[oldOutKey] - if oldOutValue ~= nil then - local destructOK, err = xpcall(self._destructor or cleanup, parseError, oldOutKey, oldOutValue, oldMetaValue) - if not destructOK then - logErrorNonFatal("forPairsDestructorError", err) - end - end - - -- check if the key was completely removed from the output table - if newOutputTable[oldOutKey] == nil then - meta[oldOutKey] = nil - self._keyData[oldOutKey] = nil - end - - didChange = true - end - end - - for key in pairs(oldInputTable) do - if newInputTable[key] == nil then - oldInputTable[key] = nil - keyIOMap[key] = nil - end - end - - return didChange -end - -local function ForPairs( - inputTable: PubTypes.CanBeState<{ [KI]: VI }>, - processor: (KI, VI) -> (KO, VO, M?), - destructor: (KO, VO, M?) -> ()? -): Types.ForPairs - - local inputIsState = inputTable.type == "State" and typeof(inputTable.get) == "function" - - local self = setmetatable({ - type = "State", - kind = "ForPairs", - dependencySet = {}, - -- if we held strong references to the dependents, then they wouldn't be - -- able to get garbage collected when they fall out of scope - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _oldDependencySet = {}, - - _processor = processor, - _destructor = destructor, - _inputIsState = inputIsState, - - _inputTable = inputTable, - _oldInputTable = {}, - _outputTable = {}, - _oldOutputTable = {}, - _keyIOMap = {}, - _keyData = {}, - _meta = {}, - }, CLASS_METATABLE) - - initDependency(self) - self:update() - - return self -end - -return ForPairs end, - Properties = { - Name = "ForPairs" - }, - Reference = 42, - ClassName = "ModuleScript" - }, - { - Closure = function() --!nonstrict - ---[[ - Constructs a new state object which can listen for updates on another state - object. - - FIXME: enabling strict types here causes free types to leak -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Types = require(Package.Types) -local initDependency = require(Package.Dependencies.initDependency) - -type Set = {[T]: any} - -local class = {} -local CLASS_METATABLE = {__index = class} - --- Table used to hold Observer objects in memory. -local strongRefs: Set = {} - ---[[ - Called when the watched state changes value. -]] -function class:update(): boolean - for _, callback in pairs(self._changeListeners) do - task.spawn(callback) - end - return false -end - ---[[ - Adds a change listener. When the watched state changes value, the listener - will be fired. - - Returns a function which, when called, will disconnect the change listener. - As long as there is at least one active change listener, this Observer - will be held in memory, preventing GC, so disconnecting is important. -]] -function class:onChange(callback: () -> ()): () -> () - local uniqueIdentifier = {} - - self._numChangeListeners += 1 - self._changeListeners[uniqueIdentifier] = callback - - -- disallow gc (this is important to make sure changes are received) - strongRefs[self] = true - - local disconnected = false - return function() - if disconnected then - return - end - disconnected = true - self._changeListeners[uniqueIdentifier] = nil - self._numChangeListeners -= 1 - - if self._numChangeListeners == 0 then - -- allow gc if all listeners are disconnected - strongRefs[self] = nil - end - end -end - -local function Observer(watchedState: PubTypes.Value): Types.Observer - local self = setmetatable({ - type = "State", - kind = "Observer", - dependencySet = {[watchedState] = true}, - dependentSet = {}, - _changeListeners = {}, - _numChangeListeners = 0, - }, CLASS_METATABLE) - - initDependency(self) - -- add this object to the watched state's dependent set - watchedState.dependentSet[self] = true - - return self -end - -return Observer end, - Properties = { - Name = "Observer" - }, - Reference = 44, - ClassName = "ModuleScript" - }, - { - Closure = function() --!nonstrict - ---[[ - Constructs a new ForValues object which maps values of a table using - a `processor` function. - - Optionally, a `destructor` function can be specified for cleaning up values. - If omitted, the default cleanup function will be used instead. - - Additionally, a `meta` table/value can optionally be returned to pass data created - when running the processor to the destructor when the created object is cleaned up. -]] -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Types = require(Package.Types) -local captureDependencies = require(Package.Dependencies.captureDependencies) -local initDependency = require(Package.Dependencies.initDependency) -local useDependency = require(Package.Dependencies.useDependency) -local parseError = require(Package.Logging.parseError) -local logErrorNonFatal = require(Package.Logging.logErrorNonFatal) -local logWarn = require(Package.Logging.logWarn) -local cleanup = require(Package.Utility.cleanup) -local needsDestruction = require(Package.Utility.needsDestruction) - -local class = {} - -local CLASS_METATABLE = { __index = class } -local WEAK_KEYS_METATABLE = { __mode = "k" } - ---[[ - Returns the current value of this ForValues object. - The object will be registered as a dependency unless `asDependency` is false. -]] -function class:get(asDependency: boolean?): any - if asDependency ~= false then - useDependency(self) - end - return self._outputTable -end - ---[[ - Called when the original table is changed. - - This will firstly find any values meeting any of the following criteria: - - - they were not previously present - - a dependency used during generation of this value has changed - - It will recalculate those values, storing information about any dependencies - used in the processor callback during value generation, and save the new value - to the output array with the same key. If it is overwriting an older value, - that older value will be passed to the destructor for cleanup. - - Finally, this function will find values that are no longer present, and remove - their values from the output table and pass them to the destructor. You can re-use - the same value multiple times and this will function will update them as little as - possible; reusing the same values where possible. -]] -function class:update(): boolean - local inputIsState = self._inputIsState - local inputTable = if inputIsState then self._inputTable:get(false) else self._inputTable - local outputValues = {} - - local didChange = false - - -- clean out value cache - self._oldValueCache, self._valueCache = self._valueCache, self._oldValueCache - local newValueCache = self._valueCache - local oldValueCache = self._oldValueCache - table.clear(newValueCache) - - -- clean out main dependency set - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = nil - end - self._oldDependencySet, self.dependencySet = self.dependencySet, self._oldDependencySet - table.clear(self.dependencySet) - - -- if the input table is a state object, add it as a dependency - if inputIsState then - self._inputTable.dependentSet[self] = true - self.dependencySet[self._inputTable] = true - end - - - -- STEP 1: find values that changed or were not previously present - for inKey, inValue in pairs(inputTable) do - -- check if the value is new or changed - local oldCachedValues = oldValueCache[inValue] - local shouldRecalculate = oldCachedValues == nil - - -- get a cached value and its dependency/meta data if available - local value, valueData, meta - - if type(oldCachedValues) == "table" and #oldCachedValues > 0 then - local valueInfo = table.remove(oldCachedValues, #oldCachedValues) - value = valueInfo.value - valueData = valueInfo.valueData - meta = valueInfo.meta - - if #oldCachedValues <= 0 then - oldValueCache[inValue] = nil - end - elseif oldCachedValues ~= nil then - oldValueCache[inValue] = nil - shouldRecalculate = true - end - - if valueData == nil then - valueData = { - dependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - oldDependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - dependencyValues = setmetatable({}, WEAK_KEYS_METATABLE), - } - end - - -- check if the value's dependencies have changed - if shouldRecalculate == false then - for dependency, oldValue in pairs(valueData.dependencyValues) do - if oldValue ~= dependency:get(false) then - shouldRecalculate = true - break - end - end - end - - -- recalculate the output value if necessary - if shouldRecalculate then - valueData.oldDependencySet, valueData.dependencySet = valueData.dependencySet, valueData.oldDependencySet - table.clear(valueData.dependencySet) - - local processOK, newOutValue, newMetaValue = captureDependencies( - valueData.dependencySet, - self._processor, - inValue - ) - - if processOK then - if self._destructor == nil and (needsDestruction(newOutValue) or needsDestruction(newMetaValue)) then - logWarn("destructorNeededForValues") - end - - -- pass the old value to the destructor if it exists - if value ~= nil then - local destructOK, err = xpcall(self._destructor or cleanup, parseError, value, meta) - if not destructOK then - logErrorNonFatal("forValuesDestructorError", err) - end - end - - -- store the new value and meta data - value = newOutValue - meta = newMetaValue - didChange = true - else - -- restore old dependencies, because the new dependencies may be corrupt - valueData.oldDependencySet, valueData.dependencySet = valueData.dependencySet, valueData.oldDependencySet - - logErrorNonFatal("forValuesProcessorError", newOutValue) - end - end - - - -- store the value and its dependency/meta data - local newCachedValues = newValueCache[inValue] - if newCachedValues == nil then - newCachedValues = {} - newValueCache[inValue] = newCachedValues - end - - table.insert(newCachedValues, { - value = value, - valueData = valueData, - meta = meta, - }) - - outputValues[inKey] = value - - - -- save dependency values and add to main dependency set - for dependency in pairs(valueData.dependencySet) do - valueData.dependencyValues[dependency] = dependency:get(false) - - self.dependencySet[dependency] = true - dependency.dependentSet[self] = true - end - end - - - -- STEP 2: find values that were removed - -- for tables of data, we just need to check if it's still in the cache - for _oldInValue, oldCachedValueInfo in pairs(oldValueCache) do - for _, valueInfo in ipairs(oldCachedValueInfo) do - local oldValue = valueInfo.value - local oldMetaValue = valueInfo.meta - - local destructOK, err = xpcall(self._destructor or cleanup, parseError, oldValue, oldMetaValue) - if not destructOK then - logErrorNonFatal("forValuesDestructorError", err) - end - - didChange = true - end - - table.clear(oldCachedValueInfo) - end - - self._outputTable = outputValues - - return didChange -end - -local function ForValues( - inputTable: PubTypes.CanBeState<{ [any]: VI }>, - processor: (VI) -> (VO, M?), - destructor: (VO, M?) -> ()? -): Types.ForValues - - local inputIsState = inputTable.type == "State" and typeof(inputTable.get) == "function" - - local self = setmetatable({ - type = "State", - kind = "ForValues", - dependencySet = {}, - -- if we held strong references to the dependents, then they wouldn't be - -- able to get garbage collected when they fall out of scope - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _oldDependencySet = {}, - - _processor = processor, - _destructor = destructor, - _inputIsState = inputIsState, - - _inputTable = inputTable, - _outputTable = {}, - _valueCache = {}, - _oldValueCache = {}, - }, CLASS_METATABLE) - - initDependency(self) - self:update() - - return self -end - -return ForValues end, - Properties = { - Name = "ForValues" - }, - Reference = 43, - ClassName = "ModuleScript" - } - }, - Properties = { - Name = "State" - }, - Reference = 39, - ClassName = "Folder" - }, - { - Children = { - { - Closure = function() --!strict - ---[[ - Given a reactive object, updates all dependent reactive objects. - Objects are only ever updated after all of their dependencies are updated, - are only ever updated once, and won't be updated if their dependencies are - unchanged. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) - -type Set = {[T]: any} -type Descendant = (PubTypes.Dependent & PubTypes.Dependency) | PubTypes.Dependent - --- Credit: https://blog.elttob.uk/2022/11/07/sets-efficient-topological-search.html -local function updateAll(root: PubTypes.Dependency) - local counters: {[Descendant]: number} = {} - local flags: {[Descendant]: boolean} = {} - local queue: {Descendant} = {} - local queueSize = 0 - local queuePos = 1 - - for object in root.dependentSet do - queueSize += 1 - queue[queueSize] = object - flags[object] = true - end - - -- Pass 1: counting up - while queuePos <= queueSize do - local next = queue[queuePos] - local counter = counters[next] - counters[next] = if counter == nil then 1 else counter + 1 - if (next :: any).dependentSet ~= nil then - for object in (next :: any).dependentSet do - queueSize += 1 - queue[queueSize] = object - end - end - queuePos += 1 - end - - -- Pass 2: counting down + processing - queuePos = 1 - while queuePos <= queueSize do - local next = queue[queuePos] - local counter = counters[next] - 1 - counters[next] = counter - if counter == 0 and flags[next] and next:update() and (next :: any).dependentSet ~= nil then - for object in (next :: any).dependentSet do - flags[object] = true - end - end - queuePos += 1 - end -end - -return updateAll end, - Properties = { - Name = "updateAll" - }, - Reference = 19, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Calls the given callback, and stores any used external dependencies. - Arguments can be passed in after the callback. - If the callback completed successfully, returns true and the returned value, - otherwise returns false and the error thrown. - The callback shouldn't yield or run asynchronously. - - NOTE: any calls to useDependency() inside the callback (even if inside any - nested captureDependencies() call) will not be included in the set, to avoid - self-dependencies. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local parseError = require(Package.Logging.parseError) -local sharedState = require(Package.Dependencies.sharedState) - -type Set = {[T]: any} - -local initialisedStack = sharedState.initialisedStack -local initialisedStackCapacity = 0 - -local function captureDependencies( - saveToSet: Set, - callback: (...any) -> any, - ... -): (boolean, any) - - local prevDependencySet = sharedState.dependencySet - sharedState.dependencySet = saveToSet - - sharedState.initialisedStackSize += 1 - local initialisedStackSize = sharedState.initialisedStackSize - - local initialisedSet - if initialisedStackSize > initialisedStackCapacity then - initialisedSet = {} - initialisedStack[initialisedStackSize] = initialisedSet - initialisedStackCapacity = initialisedStackSize - else - initialisedSet = initialisedStack[initialisedStackSize] - table.clear(initialisedSet) - end - - local data = table.pack(xpcall(callback, parseError, ...)) - - sharedState.dependencySet = prevDependencySet - sharedState.initialisedStackSize -= 1 - - return table.unpack(data, 1, data.n) -end - -return captureDependencies - end, - Properties = { - Name = "captureDependencies" - }, - Reference = 16, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - If a target set was specified by captureDependencies(), this will add the - given dependency to the target set. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local sharedState = require(Package.Dependencies.sharedState) - -local initialisedStack = sharedState.initialisedStack - -local function useDependency(dependency: PubTypes.Dependency) - local dependencySet = sharedState.dependencySet - - if dependencySet ~= nil then - local initialisedStackSize = sharedState.initialisedStackSize - if initialisedStackSize > 0 then - local initialisedSet = initialisedStack[initialisedStackSize] - if initialisedSet[dependency] ~= nil then - return - end - end - dependencySet[dependency] = true - end -end - -return useDependency end, - Properties = { - Name = "useDependency" - }, - Reference = 20, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Stores shared state for dependency management functions. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) - -type Set = {[T]: any} - --- The set where used dependencies should be saved to. -local dependencySet: Set? = nil - --- A stack of sets where newly created dependencies should be stored. -local initialisedStack: {Set} = {} -local initialisedStackSize = 0 - -return { - dependencySet = dependencySet, - initialisedStack = initialisedStack, - initialisedStackSize = initialisedStackSize -} end, - Properties = { - Name = "sharedState" - }, - Reference = 18, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Registers the creation of an object which can be used as a dependency. - - This is used to make sure objects don't capture dependencies originating - from inside of themselves. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local sharedState = require(Package.Dependencies.sharedState) - -local initialisedStack = sharedState.initialisedStack - -local function initDependency(dependency: PubTypes.Dependency) - local initialisedStackSize = sharedState.initialisedStackSize - - for index, initialisedSet in ipairs(initialisedStack) do - if index > initialisedStackSize then - return - end - - initialisedSet[dependency] = true - end -end - -return initDependency end, - Properties = { - Name = "initDependency" - }, - Reference = 17, - ClassName = "ModuleScript" - } - }, - Properties = { - Name = "Dependencies" - }, - Reference = 15, - ClassName = "Folder" - }, - { - Children = { - { - Closure = function() --!strict - ---[[ - An empty function. Often used as a destructor to indicate no destruction. -]] - -local function doNothing(...: any) -end - -return doNothing end, - Properties = { - Name = "doNothing" - }, - Reference = 51, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Restricts the reading of missing members for a table. -]] - -local Package = script.Parent.Parent -local logError = require(Package.Logging.logError) - -type table = {[any]: any} - -local function restrictRead(tableName: string, strictTable: table): table - -- FIXME: Typed Luau doesn't recognise this correctly yet - local metatable = getmetatable(strictTable :: any) - - if metatable == nil then - metatable = {} - setmetatable(strictTable, metatable) - end - - function metatable:__index(memberName) - logError("strictReadError", nil, tostring(memberName), tableName) - end - - return strictTable -end - -return restrictRead end, - Properties = { - Name = "restrictRead" - }, - Reference = 54, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - A symbol for representing nil values in contexts where nil is not usable. -]] - -local Package = script.Parent.Parent -local Types = require(Package.Types) - -return { - type = "Symbol", - name = "None" -} :: Types.None end, - Properties = { - Name = "None" - }, - Reference = 49, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Extended typeof, designed for identifying custom objects. - If given a table with a `type` string, returns that. - Otherwise, returns `typeof()` the argument. -]] - -local function xtypeof(x: any) - local typeString = typeof(x) - - if typeString == "table" and typeof(x.type) == "string" then - return x.type - else - return typeString - end -end - -return xtypeof end, - Properties = { - Name = "xtypeof" - }, - Reference = 55, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Cleans up the tasks passed in as the arguments. - A task can be any of the following: - - - an Instance - will be destroyed - - an RBXScriptConnection - will be disconnected - - a function - will be run - - a table with a `Destroy` or `destroy` function - will be called - - an array - `cleanup` will be called on each item -]] - -local function cleanupOne(task: any) - local taskType = typeof(task) - - -- case 1: Instance - if taskType == "Instance" then - task:Destroy() - - -- case 2: RBXScriptConnection - elseif taskType == "RBXScriptConnection" then - task:Disconnect() - - -- case 3: callback - elseif taskType == "function" then - task() - - elseif taskType == "table" then - -- case 4: destroy() function - if typeof(task.destroy) == "function" then - task:destroy() - - -- case 5: Destroy() function - elseif typeof(task.Destroy) == "function" then - task:Destroy() - - -- case 6: array of tasks - elseif task[1] ~= nil then - for _, subtask in ipairs(task) do - cleanupOne(subtask) - end - end - end -end - -local function cleanup(...: any) - for index = 1, select("#", ...) do - cleanupOne(select(index, ...)) - end -end - -return cleanup end, - Properties = { - Name = "cleanup" - }, - Reference = 50, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Returns true if the given value is not automatically memory managed, and - requires manual cleanup. -]] - -local function needsDestruction(x: any): boolean - return typeof(x) == "Instance" -end - -return needsDestruction end, - Properties = { - Name = "needsDestruction" - }, - Reference = 53, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict ---[[ - Returns true if A and B are 'similar' - i.e. any user of A would not need - to recompute if it changed to B. -]] - -local function isSimilar(a: any, b: any): boolean - -- HACK: because tables are mutable data structures, don't make assumptions - -- about similarity from equality for now (see issue #44) - if typeof(a) == "table" then - return false - else - return a == b - end -end - -return isSimilar end, - Properties = { - Name = "isSimilar" - }, - Reference = 52, - ClassName = "ModuleScript" - } - }, - Properties = { - Name = "Utility" - }, - Reference = 48, - ClassName = "Folder" - }, - { - Children = { - { - Closure = function() --!strict - ---[[ - Packs an array of numbers into a given animatable data type. - If the type is not animatable, nil will be returned. - - FUTURE: When Luau supports singleton types, those could be used in - conjunction with intersection types to make this function fully statically - type checkable. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Oklab = require(Package.Colour.Oklab) - -local function packType(numbers: {number}, typeString: string): PubTypes.Animatable? - if typeString == "number" then - return numbers[1] - - elseif typeString == "CFrame" then - return - CFrame.new(numbers[1], numbers[2], numbers[3]) * - CFrame.fromAxisAngle( - Vector3.new(numbers[4], numbers[5], numbers[6]).Unit, - numbers[7] - ) - - elseif typeString == "Color3" then - return Oklab.from( - Vector3.new(numbers[1], numbers[2], numbers[3]), - false - ) - - elseif typeString == "ColorSequenceKeypoint" then - return ColorSequenceKeypoint.new( - numbers[4], - Oklab.from( - Vector3.new(numbers[1], numbers[2], numbers[3]), - false - ) - ) - - elseif typeString == "DateTime" then - return DateTime.fromUnixTimestampMillis(numbers[1]) - - elseif typeString == "NumberRange" then - return NumberRange.new(numbers[1], numbers[2]) - - elseif typeString == "NumberSequenceKeypoint" then - return NumberSequenceKeypoint.new(numbers[2], numbers[1], numbers[3]) - - elseif typeString == "PhysicalProperties" then - return PhysicalProperties.new(numbers[1], numbers[2], numbers[3], numbers[4], numbers[5]) - - elseif typeString == "Ray" then - return Ray.new( - Vector3.new(numbers[1], numbers[2], numbers[3]), - Vector3.new(numbers[4], numbers[5], numbers[6]) - ) - - elseif typeString == "Rect" then - return Rect.new(numbers[1], numbers[2], numbers[3], numbers[4]) - - elseif typeString == "Region3" then - -- FUTURE: support rotated Region3s if/when they become constructable - local position = Vector3.new(numbers[1], numbers[2], numbers[3]) - local halfSize = Vector3.new(numbers[4] / 2, numbers[5] / 2, numbers[6] / 2) - return Region3.new(position - halfSize, position + halfSize) - - elseif typeString == "Region3int16" then - return Region3int16.new( - Vector3int16.new(numbers[1], numbers[2], numbers[3]), - Vector3int16.new(numbers[4], numbers[5], numbers[6]) - ) - - elseif typeString == "UDim" then - return UDim.new(numbers[1], numbers[2]) - - elseif typeString == "UDim2" then - return UDim2.new(numbers[1], numbers[2], numbers[3], numbers[4]) - - elseif typeString == "Vector2" then - return Vector2.new(numbers[1], numbers[2]) - - elseif typeString == "Vector2int16" then - return Vector2int16.new(numbers[1], numbers[2]) - - elseif typeString == "Vector3" then - return Vector3.new(numbers[1], numbers[2], numbers[3]) - - elseif typeString == "Vector3int16" then - return Vector3int16.new(numbers[1], numbers[2], numbers[3]) - else - return nil - end -end - -return packType end, - Properties = { - Name = "packType" - }, - Reference = 10, - ClassName = "ModuleScript" - }, - { - Closure = function() --!nonstrict - ---[[ - Constructs a new computed state object, which follows the value of another - state object using a tween. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Types = require(Package.Types) -local TweenScheduler = require(Package.Animation.TweenScheduler) -local useDependency = require(Package.Dependencies.useDependency) -local initDependency = require(Package.Dependencies.initDependency) -local logError = require(Package.Logging.logError) -local logErrorNonFatal = require(Package.Logging.logErrorNonFatal) -local xtypeof = require(Package.Utility.xtypeof) - -local class = {} - -local CLASS_METATABLE = {__index = class} -local WEAK_KEYS_METATABLE = {__mode = "k"} - ---[[ - Returns the current value of this Tween object. - The object will be registered as a dependency unless `asDependency` is false. -]] -function class:get(asDependency: boolean?): any - if asDependency ~= false then - useDependency(self) - end - return self._currentValue -end - ---[[ - Called when the goal state changes value; this will initiate a new tween. - Returns false as the current value doesn't change right away. -]] -function class:update(): boolean - local goalValue = self._goalState:get(false) - - -- if the goal hasn't changed, then this is a TweenInfo change. - -- in that case, if we're not currently animating, we can skip everything - if goalValue == self._nextValue and not self._currentlyAnimating then - return false - end - - local tweenInfo = self._tweenInfo - if self._tweenInfoIsState then - tweenInfo = tweenInfo:get() - end - - -- if we receive a bad TweenInfo, then error and stop the update - if typeof(tweenInfo) ~= "TweenInfo" then - logErrorNonFatal("mistypedTweenInfo", nil, typeof(tweenInfo)) - return false - end - - self._prevValue = self._currentValue - self._nextValue = goalValue - - self._currentTweenStartTime = os.clock() - self._currentTweenInfo = tweenInfo - - local tweenDuration = tweenInfo.DelayTime + tweenInfo.Time - if tweenInfo.Reverses then - tweenDuration += tweenInfo.Time - end - tweenDuration *= tweenInfo.RepeatCount + 1 - self._currentTweenDuration = tweenDuration - - -- start animating this tween - TweenScheduler.add(self) - - return false -end - -local function Tween( - goalState: PubTypes.StateObject, - tweenInfo: PubTypes.CanBeState? -): Types.Tween - local currentValue = goalState:get(false) - - -- apply defaults for tween info - if tweenInfo == nil then - tweenInfo = TweenInfo.new() - end - - local dependencySet = {[goalState] = true} - local tweenInfoIsState = xtypeof(tweenInfo) == "State" - - if tweenInfoIsState then - dependencySet[tweenInfo] = true - end - - local startingTweenInfo = tweenInfo - if tweenInfoIsState then - startingTweenInfo = startingTweenInfo:get() - end - - -- If we start with a bad TweenInfo, then we don't want to construct a Tween - if typeof(startingTweenInfo) ~= "TweenInfo" then - logError("mistypedTweenInfo", nil, typeof(startingTweenInfo)) - end - - local self = setmetatable({ - type = "State", - kind = "Tween", - dependencySet = dependencySet, - -- if we held strong references to the dependents, then they wouldn't be - -- able to get garbage collected when they fall out of scope - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _goalState = goalState, - _tweenInfo = tweenInfo, - _tweenInfoIsState = tweenInfoIsState, - - _prevValue = currentValue, - _nextValue = currentValue, - _currentValue = currentValue, - - -- store current tween into separately from 'real' tween into, so it - -- isn't affected by :setTweenInfo() until next change - _currentTweenInfo = tweenInfo, - _currentTweenDuration = 0, - _currentTweenStartTime = 0, - _currentlyAnimating = false - }, CLASS_METATABLE) - - initDependency(self) - -- add this object to the goal state's dependent set - goalState.dependentSet[self] = true - - return self -end - -return Tween end, - Properties = { - Name = "Tween" - }, - Reference = 6, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Manages batch updating of spring objects. -]] - -local RunService = game:GetService("RunService") - -local Package = script.Parent.Parent -local Types = require(Package.Types) -local packType = require(Package.Animation.packType) -local springCoefficients = require(Package.Animation.springCoefficients) -local updateAll = require(Package.Dependencies.updateAll) - -type Set = {[T]: any} -type Spring = Types.Spring - -local SpringScheduler = {} - -local EPSILON = 0.0001 -local activeSprings: Set = {} -local lastUpdateTime = os.clock() - -function SpringScheduler.add(spring: Spring) - -- we don't necessarily want to use the most accurate time - here we snap to - -- the last update time so that springs started within the same frame have - -- identical time steps - spring._lastSchedule = lastUpdateTime - spring._startDisplacements = {} - spring._startVelocities = {} - for index, goal in ipairs(spring._springGoals) do - spring._startDisplacements[index] = spring._springPositions[index] - goal - spring._startVelocities[index] = spring._springVelocities[index] - end - - activeSprings[spring] = true -end - -function SpringScheduler.remove(spring: Spring) - activeSprings[spring] = nil -end - - -local function updateAllSprings() - local springsToSleep: Set = {} - lastUpdateTime = os.clock() - - for spring in pairs(activeSprings) do - local posPos, posVel, velPos, velVel = springCoefficients(lastUpdateTime - spring._lastSchedule, spring._currentDamping, spring._currentSpeed) - - local positions = spring._springPositions - local velocities = spring._springVelocities - local startDisplacements = spring._startDisplacements - local startVelocities = spring._startVelocities - local isMoving = false - - for index, goal in ipairs(spring._springGoals) do - local oldDisplacement = startDisplacements[index] - local oldVelocity = startVelocities[index] - local newDisplacement = oldDisplacement * posPos + oldVelocity * posVel - local newVelocity = oldDisplacement * velPos + oldVelocity * velVel - - if math.abs(newDisplacement) > EPSILON or math.abs(newVelocity) > EPSILON then - isMoving = true - end - - positions[index] = newDisplacement + goal - velocities[index] = newVelocity - end - - if not isMoving then - springsToSleep[spring] = true - end - end - - for spring in pairs(activeSprings) do - spring._currentValue = packType(spring._springPositions, spring._currentType) - updateAll(spring) - end - - for spring in pairs(springsToSleep) do - activeSprings[spring] = nil - end -end - -RunService:BindToRenderStep( - "__FusionSpringScheduler", - Enum.RenderPriority.First.Value, - updateAllSprings -) - -return SpringScheduler end, - Properties = { - Name = "SpringScheduler" - }, - Reference = 5, - ClassName = "ModuleScript" - }, - { - Closure = function() --!nonstrict - ---[[ - Constructs a new computed state object, which follows the value of another - state object using a spring simulation. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Types = require(Package.Types) -local logError = require(Package.Logging.logError) -local logErrorNonFatal = require(Package.Logging.logErrorNonFatal) -local unpackType = require(Package.Animation.unpackType) -local SpringScheduler = require(Package.Animation.SpringScheduler) -local useDependency = require(Package.Dependencies.useDependency) -local initDependency = require(Package.Dependencies.initDependency) -local updateAll = require(Package.Dependencies.updateAll) -local xtypeof = require(Package.Utility.xtypeof) -local unwrap = require(Package.State.unwrap) - -local class = {} - -local CLASS_METATABLE = {__index = class} -local WEAK_KEYS_METATABLE = {__mode = "k"} - ---[[ - Returns the current value of this Spring object. - The object will be registered as a dependency unless `asDependency` is false. -]] -function class:get(asDependency: boolean?): any - if asDependency ~= false then - useDependency(self) - end - return self._currentValue -end - ---[[ - Sets the position of the internal springs, meaning the value of this - Spring will jump to the given value. This doesn't affect velocity. - - If the type doesn't match the current type of the spring, an error will be - thrown. -]] -function class:setPosition(newValue: PubTypes.Animatable) - local newType = typeof(newValue) - if newType ~= self._currentType then - logError("springTypeMismatch", nil, newType, self._currentType) - end - - self._springPositions = unpackType(newValue, newType) - self._currentValue = newValue - SpringScheduler.add(self) - updateAll(self) -end - ---[[ - Sets the velocity of the internal springs, overwriting the existing velocity - of this Spring. This doesn't affect position. - - If the type doesn't match the current type of the spring, an error will be - thrown. -]] -function class:setVelocity(newValue: PubTypes.Animatable) - local newType = typeof(newValue) - if newType ~= self._currentType then - logError("springTypeMismatch", nil, newType, self._currentType) - end - - self._springVelocities = unpackType(newValue, newType) - SpringScheduler.add(self) -end - ---[[ - Adds to the velocity of the internal springs, on top of the existing - velocity of this Spring. This doesn't affect position. - - If the type doesn't match the current type of the spring, an error will be - thrown. -]] -function class:addVelocity(deltaValue: PubTypes.Animatable) - local deltaType = typeof(deltaValue) - if deltaType ~= self._currentType then - logError("springTypeMismatch", nil, deltaType, self._currentType) - end - - local springDeltas = unpackType(deltaValue, deltaType) - for index, delta in ipairs(springDeltas) do - self._springVelocities[index] += delta - end - SpringScheduler.add(self) -end - ---[[ - Called when the goal state changes value, or when the speed or damping has - changed. -]] -function class:update(): boolean - local goalValue = self._goalState:get(false) - - -- figure out if this was a goal change or a speed/damping change - if goalValue == self._goalValue then - -- speed/damping change - local damping = unwrap(self._damping) - if typeof(damping) ~= "number" then - logErrorNonFatal("mistypedSpringDamping", nil, typeof(damping)) - elseif damping < 0 then - logErrorNonFatal("invalidSpringDamping", nil, damping) - else - self._currentDamping = damping - end - - local speed = unwrap(self._speed) - if typeof(speed) ~= "number" then - logErrorNonFatal("mistypedSpringSpeed", nil, typeof(speed)) - elseif speed < 0 then - logErrorNonFatal("invalidSpringSpeed", nil, speed) - else - self._currentSpeed = speed - end - - return false - else - -- goal change - reconfigure spring to target new goal - self._goalValue = goalValue - - local oldType = self._currentType - local newType = typeof(goalValue) - self._currentType = newType - - local springGoals = unpackType(goalValue, newType) - local numSprings = #springGoals - self._springGoals = springGoals - - if newType ~= oldType then - -- if the type changed, snap to the new value and rebuild the - -- position and velocity tables - self._currentValue = self._goalValue - - local springPositions = table.create(numSprings, 0) - local springVelocities = table.create(numSprings, 0) - for index, springGoal in ipairs(springGoals) do - springPositions[index] = springGoal - end - self._springPositions = springPositions - self._springVelocities = springVelocities - - -- the spring may have been animating before, so stop that - SpringScheduler.remove(self) - return true - - -- otherwise, the type hasn't changed, just the goal... - elseif numSprings == 0 then - -- if the type isn't animatable, snap to the new value - self._currentValue = self._goalValue - return true - - else - -- if it's animatable, let it animate to the goal - SpringScheduler.add(self) - return false - end - end -end - -local function Spring( - goalState: PubTypes.Value, - speed: PubTypes.CanBeState?, - damping: PubTypes.CanBeState? -): Types.Spring - -- apply defaults for speed and damping - if speed == nil then - speed = 10 - end - if damping == nil then - damping = 1 - end - - local dependencySet = {[goalState] = true} - if xtypeof(speed) == "State" then - dependencySet[speed] = true - end - if xtypeof(damping) == "State" then - dependencySet[damping] = true - end - - local self = setmetatable({ - type = "State", - kind = "Spring", - dependencySet = dependencySet, - -- if we held strong references to the dependents, then they wouldn't be - -- able to get garbage collected when they fall out of scope - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _speed = speed, - _damping = damping, - - _goalState = goalState, - _goalValue = nil, - - _currentType = nil, - _currentValue = nil, - _currentSpeed = unwrap(speed), - _currentDamping = unwrap(damping), - - _springPositions = nil, - _springGoals = nil, - _springVelocities = nil - }, CLASS_METATABLE) - - initDependency(self) - -- add this object to the goal state's dependent set - goalState.dependentSet[self] = true - self:update() - - return self -end - -return Spring end, - Properties = { - Name = "Spring" - }, - Reference = 4, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Linearly interpolates the given animatable types by a ratio. - If the types are different or not animatable, then the first value will be - returned for ratios below 0.5, and the second value for 0.5 and above. - - FIXME: This function uses a lot of redefinitions to suppress false positives - from the Luau typechecker - ideally these wouldn't be required -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Oklab = require(Package.Colour.Oklab) - -local function lerpType(from: any, to: any, ratio: number): any - local typeString = typeof(from) - - if typeof(to) == typeString then - -- both types must match for interpolation to make sense - if typeString == "number" then - local to, from = to :: number, from :: number - return (to - from) * ratio + from - - elseif typeString == "CFrame" then - local to, from = to :: CFrame, from :: CFrame - return from:Lerp(to, ratio) - - elseif typeString == "Color3" then - local to, from = to :: Color3, from :: Color3 - local fromLab = Oklab.to(from) - local toLab = Oklab.to(to) - return Oklab.from( - fromLab:Lerp(toLab, ratio), - false - ) - - elseif typeString == "ColorSequenceKeypoint" then - local to, from = to :: ColorSequenceKeypoint, from :: ColorSequenceKeypoint - local fromLab = Oklab.to(from.Value) - local toLab = Oklab.to(to.Value) - return ColorSequenceKeypoint.new( - (to.Time - from.Time) * ratio + from.Time, - Oklab.from( - fromLab:Lerp(toLab, ratio), - false - ) - ) - - elseif typeString == "DateTime" then - local to, from = to :: DateTime, from :: DateTime - return DateTime.fromUnixTimestampMillis( - (to.UnixTimestampMillis - from.UnixTimestampMillis) * ratio + from.UnixTimestampMillis - ) - - elseif typeString == "NumberRange" then - local to, from = to :: NumberRange, from :: NumberRange - return NumberRange.new( - (to.Min - from.Min) * ratio + from.Min, - (to.Max - from.Max) * ratio + from.Max - ) - - elseif typeString == "NumberSequenceKeypoint" then - local to, from = to :: NumberSequenceKeypoint, from :: NumberSequenceKeypoint - return NumberSequenceKeypoint.new( - (to.Time - from.Time) * ratio + from.Time, - (to.Value - from.Value) * ratio + from.Value, - (to.Envelope - from.Envelope) * ratio + from.Envelope - ) - - elseif typeString == "PhysicalProperties" then - local to, from = to :: PhysicalProperties, from :: PhysicalProperties - return PhysicalProperties.new( - (to.Density - from.Density) * ratio + from.Density, - (to.Friction - from.Friction) * ratio + from.Friction, - (to.Elasticity - from.Elasticity) * ratio + from.Elasticity, - (to.FrictionWeight - from.FrictionWeight) * ratio + from.FrictionWeight, - (to.ElasticityWeight - from.ElasticityWeight) * ratio + from.ElasticityWeight - ) - - elseif typeString == "Ray" then - local to, from = to :: Ray, from :: Ray - return Ray.new( - from.Origin:Lerp(to.Origin, ratio), - from.Direction:Lerp(to.Direction, ratio) - ) - - elseif typeString == "Rect" then - local to, from = to :: Rect, from :: Rect - return Rect.new( - from.Min:Lerp(to.Min, ratio), - from.Max:Lerp(to.Max, ratio) - ) - - elseif typeString == "Region3" then - local to, from = to :: Region3, from :: Region3 - -- FUTURE: support rotated Region3s if/when they become constructable - local position = from.CFrame.Position:Lerp(to.CFrame.Position, ratio) - local halfSize = from.Size:Lerp(to.Size, ratio) / 2 - return Region3.new(position - halfSize, position + halfSize) - - elseif typeString == "Region3int16" then - local to, from = to :: Region3int16, from :: Region3int16 - return Region3int16.new( - Vector3int16.new( - (to.Min.X - from.Min.X) * ratio + from.Min.X, - (to.Min.Y - from.Min.Y) * ratio + from.Min.Y, - (to.Min.Z - from.Min.Z) * ratio + from.Min.Z - ), - Vector3int16.new( - (to.Max.X - from.Max.X) * ratio + from.Max.X, - (to.Max.Y - from.Max.Y) * ratio + from.Max.Y, - (to.Max.Z - from.Max.Z) * ratio + from.Max.Z - ) - ) - - elseif typeString == "UDim" then - local to, from = to :: UDim, from :: UDim - return UDim.new( - (to.Scale - from.Scale) * ratio + from.Scale, - (to.Offset - from.Offset) * ratio + from.Offset - ) - - elseif typeString == "UDim2" then - local to, from = to :: UDim2, from :: UDim2 - return from:Lerp(to, ratio) - - elseif typeString == "Vector2" then - local to, from = to :: Vector2, from :: Vector2 - return from:Lerp(to, ratio) - - elseif typeString == "Vector2int16" then - local to, from = to :: Vector2int16, from :: Vector2int16 - return Vector2int16.new( - (to.X - from.X) * ratio + from.X, - (to.Y - from.Y) * ratio + from.Y - ) - - elseif typeString == "Vector3" then - local to, from = to :: Vector3, from :: Vector3 - return from:Lerp(to, ratio) - - elseif typeString == "Vector3int16" then - local to, from = to :: Vector3int16, from :: Vector3int16 - return Vector3int16.new( - (to.X - from.X) * ratio + from.X, - (to.Y - from.Y) * ratio + from.Y, - (to.Z - from.Z) * ratio + from.Z - ) - end - end - - -- fallback case: the types are different or not animatable - if ratio < 0.5 then - return from - else - return to - end -end - -return lerpType end, - Properties = { - Name = "lerpType" - }, - Reference = 9, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Unpacks an animatable type into an array of numbers. - If the type is not animatable, an empty array will be returned. - - FIXME: This function uses a lot of redefinitions to suppress false positives - from the Luau typechecker - ideally these wouldn't be required - - FUTURE: When Luau supports singleton types, those could be used in - conjunction with intersection types to make this function fully statically - type checkable. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Oklab = require(Package.Colour.Oklab) - -local function unpackType(value: any, typeString: string): {number} - if typeString == "number" then - local value = value :: number - return {value} - - elseif typeString == "CFrame" then - -- FUTURE: is there a better way of doing this? doing distance - -- calculations on `angle` may be incorrect - local axis, angle = value:ToAxisAngle() - return {value.X, value.Y, value.Z, axis.X, axis.Y, axis.Z, angle} - - elseif typeString == "Color3" then - local lab = Oklab.to(value) - return {lab.X, lab.Y, lab.Z} - - elseif typeString == "ColorSequenceKeypoint" then - local lab = Oklab.to(value.Value) - return {lab.X, lab.Y, lab.Z, value.Time} - - elseif typeString == "DateTime" then - return {value.UnixTimestampMillis} - - elseif typeString == "NumberRange" then - return {value.Min, value.Max} - - elseif typeString == "NumberSequenceKeypoint" then - return {value.Value, value.Time, value.Envelope} - - elseif typeString == "PhysicalProperties" then - return {value.Density, value.Friction, value.Elasticity, value.FrictionWeight, value.ElasticityWeight} - - elseif typeString == "Ray" then - return {value.Origin.X, value.Origin.Y, value.Origin.Z, value.Direction.X, value.Direction.Y, value.Direction.Z} - - elseif typeString == "Rect" then - return {value.Min.X, value.Min.Y, value.Max.X, value.Max.Y} - - elseif typeString == "Region3" then - -- FUTURE: support rotated Region3s if/when they become constructable - return { - value.CFrame.X, value.CFrame.Y, value.CFrame.Z, - value.Size.X, value.Size.Y, value.Size.Z - } - - elseif typeString == "Region3int16" then - return {value.Min.X, value.Min.Y, value.Min.Z, value.Max.X, value.Max.Y, value.Max.Z} - - elseif typeString == "UDim" then - return {value.Scale, value.Offset} - - elseif typeString == "UDim2" then - return {value.X.Scale, value.X.Offset, value.Y.Scale, value.Y.Offset} - - elseif typeString == "Vector2" then - return {value.X, value.Y} - - elseif typeString == "Vector2int16" then - return {value.X, value.Y} - - elseif typeString == "Vector3" then - return {value.X, value.Y, value.Z} - - elseif typeString == "Vector3int16" then - return {value.X, value.Y, value.Z} - else - return {} - end -end - -return unpackType end, - Properties = { - Name = "unpackType" - }, - Reference = 12, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Returns a 2x2 matrix of coefficients for a given time, damping and speed. - Specifically, this returns four coefficients - posPos, posVel, velPos, and - velVel - which can be multiplied with position and velocity like so: - - local newPosition = oldPosition * posPos + oldVelocity * posVel - local newVelocity = oldPosition * velPos + oldVelocity * velVel - - Special thanks to AxisAngle for helping to improve numerical precision. -]] - -local function springCoefficients(time: number, damping: number, speed: number): (number, number, number, number) - -- if time or speed is 0, then the spring won't move - if time == 0 or speed == 0 then - return 1, 0, 0, 1 - end - local posPos, posVel, velPos, velVel - - if damping > 1 then - -- overdamped spring - -- solution to the characteristic equation: - -- z = -?? ± Sqrt[?^2 - 1] ? - -- x[t] -> x0(e^(t z2) z1 - e^(t z1) z2)/(z1 - z2) - -- + v0(e^(t z1) - e^(t z2))/(z1 - z2) - -- v[t] -> x0(z1 z2(-e^(t z1) + e^(t z2)))/(z1 - z2) - -- + v0(z1 e^(t z1) - z2 e^(t z2))/(z1 - z2) - - local scaledTime = time * speed - local alpha = math.sqrt(damping^2 - 1) - local scaledInvAlpha = -0.5 / alpha - local z1 = -alpha - damping - local z2 = 1 / z1 - local expZ1 = math.exp(scaledTime * z1) - local expZ2 = math.exp(scaledTime * z2) - - posPos = (expZ2*z1 - expZ1*z2) * scaledInvAlpha - posVel = (expZ1 - expZ2) * scaledInvAlpha / speed - velPos = (expZ2 - expZ1) * scaledInvAlpha * speed - velVel = (expZ1*z1 - expZ2*z2) * scaledInvAlpha - - elseif damping == 1 then - -- critically damped spring - -- x[t] -> x0(e^-t?)(1+t?) + v0(e^-t?)t - -- v[t] -> x0(t ?^2)(-e^-t?) + v0(1 - t?)(e^-t?) - - local scaledTime = time * speed - local expTerm = math.exp(-scaledTime) - - posPos = expTerm * (1 + scaledTime) - posVel = expTerm * time - velPos = expTerm * (-scaledTime*speed) - velVel = expTerm * (1 - scaledTime) - - else - -- underdamped spring - -- factored out of the solutions to the characteristic equation: - -- a = Sqrt[1 - ?^2] - -- x[t] -> x0(e^-t??)(a Cos[ta] + ?? Sin[ta])/a - -- + v0(e^-t??)(Sin[ta])/a - -- v[t] -> x0(-e^-t??)(a^2 + ?^2 ?^2)(Sin[ta])/a - -- + v0(e^-t??)(a Cos[ta] - ?? Sin[ta])/a - - local scaledTime = time * speed - local alpha = math.sqrt(1 - damping^2) - local invAlpha = 1 / alpha - local alphaTime = alpha * scaledTime - local expTerm = math.exp(-scaledTime*damping) - local sinTerm = expTerm * math.sin(alphaTime) - local cosTerm = expTerm * math.cos(alphaTime) - local sinInvAlpha = sinTerm*invAlpha - local sinInvAlphaDamp = sinInvAlpha*damping - - posPos = sinInvAlphaDamp + cosTerm - posVel = sinInvAlpha - velPos = -(sinInvAlphaDamp*damping + sinTerm*alpha) - velVel = cosTerm - sinInvAlphaDamp - end - - return posPos, posVel, velPos, velVel -end - -return springCoefficients - end, - Properties = { - Name = "springCoefficients" - }, - Reference = 11, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Manages batch updating of tween objects. -]] - -local RunService = game:GetService("RunService") - -local Package = script.Parent.Parent -local Types = require(Package.Types) -local lerpType = require(Package.Animation.lerpType) -local getTweenRatio = require(Package.Animation.getTweenRatio) -local updateAll = require(Package.Dependencies.updateAll) - -local TweenScheduler = {} - -type Set = {[T]: any} -type Tween = Types.Tween - -local WEAK_KEYS_METATABLE = {__mode = "k"} - --- all the tweens currently being updated -local allTweens: Set = {} -setmetatable(allTweens, WEAK_KEYS_METATABLE) - ---[[ - Adds a Tween to be updated every render step. -]] -function TweenScheduler.add(tween: Tween) - allTweens[tween] = true -end - ---[[ - Removes a Tween from the scheduler. -]] -function TweenScheduler.remove(tween: Tween) - allTweens[tween] = nil -end - ---[[ - Updates all Tween objects. -]] -local function updateAllTweens() - local now = os.clock() - -- FIXME: Typed Luau doesn't understand this loop yet - for tween: Tween in pairs(allTweens :: any) do - local currentTime = now - tween._currentTweenStartTime - - if currentTime > tween._currentTweenDuration then - if tween._currentTweenInfo.Reverses then - tween._currentValue = tween._prevValue - else - tween._currentValue = tween._nextValue - end - tween._currentlyAnimating = false - updateAll(tween) - TweenScheduler.remove(tween) - else - local ratio = getTweenRatio(tween._currentTweenInfo, currentTime) - local currentValue = lerpType(tween._prevValue, tween._nextValue, ratio) - tween._currentValue = currentValue - tween._currentlyAnimating = true - updateAll(tween) - end - end -end - -RunService:BindToRenderStep( - "__FusionTweenScheduler", - Enum.RenderPriority.First.Value, - updateAllTweens -) - -return TweenScheduler end, - Properties = { - Name = "TweenScheduler" - }, - Reference = 7, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Given a `tweenInfo` and `currentTime`, returns a ratio which can be used to - tween between two values over time. -]] - -local TweenService = game:GetService("TweenService") - -local function getTweenRatio(tweenInfo: TweenInfo, currentTime: number): number - local delay = tweenInfo.DelayTime - local duration = tweenInfo.Time - local reverses = tweenInfo.Reverses - local numCycles = 1 + tweenInfo.RepeatCount - local easeStyle = tweenInfo.EasingStyle - local easeDirection = tweenInfo.EasingDirection - - local cycleDuration = delay + duration - if reverses then - cycleDuration += duration - end - - if currentTime >= cycleDuration * numCycles then - return 1 - end - - local cycleTime = currentTime % cycleDuration - - if cycleTime <= delay then - return 0 - end - - local tweenProgress = (cycleTime - delay) / duration - if tweenProgress > 1 then - tweenProgress = 2 - tweenProgress - end - - local ratio = TweenService:GetValue(tweenProgress, easeStyle, easeDirection) - return ratio -end - -return getTweenRatio end, - Properties = { - Name = "getTweenRatio" - }, - Reference = 8, - ClassName = "ModuleScript" - } - }, - Properties = { - Name = "Animation" - }, - Reference = 3, - ClassName = "Folder" - }, - { - Children = { - { - Closure = function() --!strict - ---[[ - Provides functions for converting Color3s into Oklab space, for more - perceptually uniform colour blending. - - See: https://bottosson.github.io/posts/oklab/ -]] - -local Oklab = {} - --- Converts a Color3 in RGB space to a Vector3 in Oklab space. -function Oklab.to(rgb: Color3): Vector3 - local l = rgb.R * 0.4122214708 + rgb.G * 0.5363325363 + rgb.B * 0.0514459929 - local m = rgb.R * 0.2119034982 + rgb.G * 0.6806995451 + rgb.B * 0.1073969566 - local s = rgb.R * 0.0883024619 + rgb.G * 0.2817188376 + rgb.B * 0.6299787005 - - local lRoot = l ^ (1/3) - local mRoot = m ^ (1/3) - local sRoot = s ^ (1/3) - - return Vector3.new( - lRoot * 0.2104542553 + mRoot * 0.7936177850 - sRoot * 0.0040720468, - lRoot * 1.9779984951 - mRoot * 2.4285922050 + sRoot * 0.4505937099, - lRoot * 0.0259040371 + mRoot * 0.7827717662 - sRoot * 0.8086757660 - ) -end - --- Converts a Vector3 in CIELAB space to a Color3 in RGB space. --- The Color3 will be clamped by default unless specified otherwise. -function Oklab.from(lab: Vector3, unclamped: boolean?): Color3 - local lRoot = lab.X + lab.Y * 0.3963377774 + lab.Z * 0.2158037573 - local mRoot = lab.X - lab.Y * 0.1055613458 - lab.Z * 0.0638541728 - local sRoot = lab.X - lab.Y * 0.0894841775 - lab.Z * 1.2914855480 - - local l = lRoot ^ 3 - local m = mRoot ^ 3 - local s = sRoot ^ 3 - - local red = l * 4.0767416621 - m * 3.3077115913 + s * 0.2309699292 - local green = l * -1.2684380046 + m * 2.6097574011 - s * 0.3413193965 - local blue = l * -0.0041960863 - m * 0.7034186147 + s * 1.7076147010 - - if not unclamped then - red = math.clamp(red, 0, 1) - green = math.clamp(green, 0, 1) - blue = math.clamp(blue, 0, 1) - end - - return Color3.new(red, green, blue) -end - -return Oklab - end, - Properties = { - Name = "Oklab" - }, - Reference = 14, - ClassName = "ModuleScript" - } - }, - Properties = { - Name = "Colour" - }, - Reference = 13, - ClassName = "Folder" - }, - { - Children = { - { - Closure = function() --!strict - ---[[ - A special key for property tables, which stores a reference to the instance - in a user-provided Value object. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local logError = require(Package.Logging.logError) -local xtypeof = require(Package.Utility.xtypeof) - -local Ref = {} -Ref.type = "SpecialKey" -Ref.kind = "Ref" -Ref.stage = "observer" - -function Ref:apply(refState: any, applyTo: Instance, cleanupTasks: {PubTypes.Task}) - if xtypeof(refState) ~= "State" or refState.kind ~= "Value" then - logError("invalidRefType") - else - refState:set(applyTo) - table.insert(cleanupTasks, function() - refState:set(nil) - end) - end -end - -return Ref end, - Properties = { - Name = "Ref" - }, - Reference = 29, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Stores 'sensible default' properties to be applied to instances created by - the New function. -]] - -return { - ScreenGui = { - ResetOnSpawn = false, - ZIndexBehavior = Enum.ZIndexBehavior.Sibling - }, - - BillboardGui = { - ResetOnSpawn = false, - ZIndexBehavior = Enum.ZIndexBehavior.Sibling - }, - - SurfaceGui = { - ResetOnSpawn = false, - ZIndexBehavior = Enum.ZIndexBehavior.Sibling, - - SizingMode = Enum.SurfaceGuiSizingMode.PixelsPerStud, - PixelsPerStud = 50 - }, - - Frame = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0 - }, - - ScrollingFrame = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - - ScrollBarImageColor3 = Color3.new(0, 0, 0) - }, - - TextLabel = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - - Font = Enum.Font.SourceSans, - Text = "", - TextColor3 = Color3.new(0, 0, 0), - TextSize = 14 - }, - - TextButton = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - - AutoButtonColor = false, - - Font = Enum.Font.SourceSans, - Text = "", - TextColor3 = Color3.new(0, 0, 0), - TextSize = 14 - }, - - TextBox = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - - ClearTextOnFocus = false, - - Font = Enum.Font.SourceSans, - Text = "", - TextColor3 = Color3.new(0, 0, 0), - TextSize = 14 - }, - - ImageLabel = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0 - }, - - ImageButton = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - - AutoButtonColor = false - }, - - ViewportFrame = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0 - }, - - VideoFrame = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0 - }, - - CanvasGroup = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0 - } -} - end, - Properties = { - Name = "defaultProps" - }, - Reference = 31, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Constructs special keys for property tables which connect property change - listeners to an instance. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local logError = require(Package.Logging.logError) - -local function OnChange(propertyName: string): PubTypes.SpecialKey - local changeKey = {} - changeKey.type = "SpecialKey" - changeKey.kind = "OnChange" - changeKey.stage = "observer" - - function changeKey:apply(callback: any, applyTo: Instance, cleanupTasks: {PubTypes.Task}) - local ok, event = pcall(applyTo.GetPropertyChangedSignal, applyTo, propertyName) - if not ok then - logError("cannotConnectChange", nil, applyTo.ClassName, propertyName) - elseif typeof(callback) ~= "function" then - logError("invalidChangeHandler", nil, propertyName) - else - table.insert(cleanupTasks, event:Connect(function() - callback((applyTo :: any)[propertyName]) - end)) - end - end - - return changeKey -end - -return OnChange end, - Properties = { - Name = "OnChange" - }, - Reference = 26, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Constructs special keys for property tables which connect event listeners to - an instance. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local logError = require(Package.Logging.logError) - -local function getProperty_unsafe(instance: Instance, property: string) - return (instance :: any)[property] -end - -local function OnEvent(eventName: string): PubTypes.SpecialKey - local eventKey = {} - eventKey.type = "SpecialKey" - eventKey.kind = "OnEvent" - eventKey.stage = "observer" - - function eventKey:apply(callback: any, applyTo: Instance, cleanupTasks: {PubTypes.Task}) - local ok, event = pcall(getProperty_unsafe, applyTo, eventName) - if not ok or typeof(event) ~= "RBXScriptSignal" then - logError("cannotConnectEvent", nil, applyTo.ClassName, eventName) - elseif typeof(callback) ~= "function" then - logError("invalidEventHandler", nil, eventName) - else - table.insert(cleanupTasks, event:Connect(callback)) - end - end - - return eventKey -end - -return OnEvent end, - Properties = { - Name = "OnEvent" - }, - Reference = 27, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Processes and returns an existing instance, with options for setting - properties, event handlers and other attributes on the instance. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local applyInstanceProps = require(Package.Instances.applyInstanceProps) - -local function Hydrate(target: Instance) - return function(props: PubTypes.PropertyTable): Instance - applyInstanceProps(props, target) - return target - end -end - -return Hydrate end, - Properties = { - Name = "Hydrate" - }, - Reference = 24, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - A special key for property tables, which adds user-specified tasks to be run - when the instance is destroyed. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) - -local Cleanup = {} -Cleanup.type = "SpecialKey" -Cleanup.kind = "Cleanup" -Cleanup.stage = "observer" - -function Cleanup:apply(userTask: any, applyTo: Instance, cleanupTasks: {PubTypes.Task}) - table.insert(cleanupTasks, userTask) -end - -return Cleanup end, - Properties = { - Name = "Cleanup" - }, - Reference = 23, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Applies a table of properties to an instance, including binding to any - given state objects and applying any special keys. - - No strong reference is kept by default - special keys should take care not - to accidentally hold strong references to instances forever. - - If a key is used twice, an error will be thrown. This is done to avoid - double assignments or double bindings. However, some special keys may want - to enable such assignments - in which case unique keys should be used for - each occurence. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local cleanup = require(Package.Utility.cleanup) -local xtypeof = require(Package.Utility.xtypeof) -local logError = require(Package.Logging.logError) -local Observer = require(Package.State.Observer) - -local function setProperty_unsafe(instance: Instance, property: string, value: any) - (instance :: any)[property] = value -end - -local function testPropertyAssignable(instance: Instance, property: string) - (instance :: any)[property] = (instance :: any)[property] -end - -local function setProperty(instance: Instance, property: string, value: any) - if not pcall(setProperty_unsafe, instance, property, value) then - if not pcall(testPropertyAssignable, instance, property) then - if instance == nil then - -- reference has been lost - logError("setPropertyNilRef", nil, property, tostring(value)) - else - -- property is not assignable - logError("cannotAssignProperty", nil, instance.ClassName, property) - end - else - -- property is assignable, but this specific assignment failed - -- this typically implies the wrong type was received - local givenType = typeof(value) - local expectedType = typeof((instance :: any)[property]) - logError("invalidPropertyType", nil, instance.ClassName, property, expectedType, givenType) - end - end -end - -local function bindProperty(instance: Instance, property: string, value: PubTypes.CanBeState, cleanupTasks: {PubTypes.Task}) - if xtypeof(value) == "State" then - -- value is a state object - assign and observe for changes - local willUpdate = false - local function updateLater() - if not willUpdate then - willUpdate = true - task.defer(function() - willUpdate = false - setProperty(instance, property, value:get(false)) - end) - end - end - - setProperty(instance, property, value:get(false)) - table.insert(cleanupTasks, Observer(value :: any):onChange(updateLater)) - else - -- value is a constant - assign once only - setProperty(instance, property, value) - end -end - -local function applyInstanceProps(props: PubTypes.PropertyTable, applyTo: Instance) - local specialKeys = { - self = {} :: {[PubTypes.SpecialKey]: any}, - descendants = {} :: {[PubTypes.SpecialKey]: any}, - ancestor = {} :: {[PubTypes.SpecialKey]: any}, - observer = {} :: {[PubTypes.SpecialKey]: any} - } - local cleanupTasks = {} - - for key, value in pairs(props) do - local keyType = xtypeof(key) - - if keyType == "string" then - if key ~= "Parent" then - bindProperty(applyTo, key :: string, value, cleanupTasks) - end - elseif keyType == "SpecialKey" then - local stage = (key :: PubTypes.SpecialKey).stage - local keys = specialKeys[stage] - if keys == nil then - logError("unrecognisedPropertyStage", nil, stage) - else - keys[key] = value - end - else - -- we don't recognise what this key is supposed to be - logError("unrecognisedPropertyKey", nil, xtypeof(key)) - end - end - - for key, value in pairs(specialKeys.self) do - key:apply(value, applyTo, cleanupTasks) - end - for key, value in pairs(specialKeys.descendants) do - key:apply(value, applyTo, cleanupTasks) - end - - if props.Parent ~= nil then - bindProperty(applyTo, "Parent", props.Parent, cleanupTasks) - end - - for key, value in pairs(specialKeys.ancestor) do - key:apply(value, applyTo, cleanupTasks) - end - for key, value in pairs(specialKeys.observer) do - key:apply(value, applyTo, cleanupTasks) - end - - applyTo.Destroying:Connect(function() - cleanup(cleanupTasks) - end) -end - -return applyInstanceProps end, - Properties = { - Name = "applyInstanceProps" - }, - Reference = 30, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - Constructs and returns a new instance, with options for setting properties, - event handlers and other attributes on the instance right away. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local defaultProps = require(Package.Instances.defaultProps) -local applyInstanceProps = require(Package.Instances.applyInstanceProps) -local logError= require(Package.Logging.logError) - -local function New(className: string) - return function(props: PubTypes.PropertyTable): Instance - local ok, instance = pcall(Instance.new, className) - - if not ok then - logError("cannotCreateClass", nil, className) - end - - local classDefaults = defaultProps[className] - if classDefaults ~= nil then - for defaultProp, defaultValue in pairs(classDefaults) do - instance[defaultProp] = defaultValue - end - end - - applyInstanceProps(props, instance) - - return instance - end -end - -return New end, - Properties = { - Name = "New" - }, - Reference = 25, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - A special key for property tables, which allows users to extract values from - an instance into an automatically-updated Value object. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local logError = require(Package.Logging.logError) -local xtypeof = require(Package.Utility.xtypeof) - -local function Out(propertyName: string): PubTypes.SpecialKey - local outKey = {} - outKey.type = "SpecialKey" - outKey.kind = "Out" - outKey.stage = "observer" - - function outKey:apply(outState: any, applyTo: Instance, cleanupTasks: { PubTypes.Task }) - local ok, event = pcall(applyTo.GetPropertyChangedSignal, applyTo, propertyName) - if not ok then - logError("invalidOutProperty", nil, applyTo.ClassName, propertyName) - elseif xtypeof(outState) ~= "State" or outState.kind ~= "Value" then - logError("invalidOutType") - else - outState:set((applyTo :: any)[propertyName]) - table.insert( - cleanupTasks, - event:Connect(function() - outState:set((applyTo :: any)[propertyName]) - end) - ) - table.insert(cleanupTasks, function() - outState:set(nil) - end) - end - end - - return outKey -end - -return Out - end, - Properties = { - Name = "Out" - }, - Reference = 28, - ClassName = "ModuleScript" - }, - { - Closure = function() --!strict - ---[[ - A special key for property tables, which parents any given descendants into - an instance. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local logWarn = require(Package.Logging.logWarn) -local Observer = require(Package.State.Observer) -local xtypeof = require(Package.Utility.xtypeof) - -type Set = {[T]: boolean} - --- Experimental flag: name children based on the key used in the [Children] table -local EXPERIMENTAL_AUTO_NAMING = false - -local Children = {} -Children.type = "SpecialKey" -Children.kind = "Children" -Children.stage = "descendants" - -function Children:apply(propValue: any, applyTo: Instance, cleanupTasks: {PubTypes.Task}) - local newParented: Set = {} - local oldParented: Set = {} - - -- save disconnection functions for state object observers - local newDisconnects: {[PubTypes.StateObject]: () -> ()} = {} - local oldDisconnects: {[PubTypes.StateObject]: () -> ()} = {} - - local updateQueued = false - local queueUpdate: () -> () - - -- Rescans this key's value to find new instances to parent and state objects - -- to observe for changes; then unparents instances no longer found and - -- disconnects observers for state objects no longer present. - local function updateChildren() - if not updateQueued then - return -- this update may have been canceled by destruction, etc. - end - updateQueued = false - - oldParented, newParented = newParented, oldParented - oldDisconnects, newDisconnects = newDisconnects, oldDisconnects - table.clear(newParented) - table.clear(newDisconnects) - - local function processChild(child: any, autoName: string?) - local kind = xtypeof(child) - - if kind == "Instance" then - -- case 1; single instance - - newParented[child] = true - if oldParented[child] == nil then - -- wasn't previously present - - -- TODO: check for ancestry conflicts here - child.Parent = applyTo - else - -- previously here; we want to reuse, so remove from old - -- set so we don't encounter it during unparenting - oldParented[child] = nil - end - - if EXPERIMENTAL_AUTO_NAMING and autoName ~= nil then - child.Name = autoName - end - - elseif kind == "State" then - -- case 2; state object - - local value = child:get(false) - -- allow nil to represent the absence of a child - if value ~= nil then - processChild(value, autoName) - end - - local disconnect = oldDisconnects[child] - if disconnect == nil then - -- wasn't previously present - disconnect = Observer(child):onChange(queueUpdate) - else - -- previously here; we want to reuse, so remove from old - -- set so we don't encounter it during unparenting - oldDisconnects[child] = nil - end - - newDisconnects[child] = disconnect - - elseif kind == "table" then - -- case 3; table of objects - - for key, subChild in pairs(child) do - local keyType = typeof(key) - local subAutoName: string? = nil - - if keyType == "string" then - subAutoName = key - elseif keyType == "number" and autoName ~= nil then - subAutoName = autoName .. "_" .. key - end - - processChild(subChild, subAutoName) - end - - else - logWarn("unrecognisedChildType", kind) - end - end - - if propValue ~= nil then - -- `propValue` is set to nil on cleanup, so we don't process children - -- in that case - processChild(propValue) - end - - -- unparent any children that are no longer present - for oldInstance in pairs(oldParented) do - oldInstance.Parent = nil - end - - -- disconnect observers which weren't reused - for oldState, disconnect in pairs(oldDisconnects) do - disconnect() - end - end - - queueUpdate = function() - if not updateQueued then - updateQueued = true - task.defer(updateChildren) - end - end - - table.insert(cleanupTasks, function() - propValue = nil - updateQueued = true - updateChildren() - end) - - -- perform initial child parenting - updateQueued = true - updateChildren() -end - -return Children :: PubTypes.SpecialKey end, - Properties = { - Name = "Children" - }, - Reference = 22, - ClassName = "ModuleScript" - } - }, - Properties = { - Name = "Instances" - }, - Reference = 21, - ClassName = "Folder" - }, - { - Closure = function() --!strict - ---[[ - Stores common type information used internally. - - These types may be used internally so Fusion code can type-check, but - should never be exposed to public users, as these definitions are fair game - for breaking changes. -]] - -local Package = script.Parent -local PubTypes = require(Package.PubTypes) - -type Set = {[T]: any} - ---[[ - General use types -]] - --- A symbol that represents the absence of a value. -export type None = PubTypes.Symbol & { - -- name: "None" (add this when Luau supports singleton types) -} - --- Stores useful information about Luau errors. -export type Error = { - type: string, -- replace with "Error" when Luau supports singleton types - raw: string, - message: string, - trace: string -} - ---[[ - Specific reactive graph types -]] - --- A state object whose value can be set at any time by the user. -export type State = PubTypes.Value & { - _value: T -} - --- A state object whose value is derived from other objects using a callback. -export type Computed = PubTypes.Computed & { - _oldDependencySet: Set, - _callback: () -> T, - _value: T -} - --- A state object whose value is derived from other objects using a callback. -export type ForPairs = PubTypes.ForPairs & { - _oldDependencySet: Set, - _processor: (KI, VI) -> (KO, VO), - _destructor: (VO, M?) -> (), - _inputIsState: boolean, - _inputTable: PubTypes.CanBeState<{ [KI]: VI }>, - _oldInputTable: { [KI]: VI }, - _outputTable: { [KO]: VO }, - _oldOutputTable: { [KO]: VO }, - _keyIOMap: { [KI]: KO }, - _meta: { [KO]: M? }, - _keyData: { - [KI]: { - dependencySet: Set, - oldDependencySet: Set, - dependencyValues: { [PubTypes.Dependency]: any }, - }, - }, -} - --- A state object whose value is derived from other objects using a callback. -export type ForKeys = PubTypes.ForKeys & { - _oldDependencySet: Set, - _processor: (KI) -> (KO), - _destructor: (KO, M?) -> (), - _inputIsState: boolean, - _inputTable: PubTypes.CanBeState<{ [KI]: KO }>, - _oldInputTable: { [KI]: KO }, - _outputTable: { [KO]: any }, - _keyOIMap: { [KO]: KI }, - _meta: { [KO]: M? }, - _keyData: { - [KI]: { - dependencySet: Set, - oldDependencySet: Set, - dependencyValues: { [PubTypes.Dependency]: any }, - }, - }, -} - --- A state object whose value is derived from other objects using a callback. -export type ForValues = PubTypes.ForValues & { - _oldDependencySet: Set, - _processor: (VI) -> (VO), - _destructor: (VO, M?) -> (), - _inputIsState: boolean, - _inputTable: PubTypes.CanBeState<{ [VI]: VO }>, - _outputTable: { [any]: VI }, - _valueCache: { [VO]: any }, - _oldValueCache: { [VO]: any }, - _meta: { [VO]: M? }, - _valueData: { - [VI]: { - dependencySet: Set, - oldDependencySet: Set, - dependencyValues: { [PubTypes.Dependency]: any }, - }, - }, -} - --- A state object which follows another state object using tweens. -export type Tween = PubTypes.Tween & { - _goalState: State, - _tweenInfo: TweenInfo, - _prevValue: T, - _nextValue: T, - _currentValue: T, - _currentTweenInfo: TweenInfo, - _currentTweenDuration: number, - _currentTweenStartTime: number, - _currentlyAnimating: boolean -} - --- A state object which follows another state object using spring simulation. -export type Spring = PubTypes.Spring & { - _speed: PubTypes.CanBeState, - _speedIsState: boolean, - _lastSpeed: number, - _damping: PubTypes.CanBeState, - _dampingIsState: boolean, - _lastDamping: number, - _goalState: State, - _goalValue: T, - _currentType: string, - _currentValue: T, - _springPositions: {number}, - _springGoals: {number}, - _springVelocities: {number} -} - --- An object which can listen for updates on another state object. -export type Observer = PubTypes.Observer & { - _changeListeners: Set<() -> ()>, - _numChangeListeners: number -} - -return nil end, - Properties = { - Name = "Types" - }, - Reference = 47, - ClassName = "ModuleScript" - } - } - } - } - } -} - -do local a,b='0.1.0',game:GetService'RunService'local c,d,e,f,g,h,i,j,k=b:IsServer(),b:IsClient(),getfenv(0),{},{},{},{},{},{}local function l(m)local n,o=pcall(Instance.new,m.ClassName)if not n then return end f[m.Reference]=o if m.Closure then h[o]=m.Closure if o:IsA'BaseScript'then table.insert(j,o)end end if m.Properties then for p,q in next,m.Properties do pcall(function()o[p]=q end)end end if m.RefProperties then for p,q in next,m.RefProperties do table.insert(g,{InstanceObject=o,Property=p,ReferenceId=q})end end if m.Attributes then for p,q in next,m.Attributes do pcall(o.SetAttribute,o,p,q)end end if m.Children then for p,q in next,m.Children do local r=l(q)if r then r.Parent=o end end end return o end local m={}do for n,o in next,ModuleRoot do table.insert(m,l(o))end end local function n(o)local p=i[o]if o.ClassName=='ModuleScript'and p then return unpack(p)end local q=h[o]if not q then return end do local r local s={['maui']=table.freeze{Version=a,GetScript=function()return script end,GetShared=function()return k end},['script']=o,['require']=function(s,...)if s and s.ClassName=='ModuleScript'and h[s]then return n(s)end return require(s,...)end,['getfenv']=function(s,...)if type(s)=='number'and s>=0 then if s==0 then return r else local t,u=pcall(getfenv,s)if t and u==e then return r end end end return getfenv(s,...)end,['setfenv']=function(s,t,...)if type(s)=='number'and s>=0 then if s==0 then return setfenv(r,t)else local u,v=pcall(getfenv,s)if u and v==e then return setfenv(r,t)end end end return setfenv(s,t,...)end}r=setmetatable({},{__index=function(t,u)local v=rawget(r,u)if v~=nil then return v end local w=s[u]if w~=nil then return w end return e[u]end,__newindex=function(t,u,v)rawset(r,u,v)end})setfenv(q,r)end local r=coroutine.wrap(q)if o:IsA'BaseScript'then local s,t=if o.Enabled then task.defer(r)else nil t=o:GetPropertyChangedSignal'Enabled':Connect(function(u)t:Disconnect()if u==true then n(o)else pcall(task.cancel,s)end end)return else local s={r()}i[o]=s return unpack(s)end end for o,p in next,g do pcall(function()p.InstanceObject[p.Property]=f[p.ReferenceId]end)end for q,r in next,j do if(c and r.ClassName=='Script')or(d and r.ClassName=='LocalScript')then n(r)end end end diff --git a/examples/Maui_2023-01-06_13-25-04.lua b/examples/Maui_2023-01-06_13-25-04.lua deleted file mode 100644 index 4de0a5d..0000000 --- a/examples/Maui_2023-01-06_13-25-04.lua +++ /dev/null @@ -1,5641 +0,0 @@ --- This script was automatically @generated by Maui, it is not intended for manual editing. -local ModuleRoot={{ClassName="Script",Closure=function() --[[ - Maui - Roblox Studio Plugin for Packing Modules as Executable Luau Scripts - Licensed Under the LGPLv3 | Copyright (c) 2022-2023 Latte Softworks - https://github.com/latte-soft/maui - - File: /src/init.server.lua - Desc: Creates the plugin widget, and connects to the front-end app -]] - -assert(plugin, "Maui: Not running as a plugin, did you place this somewhere else by mistake?", 0) - -local Tarmac = script.Tarmac -- Tarmac generated assets (See `/tarmac.toml`) -local Version = script.Version.Value -- /version.txt - -local App = require(script.App) -- Front-end interface app ---local PluginSettings = require(script.PluginSettings) -- Global plugin config module -local Assets = require(Tarmac.Assets) -- Tarmac generated assets (See `/tarmac.toml`) - -local NameWithVersion = "Maui " .. Version -local StudioTheme = settings().Studio.Theme - --- Initialize plugin widget -local PluginWidget do - -- Proper icon color for the Studio theme, due to it being monochrome - local WidgetToggleIcon = if StudioTheme.Name == "Light" then - Assets["MauiLogo-LightMode"] - else Assets["MauiLogo-DarkMode"] -- Then it's probably dark mode; default - - -- Create widget BEFORE the visible front-end toolbar - PluginWidget = plugin:CreateDockWidgetPluginGui( - "Maui", -- ID - DockWidgetPluginGuiInfo.new( - Enum.InitialDockState.Float, -- Floating widget by default - false, -- NOT always be initially enabled - false, -- DON'T override any saved enabled state - 250, -- Initial X size - 325, -- Initial Y size - 200, -- MINIMUM X size - 200 -- Minimum Y size - ) - ) - - -- Name & title it, it isn't assigned by default.. - PluginWidget.Name = NameWithVersion - PluginWidget.Title = NameWithVersion - PluginWidget.ZIndexBehavior = Enum.ZIndexBehavior.Sibling - - local Toolbar = plugin:CreateToolbar(NameWithVersion) - local WidgetToggle = Toolbar:CreateButton( - "MauiToggle", -- ID - "Open the Maui Widget", -- Tooltip; changes to "Open"/"Close" on toggle - WidgetToggleIcon, -- Icon asset URL ("rbxasset://123456") - "Maui" - ) - - -- Not true by default! - WidgetToggle.ClickableWhenViewportHidden = true - - -- Fix enabled highlight on click - WidgetToggle.Click:Connect(function() - PluginWidget.Enabled = not PluginWidget.Enabled - WidgetToggle:SetActive(PluginWidget.Enabled) -- Set the selected highlight to on/off - end) - - -- When "X" is clicked on the plugins Qt widget - PluginWidget:BindToClose(function() - PluginWidget.Enabled = false - WidgetToggle:SetActive(false) -- Like before, set the button to not active! - end) -end - --- Initialize the real app with the plugin widget we've created -App(plugin, PluginWidget) - end,Properties={Name="Maui"},Reference=1,Children={{Closure=function() --[[ - Maui - Roblox Studio Plugin for Packing Modules as Executable Luau Scripts - Licensed Under the LGPLv3 | Copyright (c) 2022-2023 Latte Softworks - https://github.com/latte-soft/maui - - File: /src/PluginSettings.lua - Desc: Handles fairly basic (but convenient!) funcs for getting/setting internal - plugin settings and configuration -]] - -local CONFIG = { - Key = "MauiSettings", - DefaultSettings = { - ConsoleAutoscroll = false - } -} - --- We need to pass the plugin object THROUGH the module.. yep -return function(plugin) - -- Setup default settings (if just installed or whatever) - if not plugin:GetSetting(CONFIG.Key) then - plugin:SetSetting(CONFIG.Key, CONFIG.DefaultSettings) - end - - local function Get(settingName) - local RealSettings = plugin:GetSetting(CONFIG.Key) - - -- Index the tbl with a string recursively to get the value - local FoundValue = RealSettings do - for _, KeyName in string.split(settingName, ".") do - FoundValue = FoundValue[KeyName] - end - end - - return FoundValue - end - - local function Set(settingName, newValue) - -- Get the real settings first, we can't set the value with the settingName without - -- knowing it - local RealSettings = plugin:GetSetting(CONFIG.Key) - - -- Set the desired key with newValue - local Parent, Key = RealSettings, nil do - -- Need to pre-define here so we can check if there's a value after the current index - local SplitPath = string.split(settingName, ".") - - for Index, KeyName in SplitPath do - Key = KeyName - - -- If there's a key after the current index still, we still want to add the current - -- index as the parent - if next(SplitPath, Index) then - Parent = Parent[KeyName] - end - end - end - - Parent[Key] = newValue - - -- And we're done! - plugin:SetSetting(CONFIG.Key, RealSettings) -- We've already modifed `RealSettings` with our new val! - end - - return { - Get = Get, - Set = Set - } -end - end,Properties={Name="PluginSettings"},Reference=13,ClassName="ModuleScript"},{ClassName="ModuleScript",Closure=function() --[[ - Maui - Roblox Studio Plugin for Packing Modules as Executable Luau Scripts - Licensed Under the LGPLv3 | Copyright (c) 2022-2023 Latte Softworks - https://github.com/latte-soft/maui - - File: /src/Codegen/init.lua - Desc: Handles the actual codegen assembly of a script, using instance props and code - templates respectively -]] - -local HttpService = game:GetService("HttpService") - -local Root = script.Parent -local Packages = Root.Packages - -local LuaEncode = require(Packages.LuaEncode) -local GetInstanceProperties = require(script.GetInstanceProperties) -local GetDefaultInstanceProperty = require(script.GetDefaultInstanceProperty) - -local Version = Root.Version.Value - --- Get the LoadModule code (minified) -local LoadModuleCode = { - Normal = script.LoadModuleCode["LoadModule.lua"].Value, - Minified = script.LoadModuleCode["LoadModule.min.lua"].Value -} - --- Codegen format templates -local CodegenFormatTemplates = { - Normal = [[ --- This script was automatically @generated by Maui, it is not intended for manual editing. - -local ModuleRoot = ${SerializedModuleRoot} - -${LoadModuleCode}]], - Minified = [[ --- This script was automatically @generated by Maui, it is not intended for manual editing. -local ModuleRoot=${SerializedModuleRoot} ${LoadModuleCode}]] -} - --- VERY simple function to interpolate a certain string with a set of replacements, following --- Luau's basic variable syntax rules -local function ReplaceString(inputString, replacements) - return string.gsub(inputString, "${([A-Za-z_][A-Za-z0-9_]*)}", replacements) -end - --- Basic functions to get if we'll actually be able to read a(n) instance/selection, because --- if it's parented to some locked-down service or whatevrr, we won't even be able to index basic --- properties like even the name! -local function CanReadInstance(object, property) - local CanRead = pcall(function() - return object[property or "Name"] - end) - - if CanRead then - return true - else - return false - end -end - --- Build script directly from a selection, passes through the console logging func --- This returns the built script, IF it succeeded -local function BuildFromSelection(selection, options, log) - local MinifyTable = options.Output.MinifyTable - local UseMinifiedLoader = options.Output.UseMinifiedLoader - - -- Get instance properties from API dump (uses http) - log("Retrieving all instance properties from API dump..", 2) - local InstanceProperties, WasCached = GetInstanceProperties() - - assert(InstanceProperties, "Failed to get API dump, did you forget to allow HTTP requests on the plugin?") - - if WasCached then - log("Using previously cached API dump", 2) - end - - -- Now that we have the up-to-date API dump, we'll track how long it took to build for later - local StartTime = os.clock() - - -- Initialize the output module root, then recursively walk through the real instance tree - local ModuleRoot = {} do - -- When we initially scrape the instance tree, we'll apply all references and their children here - local ScrapedInstanceTree = {} -- [RealRef] = { ...} - -- We need to keep track of ALL objects and create fake ref ids as we go - local ReferenceIds = {} -- [[FakeRefId] = RealRef - - -- Recursive function to actually walk through the real instance tree, and assign refs - local function ScrapeInstanceChildren(instanceObject) - if not CanReadInstance(instanceObject) then - return {} - end - - -- Add to ref ids - table.insert(ReferenceIds, instanceObject) - - local ScrapedChildren = {} do - for _, Child in instanceObject:GetChildren() do - ScrapedChildren[Child] = ScrapeInstanceChildren(Child) - end - end - - return ScrapedChildren - end - - -- Initialize the scraped instance tree and assign all refs from root - log("Scraping instance tree..", 2) - for _, InstanceObject in selection do - ScrapedInstanceTree[InstanceObject] = ScrapeInstanceChildren(InstanceObject) - end - - -- Now, we'll recursively create the fake module root! - local function CreateObjectTree(instanceObject, children) - local ClassName = instanceObject.ClassName - local InstanceIsAScript = instanceObject:IsA("LuaSourceContainer") - - local ObjectTree = { - ClassName = ClassName, - Reference = table.find(ReferenceIds, instanceObject) - } - - if InstanceIsAScript then - local ScriptSource = instanceObject.Source - - -- We need to make sure the script compiles correctly, EVEN IF the script is disabled. - -- Since this serializes as a literal closure, we can't include syntax-incorrect code - -- at ALL, or the codegen could be incorrect! We aren't RUNNING the func, though - local LoadstringClosure = loadstring(ScriptSource) - - if LoadstringClosure then - -- We're using `FunctionsReturnRaw` on LuaEncode later, this will set the return - -- to the rew value, which is the script closure - ObjectTree.Closure = function() - return "function() " .. ScriptSource .. " end" - end - end - end - - -- A class may not *ALWAYS* have an API dump entry, so we're just being safe here - local ObjectProperties = InstanceProperties[ClassName] - if ObjectProperties then - for _, PropertyName in ObjectProperties do - -- Ignore BaseScript.Source (Because of script closure) and non-readable properties - if (InstanceIsAScript and PropertyName == "Source") or not CanReadInstance(instanceObject, PropertyName) then - continue - end - - local PropertyValue = instanceObject[PropertyName] - - if PropertyValue == GetDefaultInstanceProperty(ClassName, PropertyName) then - continue - end - - -- At this point, register the properties tbl in the object tree if it hasn't already been - if not ObjectTree["Properties"] then - ObjectTree.Properties = {} - end - - -- Handle any special circumstances like refs, or else just add the property to the - -- table directly - if typeof(PropertyValue) == "Instance" then -- Then it's a ref - -- We'll actually create a dedicated tbl (if it doesn't exist) for ref props - if not ObjectTree["RefProperties"] then - ObjectTree.RefProperties = {} - end - - local ReferenceId = table.find(ReferenceIds, instanceObject) - if ReferenceId then - ObjectTree.RefProperties[PropertyName] = ReferenceId - end - else - ObjectTree.Properties[PropertyName] = PropertyValue - end - end - end - - -- We meet again, attributes.. Luckily, LuaEncode takes care of this directly! - local ObjectAttributes = instanceObject:GetAttributes() - if next(ObjectAttributes) then - ObjectTree.Attributes = ObjectAttributes - end - - -- Recursively add children - if next(children) then - ObjectTree.Children = {} - - for Child, ChildrenOfChild in children do - table.insert(ObjectTree.Children, CreateObjectTree(Child, ChildrenOfChild)) - end - end - - return ObjectTree - end - - log("Creating encodable object tree..", 2) - for Object, Children in ScrapedInstanceTree do - table.insert(ModuleRoot, CreateObjectTree(Object, Children)) - end - end - - log("Encoding object tree..", 2) - - -- Let's track when LuaEncode starts - local LuaEncodeStartTime = os.clock() - - -- Serialize the module root, properties and all - local SerializedModuleRoot = LuaEncode(ModuleRoot, { - PrettyPrinting = if MinifyTable then false else true, - IndentCount = if MinifyTable then 0 else 4, - StackLimit = math.huge, -- I sure HOPE the user doesn't have a 500-object-deep instance tree, but we're not dealing with duplicates! - FunctionsReturnRaw = true -- For Script.Source function closures - }) - - log("(LuaEncode) Serialized object tree in " .. string.format("%.3f", os.clock() - LuaEncodeStartTime) .. " (seconds)", 3) - - -- `MinifyTable` is the significant output, if that's minified we'll use the minified formatting for codegen directly - local GeneratedScript = ReplaceString(if MinifyTable then CodegenFormatTemplates.Minified else CodegenFormatTemplates.Normal, { - SerializedModuleRoot = SerializedModuleRoot, - LoadModuleCode = ReplaceString(if UseMinifiedLoader then LoadModuleCode.Minified else LoadModuleCode.Normal, { - Version = Version - }), - }) - - -- Total end time for the whole build - log("Finished codegen! Total build time: " .. string.format("%.3f", os.clock() - StartTime) .. " (seconds)", 3) - - return GeneratedScript -end - -return { - CanReadInstance = CanReadInstance, - BuildFromSelection = BuildFromSelection -} - end,Properties={Name="Codegen"},Reference=4,Children={{Closure=function() --[[ - Maui - Roblox Studio Plugin for Packing Modules as Executable Luau Scripts - Licensed Under the LGPLv3 | Copyright (c) 2022-2023 Latte Softworks - https://github.com/latte-soft/maui - - File: /src/Codegen/InstanceProperties.lua - Desc: Gets the latest API dump from Max's repo via HttpService, then parses each - class entry to get the properties of each class, respecting all inheritance -]] - -local HttpService = game:GetService("HttpService") - -local CONFIG = { - ApiDumpUrl = "https://github.com/MaximumADHD/Roblox-Client-Tracker/raw/roblox/Mini-API-Dump.json", - HttpMaxRetryAttempts = 5, - HttpRetryDelay = 5, - CacheDelay = 60 * 15 -- 15 mins -} - --- Cache the current API dump for however long (`CONFIG.CacheDelay`) -local CurrentApiDump, ApiDumpLastFetched - -local function GetApiDump(_currentRetryCount) - -- Check if there's a cached API dump - if CurrentApiDump and ApiDumpLastFetched and tick() - ApiDumpLastFetched < CONFIG.CacheDelay then - return CurrentApiDump, true -- The `true` means it returned a cached dump - end - - -- For the HTTP retry delay sys - _currentRetryCount = _currentRetryCount or 0 - - if _currentRetryCount > CONFIG.HttpMaxRetryAttempts then - -- Signal back that it couldn't be found! - return nil, false - end - - local ResponseOk, ApiDumpResponse = pcall(HttpService.GetAsync, HttpService, CONFIG.ApiDumpUrl) - - if not ResponseOk then -- Then `ApiDumpResponse` is the error message - _currentRetryCount += 1 - - if _currentRetryCount >= CONFIG.HttpMaxRetryAttempts then - -- Signal back that it couldn't be found - return nil, false - else - warn(string.format( - "Maui: Failed to get API dump: HTTP Error (%d retries so far, maximum: %d)\nError: \"%s\"", - _currentRetryCount, - CONFIG.HttpMaxRetryAttempts, - ApiDumpResponse or "[No error message attached]" - )) - - -- Delay, run `GetApiDump` again, make sure to attach times - return task.delay( - CONFIG.HttpRetryDelay, - GetApiDump, - _currentRetryCount - ) - end - end - - local ApiDumpJson = HttpService:JSONDecode(ApiDumpResponse) - - -- Set the cached dump & current time - CurrentApiDump = ApiDumpJson - ApiDumpLastFetched = tick() - - return ApiDumpJson, false -end - -local function GetInstanceProperties() - local ApiDump = GetApiDump() - - if not ApiDump then - error("Maui: Failed to get API dump: No value returned", 0) - end - - -- Check API dump format version - if ApiDump.Version ~= 1 then - warn(string.format( - "Maui: Failed to get API Dump: Format version incorrect; expected %d, got %s\nYou may need to update Maui for the latest format support", - 1, - tostring(ApiDump.Version) -- JIC it's not actually a number, just get the raw val - )) - - return - end - - -- All instance properties will be placed here - local InstanceProperties = {} do - -- First, we'll get each class's individual properties, and later after we track - -- all inherited superclasses, we'll set them to `InstanceProperties` - local IndividualInstanceProperties = {} - - -- Track all inherited super-classes on classes for later - local InheritedClassBindings = {} -- [ClassName] = {...} - - -- Loop through all classes and set props in index order. This only get's a class's - -- OWN properties for now, inheritance will be handled next - for _, ClassObject in ApiDump.Classes do - local Properties = {} - - -- Assign inherited classes (again, for later!) - if ClassObject.Superclass ~= "<<>>" then - local InheritedClasses = {ClassObject.Superclass} - - local SuperClassInheritedClasses = InheritedClassBindings[ClassObject.Superclass] - if SuperClassInheritedClasses then - for _, ClassName in SuperClassInheritedClasses do - table.insert(InheritedClasses, ClassName) - end - end - - InheritedClassBindings[ClassObject.Name] = InheritedClasses - end - - -- Go through prop members of the current class now - for _, MemberObject in ClassObject.Members do - -- It isn't always a property, and `CanSave` and `CanLoad` need to be true - if MemberObject.MemberType == "Property" and MemberObject.Serialization.CanSave and MemberObject.Serialization.CanLoad then - table.insert(Properties, MemberObject.Name) - end - end - - IndividualInstanceProperties[ClassObject.Name] = Properties - end - - -- Now, we'll bind all inherited properties for classes - for ClassName, InheritedClasses in InheritedClassBindings do - -- Shallow-clone the known individual properties - local ClassInstanceProperties = table.clone(IndividualInstanceProperties[ClassName]) - - -- We now need to go through EVERY inherited class and get each property of those classes - for _, InheritedClass in InheritedClasses do - for _, PropertyName in IndividualInstanceProperties[InheritedClass] do - table.insert(ClassInstanceProperties, PropertyName) - end - end - - InstanceProperties[ClassName] = ClassInstanceProperties - end - end - - return InstanceProperties -end - -return GetInstanceProperties - end,Properties={Name="GetInstanceProperties"},Reference=6,ClassName="ModuleScript"},{Children={{Properties={Value="do local a,b='${Version}',game:GetService'RunService'local c,d,e,f,g,h,i,j,k=b:IsServer(),b:IsClient(),getfenv(0),{},{},{},{},{},{}local function l(m)local n,o=pcall(Instance.new,m.ClassName)if not n then return end f[m.Reference]=o if m.Closure then h[o]=m.Closure if o:IsA'BaseScript'then table.insert(j,o)end end if m.Properties then for p,q in next,m.Properties do pcall(function()o[p]=q end)end end if m.RefProperties then for p,q in next,m.RefProperties do table.insert(g,{InstanceObject=o,Property=p,ReferenceId=q})end end if m.Attributes then for p,q in next,m.Attributes do pcall(o.SetAttribute,o,p,q)end end if m.Children then for p,q in next,m.Children do local r=l(q)if r then r.Parent=o end end end return o end local m={}do for n,o in next,ModuleRoot do table.insert(m,l(o))end end local function n(o)local p=i[o]if o.ClassName=='ModuleScript'and p then return unpack(p)end local q=h[o]if not q then return end do local r local s={['maui']=table.freeze{Version=a,GetScript=function()return script end,GetShared=function()return k end},['script']=o,['require']=function(s,...)if s and s.ClassName=='ModuleScript'and h[s]then return n(s)end return require(s,...)end,['getfenv']=function(s,...)if type(s)=='number'and s>=0 then if s==0 then return r else local t,u=pcall(getfenv,s)if t and u==e then return r end end end return getfenv(s,...)end,['setfenv']=function(s,t,...)if type(s)=='number'and s>=0 then if s==0 then return setfenv(r,t)else local u,v=pcall(getfenv,s)if u and v==e then return setfenv(r,t)end end end return setfenv(s,t,...)end}r=setmetatable({},{__index=function(t,u)local v=rawget(r,u)if v~=nil then return v end local w=s[u]if w~=nil then return w end return e[u]end,__newindex=function(t,u,v)rawset(r,u,v)end})setfenv(q,r)end local r=coroutine.wrap(q)if o:IsA'BaseScript'then local s,t=if o.Enabled then task.defer(r)else nil t=o:GetPropertyChangedSignal'Enabled':Connect(function(u)t:Disconnect()if u==true then n(o)else pcall(task.cancel,s)end end)return else local s={r()}i[o]=s return unpack(s)end end for o,p in next,g do pcall(function()p.InstanceObject[p.Property]=f[p.ReferenceId]end)end for q,r in next,j do if(c and r.ClassName=='Script')or(d and r.ClassName=='LocalScript')then n(r)end end end",Name="LoadModule.min.lua"},Reference=9,ClassName="StringValue"},{Properties={Value="do\n local Version = \"${Version}\"\n\n local RunService = game:GetService(\"RunService\")\n local IsServer = RunService:IsServer()\n local IsClient = RunService:IsClient()\n\n local RealEnvironment = getfenv(0)\n\n local ReferenceBindings = {}\n local ReferencesToSet = {}\n\n local ScriptClosures = {}\n local StoredModuleValues = {}\n local ScriptsToRun = {}\n\n local SharedEnvironment = {}\n\n local function CreateInstanceFromObject(objectTree)\n local CreatedOk, CreatedInstance = pcall(Instance.new, objectTree.ClassName)\n if not CreatedOk then\n return\n end\n\n ReferenceBindings[objectTree.Reference] = CreatedInstance\n\n if objectTree[\"Closure\"] then\n ScriptClosures[CreatedInstance] = objectTree.Closure\n\n if CreatedInstance:IsA(\"BaseScript\") then\n table.insert(ScriptsToRun, CreatedInstance)\n end\n end\n\n if objectTree[\"Properties\"] then\n for Property, Value in next, objectTree.Properties do\n pcall(function()\n CreatedInstance[Property] = Value\n end)\n end\n end\n\n if objectTree[\"RefProperties\"] then\n for Property, ReferenceId in next, objectTree.RefProperties do\n table.insert(ReferencesToSet, {\n InstanceObject = CreatedInstance,\n Property = Property,\n ReferenceId = ReferenceId\n })\n end\n end\n\n if objectTree[\"Attributes\"] then\n for Attribute, Value in next, objectTree.Attributes do\n pcall(CreatedInstance.SetAttribute, CreatedInstance, Attribute, Value)\n end\n end\n\n if objectTree[\"Children\"] then\n for _, ChildObjectTree in next, objectTree.Children do\n local CreatedChildInstance = CreateInstanceFromObject(ChildObjectTree)\n if CreatedChildInstance then\n CreatedChildInstance.Parent = CreatedInstance\n end\n end\n end\n\n return CreatedInstance\n end\n\n local RealObjectRoot = {} do\n for _, ObjectTree in next, ModuleRoot do\n table.insert(RealObjectRoot, CreateInstanceFromObject(ObjectTree))\n end\n end\n\n local function LoadScript(scriptObject)\n local StoredModuleValue = StoredModuleValues[scriptObject]\n if scriptObject.ClassName == \"ModuleScript\" and StoredModuleValue then\n return unpack(StoredModuleValue)\n end\n\n local Closure = ScriptClosures[scriptObject]\n if not Closure then\n return\n end\n\n do\n local VirtualEnvironment\n\n local GlobalEnvironmentOverride = {\n [\"maui\"] = table.freeze({\n Version = Version,\n GetScript = function()\n return script\n end,\n GetShared = function()\n return SharedEnvironment\n end\n }),\n [\"script\"] = scriptObject,\n [\"require\"] = function(ModuleToRequire, ...)\n if ModuleToRequire and ModuleToRequire.ClassName == \"ModuleScript\" and ScriptClosures[ModuleToRequire] then\n return LoadScript(ModuleToRequire)\n end\n\n return require(ModuleToRequire, ...)\n end,\n [\"getfenv\"] = function(StackLevel, ...)\n if type(StackLevel) == \"number\" and StackLevel >= 0 then\n if StackLevel == 0 then\n return VirtualEnvironment\n else\n local GetOk, FunctionEnvironment = pcall(getfenv, StackLevel)\n if GetOk and FunctionEnvironment == RealEnvironment then\n return VirtualEnvironment\n end\n end\n end\n\n return getfenv(StackLevel, ...)\n end,\n [\"setfenv\"] = function(StackLevel, NewEnvironment, ...)\n if type(StackLevel) == \"number\" and StackLevel >= 0 then\n if StackLevel == 0 then\n return setfenv(VirtualEnvironment, NewEnvironment)\n else\n local GetOk, FunctionEnvironment = pcall(getfenv, StackLevel)\n if GetOk and FunctionEnvironment == RealEnvironment then\n return setfenv(VirtualEnvironment, NewEnvironment)\n end\n end\n end\n\n return setfenv(StackLevel, NewEnvironment, ...)\n end\n }\n\n VirtualEnvironment = setmetatable({}, {\n __index = function(_, index)\n local IndexInVirtualEnvironment = rawget(VirtualEnvironment, index)\n if IndexInVirtualEnvironment ~= nil then\n return IndexInVirtualEnvironment\n end\n\n local IndexInGlobalEnvironmentOverride = GlobalEnvironmentOverride[index]\n if IndexInGlobalEnvironmentOverride ~= nil then\n return IndexInGlobalEnvironmentOverride\n end\n\n return RealEnvironment[index]\n end,\n __newindex = function(_, index, newValue)\n rawset(VirtualEnvironment, index, newValue)\n end\n })\n\n setfenv(Closure, VirtualEnvironment)\n end\n\n local ClosureCoroutine = coroutine.wrap(Closure)\n\n if scriptObject:IsA(\"BaseScript\") then\n local ClosureThread = if scriptObject.Enabled then task.defer(ClosureCoroutine) else nil\n\n local EnabledChangedConnection\n EnabledChangedConnection = scriptObject:GetPropertyChangedSignal(\"Enabled\"):Connect(function(newEnabledState)\n EnabledChangedConnection:Disconnect()\n\n if newEnabledState == true then\n LoadScript(scriptObject)\n else\n pcall(task.cancel, ClosureThread)\n end\n end)\n\n return\n else\n local ClosureReturn = {ClosureCoroutine()}\n StoredModuleValues[scriptObject] = ClosureReturn\n return unpack(ClosureReturn)\n end\n end\n\n for _, ReferenceInfo in next, ReferencesToSet do\n pcall(function()\n ReferenceInfo.InstanceObject[ReferenceInfo.Property] = ReferenceBindings[ReferenceInfo.ReferenceId]\n end)\n end\n\n for _, ScriptObject in next, ScriptsToRun do\n if (IsServer and ScriptObject.ClassName == \"Script\") or (IsClient and ScriptObject.ClassName == \"LocalScript\") then\n LoadScript(ScriptObject)\n end\n end\nend\n",Name="LoadModule.lua"},Reference=8,ClassName="StringValue"}},Properties={Name="LoadModuleCode"},Reference=7,ClassName="Folder"},{Closure=function() --[[ - Maui - Roblox Studio Plugin for Packing Modules as Executable Luau Scripts - Licensed Under the LGPLv3 | Copyright (c) 2022-2023 Latte Softworks - https://github.com/latte-soft/maui - - File: /src/Codegen/GetDefaultInstanceProperty.lua - Desc: Get the "default" property of a certain instance class, Roblox doesn't provide - these in the API dump, or as an engine API in general. This is an idea taken from the - Roact script `getDefaultInstanceProperty.lua`: https://github.com/Roblox/roact/blob/master/src/getDefaultInstanceProperty.lua -]] - --- We'll cache all created instances, and indexed properties -local CachedInstances = {} -- [ClassName] = SomeInstance -local CachedPropertyValues = {} -- [ClassName] = {[PropertyName] = DefaultValue, ...} - --- Like Roact! -local PropertyValueOfNil = setmetatable(getmetatable(newproxy(true)), {__tostring = "PropertyValueOfNil"}) - --- Returns `DidReadValue`, `DefaultValue`; if `DidReadValue` is fakse, the `Instance.new` call failed -local function GetDefaultInstanceProperty(className, propertyToGet) - local CachedInstance = CachedInstances[className] - local CachedProperties = CachedPropertyValues[propertyToGet] - - if CachedProperties then - local PropertyInCache = CachedProperties[propertyToGet] - - -- Like noted in Roact's script, Lua doesn't tell the diff between a value actually being nil, or - -- just not in a table. We'll use userdata symbols for if the property's default value is ACTUALLY nil - if PropertyInCache == PropertyValueOfNil then - return true, nil - end - - if PropertyInCache ~= nil then - return true, PropertyInCache - end - else - -- Then add it - CachedProperties = {} - CachedPropertyValues[className] = CachedProperties - end - - -- Get/add cached instance value - if not CachedInstance then - local CreatedOk, NewInstance = pcall(Instance.new, className) - - if not CreatedOk then - -- `DidReadValue` false! - return false, nil - end - - CachedInstance = NewInstance - CachedInstances[className] = CachedInstance - end - - local DefaultValue = CachedInstance[propertyToGet] - - -- Add to cache - if DefaultValue == nil then -- Then the value of the property is ACTUALLY `nil`\ - CachedProperties[propertyToGet] = PropertyValueOfNil - else - CachedProperties[propertyToGet] = DefaultValue - end - - return DefaultValue -end - -return GetDefaultInstanceProperty - end,Properties={Name="GetDefaultInstanceProperty"},Reference=5,ClassName="ModuleScript"}}},{Closure=function() --[[ - Maui - Roblox Studio Plugin for Packing Modules as Executable Luau Scripts - Licensed Under the LGPLv3 | Copyright (c) 2022-2023 Latte Softworks - https://github.com/latte-soft/maui - - File: /src/App.story.lua - Desc: UI storybook script for Hoarcekat, lists all UI components -]] - -local Root = script.Parent -local Submodules = Root.Submodules -local Components = Root.Components - -local Fusion = require(Submodules.Fusion) - --- Components -local Button = require(Components.Button) -local Console = require(Components.Console) - --- Fusion definitions -local New = Fusion.New -local Children = Fusion.Children -local Value = Fusion.Value - -return function(target) - local List = New "Frame" { - Name = "MauiAppPreview", - Parent = target, - - Position = UDim2.fromOffset(12, 12), - Size = UDim2.new(1, -12, 1, -12), - - BackgroundTransparency = 1, - - [Children] = { - New "UIListLayout" { - Padding = UDim.new(0, 12), - }, - - -- COMPONENTS -- - - Button { - Text = "Hello, world!", - }, - - Button { - Text = "This button is disabled..", - Enabled = false - }, - - Console { - Text = Value("This is a (pretty cool) console!\n\n") - } - } - } - - -- Destructor function - return function() - pcall(List.Destroy, Button) - end -end - end,Properties={Name="App.story"},Reference=3,ClassName="ModuleScript"},{Children={{Closure=function() return require(script.Parent._Index["regginator_luaencode@1.1.4"]["luaencode"]) - end,Properties={Name="LuaEncode"},Reference=18,ClassName="ModuleScript"},{Children={{Children={{Closure=function() -- LuaEncode - Optimal Serialization of Lua Tables in Native Luau/Lua 5.1+ --- Copyright (c) 2022 Reggie | MIT License --- https://github.com/regginator/LuaEncode - -local Type = typeof or type -- For custom Roblox engine data-type support via `typeof`, if it exists - --- Lua 5.1 doesn't have table.find -local FindInTable = table.find or function(inputTable, valueToFind) -- Ignoring the `init` arg, unneeded for us - for Key, Value in ipairs(inputTable) do - if Value == valueToFind then - return Key -- Return the key idx - end - end - - return nil -end - --- Simple function for directly checking the type on values, with their input, variable name, --- and desired type name(s) to check -local function CheckType(inputData, dataName, ...) - local DesiredTypes = {...} - local InputDataType = Type(inputData) - - if not FindInTable(DesiredTypes, InputDataType) then - error(string.format( - "LuaEncode: Incorrect type for `%s`: `%s` expected, got `%s`", - dataName, - table.concat(DesiredTypes, ", "), -- For if multiple types are accepted - InputDataType - ), 0) - end - - return inputData -- Return back input directly -end - --- Shallow clone function defaulting to `table.clone()` (only Luau), or manually --- doing so for Lua 5.1+ -local ShallowClone = table.clone or function(inputTable) - local ClonedTable = {} - - for Key, Value in next, inputTable do - ClonedTable[Key] = Value - end - - return ClonedTable -end - --- This re-serializes a string back into Lua, for the interpreter --- AND humans to read. This fixes `string.format("%q")` only outputting --- in system encoding, instead of explicit Lua byte escapes -local SerializeString do - -- These are control characters to be encoded in a certain way in Lua - -- rather than just a byte escape (e.g. "\n" -> "\10") - local SpecialCharacters = { - -- Since the string is being wrapped with double quotes, - -- we don't need to escape single quotes - ["\""] = "\\\"", -- Double-Quote - ["\\"] = "\\\\", -- (Literal) Backslash - -- Special ASCII control char codes - ["\a"] = "\\a", -- Bell; ASCII #7 - ["\b"] = "\\b", -- Backspace; ASCII #8 - ["\t"] = "\\t", -- Horizontal-Tab; ASCII #9 - ["\n"] = "\\n", -- Newline; ASCII #10 - ["\v"] = "\\v", -- Vertical-Tab; ASCII #11 - ["\f"] = "\\f", -- Form-Feed; ASCII #12 - ["\r"] = "\\r", -- Carriage-Return; ASCII #13 - } - - -- We need to assign all extra normal byte escapes for runtime optimization - -- ASCII #s 32-126 include all printable characters that AREN'T - -- in the extended ASCII list, aside from #127, `DEL`. This - -- hits the signed 8-bit integer limit - for Index = 0, 255 do - local Character = string.char(Index) - - if not SpecialCharacters[Character] and (Index < 32 or Index > 126) then - SpecialCharacters[Character] = "\\" .. Index - end - end - - function SerializeString(inputString) - -- Replace all null bytes, ctrl chars, dbl quotes, literal backslashes, - -- and bytes 0-31 and 127-255 with their respecive escapes - return "\"" .. string.gsub(inputString, "[\"\\\0-\31\127-\255]", SpecialCharacters) .. "\"" - end -end - --- VERY simple function to get if an object is a service, used in instance path eval -local function IsService(object) - local FindServiceSuccess, ServiceObject = pcall(game.GetService, game, object.ClassName) - - if FindServiceSuccess and ServiceObject then - return true - end - - return false -end - --- Evaluating an instances' accessable "path" with just it's ref, and if --- the root parent is nil/isn't under `game` or `workspace`, returns nil. --- The use of this in encoding is optional, (false by default for --- consistency) and will always fallback to `Instance.new(ClassName)` -local function EvaluateInstancePath(object, currentPath) - currentPath = currentPath or "" - - local ObjectName = object.Name - local ObjectClassName = object.ClassName - local ObjectParent = object.Parent - - if ObjectParent == game and IsService(object) then - -- ^^ Then we'll use GetService directly, since it's actually a service - -- under the DataModel. FYI, GetService uses the ClassName of the - -- service, not the "name" - - currentPath = ":GetService(" .. SerializeString(ObjectClassName) .. ")" .. currentPath - elseif string.match(ObjectName, "^[A-Za-z_][A-Za-z0-9_]*$") then - -- ^^ Like the `string` DataType, this means means we can - -- index the name directly in Lua without an explicit string - currentPath = "." .. ObjectName .. currentPath - else - currentPath = "[" .. SerializeString(ObjectName) .. "]" .. currentPath - end - - -- These cases are SPECIFICALLY for getting if the path has reached the - -- "end" for the evaluation process, including if the root parent is nil - -- or isn't under the `game` DataModel - if not ObjectParent then - return -- Fallback, parent is nil etc - elseif ObjectParent == game then - currentPath = "game" .. currentPath - return currentPath - elseif ObjectParent == workspace then - currentPath = "workspace" .. currentPath - return currentPath - end - - return EvaluateInstancePath(ObjectParent, currentPath) -end - ---[[ - LuaEncode( inputTable, options): - - ---------- SETTINGS: ---------- - - PrettyPrinting | Whether or not the output should use "pretty - printing". - - IndentCount | The amount of "spaces" that should be indented per entry. - - OutputWarnings | If "warnings" should be outputted to the output - (as comments); It's recommended to keep this enabled. - - StackLimit | The limit to the stack level before recursive encoding - cuts off, and stops execution. This is used to prevent stack overflows and infinite - cyclic references. You could use `math.huge` here if you really wanted. - - FunctionsReturnRaw | If functions in said table return back a "raw" - value to place in the output as the key/value. - - UseInstancePaths | If `Instance` reference objects should attempt to - get its Lua-accessable path for encoding. If the instance is parented under `nil` or - isn't under `game`/`workspace`, it'll always fall back to `Instance.new(ClassName)` as - before. - -]] -local function LuaEncode(inputTable, options) - -- Check all arg and option types - CheckType(inputTable, "inputTable", "table") -- Required*, nil not allowed - options = CheckType(options, "options", "table", "nil") or {} -- `options` is an optional arg - - -- Options - if options then - CheckType(options.PrettyPrinting, "options.PrettyPrinting", "boolean", "nil") - CheckType(options.IndentCount, "options.IndentCount", "number", "nil") - CheckType(options.OutputWarnings, "options.OutputWarnings", "boolean", "nil") - CheckType(options.StackLimit, "options.StackLimit", "number", "nil") - CheckType(options.FunctionsReturnRaw, "options.FunctionsReturnRaw", "boolean", "nil") - CheckType(options.UseInstancePaths, "options.UseInstancePaths", "boolean", "nil") - - -- Internal options: - CheckType(options._StackLevel, "options._StackLevel", "number", "nil") - CheckType(options._VisitedTables, "options._StackLevel", "table", "nil") - end - - -- Because no if-else-then exp. in Lua 5.1+ (only Luau), for optional boolean values we need to check - -- if it's nil first, THEN fall back to whatever it's actually set to if it's not nil - local PrettyPrinting = (options.PrettyPrinting == nil and false) or options.PrettyPrinting - local IndentCount = options.IndentCount or 0 - local OutputWarnings = (options.OutputWarnings == nil and true) or options.OutputWarnings - local StackLimit = options.StackLimit or 500 - local FunctionsReturnRaw = (options.FunctionsReturnRaw == nil and false) or options.FunctionsReturnRaw - local UseInstancePaths = (options.UseInstancePaths == nil and false) or options.UseInstancePaths - - -- Internal options: - - -- Internal stack level for depth checks and indenting - local StackLevel = options._StackLevel or 1 - -- Set root as visited; cyclic detection - local VisitedTables = options._VisitedTables or {[inputTable] = true} -- [`visitedTable
`] = `isVisited ` - - -- Stack overflow/output abuse or whatever, default StackLimit is 199 - -- FYI, this is just a very temporary solution for table cyclic refs - if StackLevel >= StackLimit then - return string.format("{--[[LuaEncode: Stack level limit of `%d` reached]]}", StackLimit) - end - - -- Easy-to-reference values for specific args - local NewEntryString = (PrettyPrinting and "\n") or "" - local ValueSeperator = (PrettyPrinting and ", ") or "," - - -- For pretty printing (which is optional, and false by default) we need to keep track - -- of the current stack, then repeat InitialIndentString by that count - local InitialIndentString = string.rep(" ", IndentCount) -- If 0 this will just be "" - local IndentString = (PrettyPrinting and InitialIndentString:rep(StackLevel)) or InitialIndentString - - local EndingString = (#IndentString > 0 and IndentString:sub(1, -IndentCount - 1)) or "" - - -- For number key values, incrementing the current internal index - local KeyIndex = 1 - - -- Cases (C-Like) for encoding values, then end setup. Using cases so no elseif bs! - -- Functions are all expected to return a ( EncodedKey, EncloseInBrackets) - local TypeCases = {} do - -- Basic func for getting the direct value of an encoded type without weird table.pack()[1] syntax - local function TypeCase(typeName, value) - -- Each of these funcs return a tuple, so it'd be annoying to do case-by-case - local EncodedValue = TypeCases[typeName](value, false) -- False to label as NOT `isKey` - return EncodedValue - end - - TypeCases["number"] = function(value, isKey) - -- If the number isn't the current real index of the table, we DO want to - -- explicitly define it in the serialization no matter what for accuracy - if isKey and value == KeyIndex then - -- ^^ What's EXPECTED unless otherwise explicitly defined, if so, return no encoded num - KeyIndex = KeyIndex + 1 - return nil, false - end - - return tostring(value), true -- True return for 2nd arg means it SHOULD be enclosed with brackets, if it is a key - end - - TypeCases["string"] = function(value, isKey) - if isKey and string.match(value, "^[A-Za-z_][A-Za-z0-9_]*$") then - -- ^^ Then it's a syntaxically-correct variable, doesn't need explicit string def - return value, false -- `EncloseInBrackets` false because ^^^ - end - - return SerializeString(value), true - end - - TypeCases["table"] = function(value, isKey) - -- Check duplicate/cyclic references - do - local VisitedTable = VisitedTables[value] - if VisitedTable then - return string.format( - "{--[[LuaEncode: Duplicate reference%s]]}", - (value == inputTable and " (of parent)") or "" - ) - end - - VisitedTables[value] = true - end - - -- Shallow clone original options tbl - local NewOptions = ShallowClone(options) do - -- Overriding if key because it'd look worse pretty printed in a key - NewOptions.PrettyPrinting = (isKey and false) or (not isKey and PrettyPrinting) - - -- If PrettyPrinting is already false in the real args, set the indent to whatever - -- the REAL IndentCount is set to - NewOptions.IndentCount = (isKey and ((not PrettyPrinting and IndentCount) or 1)) or IndentCount - - -- Internal options - NewOptions._StackLevel = (isKey and 1) or StackLevel + 1 -- If isKey, stack lvl is set to the **LOWEST** because it's the key to a value - NewOptions._VisitedTables = VisitedTables - end - - return LuaEncode(value, NewOptions), true - end - - TypeCases["boolean"] = function(value) - return tostring(value), true - end - - TypeCases["nil"] = function(value) - return "nil", true - end - - TypeCases["function"] = function(value) - -- If `FunctionsReturnRaw` is set as true, we'll call the function here itself, expecting - -- a raw value tFunctionsReturnRawo add as the key/value, you may want to do this for custom userdata or - -- function closures. Thank's for listening to my Ted Talk! - if FunctionsReturnRaw then - return value(), true - end - - -- If all else, force key func to return nil; can't handle a func val.. - return "function() --[[LuaEncode: `options.FunctionsReturnRaw` false, can't encode functions]] return end", true - end - - ---------- ROBLOX CUSTOM DATATYPES BELOW ---------- - - -- Axes.new() - TypeCases["Axes"] = function(value) - local EncodedArgs = {} - local EnumValues = { - ["Enum.Axis.X"] = value.X, -- These return bools - ["Enum.Axis.Y"] = value.Y, - ["Enum.Axis.Z"] = value.Z, - } - - for EnumValue, IsEnabled in next, EnumValues do - if IsEnabled then - table.insert(EncodedArgs, EnumValue) - end - end - - return string.format( - "Axes.new(%s)", - table.concat(EncodedArgs, ValueSeperator) - ), true - end - - -- BrickColor.new() - TypeCases["BrickColor"] = function(value) - -- BrickColor.Name represents exactly what we want to encode - return string.format("BrickColor.new(%s)", TypeCase("string", value.Name)), true - end - - -- CFrame.new() - TypeCases["CFrame"] = function(value) - return string.format( - "CFrame.new(%s)", - table.concat({value:components()}, ValueSeperator) - ), true - end - - -- CatalogSearchParams.new() - TypeCases["CatalogSearchParams"] = function(value) - return string.format( - "(function(v, p) for pn, pv in next, p do v[pn] = pv end return v end)(%s)", - table.concat( - { - "CatalogSearchParams.new()", - TypeCase("table", { - SearchKeyword = value.SearchKeyword, - MinPrice = value.MinPrice, - MaxPrice = value.MaxPrice, - SortType = value.SortType, -- EnumItem - CategoryFilter = value.CategoryFilter, -- EnumItem - BundleTypes = value.BundleTypes, -- table - AssetTypes = value.AssetTypes -- table - }) - }, - ValueSeperator - ) - ) - end - - -- Color3.new() - TypeCases["Color3"] = function(value) - -- Using floats for RGB values, most accurate for direct serialization - return string.format( - "Color3.new(%s)", - table.concat({value.R, value.G, value.B}, ValueSeperator) - ), true - end - - -- ColorSequence.new() - TypeCases["ColorSequence"] = function(value) - return string.format( - "ColorSequence.new(%s)", - TypeCase("table", value.Keypoints) - ), true - end - - -- ColorSequenceKeypoint.new() - TypeCases["ColorSequenceKeypoint"] = function(value) - return string.format( - "ColorSequenceKeypoint.new(%s)", - table.concat( - { - value.Time, - TypeCase("Color3", value.Value), - }, - ValueSeperator - ) - ), true - end - - -- DateTime.now()/DateTime.fromUnixTimestamp() | We're using fromUnixTimestamp to serialize the object - TypeCases["DateTime"] = function(value) - return string.format("DateTime.fromUnixTimestamp(%d)", value.UnixTimestamp), true - end - - -- DockWidgetPluginGuiInfo.new() | Properties seem to throw an error on index if the scope isn't a Studio - -- plugin, so we're directly getting values! (so fun!!!!) - TypeCases["DockWidgetPluginGuiInfo"] = function(value) - local ValueString = tostring(value) -- e.g.: "InitialDockState:Right InitialEnabled:0 InitialEnabledShouldOverrideRestore:0 FloatingXSize:0 FloatingYSize:0 MinWidth:0 MinHeight:0" - - return string.format( - "DockWidgetPluginGuiInfo.new(%s)", - table.concat( - { - -- InitialDockState (Enum.InitialDockState) - TypeCase("EnumItem", Enum.InitialDockState[string.match(ValueString, "InitialDockState:(%w+)")]), -- Enum.InitialDockState.Right - -- InitialEnabled and InitialEnabledShouldOverrideRestore (boolean as number; `0` or `1`) - TypeCase("boolean", string.match(ValueString, "InitialEnabled:(%w+)") == "1"), -- false - TypeCase("boolean", string.match(ValueString, "InitialEnabledShouldOverrideRestore:(%w+)") == "1"), -- false - -- FloatingXSize/FloatingYSize (numbers) - string.match(ValueString, "FloatingXSize:(%w+)"), -- 0 - string.match(ValueString, "FloatingYSize:(%w+)"), -- 0 - -- MinWidth/MinHeight (numbers) - string.match(ValueString, "MinWidth:(%w+)"), -- 0 - string.match(ValueString, "MinHeight:(%w+)"), -- 0 - }, - ValueSeperator - ) - ), true - end - - -- Enum (e.g. `Enum.UserInputType`) - TypeCases["Enum"] = function(value) - return "Enum." .. tostring(value), true -- For now, this is the behavior of enums in tostring.. I have no other choice atm - end - - -- EnumItem | e.g. `Enum.UserInputType.Gyro` - TypeCases["EnumItem"] = function(value) - return tostring(value), true -- Returns the full enum index for now (e.g. "Enum.UserInputType.Gyro") - end - - -- Enums | i.e. the `Enum` global return - TypeCases["Enums"] = function(value) - return "Enum", true - end - - -- Faces.new() | Similar to Axes.new() - TypeCases["Faces"] = function(value) - local EncodedArgs = {} - local EnumValues = { - ["Enum.NormalId.Top"] = value.Top, -- These return bools - ["Enum.NormalId.Bottom"] = value.Bottom, - ["Enum.NormalId.Left"] = value.Left, - ["Enum.NormalId.Right"] = value.Right, - ["Enum.NormalId.Back"] = value.Back, - ["Enum.NormalId.Front"] = value.Front, - } - - for EnumValue, IsEnabled in next, EnumValues do - if IsEnabled then - table.insert(EncodedArgs, EnumValue) - end - end - - return string.format( - "Faces.new(%s)", - table.concat(EncodedArgs, ValueSeperator) - ), true - end - - -- FloatCurveKey.new() - TypeCases["FloatCurveKey"] = function(value) - return string.format( - "FloatCurveKey.new(%s)", - table.concat( - { - value.Time, - value.Value, - TypeCase("EnumItem", value.Interpolation), - }, - ValueSeperator - ) - ), true - end - - -- Font.new() - TypeCases["Font"] = function(value) - return string.format( - "Font.new(%s)", - table.concat( - { - TypeCase("string", value.Family), - TypeCase("EnumItem", value.Weight), - TypeCase("EnumItem", value.Style), - }, - ValueSeperator - ) - ), true - end - - -- Instance.new() | Instance refs can be evaluated to their paths (optional), but if - -- parented to nil or some DataModel not under `game`, it'll just return `Instance.new(ClassName)` - TypeCases["Instance"] = function(value) - if UseInstancePaths then - local InstancePath = EvaluateInstancePath(value) - if InstancePath then - return InstancePath, true - end - - -- ^^ Now, if the path isn't accessable, falls back to the return below - end - - return string.format("Instance.new(%s)", TypeCase("string", value.ClassName)), true - end - - -- NumberRange.new() - TypeCases["NumberRange"] = function(value) - return string.format( - "NumberRange.new(%s)", - table.concat({value.Min, value.Max}, ValueSeperator) - ), true - end - - -- NumberSequence.new() - TypeCases["NumberSequence"] = function(value) - return string.format( - "NumberSequence.new(%s)", - TypeCase("table", value.Keypoints) - ), true - end - - -- NumberSequenceKeypoint.new() - TypeCases["NumberSequenceKeypoint"] = function(value) - return string.format( - "NumberSequenceKeypoint.new(%s)", - table.concat( - { - value.Time, - value.Value, - value.Envelope, - }, - ValueSeperator - ) - ), true - end - - -- OverlapParams.new() - TypeCases["OverlapParams"] = function(value) - return string.format( - "(function(v, p) for pn, pv in next, p do v[pn] = pv end return v end)(%s)", - table.concat( - { - "OverlapParams.new()", - TypeCase("table", { - FilterDescendantsInstances = value.FilterDescendantsInstances, - FilterType = value.FilterType, - MaxParts = value.MaxParts, - CollisionGroup = value.CollisionGroup, - RespectCanCollide = value.RespectCanCollide - }) - }, - ValueSeperator - ) - ) - end - - -- PathWaypoint.new() - TypeCases["PathWaypoint"] = function(value) - return string.format( - "PathWaypoint.new(%s)", - table.concat( - { - TypeCase("Vector3", value.Position), - TypeCase("EnumItem", value.Action), - TypeCase("string", value.Label), - }, - ValueSeperator - ) - ), true - end - - -- PhysicalProperties.new() - TypeCases["PhysicalProperties"] = function(value) - return string.format( - "PhysicalProperties.new(%s)", - table.concat( - { - value.Density, - value.Friction, - value.Elasticity, - value.FrictionWeight, - value.ElasticityWeight, - }, - ValueSeperator - ) - ), true - end - - -- Random.new() - TypeCases["Random"] = function() - return "Random.new()", true - end - - -- Ray.new() - TypeCases["Ray"] = function(value) - return string.format( - "Ray.new(%s)", - table.concat( - { - TypeCase("Vector3", value.Origin), - TypeCase("Vector3", value.Direction), - }, - ValueSeperator - ) - ), true - end - - -- RaycastParams.new() - TypeCases["RaycastParams"] = function(value) - return string.format( - "(function(v, p) for pn, pv in next, p do v[pn] = pv end return v end)(%s)", - table.concat( - { - "RaycastParams.new()", - TypeCase("table", { - FilterDescendantsInstances = value.FilterDescendantsInstances, - FilterType = value.FilterType, - IgnoreWater = value.IgnoreWater, - CollisionGroup = value.CollisionGroup, - RespectCanCollide = value.RespectCanCollide - }) - }, - ValueSeperator - ) - ) - end - - -- Rect.new() - TypeCases["Rect"] = function(value) - return string.format( - "Rect.new(%s)", - table.concat( - { - TypeCase("Vector2", value.Min), - TypeCase("Vector2", value.Max), - }, - ValueSeperator - ) - ), true - end - - -- Region3.new() | Roblox doesn't provide read properties for min/max on `Region3`, but they - -- do on Region3int16.. Anyway, we CAN calculate the min/max of a Region3 from just .CFrame - -- and .Size.. Thanks to wally for linking me the thread for this method lol - TypeCases["Region3"] = function(value) - local ValueCFrame = value.CFrame - local ValueSize = value.Size - - -- These both are returned CFrames, we need to use Minimum.Position/Maximum.Position for the - -- min/max args to Region3.new() - local Minimum = ValueCFrame * CFrame.new(-ValueSize / 2) - local Maximum = ValueCFrame * CFrame.new(ValueSize / 2) - - return string.format( - "Region3.new(%s)", - table.concat( - { - TypeCase("Vector3", Minimum.Position), -- min - TypeCase("Vector3", Maximum.Position) -- max - }, - ValueSeperator - ) - ), true - end - - -- Region3int16.new() - TypeCases["Region3int16"] = function(value) - return string.format( - "Region3int16.new(%s)", - table.concat( - { - TypeCase("Vector3int16", value.Min), - TypeCase("Vector3int16", value.Max), - }, - ValueSeperator - ) - ), true - end - - -- TweenInfo.new() - TypeCases["TweenInfo"] = function(value) - return string.format( - "TweenInfo.new(%s)", - table.concat( - { - value.Time, - TypeCase("EnumItem", value.EasingStyle), - TypeCase("EnumItem", value.EasingDirection), - value.RepeatCount, - TypeCase("boolean", value.Reverses), - value.DelayTime, - }, - ValueSeperator - ) - ), true - end - - -- RotationCurveKey.new() | UNDOCUMENTED - TypeCases["RotationCurveKey"] = function(value) - return string.format( - "RotationCurveKey.new(%s)", - table.concat( - { - value.Time, - TypeCase("CFrame", value.Value), - TypeCase("EnumItem", value.Interpolation) - }, - ValueSeperator - ) - ), true - end - - -- UDim.new() - TypeCases["UDim"] = function(value) - return string.format( - "UDim.new(%s)", - table.concat({value.Scale, value.Offset}, ValueSeperator) - ), true - end - - -- UDim2.new() - TypeCases["UDim2"] = function(value) - return string.format( - "UDim2.new(%s)", - table.concat( - { - -- Not directly using X and Y UDims for better output (i.e. would - -- be UDim2.new(UDim.new(1, 0), UDim.new(1, 0)) if I did) - value.X.Scale, - value.X.Offset, - value.Y.Scale, - value.Y.Offset, - }, - ValueSeperator - ) - ), true - end - - -- Vector2.new() - TypeCases["Vector2"] = function(value) - return string.format( - "Vector2.new(%s)", - table.concat({value.X, value.Y}, ValueSeperator) - ), true - end - - -- Vector2int16.new() - TypeCases["Vector2int16"] = function(value) - return string.format( - "Vector2int16.new(%s)", - table.concat({value.X, value.Y}, ValueSeperator) - ), true - end - - -- Vector3.new() - TypeCases["Vector3"] = function(value) - return string.format( - "Vector3.new(%s)", - table.concat({value.X, value.Y, value.Z}, ValueSeperator) - ), true - end - - -- Vector3int16.new() - TypeCases["Vector3int16"] = function(value) - return string.format( - "Vector3int16.new(%s)", - table.concat({value.X, value.Y, value.Z}, ValueSeperator) - ), true - end - - -- `userdata`, just encode directly - TypeCases["userdata"] = function(value) - if getmetatable(value) then -- Has mt - return "newproxy(true)", true - else - return "newproxy()", true -- newproxy() defaults to false (no mt) - end - end - end - - -- Setup output tbl - local EncodedEntries = {} - - for Key, Value in next, inputTable do - local KeyType = Type(Key) - local ValueType = Type(Value) - - if TypeCases[KeyType] and TypeCases[ValueType] then - local EntryOutput = (PrettyPrinting and NewEntryString .. IndentString) or "" - - -- Go through and get key val - local KeyEncodedSuccess, EncodedKeyOrError, EncloseInBrackets = pcall(TypeCases[KeyType], Key, true) -- The `true` represents if it's a key or not, here it is - - -- Ignoring 2nd arg (`EncloseInBrackets`) because this isn't the key - local ValueEncodedSuccess, EncodedValueOrError = pcall(TypeCases[ValueType], Value, false) -- `false` because it's NOT the key, it's the value - - -- Im sorry for this logic chain here, I can't use `continue`/`continue()`.. :sob: - -- Ignoring `if EncodedKeyOrError` because the key doesn't actually need to ALWAYS - -- be explicitly encoded, like if it's a number of the current key index! - if KeyEncodedSuccess and ValueEncodedSuccess and EncodedValueOrError then - -- NOW we'll check for if the key was explicitly encoded, because we don't to stop - -- the value from encoding, since we've already checked that and it *has* been - local KeyValue = EncodedKeyOrError and ((EncloseInBrackets and string.format("[%s]", EncodedKeyOrError)) or EncodedKeyOrError) .. ((PrettyPrinting and " = ") or "=") or "" - - -- Encode key/value together, we've already checked if `EncodedValueOrError` was returned - EntryOutput = EntryOutput .. KeyValue .. EncodedValueOrError - elseif OutputWarnings then -- Then `Encoded(Key/Value)OrError` is the error msg - -- ^^ Then either the key or value wasn't properly checked or encoded, and there - -- was an error we need to log! - local ErrorMessage = string.format( - "LuaEncode: Failed to encode %s of DataType `%s`: %s", - (not KeyEncodedSuccess and "key") or (not ValueEncodedSuccess and "value") or "key/value", -- "key/value" for bool type fallback - ValueType, - (not KeyEncodedSuccess and SerializeString(EncodedKeyOrError)) or (not ValueEncodedSuccess and SerializeString(EncodedValueOrError)) or "(Failed to get error message)" - ) - - EntryOutput = EntryOutput .. string.format( - "nil%s--[[%s]]", - (PrettyPrinting and " ") or "", -- Adding a space between `nil` or not - ErrorMessage:gsub("%[*%]*", "") -- Not using string global lib because it returns a tuple - ) - end - - -- If there isn't another value after the current index, add ending formatting - if not next(inputTable, Key) then - EntryOutput = EntryOutput .. NewEntryString .. EndingString - end - - table.insert(EncodedEntries, EntryOutput) - end - end - - -- Return wrapped table - return "{" .. table.concat(EncodedEntries, ",") .. "}" -end - -return LuaEncode - end,Properties={Name="luaencode"},Reference=21,ClassName="ModuleScript"}},Properties={Name="regginator_luaencode@1.1.4"},Reference=20,ClassName="Folder"}},Properties={Name="_Index"},Reference=19,ClassName="Folder"}},Properties={Name="Packages"},Reference=17,ClassName="Folder"},{Closure=function() --[[ - Maui - Roblox Studio Plugin for Packing Modules as Executable Luau Scripts - Licensed Under the LGPLv3 | Copyright (c) 2022-2023 Latte Softworks - https://github.com/latte-soft/maui - - File: /src/App.lua - Desc: Initializes the front-end app client, works with codegen assembler and Fusion - components for UI -]] -local HttpService = game:GetService("HttpService") - -local ServerStorage = game:GetService("ServerStorage") -local Selection = game:GetService("Selection") -local ScriptEditorService = game:GetService("ScriptEditorService") - -local Root = script.Parent -local Submodules = Root.Submodules -local Components = Root.Components - -local Codegen = require(Root.Codegen) -local Fusion = require(Submodules.Fusion) - --- Constants -local DEFAULT_CONSOLE_TEXT = "Maui | Copyright (c) 2022-2023 Latte Softworks \nGitHub: latte-soft/maui\n\nTo build a new script from a model, make a selection in your explorer, then just use the options below.\nRight click this console for further options.\n\n" -local INITIAL_OUTPUT_TEXT = "-- Maui: Waiting to add real script output to the editor..\n" --- Default options for the `.maui` format -local DEFAULT_OPTIONS = { - FormatVersion = 1, -- Isn't necessary in the project file, but just for future proofing the format incase we ever change anything - -- All output options - Output = { - MinifyTable = false, -- If the codegen table itself (made from LuaEncode) is to be minified - UseMinifiedLoader = true -- Use the pre-minified LoadModule script in the codegen, which is always predefined and not useful for debugging - } -} - --- Fusion definitions -local New = Fusion.New -local Children = Fusion.Children -local Value = Fusion.Value -local Computed = Fusion.Computed -local Observer = Fusion.Observer - --- Fusion UI components -local Button = require(Components.Button) -local Console = require(Components.Console) - --- yeah.. classic -local function DeepClone(inputTable) - local NewTable = {} - - for Key, NewValue in inputTable do - NewTable[Key] = if type(NewValue) == "table" then DeepClone(NewValue) else NewValue - end - - return NewTable -end - -return function(plugin, pluginWidget) - -- We need to pass through the plugin object here - local PluginSettings = require(Root.PluginSettings)(plugin) - - local CurrentSelection = Value({}) - local SelectedScript = Value(nil) - - -- GUI console state values - local ConsoleOutput = Value(DEFAULT_CONSOLE_TEXT) - local AutoScrollEnabled = Value(PluginSettings.Get("ConsoleAutoscroll")) -- Can be changed thru context menu - - local function Log(text, logStack) - ConsoleOutput:set(ConsoleOutput:get() .. string.rep("-", logStack or 1) .. "> " .. text .. "\n") - end - - -- Send everything to the codegen assembler when a build is requested - local function Build() - local SelectionToBuild = CurrentSelection:get() - local FirstObjectSelected = SelectionToBuild[1] - - -- Check if ther selection actually CONTAINS anything.. - if not FirstObjectSelected or not Codegen.CanReadInstance(FirstObjectSelected) then - return - end - - -- Get default options, and if there's a `.maui` project file, use those - local Options = DeepClone(DEFAULT_OPTIONS) do - local MauiProjectFileModule = FirstObjectSelected:FindFirstChild(".maui") -- This will be `.maui.json` or `.maui.lua` if built from rojo - if MauiProjectFileModule then - -- Try to read and parse project format - local MauiProjectFile = require(MauiProjectFileModule) - if type(MauiProjectFile) ~= "table" then - Log("`.maui` project file given isn't a valid Lua/JSON module, failed to parse.") - return - end - - -- Replace all eligable options - local function RecursiveAddOptions(realTable, tableToAddFrom) - for Key, NewValue in tableToAddFrom do - if realTable[Key] ~= nil then -- Then it's in the real default options, we can add - -- If the value is just another table, we'll recursively go thru (yep:tm: x2) - if type(NewValue) == "table" then - RecursiveAddOptions(realTable[Key], NewValue) - continue - end - - realTable[Key] = NewValue - else - Log("Invalid option \"" .. tostring(Key) .. "\" in `.maui` project file, did you make a typo?") - end - end - end - - RecursiveAddOptions(Options, MauiProjectFile) - end - end - - -- Make the first object in the selection (USUALLY the only) the name of the outputted script, just - -- so we can have something to base off of. We'll remove all ctrl chars, spaces, and periods from this - -- string, so it's a little more friendly for the explorer. We'll also truncate to 50 characters - local ScriptName = FirstObjectSelected.Name:sub(1, 50):gsub("[\0-\31\127-\255]", ""):gsub("[\32%.]", "_") - - Log("Attempting to build script \"" .. ScriptName .. "\"") - - -- Pass through the log function aswell, for detailed steps of what the assembler is actually doing - local DidBuild, GeneratedScriptOrError = pcall(Codegen.BuildFromSelection, SelectionToBuild, Options, Log) - - if not DidBuild then - Log("Failed to build due to error: \"" .. GeneratedScriptOrError or "[No error message attached]" .. "\"", 2) - return - end - - Log("Successfully built, opening..", 2) - - -- ServerStorage["Maui | Built Scripts"] - local MauiScriptsFolder = ServerStorage:FindFirstChild("Maui | Built Scripts") - if not MauiScriptsFolder then - MauiScriptsFolder = Instance.new("Folder") - MauiScriptsFolder.Name = "Maui | Built Scripts" - MauiScriptsFolder.Parent = ServerStorage - end - - -- ServerStorage["Maui | Built Scripts"][ScriptName] - local SpecificScriptBuilds = MauiScriptsFolder:FindFirstChild(ScriptName) - if not SpecificScriptBuilds then - SpecificScriptBuilds = Instance.new("Folder") - SpecificScriptBuilds.Name = ScriptName - SpecificScriptBuilds.Parent = MauiScriptsFolder - end - - -- Studio may prompt the user to give the plugin access to writing `Script.Source`, it - -- doesn't just yield and set it when they click "Allow", we have to do it again outselves - -- ALSO, setting this property can err if the str is too long (yay!) - local CreateScriptOk, ErrorMessage = pcall(function() - -- Create the real script obj - local ScriptObject = Instance.new("Script") - - -- Format the current date-time into the name, just for basic organization for - -- the user. I'd make it just the epoch time, but just doing this for readability - ScriptObject.Name = os.date(ScriptName .. "_%Y-%m-%d_%H-%M-%S") - - pcall(function() - ScriptObject.Source = INITIAL_OUTPUT_TEXT - end) - - ScriptObject.Parent = SpecificScriptBuilds - Selection:Set({ScriptObject}) -- Select the script object for the "Save" button feature - ScriptEditorService:OpenScriptDocumentAsync(ScriptObject) - - Log("Opened from location \"" .. ScriptObject:GetFullName() .. "\", adding output..", 2) - - local ScriptDocument = ScriptEditorService:FindScriptDocument(ScriptObject) - if not ScriptDocument then - error("Failed to get the open `ScriptDocument` object to edit source, was the script disallowed from opening?", 0) - end - - local DidEdit, EditTextErrorMessage = ScriptDocument:EditTextAsync(GeneratedScriptOrError, 1, 1, 1, #GeneratedScriptOrError) - - if DidEdit then - Log("Added full output to script", 2) - elseif EditTextErrorMessage then - error("`ScriptDocument:EditTextAsync` error: " .. EditTextErrorMessage, 0) - end - end) - - if not CreateScriptOk and ErrorMessage then - Log("Failed to open script due to error: \"" .. ErrorMessage .. "\"", 2) - end - end - - -- Sadly, the `plugin:PromptSaveSelection` API doesn't really allow for any custom extension - -- trickery like ".client.lua" or ".server.lua", so we just have to deal with that I guess.. - local function SaveScript() - local ScriptToSave = SelectedScript:get() - - if not ScriptToSave then - return - end - - Log("Attempting to save file: \"" .. ScriptToSave.Name .. ".lua\"") - - -- This call yields btw - local DidSave = plugin:PromptSaveSelection(ScriptToSave.Name) - - if DidSave then - Log("Successfully saved to disk", 2) - else - Log("Failed to save, prompt failed/cancelled", 2) - end - end - - -- We need to always keep track of if there's a script selected. For best measure, - -- let's initialize the observer right now - Observer(CurrentSelection):onChange(function() - local SelectionFromState = CurrentSelection:get() - - -- The save selection functionality should only have a single script selected - if #SelectionFromState == 1 then - local SelectionObject = SelectionFromState[1] - - local CallOk, IsASaveableScript = pcall(function() - return if SelectionObject:IsA("LuaSourceContainer") and #SelectionObject:GetChildren() == 0 then true else false - end) - - if CallOk and IsASaveableScript then - SelectedScript:set(SelectionObject) - return - end - end - - -- Fallback to no script selected - SelectedScript:set(nil) - end) - - -- SelectionChanged listener - Selection.SelectionChanged:Connect(function() - CurrentSelection:set(Selection:Get()) -- For the UI to know that there is/isn't currently a selection - end) - - -- Context menu for the console menu, we'll provide this to the console component to run later - local ConsoleContextMenu do - ConsoleContextMenu = plugin:CreatePluginMenu("MauiConsoleContextMenu", "Maui Console Context Menu") - - local ClearConsoleAction = ConsoleContextMenu:AddNewAction("MauiConsoleContextMenuClearConsole", "Clear Console") - ClearConsoleAction.Triggered:Connect(function() - ConsoleOutput:set("") - end) - - local ReloadConsoleAction = ConsoleContextMenu:AddNewAction("MauiConsoleContextMenuReloadConsole", "Reload Console") - ReloadConsoleAction.Triggered:Connect(function() - ConsoleOutput:set(DEFAULT_CONSOLE_TEXT) - end) - - local ToggleAutoscrollAction = ConsoleContextMenu:AddNewAction("MauiConsoleContextMenuToggleAutoscroll", "Toggle Autoscroll") - ToggleAutoscrollAction.Triggered:Connect(function() - local NewStatus = not AutoScrollEnabled:get() - - AutoScrollEnabled:set(NewStatus) - PluginSettings.Set("ConsoleAutoscroll", NewStatus) - end) - end - - -- Assemble the interface with the button and console components - New "Frame" { - Name = "Window", - Parent = pluginWidget, - - Size = UDim2.fromScale(1, 1), - BackgroundTransparency = 1, - - [Children] = { - Console { - Text = ConsoleOutput, - Position = UDim2.fromOffset(12, 12), - Size = UDim2.new(1, -24, 1, -24 - 24 - 12), -- Make sure to account for the control bar! - AutoScrollEnabled = AutoScrollEnabled, - RightClickContextMenu = ConsoleContextMenu - }, - - -- Control bar w/ buttons - New "Frame" { - Name = "ControlBar", - - AnchorPoint = Vector2.new(0, 1), - Position = UDim2.new(0, 0, 1, -12), - Size = UDim2.new(1, 0, 0, 24), - - BackgroundTransparency = 1, - - [Children] = { - New "UIListLayout" { - Padding = UDim.new(0, 6), - FillDirection = Enum.FillDirection.Horizontal, - HorizontalAlignment = Enum.HorizontalAlignment.Center, - VerticalAlignment = Enum.VerticalAlignment.Center, - SortOrder = Enum.SortOrder.LayoutOrder - }, - - -- Build & open new script - Button { - Text = "Build", - Enabled = Computed(function() - return #CurrentSelection:get() >= 1 - end), - OnClick = Build, - LayoutOrder = 1 - }, - - -- Save script (selection) to file - Button { - Text = "Save", - Enabled = Computed(function() - return SelectedScript:get() ~= nil - end), - OnClick = SaveScript, - LayoutOrder = 1 - } - } - } - } - } -end - end,Properties={Name="App"},Reference=2,ClassName="ModuleScript"},{Children={{Closure=function() return require(script.Parent._Index.Fusion) end,Properties={Name="Fusion"},Reference=23,ClassName="ModuleScript"},{Children={{ClassName="ModuleScript",Closure=function() --!strict - ---[[ - The entry point for the Fusion library. -]] - -local PubTypes = require(script.PubTypes) -local restrictRead = require(script.Utility.restrictRead) - -export type StateObject = PubTypes.StateObject -export type CanBeState = PubTypes.CanBeState -export type Symbol = PubTypes.Symbol -export type Value = PubTypes.Value -export type Computed = PubTypes.Computed -export type ForPairs = PubTypes.ForPairs -export type ForKeys = PubTypes.ForKeys -export type ForValues = PubTypes.ForKeys -export type Observer = PubTypes.Observer -export type Tween = PubTypes.Tween -export type Spring = PubTypes.Spring - -type Fusion = { - version: PubTypes.Version, - - New: (className: string) -> ((propertyTable: PubTypes.PropertyTable) -> Instance), - Hydrate: (target: Instance) -> ((propertyTable: PubTypes.PropertyTable) -> Instance), - Ref: PubTypes.SpecialKey, - Cleanup: PubTypes.SpecialKey, - Children: PubTypes.SpecialKey, - Out: PubTypes.SpecialKey, - OnEvent: (eventName: string) -> PubTypes.SpecialKey, - OnChange: (propertyName: string) -> PubTypes.SpecialKey, - - Value: (initialValue: T) -> Value, - Computed: (callback: () -> T, destructor: (T) -> ()?) -> Computed, - ForPairs: (inputTable: CanBeState<{[KI]: VI}>, processor: (KI, VI) -> (KO, VO, M?), destructor: (KO, VO, M?) -> ()?) -> ForPairs, - ForKeys: (inputTable: CanBeState<{[KI]: any}>, processor: (KI) -> (KO, M?), destructor: (KO, M?) -> ()?) -> ForKeys, - ForValues: (inputTable: CanBeState<{[any]: VI}>, processor: (VI) -> (VO, M?), destructor: (VO, M?) -> ()?) -> ForValues, - Observer: (watchedState: StateObject) -> Observer, - - Tween: (goalState: StateObject, tweenInfo: TweenInfo?) -> Tween, - Spring: (goalState: StateObject, speed: number?, damping: number?) -> Spring, - - cleanup: (...any) -> (), - doNothing: (...any) -> () -} - -return restrictRead("Fusion", { - version = {major = 0, minor = 2, isRelease = false}, - - New = require(script.Instances.New), - Hydrate = require(script.Instances.Hydrate), - Ref = require(script.Instances.Ref), - Out = require(script.Instances.Out), - Cleanup = require(script.Instances.Cleanup), - Children = require(script.Instances.Children), - OnEvent = require(script.Instances.OnEvent), - OnChange = require(script.Instances.OnChange), - - Value = require(script.State.Value), - Computed = require(script.State.Computed), - ForPairs = require(script.State.ForPairs), - ForKeys = require(script.State.ForKeys), - ForValues = require(script.State.ForValues), - Observer = require(script.State.Observer), - - Tween = require(script.Animation.Tween), - Spring = require(script.Animation.Spring), - - cleanup = require(script.Utility.cleanup), - doNothing = require(script.Utility.doNothing) -}) :: Fusion - end,Properties={Name="Fusion"},Reference=25,Children={{Closure=function() --!strict - ---[[ - Stores common public-facing type information for Fusion APIs. -]] - -type Set = {[T]: any} - ---[[ - General use types -]] - --- A unique symbolic value. -export type Symbol = { - type: string, -- replace with "Symbol" when Luau supports singleton types - name: string -} - --- Types that can be expressed as vectors of numbers, and so can be animated. -export type Animatable = - number | - CFrame | - Color3 | - ColorSequenceKeypoint | - DateTime | - NumberRange | - NumberSequenceKeypoint | - PhysicalProperties | - Ray | - Rect | - Region3 | - Region3int16 | - UDim | - UDim2 | - Vector2 | - Vector2int16 | - Vector3 | - Vector3int16 - --- A task which can be accepted for cleanup. -export type Task = - Instance | - RBXScriptConnection | - () -> () | - {destroy: (any) -> ()} | - {Destroy: (any) -> ()} | - {Task} - --- Script-readable version information. -export type Version = { - major: number, - minor: number, - isRelease: boolean -} ---[[ - Generic reactive graph types -]] - --- A graph object which can have dependents. -export type Dependency = { - dependentSet: Set -} - --- A graph object which can have dependencies. -export type Dependent = { - update: (Dependent) -> boolean, - dependencySet: Set -} - --- An object which stores a piece of reactive state. -export type StateObject = Dependency & { - type: string, -- replace with "State" when Luau supports singleton types - kind: string, - get: (StateObject, asDependency: boolean?) -> T -} - --- Either a constant value of type T, or a state object containing type T. -export type CanBeState = StateObject | T - ---[[ - Specific reactive graph types -]] - --- A state object whose value can be set at any time by the user. -export type Value = StateObject & { - -- kind: "State" (add this when Luau supports singleton types) - set: (Value, newValue: any, force: boolean?) -> () -} - --- A state object whose value is derived from other objects using a callback. -export type Computed = StateObject & Dependent & { - -- kind: "Computed" (add this when Luau supports singleton types) -} - --- A state object whose value is derived from other objects using a callback. -export type ForPairs = StateObject<{ [KO]: VO }> & Dependent & { - -- kind: "ForPairs" (add this when Luau supports singleton types) -} --- A state object whose value is derived from other objects using a callback. -export type ForKeys = StateObject<{ [KO]: V }> & Dependent & { - -- kind: "ForKeys" (add this when Luau supports singleton types) -} --- A state object whose value is derived from other objects using a callback. -export type ForValues = StateObject<{ [K]: VO }> & Dependent & { - -- kind: "ForKeys" (add this when Luau supports singleton types) -} - --- A state object which follows another state object using tweens. -export type Tween = StateObject & Dependent & { - -- kind: "Tween" (add this when Luau supports singleton types) -} - --- A state object which follows another state object using spring simulation. -export type Spring = StateObject & Dependent & { - -- kind: "Spring" (add this when Luau supports singleton types) - -- Uncomment when ENABLE_PARAM_SETTERS is enabled - -- setPosition: (Spring, newValue: Animatable) -> (), - -- setVelocity: (Spring, newValue: Animatable) -> (), - -- addVelocity: (Spring, deltaValue: Animatable) -> () -} - --- An object which can listen for updates on another state object. -export type Observer = Dependent & { - -- kind: "Observer" (add this when Luau supports singleton types) - onChange: (Observer, callback: () -> ()) -> (() -> ()) -} - ---[[ - Instance related types -]] - --- Denotes children instances in an instance or component's property table. -export type SpecialKey = { - type: string, -- replace with "SpecialKey" when Luau supports singleton types - kind: string, - stage: string, -- replace with "self" | "descendants" | "ancestor" | "observer" when Luau supports singleton types - apply: (SpecialKey, value: any, applyTo: Instance, cleanupTasks: {Task}) -> () -} - --- A collection of instances that may be parented to another instance. -export type Children = Instance | StateObject | {[any]: Children} - --- A table that defines an instance's properties, handlers and children. -export type PropertyTable = {[string | SpecialKey]: any} - -return nil end,Properties={Name="PubTypes"},Reference=61,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Stores common type information used internally. - - These types may be used internally so Fusion code can type-check, but - should never be exposed to public users, as these definitions are fair game - for breaking changes. -]] - -local Package = script.Parent -local PubTypes = require(Package.PubTypes) - -type Set = {[T]: any} - ---[[ - General use types -]] - --- A symbol that represents the absence of a value. -export type None = PubTypes.Symbol & { - -- name: "None" (add this when Luau supports singleton types) -} - --- Stores useful information about Luau errors. -export type Error = { - type: string, -- replace with "Error" when Luau supports singleton types - raw: string, - message: string, - trace: string -} - ---[[ - Specific reactive graph types -]] - --- A state object whose value can be set at any time by the user. -export type State = PubTypes.Value & { - _value: T -} - --- A state object whose value is derived from other objects using a callback. -export type Computed = PubTypes.Computed & { - _oldDependencySet: Set, - _callback: () -> T, - _value: T -} - --- A state object whose value is derived from other objects using a callback. -export type ForPairs = PubTypes.ForPairs & { - _oldDependencySet: Set, - _processor: (KI, VI) -> (KO, VO), - _destructor: (VO, M?) -> (), - _inputIsState: boolean, - _inputTable: PubTypes.CanBeState<{ [KI]: VI }>, - _oldInputTable: { [KI]: VI }, - _outputTable: { [KO]: VO }, - _oldOutputTable: { [KO]: VO }, - _keyIOMap: { [KI]: KO }, - _meta: { [KO]: M? }, - _keyData: { - [KI]: { - dependencySet: Set, - oldDependencySet: Set, - dependencyValues: { [PubTypes.Dependency]: any }, - }, - }, -} - --- A state object whose value is derived from other objects using a callback. -export type ForKeys = PubTypes.ForKeys & { - _oldDependencySet: Set, - _processor: (KI) -> (KO), - _destructor: (KO, M?) -> (), - _inputIsState: boolean, - _inputTable: PubTypes.CanBeState<{ [KI]: KO }>, - _oldInputTable: { [KI]: KO }, - _outputTable: { [KO]: any }, - _keyOIMap: { [KO]: KI }, - _meta: { [KO]: M? }, - _keyData: { - [KI]: { - dependencySet: Set, - oldDependencySet: Set, - dependencyValues: { [PubTypes.Dependency]: any }, - }, - }, -} - --- A state object whose value is derived from other objects using a callback. -export type ForValues = PubTypes.ForValues & { - _oldDependencySet: Set, - _processor: (VI) -> (VO), - _destructor: (VO, M?) -> (), - _inputIsState: boolean, - _inputTable: PubTypes.CanBeState<{ [VI]: VO }>, - _outputTable: { [any]: VI }, - _valueCache: { [VO]: any }, - _oldValueCache: { [VO]: any }, - _meta: { [VO]: M? }, - _valueData: { - [VI]: { - dependencySet: Set, - oldDependencySet: Set, - dependencyValues: { [PubTypes.Dependency]: any }, - }, - }, -} - --- A state object which follows another state object using tweens. -export type Tween = PubTypes.Tween & { - _goalState: State, - _tweenInfo: TweenInfo, - _prevValue: T, - _nextValue: T, - _currentValue: T, - _currentTweenInfo: TweenInfo, - _currentTweenDuration: number, - _currentTweenStartTime: number, - _currentlyAnimating: boolean -} - --- A state object which follows another state object using spring simulation. -export type Spring = PubTypes.Spring & { - _speed: PubTypes.CanBeState, - _speedIsState: boolean, - _lastSpeed: number, - _damping: PubTypes.CanBeState, - _dampingIsState: boolean, - _lastDamping: number, - _goalState: State, - _goalValue: T, - _currentType: string, - _currentValue: T, - _springPositions: {number}, - _springGoals: {number}, - _springVelocities: {number} -} - --- An object which can listen for updates on another state object. -export type Observer = PubTypes.Observer & { - _changeListeners: Set<() -> ()>, - _numChangeListeners: number -} - -return nil end,Properties={Name="Types"},Reference=70,ClassName="ModuleScript"},{Children={{Closure=function() --!nonstrict - ---[[ - Constructs a new computed state object, which follows the value of another - state object using a spring simulation. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Types = require(Package.Types) -local logError = require(Package.Logging.logError) -local logErrorNonFatal = require(Package.Logging.logErrorNonFatal) -local unpackType = require(Package.Animation.unpackType) -local SpringScheduler = require(Package.Animation.SpringScheduler) -local useDependency = require(Package.Dependencies.useDependency) -local initDependency = require(Package.Dependencies.initDependency) -local updateAll = require(Package.Dependencies.updateAll) -local xtypeof = require(Package.Utility.xtypeof) -local unwrap = require(Package.State.unwrap) - -local class = {} - -local CLASS_METATABLE = {__index = class} -local WEAK_KEYS_METATABLE = {__mode = "k"} - ---[[ - Returns the current value of this Spring object. - The object will be registered as a dependency unless `asDependency` is false. -]] -function class:get(asDependency: boolean?): any - if asDependency ~= false then - useDependency(self) - end - return self._currentValue -end - ---[[ - Sets the position of the internal springs, meaning the value of this - Spring will jump to the given value. This doesn't affect velocity. - - If the type doesn't match the current type of the spring, an error will be - thrown. -]] -function class:setPosition(newValue: PubTypes.Animatable) - local newType = typeof(newValue) - if newType ~= self._currentType then - logError("springTypeMismatch", nil, newType, self._currentType) - end - - self._springPositions = unpackType(newValue, newType) - self._currentValue = newValue - SpringScheduler.add(self) - updateAll(self) -end - ---[[ - Sets the velocity of the internal springs, overwriting the existing velocity - of this Spring. This doesn't affect position. - - If the type doesn't match the current type of the spring, an error will be - thrown. -]] -function class:setVelocity(newValue: PubTypes.Animatable) - local newType = typeof(newValue) - if newType ~= self._currentType then - logError("springTypeMismatch", nil, newType, self._currentType) - end - - self._springVelocities = unpackType(newValue, newType) - SpringScheduler.add(self) -end - ---[[ - Adds to the velocity of the internal springs, on top of the existing - velocity of this Spring. This doesn't affect position. - - If the type doesn't match the current type of the spring, an error will be - thrown. -]] -function class:addVelocity(deltaValue: PubTypes.Animatable) - local deltaType = typeof(deltaValue) - if deltaType ~= self._currentType then - logError("springTypeMismatch", nil, deltaType, self._currentType) - end - - local springDeltas = unpackType(deltaValue, deltaType) - for index, delta in ipairs(springDeltas) do - self._springVelocities[index] += delta - end - SpringScheduler.add(self) -end - ---[[ - Called when the goal state changes value, or when the speed or damping has - changed. -]] -function class:update(): boolean - local goalValue = self._goalState:get(false) - - -- figure out if this was a goal change or a speed/damping change - if goalValue == self._goalValue then - -- speed/damping change - local damping = unwrap(self._damping) - if typeof(damping) ~= "number" then - logErrorNonFatal("mistypedSpringDamping", nil, typeof(damping)) - elseif damping < 0 then - logErrorNonFatal("invalidSpringDamping", nil, damping) - else - self._currentDamping = damping - end - - local speed = unwrap(self._speed) - if typeof(speed) ~= "number" then - logErrorNonFatal("mistypedSpringSpeed", nil, typeof(speed)) - elseif speed < 0 then - logErrorNonFatal("invalidSpringSpeed", nil, speed) - else - self._currentSpeed = speed - end - - return false - else - -- goal change - reconfigure spring to target new goal - self._goalValue = goalValue - - local oldType = self._currentType - local newType = typeof(goalValue) - self._currentType = newType - - local springGoals = unpackType(goalValue, newType) - local numSprings = #springGoals - self._springGoals = springGoals - - if newType ~= oldType then - -- if the type changed, snap to the new value and rebuild the - -- position and velocity tables - self._currentValue = self._goalValue - - local springPositions = table.create(numSprings, 0) - local springVelocities = table.create(numSprings, 0) - for index, springGoal in ipairs(springGoals) do - springPositions[index] = springGoal - end - self._springPositions = springPositions - self._springVelocities = springVelocities - - -- the spring may have been animating before, so stop that - SpringScheduler.remove(self) - return true - - -- otherwise, the type hasn't changed, just the goal... - elseif numSprings == 0 then - -- if the type isn't animatable, snap to the new value - self._currentValue = self._goalValue - return true - - else - -- if it's animatable, let it animate to the goal - SpringScheduler.add(self) - return false - end - end -end - -local function Spring( - goalState: PubTypes.Value, - speed: PubTypes.CanBeState?, - damping: PubTypes.CanBeState? -): Types.Spring - -- apply defaults for speed and damping - if speed == nil then - speed = 10 - end - if damping == nil then - damping = 1 - end - - local dependencySet = {[goalState] = true} - if xtypeof(speed) == "State" then - dependencySet[speed] = true - end - if xtypeof(damping) == "State" then - dependencySet[damping] = true - end - - local self = setmetatable({ - type = "State", - kind = "Spring", - dependencySet = dependencySet, - -- if we held strong references to the dependents, then they wouldn't be - -- able to get garbage collected when they fall out of scope - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _speed = speed, - _damping = damping, - - _goalState = goalState, - _goalValue = nil, - - _currentType = nil, - _currentValue = nil, - _currentSpeed = unwrap(speed), - _currentDamping = unwrap(damping), - - _springPositions = nil, - _springGoals = nil, - _springVelocities = nil - }, CLASS_METATABLE) - - initDependency(self) - -- add this object to the goal state's dependent set - goalState.dependentSet[self] = true - self:update() - - return self -end - -return Spring end,Properties={Name="Spring"},Reference=27,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Manages batch updating of tween objects. -]] - -local RunService = game:GetService("RunService") - -local Package = script.Parent.Parent -local Types = require(Package.Types) -local lerpType = require(Package.Animation.lerpType) -local getTweenRatio = require(Package.Animation.getTweenRatio) -local updateAll = require(Package.Dependencies.updateAll) - -local TweenScheduler = {} - -type Set = {[T]: any} -type Tween = Types.Tween - -local WEAK_KEYS_METATABLE = {__mode = "k"} - --- all the tweens currently being updated -local allTweens: Set = {} -setmetatable(allTweens, WEAK_KEYS_METATABLE) - ---[[ - Adds a Tween to be updated every render step. -]] -function TweenScheduler.add(tween: Tween) - allTweens[tween] = true -end - ---[[ - Removes a Tween from the scheduler. -]] -function TweenScheduler.remove(tween: Tween) - allTweens[tween] = nil -end - ---[[ - Updates all Tween objects. -]] -local function updateAllTweens() - local now = os.clock() - -- FIXME: Typed Luau doesn't understand this loop yet - for tween: Tween in pairs(allTweens :: any) do - local currentTime = now - tween._currentTweenStartTime - - if currentTime > tween._currentTweenDuration then - if tween._currentTweenInfo.Reverses then - tween._currentValue = tween._prevValue - else - tween._currentValue = tween._nextValue - end - tween._currentlyAnimating = false - updateAll(tween) - TweenScheduler.remove(tween) - else - local ratio = getTweenRatio(tween._currentTweenInfo, currentTime) - local currentValue = lerpType(tween._prevValue, tween._nextValue, ratio) - tween._currentValue = currentValue - tween._currentlyAnimating = true - updateAll(tween) - end - end -end - -RunService:BindToRenderStep( - "__FusionTweenScheduler", - Enum.RenderPriority.First.Value, - updateAllTweens -) - -return TweenScheduler end,Properties={Name="TweenScheduler"},Reference=30,ClassName="ModuleScript"},{Closure=function() --!nonstrict - ---[[ - Constructs a new computed state object, which follows the value of another - state object using a tween. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Types = require(Package.Types) -local TweenScheduler = require(Package.Animation.TweenScheduler) -local useDependency = require(Package.Dependencies.useDependency) -local initDependency = require(Package.Dependencies.initDependency) -local logError = require(Package.Logging.logError) -local logErrorNonFatal = require(Package.Logging.logErrorNonFatal) -local xtypeof = require(Package.Utility.xtypeof) - -local class = {} - -local CLASS_METATABLE = {__index = class} -local WEAK_KEYS_METATABLE = {__mode = "k"} - ---[[ - Returns the current value of this Tween object. - The object will be registered as a dependency unless `asDependency` is false. -]] -function class:get(asDependency: boolean?): any - if asDependency ~= false then - useDependency(self) - end - return self._currentValue -end - ---[[ - Called when the goal state changes value; this will initiate a new tween. - Returns false as the current value doesn't change right away. -]] -function class:update(): boolean - local goalValue = self._goalState:get(false) - - -- if the goal hasn't changed, then this is a TweenInfo change. - -- in that case, if we're not currently animating, we can skip everything - if goalValue == self._nextValue and not self._currentlyAnimating then - return false - end - - local tweenInfo = self._tweenInfo - if self._tweenInfoIsState then - tweenInfo = tweenInfo:get() - end - - -- if we receive a bad TweenInfo, then error and stop the update - if typeof(tweenInfo) ~= "TweenInfo" then - logErrorNonFatal("mistypedTweenInfo", nil, typeof(tweenInfo)) - return false - end - - self._prevValue = self._currentValue - self._nextValue = goalValue - - self._currentTweenStartTime = os.clock() - self._currentTweenInfo = tweenInfo - - local tweenDuration = tweenInfo.DelayTime + tweenInfo.Time - if tweenInfo.Reverses then - tweenDuration += tweenInfo.Time - end - tweenDuration *= tweenInfo.RepeatCount + 1 - self._currentTweenDuration = tweenDuration - - -- start animating this tween - TweenScheduler.add(self) - - return false -end - -local function Tween( - goalState: PubTypes.StateObject, - tweenInfo: PubTypes.CanBeState? -): Types.Tween - local currentValue = goalState:get(false) - - -- apply defaults for tween info - if tweenInfo == nil then - tweenInfo = TweenInfo.new() - end - - local dependencySet = {[goalState] = true} - local tweenInfoIsState = xtypeof(tweenInfo) == "State" - - if tweenInfoIsState then - dependencySet[tweenInfo] = true - end - - local startingTweenInfo = tweenInfo - if tweenInfoIsState then - startingTweenInfo = startingTweenInfo:get() - end - - -- If we start with a bad TweenInfo, then we don't want to construct a Tween - if typeof(startingTweenInfo) ~= "TweenInfo" then - logError("mistypedTweenInfo", nil, typeof(startingTweenInfo)) - end - - local self = setmetatable({ - type = "State", - kind = "Tween", - dependencySet = dependencySet, - -- if we held strong references to the dependents, then they wouldn't be - -- able to get garbage collected when they fall out of scope - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _goalState = goalState, - _tweenInfo = tweenInfo, - _tweenInfoIsState = tweenInfoIsState, - - _prevValue = currentValue, - _nextValue = currentValue, - _currentValue = currentValue, - - -- store current tween into separately from 'real' tween into, so it - -- isn't affected by :setTweenInfo() until next change - _currentTweenInfo = tweenInfo, - _currentTweenDuration = 0, - _currentTweenStartTime = 0, - _currentlyAnimating = false - }, CLASS_METATABLE) - - initDependency(self) - -- add this object to the goal state's dependent set - goalState.dependentSet[self] = true - - return self -end - -return Tween end,Properties={Name="Tween"},Reference=29,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Packs an array of numbers into a given animatable data type. - If the type is not animatable, nil will be returned. - - FUTURE: When Luau supports singleton types, those could be used in - conjunction with intersection types to make this function fully statically - type checkable. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Oklab = require(Package.Colour.Oklab) - -local function packType(numbers: {number}, typeString: string): PubTypes.Animatable? - if typeString == "number" then - return numbers[1] - - elseif typeString == "CFrame" then - return - CFrame.new(numbers[1], numbers[2], numbers[3]) * - CFrame.fromAxisAngle( - Vector3.new(numbers[4], numbers[5], numbers[6]).Unit, - numbers[7] - ) - - elseif typeString == "Color3" then - return Oklab.from( - Vector3.new(numbers[1], numbers[2], numbers[3]), - false - ) - - elseif typeString == "ColorSequenceKeypoint" then - return ColorSequenceKeypoint.new( - numbers[4], - Oklab.from( - Vector3.new(numbers[1], numbers[2], numbers[3]), - false - ) - ) - - elseif typeString == "DateTime" then - return DateTime.fromUnixTimestampMillis(numbers[1]) - - elseif typeString == "NumberRange" then - return NumberRange.new(numbers[1], numbers[2]) - - elseif typeString == "NumberSequenceKeypoint" then - return NumberSequenceKeypoint.new(numbers[2], numbers[1], numbers[3]) - - elseif typeString == "PhysicalProperties" then - return PhysicalProperties.new(numbers[1], numbers[2], numbers[3], numbers[4], numbers[5]) - - elseif typeString == "Ray" then - return Ray.new( - Vector3.new(numbers[1], numbers[2], numbers[3]), - Vector3.new(numbers[4], numbers[5], numbers[6]) - ) - - elseif typeString == "Rect" then - return Rect.new(numbers[1], numbers[2], numbers[3], numbers[4]) - - elseif typeString == "Region3" then - -- FUTURE: support rotated Region3s if/when they become constructable - local position = Vector3.new(numbers[1], numbers[2], numbers[3]) - local halfSize = Vector3.new(numbers[4] / 2, numbers[5] / 2, numbers[6] / 2) - return Region3.new(position - halfSize, position + halfSize) - - elseif typeString == "Region3int16" then - return Region3int16.new( - Vector3int16.new(numbers[1], numbers[2], numbers[3]), - Vector3int16.new(numbers[4], numbers[5], numbers[6]) - ) - - elseif typeString == "UDim" then - return UDim.new(numbers[1], numbers[2]) - - elseif typeString == "UDim2" then - return UDim2.new(numbers[1], numbers[2], numbers[3], numbers[4]) - - elseif typeString == "Vector2" then - return Vector2.new(numbers[1], numbers[2]) - - elseif typeString == "Vector2int16" then - return Vector2int16.new(numbers[1], numbers[2]) - - elseif typeString == "Vector3" then - return Vector3.new(numbers[1], numbers[2], numbers[3]) - - elseif typeString == "Vector3int16" then - return Vector3int16.new(numbers[1], numbers[2], numbers[3]) - else - return nil - end -end - -return packType end,Properties={Name="packType"},Reference=33,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Returns a 2x2 matrix of coefficients for a given time, damping and speed. - Specifically, this returns four coefficients - posPos, posVel, velPos, and - velVel - which can be multiplied with position and velocity like so: - - local newPosition = oldPosition * posPos + oldVelocity * posVel - local newVelocity = oldPosition * velPos + oldVelocity * velVel - - Special thanks to AxisAngle for helping to improve numerical precision. -]] - -local function springCoefficients(time: number, damping: number, speed: number): (number, number, number, number) - -- if time or speed is 0, then the spring won't move - if time == 0 or speed == 0 then - return 1, 0, 0, 1 - end - local posPos, posVel, velPos, velVel - - if damping > 1 then - -- overdamped spring - -- solution to the characteristic equation: - -- z = -?? ± Sqrt[?^2 - 1] ? - -- x[t] -> x0(e^(t z2) z1 - e^(t z1) z2)/(z1 - z2) - -- + v0(e^(t z1) - e^(t z2))/(z1 - z2) - -- v[t] -> x0(z1 z2(-e^(t z1) + e^(t z2)))/(z1 - z2) - -- + v0(z1 e^(t z1) - z2 e^(t z2))/(z1 - z2) - - local scaledTime = time * speed - local alpha = math.sqrt(damping^2 - 1) - local scaledInvAlpha = -0.5 / alpha - local z1 = -alpha - damping - local z2 = 1 / z1 - local expZ1 = math.exp(scaledTime * z1) - local expZ2 = math.exp(scaledTime * z2) - - posPos = (expZ2*z1 - expZ1*z2) * scaledInvAlpha - posVel = (expZ1 - expZ2) * scaledInvAlpha / speed - velPos = (expZ2 - expZ1) * scaledInvAlpha * speed - velVel = (expZ1*z1 - expZ2*z2) * scaledInvAlpha - - elseif damping == 1 then - -- critically damped spring - -- x[t] -> x0(e^-t?)(1+t?) + v0(e^-t?)t - -- v[t] -> x0(t ?^2)(-e^-t?) + v0(1 - t?)(e^-t?) - - local scaledTime = time * speed - local expTerm = math.exp(-scaledTime) - - posPos = expTerm * (1 + scaledTime) - posVel = expTerm * time - velPos = expTerm * (-scaledTime*speed) - velVel = expTerm * (1 - scaledTime) - - else - -- underdamped spring - -- factored out of the solutions to the characteristic equation: - -- a = Sqrt[1 - ?^2] - -- x[t] -> x0(e^-t??)(a Cos[ta] + ?? Sin[ta])/a - -- + v0(e^-t??)(Sin[ta])/a - -- v[t] -> x0(-e^-t??)(a^2 + ?^2 ?^2)(Sin[ta])/a - -- + v0(e^-t??)(a Cos[ta] - ?? Sin[ta])/a - - local scaledTime = time * speed - local alpha = math.sqrt(1 - damping^2) - local invAlpha = 1 / alpha - local alphaTime = alpha * scaledTime - local expTerm = math.exp(-scaledTime*damping) - local sinTerm = expTerm * math.sin(alphaTime) - local cosTerm = expTerm * math.cos(alphaTime) - local sinInvAlpha = sinTerm*invAlpha - local sinInvAlphaDamp = sinInvAlpha*damping - - posPos = sinInvAlphaDamp + cosTerm - posVel = sinInvAlpha - velPos = -(sinInvAlphaDamp*damping + sinTerm*alpha) - velVel = cosTerm - sinInvAlphaDamp - end - - return posPos, posVel, velPos, velVel -end - -return springCoefficients - end,Properties={Name="springCoefficients"},Reference=34,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Linearly interpolates the given animatable types by a ratio. - If the types are different or not animatable, then the first value will be - returned for ratios below 0.5, and the second value for 0.5 and above. - - FIXME: This function uses a lot of redefinitions to suppress false positives - from the Luau typechecker - ideally these wouldn't be required -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Oklab = require(Package.Colour.Oklab) - -local function lerpType(from: any, to: any, ratio: number): any - local typeString = typeof(from) - - if typeof(to) == typeString then - -- both types must match for interpolation to make sense - if typeString == "number" then - local to, from = to :: number, from :: number - return (to - from) * ratio + from - - elseif typeString == "CFrame" then - local to, from = to :: CFrame, from :: CFrame - return from:Lerp(to, ratio) - - elseif typeString == "Color3" then - local to, from = to :: Color3, from :: Color3 - local fromLab = Oklab.to(from) - local toLab = Oklab.to(to) - return Oklab.from( - fromLab:Lerp(toLab, ratio), - false - ) - - elseif typeString == "ColorSequenceKeypoint" then - local to, from = to :: ColorSequenceKeypoint, from :: ColorSequenceKeypoint - local fromLab = Oklab.to(from.Value) - local toLab = Oklab.to(to.Value) - return ColorSequenceKeypoint.new( - (to.Time - from.Time) * ratio + from.Time, - Oklab.from( - fromLab:Lerp(toLab, ratio), - false - ) - ) - - elseif typeString == "DateTime" then - local to, from = to :: DateTime, from :: DateTime - return DateTime.fromUnixTimestampMillis( - (to.UnixTimestampMillis - from.UnixTimestampMillis) * ratio + from.UnixTimestampMillis - ) - - elseif typeString == "NumberRange" then - local to, from = to :: NumberRange, from :: NumberRange - return NumberRange.new( - (to.Min - from.Min) * ratio + from.Min, - (to.Max - from.Max) * ratio + from.Max - ) - - elseif typeString == "NumberSequenceKeypoint" then - local to, from = to :: NumberSequenceKeypoint, from :: NumberSequenceKeypoint - return NumberSequenceKeypoint.new( - (to.Time - from.Time) * ratio + from.Time, - (to.Value - from.Value) * ratio + from.Value, - (to.Envelope - from.Envelope) * ratio + from.Envelope - ) - - elseif typeString == "PhysicalProperties" then - local to, from = to :: PhysicalProperties, from :: PhysicalProperties - return PhysicalProperties.new( - (to.Density - from.Density) * ratio + from.Density, - (to.Friction - from.Friction) * ratio + from.Friction, - (to.Elasticity - from.Elasticity) * ratio + from.Elasticity, - (to.FrictionWeight - from.FrictionWeight) * ratio + from.FrictionWeight, - (to.ElasticityWeight - from.ElasticityWeight) * ratio + from.ElasticityWeight - ) - - elseif typeString == "Ray" then - local to, from = to :: Ray, from :: Ray - return Ray.new( - from.Origin:Lerp(to.Origin, ratio), - from.Direction:Lerp(to.Direction, ratio) - ) - - elseif typeString == "Rect" then - local to, from = to :: Rect, from :: Rect - return Rect.new( - from.Min:Lerp(to.Min, ratio), - from.Max:Lerp(to.Max, ratio) - ) - - elseif typeString == "Region3" then - local to, from = to :: Region3, from :: Region3 - -- FUTURE: support rotated Region3s if/when they become constructable - local position = from.CFrame.Position:Lerp(to.CFrame.Position, ratio) - local halfSize = from.Size:Lerp(to.Size, ratio) / 2 - return Region3.new(position - halfSize, position + halfSize) - - elseif typeString == "Region3int16" then - local to, from = to :: Region3int16, from :: Region3int16 - return Region3int16.new( - Vector3int16.new( - (to.Min.X - from.Min.X) * ratio + from.Min.X, - (to.Min.Y - from.Min.Y) * ratio + from.Min.Y, - (to.Min.Z - from.Min.Z) * ratio + from.Min.Z - ), - Vector3int16.new( - (to.Max.X - from.Max.X) * ratio + from.Max.X, - (to.Max.Y - from.Max.Y) * ratio + from.Max.Y, - (to.Max.Z - from.Max.Z) * ratio + from.Max.Z - ) - ) - - elseif typeString == "UDim" then - local to, from = to :: UDim, from :: UDim - return UDim.new( - (to.Scale - from.Scale) * ratio + from.Scale, - (to.Offset - from.Offset) * ratio + from.Offset - ) - - elseif typeString == "UDim2" then - local to, from = to :: UDim2, from :: UDim2 - return from:Lerp(to, ratio) - - elseif typeString == "Vector2" then - local to, from = to :: Vector2, from :: Vector2 - return from:Lerp(to, ratio) - - elseif typeString == "Vector2int16" then - local to, from = to :: Vector2int16, from :: Vector2int16 - return Vector2int16.new( - (to.X - from.X) * ratio + from.X, - (to.Y - from.Y) * ratio + from.Y - ) - - elseif typeString == "Vector3" then - local to, from = to :: Vector3, from :: Vector3 - return from:Lerp(to, ratio) - - elseif typeString == "Vector3int16" then - local to, from = to :: Vector3int16, from :: Vector3int16 - return Vector3int16.new( - (to.X - from.X) * ratio + from.X, - (to.Y - from.Y) * ratio + from.Y, - (to.Z - from.Z) * ratio + from.Z - ) - end - end - - -- fallback case: the types are different or not animatable - if ratio < 0.5 then - return from - else - return to - end -end - -return lerpType end,Properties={Name="lerpType"},Reference=32,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Given a `tweenInfo` and `currentTime`, returns a ratio which can be used to - tween between two values over time. -]] - -local TweenService = game:GetService("TweenService") - -local function getTweenRatio(tweenInfo: TweenInfo, currentTime: number): number - local delay = tweenInfo.DelayTime - local duration = tweenInfo.Time - local reverses = tweenInfo.Reverses - local numCycles = 1 + tweenInfo.RepeatCount - local easeStyle = tweenInfo.EasingStyle - local easeDirection = tweenInfo.EasingDirection - - local cycleDuration = delay + duration - if reverses then - cycleDuration += duration - end - - if currentTime >= cycleDuration * numCycles then - return 1 - end - - local cycleTime = currentTime % cycleDuration - - if cycleTime <= delay then - return 0 - end - - local tweenProgress = (cycleTime - delay) / duration - if tweenProgress > 1 then - tweenProgress = 2 - tweenProgress - end - - local ratio = TweenService:GetValue(tweenProgress, easeStyle, easeDirection) - return ratio -end - -return getTweenRatio end,Properties={Name="getTweenRatio"},Reference=31,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Manages batch updating of spring objects. -]] - -local RunService = game:GetService("RunService") - -local Package = script.Parent.Parent -local Types = require(Package.Types) -local packType = require(Package.Animation.packType) -local springCoefficients = require(Package.Animation.springCoefficients) -local updateAll = require(Package.Dependencies.updateAll) - -type Set = {[T]: any} -type Spring = Types.Spring - -local SpringScheduler = {} - -local EPSILON = 0.0001 -local activeSprings: Set = {} -local lastUpdateTime = os.clock() - -function SpringScheduler.add(spring: Spring) - -- we don't necessarily want to use the most accurate time - here we snap to - -- the last update time so that springs started within the same frame have - -- identical time steps - spring._lastSchedule = lastUpdateTime - spring._startDisplacements = {} - spring._startVelocities = {} - for index, goal in ipairs(spring._springGoals) do - spring._startDisplacements[index] = spring._springPositions[index] - goal - spring._startVelocities[index] = spring._springVelocities[index] - end - - activeSprings[spring] = true -end - -function SpringScheduler.remove(spring: Spring) - activeSprings[spring] = nil -end - - -local function updateAllSprings() - local springsToSleep: Set = {} - lastUpdateTime = os.clock() - - for spring in pairs(activeSprings) do - local posPos, posVel, velPos, velVel = springCoefficients(lastUpdateTime - spring._lastSchedule, spring._currentDamping, spring._currentSpeed) - - local positions = spring._springPositions - local velocities = spring._springVelocities - local startDisplacements = spring._startDisplacements - local startVelocities = spring._startVelocities - local isMoving = false - - for index, goal in ipairs(spring._springGoals) do - local oldDisplacement = startDisplacements[index] - local oldVelocity = startVelocities[index] - local newDisplacement = oldDisplacement * posPos + oldVelocity * posVel - local newVelocity = oldDisplacement * velPos + oldVelocity * velVel - - if math.abs(newDisplacement) > EPSILON or math.abs(newVelocity) > EPSILON then - isMoving = true - end - - positions[index] = newDisplacement + goal - velocities[index] = newVelocity - end - - if not isMoving then - springsToSleep[spring] = true - end - end - - for spring in pairs(activeSprings) do - spring._currentValue = packType(spring._springPositions, spring._currentType) - updateAll(spring) - end - - for spring in pairs(springsToSleep) do - activeSprings[spring] = nil - end -end - -RunService:BindToRenderStep( - "__FusionSpringScheduler", - Enum.RenderPriority.First.Value, - updateAllSprings -) - -return SpringScheduler end,Properties={Name="SpringScheduler"},Reference=28,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Unpacks an animatable type into an array of numbers. - If the type is not animatable, an empty array will be returned. - - FIXME: This function uses a lot of redefinitions to suppress false positives - from the Luau typechecker - ideally these wouldn't be required - - FUTURE: When Luau supports singleton types, those could be used in - conjunction with intersection types to make this function fully statically - type checkable. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Oklab = require(Package.Colour.Oklab) - -local function unpackType(value: any, typeString: string): {number} - if typeString == "number" then - local value = value :: number - return {value} - - elseif typeString == "CFrame" then - -- FUTURE: is there a better way of doing this? doing distance - -- calculations on `angle` may be incorrect - local axis, angle = value:ToAxisAngle() - return {value.X, value.Y, value.Z, axis.X, axis.Y, axis.Z, angle} - - elseif typeString == "Color3" then - local lab = Oklab.to(value) - return {lab.X, lab.Y, lab.Z} - - elseif typeString == "ColorSequenceKeypoint" then - local lab = Oklab.to(value.Value) - return {lab.X, lab.Y, lab.Z, value.Time} - - elseif typeString == "DateTime" then - return {value.UnixTimestampMillis} - - elseif typeString == "NumberRange" then - return {value.Min, value.Max} - - elseif typeString == "NumberSequenceKeypoint" then - return {value.Value, value.Time, value.Envelope} - - elseif typeString == "PhysicalProperties" then - return {value.Density, value.Friction, value.Elasticity, value.FrictionWeight, value.ElasticityWeight} - - elseif typeString == "Ray" then - return {value.Origin.X, value.Origin.Y, value.Origin.Z, value.Direction.X, value.Direction.Y, value.Direction.Z} - - elseif typeString == "Rect" then - return {value.Min.X, value.Min.Y, value.Max.X, value.Max.Y} - - elseif typeString == "Region3" then - -- FUTURE: support rotated Region3s if/when they become constructable - return { - value.CFrame.X, value.CFrame.Y, value.CFrame.Z, - value.Size.X, value.Size.Y, value.Size.Z - } - - elseif typeString == "Region3int16" then - return {value.Min.X, value.Min.Y, value.Min.Z, value.Max.X, value.Max.Y, value.Max.Z} - - elseif typeString == "UDim" then - return {value.Scale, value.Offset} - - elseif typeString == "UDim2" then - return {value.X.Scale, value.X.Offset, value.Y.Scale, value.Y.Offset} - - elseif typeString == "Vector2" then - return {value.X, value.Y} - - elseif typeString == "Vector2int16" then - return {value.X, value.Y} - - elseif typeString == "Vector3" then - return {value.X, value.Y, value.Z} - - elseif typeString == "Vector3int16" then - return {value.X, value.Y, value.Z} - else - return {} - end -end - -return unpackType end,Properties={Name="unpackType"},Reference=35,ClassName="ModuleScript"}},Properties={Name="Animation"},Reference=26,ClassName="Folder"},{Children={{Closure=function() --!strict - ---[[ - Constructs and returns a new instance, with options for setting properties, - event handlers and other attributes on the instance right away. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local defaultProps = require(Package.Instances.defaultProps) -local applyInstanceProps = require(Package.Instances.applyInstanceProps) -local logError= require(Package.Logging.logError) - -local function New(className: string) - return function(props: PubTypes.PropertyTable): Instance - local ok, instance = pcall(Instance.new, className) - - if not ok then - logError("cannotCreateClass", nil, className) - end - - local classDefaults = defaultProps[className] - if classDefaults ~= nil then - for defaultProp, defaultValue in pairs(classDefaults) do - instance[defaultProp] = defaultValue - end - end - - applyInstanceProps(props, instance) - - return instance - end -end - -return New end,Properties={Name="New"},Reference=48,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Stores 'sensible default' properties to be applied to instances created by - the New function. -]] - -return { - ScreenGui = { - ResetOnSpawn = false, - ZIndexBehavior = Enum.ZIndexBehavior.Sibling - }, - - BillboardGui = { - ResetOnSpawn = false, - ZIndexBehavior = Enum.ZIndexBehavior.Sibling - }, - - SurfaceGui = { - ResetOnSpawn = false, - ZIndexBehavior = Enum.ZIndexBehavior.Sibling, - - SizingMode = Enum.SurfaceGuiSizingMode.PixelsPerStud, - PixelsPerStud = 50 - }, - - Frame = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0 - }, - - ScrollingFrame = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - - ScrollBarImageColor3 = Color3.new(0, 0, 0) - }, - - TextLabel = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - - Font = Enum.Font.SourceSans, - Text = "", - TextColor3 = Color3.new(0, 0, 0), - TextSize = 14 - }, - - TextButton = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - - AutoButtonColor = false, - - Font = Enum.Font.SourceSans, - Text = "", - TextColor3 = Color3.new(0, 0, 0), - TextSize = 14 - }, - - TextBox = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - - ClearTextOnFocus = false, - - Font = Enum.Font.SourceSans, - Text = "", - TextColor3 = Color3.new(0, 0, 0), - TextSize = 14 - }, - - ImageLabel = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0 - }, - - ImageButton = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - - AutoButtonColor = false - }, - - ViewportFrame = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0 - }, - - VideoFrame = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0 - }, - - CanvasGroup = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0 - } -} - end,Properties={Name="defaultProps"},Reference=54,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - A special key for property tables, which stores a reference to the instance - in a user-provided Value object. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local logError = require(Package.Logging.logError) -local xtypeof = require(Package.Utility.xtypeof) - -local Ref = {} -Ref.type = "SpecialKey" -Ref.kind = "Ref" -Ref.stage = "observer" - -function Ref:apply(refState: any, applyTo: Instance, cleanupTasks: {PubTypes.Task}) - if xtypeof(refState) ~= "State" or refState.kind ~= "Value" then - logError("invalidRefType") - else - refState:set(applyTo) - table.insert(cleanupTasks, function() - refState:set(nil) - end) - end -end - -return Ref end,Properties={Name="Ref"},Reference=52,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Processes and returns an existing instance, with options for setting - properties, event handlers and other attributes on the instance. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local applyInstanceProps = require(Package.Instances.applyInstanceProps) - -local function Hydrate(target: Instance) - return function(props: PubTypes.PropertyTable): Instance - applyInstanceProps(props, target) - return target - end -end - -return Hydrate end,Properties={Name="Hydrate"},Reference=47,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - A special key for property tables, which parents any given descendants into - an instance. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local logWarn = require(Package.Logging.logWarn) -local Observer = require(Package.State.Observer) -local xtypeof = require(Package.Utility.xtypeof) - -type Set = {[T]: boolean} - --- Experimental flag: name children based on the key used in the [Children] table -local EXPERIMENTAL_AUTO_NAMING = false - -local Children = {} -Children.type = "SpecialKey" -Children.kind = "Children" -Children.stage = "descendants" - -function Children:apply(propValue: any, applyTo: Instance, cleanupTasks: {PubTypes.Task}) - local newParented: Set = {} - local oldParented: Set = {} - - -- save disconnection functions for state object observers - local newDisconnects: {[PubTypes.StateObject]: () -> ()} = {} - local oldDisconnects: {[PubTypes.StateObject]: () -> ()} = {} - - local updateQueued = false - local queueUpdate: () -> () - - -- Rescans this key's value to find new instances to parent and state objects - -- to observe for changes; then unparents instances no longer found and - -- disconnects observers for state objects no longer present. - local function updateChildren() - if not updateQueued then - return -- this update may have been canceled by destruction, etc. - end - updateQueued = false - - oldParented, newParented = newParented, oldParented - oldDisconnects, newDisconnects = newDisconnects, oldDisconnects - table.clear(newParented) - table.clear(newDisconnects) - - local function processChild(child: any, autoName: string?) - local kind = xtypeof(child) - - if kind == "Instance" then - -- case 1; single instance - - newParented[child] = true - if oldParented[child] == nil then - -- wasn't previously present - - -- TODO: check for ancestry conflicts here - child.Parent = applyTo - else - -- previously here; we want to reuse, so remove from old - -- set so we don't encounter it during unparenting - oldParented[child] = nil - end - - if EXPERIMENTAL_AUTO_NAMING and autoName ~= nil then - child.Name = autoName - end - - elseif kind == "State" then - -- case 2; state object - - local value = child:get(false) - -- allow nil to represent the absence of a child - if value ~= nil then - processChild(value, autoName) - end - - local disconnect = oldDisconnects[child] - if disconnect == nil then - -- wasn't previously present - disconnect = Observer(child):onChange(queueUpdate) - else - -- previously here; we want to reuse, so remove from old - -- set so we don't encounter it during unparenting - oldDisconnects[child] = nil - end - - newDisconnects[child] = disconnect - - elseif kind == "table" then - -- case 3; table of objects - - for key, subChild in pairs(child) do - local keyType = typeof(key) - local subAutoName: string? = nil - - if keyType == "string" then - subAutoName = key - elseif keyType == "number" and autoName ~= nil then - subAutoName = autoName .. "_" .. key - end - - processChild(subChild, subAutoName) - end - - else - logWarn("unrecognisedChildType", kind) - end - end - - if propValue ~= nil then - -- `propValue` is set to nil on cleanup, so we don't process children - -- in that case - processChild(propValue) - end - - -- unparent any children that are no longer present - for oldInstance in pairs(oldParented) do - oldInstance.Parent = nil - end - - -- disconnect observers which weren't reused - for oldState, disconnect in pairs(oldDisconnects) do - disconnect() - end - end - - queueUpdate = function() - if not updateQueued then - updateQueued = true - task.defer(updateChildren) - end - end - - table.insert(cleanupTasks, function() - propValue = nil - updateQueued = true - updateChildren() - end) - - -- perform initial child parenting - updateQueued = true - updateChildren() -end - -return Children :: PubTypes.SpecialKey end,Properties={Name="Children"},Reference=45,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Constructs special keys for property tables which connect event listeners to - an instance. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local logError = require(Package.Logging.logError) - -local function getProperty_unsafe(instance: Instance, property: string) - return (instance :: any)[property] -end - -local function OnEvent(eventName: string): PubTypes.SpecialKey - local eventKey = {} - eventKey.type = "SpecialKey" - eventKey.kind = "OnEvent" - eventKey.stage = "observer" - - function eventKey:apply(callback: any, applyTo: Instance, cleanupTasks: {PubTypes.Task}) - local ok, event = pcall(getProperty_unsafe, applyTo, eventName) - if not ok or typeof(event) ~= "RBXScriptSignal" then - logError("cannotConnectEvent", nil, applyTo.ClassName, eventName) - elseif typeof(callback) ~= "function" then - logError("invalidEventHandler", nil, eventName) - else - table.insert(cleanupTasks, event:Connect(callback)) - end - end - - return eventKey -end - -return OnEvent end,Properties={Name="OnEvent"},Reference=50,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Constructs special keys for property tables which connect property change - listeners to an instance. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local logError = require(Package.Logging.logError) - -local function OnChange(propertyName: string): PubTypes.SpecialKey - local changeKey = {} - changeKey.type = "SpecialKey" - changeKey.kind = "OnChange" - changeKey.stage = "observer" - - function changeKey:apply(callback: any, applyTo: Instance, cleanupTasks: {PubTypes.Task}) - local ok, event = pcall(applyTo.GetPropertyChangedSignal, applyTo, propertyName) - if not ok then - logError("cannotConnectChange", nil, applyTo.ClassName, propertyName) - elseif typeof(callback) ~= "function" then - logError("invalidChangeHandler", nil, propertyName) - else - table.insert(cleanupTasks, event:Connect(function() - callback((applyTo :: any)[propertyName]) - end)) - end - end - - return changeKey -end - -return OnChange end,Properties={Name="OnChange"},Reference=49,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - A special key for property tables, which allows users to extract values from - an instance into an automatically-updated Value object. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local logError = require(Package.Logging.logError) -local xtypeof = require(Package.Utility.xtypeof) - -local function Out(propertyName: string): PubTypes.SpecialKey - local outKey = {} - outKey.type = "SpecialKey" - outKey.kind = "Out" - outKey.stage = "observer" - - function outKey:apply(outState: any, applyTo: Instance, cleanupTasks: { PubTypes.Task }) - local ok, event = pcall(applyTo.GetPropertyChangedSignal, applyTo, propertyName) - if not ok then - logError("invalidOutProperty", nil, applyTo.ClassName, propertyName) - elseif xtypeof(outState) ~= "State" or outState.kind ~= "Value" then - logError("invalidOutType") - else - outState:set((applyTo :: any)[propertyName]) - table.insert( - cleanupTasks, - event:Connect(function() - outState:set((applyTo :: any)[propertyName]) - end) - ) - table.insert(cleanupTasks, function() - outState:set(nil) - end) - end - end - - return outKey -end - -return Out - end,Properties={Name="Out"},Reference=51,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - A special key for property tables, which adds user-specified tasks to be run - when the instance is destroyed. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) - -local Cleanup = {} -Cleanup.type = "SpecialKey" -Cleanup.kind = "Cleanup" -Cleanup.stage = "observer" - -function Cleanup:apply(userTask: any, applyTo: Instance, cleanupTasks: {PubTypes.Task}) - table.insert(cleanupTasks, userTask) -end - -return Cleanup end,Properties={Name="Cleanup"},Reference=46,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Applies a table of properties to an instance, including binding to any - given state objects and applying any special keys. - - No strong reference is kept by default - special keys should take care not - to accidentally hold strong references to instances forever. - - If a key is used twice, an error will be thrown. This is done to avoid - double assignments or double bindings. However, some special keys may want - to enable such assignments - in which case unique keys should be used for - each occurence. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local cleanup = require(Package.Utility.cleanup) -local xtypeof = require(Package.Utility.xtypeof) -local logError = require(Package.Logging.logError) -local Observer = require(Package.State.Observer) - -local function setProperty_unsafe(instance: Instance, property: string, value: any) - (instance :: any)[property] = value -end - -local function testPropertyAssignable(instance: Instance, property: string) - (instance :: any)[property] = (instance :: any)[property] -end - -local function setProperty(instance: Instance, property: string, value: any) - if not pcall(setProperty_unsafe, instance, property, value) then - if not pcall(testPropertyAssignable, instance, property) then - if instance == nil then - -- reference has been lost - logError("setPropertyNilRef", nil, property, tostring(value)) - else - -- property is not assignable - logError("cannotAssignProperty", nil, instance.ClassName, property) - end - else - -- property is assignable, but this specific assignment failed - -- this typically implies the wrong type was received - local givenType = typeof(value) - local expectedType = typeof((instance :: any)[property]) - logError("invalidPropertyType", nil, instance.ClassName, property, expectedType, givenType) - end - end -end - -local function bindProperty(instance: Instance, property: string, value: PubTypes.CanBeState, cleanupTasks: {PubTypes.Task}) - if xtypeof(value) == "State" then - -- value is a state object - assign and observe for changes - local willUpdate = false - local function updateLater() - if not willUpdate then - willUpdate = true - task.defer(function() - willUpdate = false - setProperty(instance, property, value:get(false)) - end) - end - end - - setProperty(instance, property, value:get(false)) - table.insert(cleanupTasks, Observer(value :: any):onChange(updateLater)) - else - -- value is a constant - assign once only - setProperty(instance, property, value) - end -end - -local function applyInstanceProps(props: PubTypes.PropertyTable, applyTo: Instance) - local specialKeys = { - self = {} :: {[PubTypes.SpecialKey]: any}, - descendants = {} :: {[PubTypes.SpecialKey]: any}, - ancestor = {} :: {[PubTypes.SpecialKey]: any}, - observer = {} :: {[PubTypes.SpecialKey]: any} - } - local cleanupTasks = {} - - for key, value in pairs(props) do - local keyType = xtypeof(key) - - if keyType == "string" then - if key ~= "Parent" then - bindProperty(applyTo, key :: string, value, cleanupTasks) - end - elseif keyType == "SpecialKey" then - local stage = (key :: PubTypes.SpecialKey).stage - local keys = specialKeys[stage] - if keys == nil then - logError("unrecognisedPropertyStage", nil, stage) - else - keys[key] = value - end - else - -- we don't recognise what this key is supposed to be - logError("unrecognisedPropertyKey", nil, xtypeof(key)) - end - end - - for key, value in pairs(specialKeys.self) do - key:apply(value, applyTo, cleanupTasks) - end - for key, value in pairs(specialKeys.descendants) do - key:apply(value, applyTo, cleanupTasks) - end - - if props.Parent ~= nil then - bindProperty(applyTo, "Parent", props.Parent, cleanupTasks) - end - - for key, value in pairs(specialKeys.ancestor) do - key:apply(value, applyTo, cleanupTasks) - end - for key, value in pairs(specialKeys.observer) do - key:apply(value, applyTo, cleanupTasks) - end - - applyTo.Destroying:Connect(function() - cleanup(cleanupTasks) - end) -end - -return applyInstanceProps end,Properties={Name="applyInstanceProps"},Reference=53,ClassName="ModuleScript"}},Properties={Name="Instances"},Reference=44,ClassName="Folder"},{Children={{Closure=function() --!strict - ---[[ - Provides functions for converting Color3s into Oklab space, for more - perceptually uniform colour blending. - - See: https://bottosson.github.io/posts/oklab/ -]] - -local Oklab = {} - --- Converts a Color3 in RGB space to a Vector3 in Oklab space. -function Oklab.to(rgb: Color3): Vector3 - local l = rgb.R * 0.4122214708 + rgb.G * 0.5363325363 + rgb.B * 0.0514459929 - local m = rgb.R * 0.2119034982 + rgb.G * 0.6806995451 + rgb.B * 0.1073969566 - local s = rgb.R * 0.0883024619 + rgb.G * 0.2817188376 + rgb.B * 0.6299787005 - - local lRoot = l ^ (1/3) - local mRoot = m ^ (1/3) - local sRoot = s ^ (1/3) - - return Vector3.new( - lRoot * 0.2104542553 + mRoot * 0.7936177850 - sRoot * 0.0040720468, - lRoot * 1.9779984951 - mRoot * 2.4285922050 + sRoot * 0.4505937099, - lRoot * 0.0259040371 + mRoot * 0.7827717662 - sRoot * 0.8086757660 - ) -end - --- Converts a Vector3 in CIELAB space to a Color3 in RGB space. --- The Color3 will be clamped by default unless specified otherwise. -function Oklab.from(lab: Vector3, unclamped: boolean?): Color3 - local lRoot = lab.X + lab.Y * 0.3963377774 + lab.Z * 0.2158037573 - local mRoot = lab.X - lab.Y * 0.1055613458 - lab.Z * 0.0638541728 - local sRoot = lab.X - lab.Y * 0.0894841775 - lab.Z * 1.2914855480 - - local l = lRoot ^ 3 - local m = mRoot ^ 3 - local s = sRoot ^ 3 - - local red = l * 4.0767416621 - m * 3.3077115913 + s * 0.2309699292 - local green = l * -1.2684380046 + m * 2.6097574011 - s * 0.3413193965 - local blue = l * -0.0041960863 - m * 0.7034186147 + s * 1.7076147010 - - if not unclamped then - red = math.clamp(red, 0, 1) - green = math.clamp(green, 0, 1) - blue = math.clamp(blue, 0, 1) - end - - return Color3.new(red, green, blue) -end - -return Oklab - end,Properties={Name="Oklab"},Reference=37,ClassName="ModuleScript"}},Properties={Name="Colour"},Reference=36,ClassName="Folder"},{Children={{Closure=function() --!nonstrict - ---[[ - Constructs and returns objects which can be used to model derived reactive - state. -]] - -local Package = script.Parent.Parent -local Types = require(Package.Types) -local captureDependencies = require(Package.Dependencies.captureDependencies) -local initDependency = require(Package.Dependencies.initDependency) -local useDependency = require(Package.Dependencies.useDependency) -local logErrorNonFatal = require(Package.Logging.logErrorNonFatal) -local logWarn = require(Package.Logging.logWarn) -local isSimilar = require(Package.Utility.isSimilar) -local needsDestruction = require(Package.Utility.needsDestruction) - -local class = {} - -local CLASS_METATABLE = {__index = class} -local WEAK_KEYS_METATABLE = {__mode = "k"} - ---[[ - Returns the last cached value calculated by this Computed object. - The computed object will be registered as a dependency unless `asDependency` - is false. -]] -function class:get(asDependency: boolean?): any - if asDependency ~= false then - useDependency(self) - end - return self._value -end - ---[[ - Recalculates this Computed's cached value and dependencies. - Returns true if it changed, or false if it's identical. -]] -function class:update(): boolean - -- remove this object from its dependencies' dependent sets - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = nil - end - - -- we need to create a new, empty dependency set to capture dependencies - -- into, but in case there's an error, we want to restore our old set of - -- dependencies. by using this table-swapping solution, we can avoid the - -- overhead of allocating new tables each update. - self._oldDependencySet, self.dependencySet = self.dependencySet, self._oldDependencySet - table.clear(self.dependencySet) - - local ok, newValue, newMetaValue = captureDependencies(self.dependencySet, self._processor) - - if ok then - if self._destructor == nil and needsDestruction(newValue) then - logWarn("destructorNeededComputed") - end - - if newMetaValue ~= nil then - logWarn("multiReturnComputed") - end - - local oldValue = self._value - local similar = isSimilar(oldValue, newValue) - if self._destructor ~= nil then - self._destructor(oldValue) - end - self._value = newValue - - -- add this object to the dependencies' dependent sets - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = true - end - - return not similar - else - -- this needs to be non-fatal, because otherwise it'd disrupt the - -- update process - logErrorNonFatal("computedCallbackError", newValue) - - -- restore old dependencies, because the new dependencies may be corrupt - self._oldDependencySet, self.dependencySet = self.dependencySet, self._oldDependencySet - - -- restore this object in the dependencies' dependent sets - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = true - end - - return false - end -end - -local function Computed(processor: () -> T, destructor: ((T) -> ())?): Types.Computed - local self = setmetatable({ - type = "State", - kind = "Computed", - dependencySet = {}, - -- if we held strong references to the dependents, then they wouldn't be - -- able to get garbage collected when they fall out of scope - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _oldDependencySet = {}, - _processor = processor, - _destructor = destructor, - _value = nil, - }, CLASS_METATABLE) - - initDependency(self) - self:update() - - return self -end - -return Computed end,Properties={Name="Computed"},Reference=63,ClassName="ModuleScript"},{Closure=function() --!nonstrict - ---[[ - Constructs a new state object which can listen for updates on another state - object. - - FIXME: enabling strict types here causes free types to leak -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Types = require(Package.Types) -local initDependency = require(Package.Dependencies.initDependency) - -type Set = {[T]: any} - -local class = {} -local CLASS_METATABLE = {__index = class} - --- Table used to hold Observer objects in memory. -local strongRefs: Set = {} - ---[[ - Called when the watched state changes value. -]] -function class:update(): boolean - for _, callback in pairs(self._changeListeners) do - task.spawn(callback) - end - return false -end - ---[[ - Adds a change listener. When the watched state changes value, the listener - will be fired. - - Returns a function which, when called, will disconnect the change listener. - As long as there is at least one active change listener, this Observer - will be held in memory, preventing GC, so disconnecting is important. -]] -function class:onChange(callback: () -> ()): () -> () - local uniqueIdentifier = {} - - self._numChangeListeners += 1 - self._changeListeners[uniqueIdentifier] = callback - - -- disallow gc (this is important to make sure changes are received) - strongRefs[self] = true - - local disconnected = false - return function() - if disconnected then - return - end - disconnected = true - self._changeListeners[uniqueIdentifier] = nil - self._numChangeListeners -= 1 - - if self._numChangeListeners == 0 then - -- allow gc if all listeners are disconnected - strongRefs[self] = nil - end - end -end - -local function Observer(watchedState: PubTypes.Value): Types.Observer - local self = setmetatable({ - type = "State", - kind = "Observer", - dependencySet = {[watchedState] = true}, - dependentSet = {}, - _changeListeners = {}, - _numChangeListeners = 0, - }, CLASS_METATABLE) - - initDependency(self) - -- add this object to the watched state's dependent set - watchedState.dependentSet[self] = true - - return self -end - -return Observer end,Properties={Name="Observer"},Reference=67,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - A common interface for accessing the values of state objects or constants. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local xtypeof = require(Package.Utility.xtypeof) - -local function unwrap(item: PubTypes.CanBeState, useDependency: boolean?): T - return if xtypeof(item) == "State" then (item :: PubTypes.StateObject):get(useDependency) else (item :: T) -end - -return unwrap end,Properties={Name="unwrap"},Reference=69,ClassName="ModuleScript"},{Closure=function() --!nonstrict - ---[[ - Constructs a new ForPairs object which maps pairs of a table using - a `processor` function. - - Optionally, a `destructor` function can be specified for cleaning up values. - If omitted, the default cleanup function will be used instead. - - Additionally, a `meta` table/value can optionally be returned to pass data created - when running the processor to the destructor when the created object is cleaned up. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Types = require(Package.Types) -local captureDependencies = require(Package.Dependencies.captureDependencies) -local initDependency = require(Package.Dependencies.initDependency) -local useDependency = require(Package.Dependencies.useDependency) -local parseError = require(Package.Logging.parseError) -local logErrorNonFatal = require(Package.Logging.logErrorNonFatal) -local logError = require(Package.Logging.logError) -local logWarn = require(Package.Logging.logWarn) -local cleanup = require(Package.Utility.cleanup) -local needsDestruction = require(Package.Utility.needsDestruction) - -local class = {} - -local CLASS_METATABLE = { __index = class } -local WEAK_KEYS_METATABLE = { __mode = "k" } - ---[[ - Returns the current value of this ForPairs object. - The object will be registered as a dependency unless `asDependency` is false. -]] -function class:get(asDependency: boolean?): any - if asDependency ~= false then - useDependency(self) - end - return self._outputTable -end - ---[[ - Called when the original table is changed. - - This will firstly find any keys meeting any of the following criteria: - - - they were not previously present - - their associated value has changed - - a dependency used during generation of this value has changed - - It will recalculate those key/value pairs, storing information about any - dependencies used in the processor callback during value generation, and - save the new key/value pair to the output array. If it is overwriting an - older key/value pair, that older pair will be passed to the destructor - for cleanup. - - Finally, this function will find keys that are no longer present, and remove - their key/value pairs from the output table and pass them to the destructor. -]] -function class:update(): boolean - local inputIsState = self._inputIsState - local newInputTable = if inputIsState then self._inputTable:get(false) else self._inputTable - local oldInputTable = self._oldInputTable - - local keyIOMap = self._keyIOMap - local meta = self._meta - - local didChange = false - - - -- clean out main dependency set - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = nil - end - - self._oldDependencySet, self.dependencySet = self.dependencySet, self._oldDependencySet - table.clear(self.dependencySet) - - -- if the input table is a state object, add it as a dependency - if inputIsState then - self._inputTable.dependentSet[self] = true - self.dependencySet[self._inputTable] = true - end - - -- clean out output table - self._oldOutputTable, self._outputTable = self._outputTable, self._oldOutputTable - - local oldOutputTable = self._oldOutputTable - local newOutputTable = self._outputTable - table.clear(newOutputTable) - - -- Step 1: find key/value pairs that changed or were not previously present - - for newInKey, newInValue in pairs(newInputTable) do - -- get or create key data - local keyData = self._keyData[newInKey] - - if keyData == nil then - keyData = { - dependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - oldDependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - dependencyValues = setmetatable({}, WEAK_KEYS_METATABLE), - } - self._keyData[newInKey] = keyData - end - - - -- check if the pair is new or changed - local shouldRecalculate = oldInputTable[newInKey] ~= newInValue - - -- check if the pair's dependencies have changed - if shouldRecalculate == false then - for dependency, oldValue in pairs(keyData.dependencyValues) do - if oldValue ~= dependency:get(false) then - shouldRecalculate = true - break - end - end - end - - - -- recalculate the output pair if necessary - if shouldRecalculate then - keyData.oldDependencySet, keyData.dependencySet = keyData.dependencySet, keyData.oldDependencySet - table.clear(keyData.dependencySet) - - local processOK, newOutKey, newOutValue, newMetaValue = captureDependencies( - keyData.dependencySet, - self._processor, - newInKey, - newInValue - ) - - if processOK then - if self._destructor == nil and (needsDestruction(newOutKey) or needsDestruction(newOutValue) or needsDestruction(newMetaValue)) then - logWarn("destructorNeededForPairs") - end - - -- if this key was already written to on this run-through, throw a fatal error. - if newOutputTable[newOutKey] ~= nil then - -- figure out which key/value pair previously wrote to this key - local previousNewKey, previousNewValue - for inKey, outKey in pairs(keyIOMap) do - if outKey == newOutKey then - previousNewValue = newInputTable[inKey] - if previousNewValue ~= nil then - previousNewKey = inKey - break - end - end - end - - if previousNewKey ~= nil then - logError( - "forPairsKeyCollision", - nil, - tostring(newOutKey), - tostring(previousNewKey), - tostring(previousNewValue), - tostring(newInKey), - tostring(newInValue) - ) - end - end - - local oldOutValue = oldOutputTable[newOutKey] - - if oldOutValue ~= newOutValue then - local oldMetaValue = meta[newOutKey] - if oldOutValue ~= nil then - local destructOK, err = xpcall(self._destructor or cleanup, parseError, newOutKey, oldOutValue, oldMetaValue) - if not destructOK then - logErrorNonFatal("forPairsDestructorError", err) - end - end - - oldOutputTable[newOutKey] = nil - end - - -- update the stored data for this key/value pair - oldInputTable[newInKey] = newInValue - keyIOMap[newInKey] = newOutKey - meta[newOutKey] = newMetaValue - newOutputTable[newOutKey] = newOutValue - - -- if we had to recalculate the output, then we did change - didChange = true - else - -- restore old dependencies, because the new dependencies may be corrupt - keyData.oldDependencySet, keyData.dependencySet = keyData.dependencySet, keyData.oldDependencySet - - logErrorNonFatal("forPairsProcessorError", newOutKey) - end - else - local storedOutKey = keyIOMap[newInKey] - - -- check for key collision - if newOutputTable[storedOutKey] ~= nil then - -- figure out which key/value pair previously wrote to this key - local previousNewKey, previousNewValue - for inKey, outKey in pairs(keyIOMap) do - if storedOutKey == outKey then - previousNewValue = newInputTable[inKey] - - if previousNewValue ~= nil then - previousNewKey = inKey - break - end - end - end - - if previousNewKey ~= nil then - logError( - "forPairsKeyCollision", - nil, - tostring(storedOutKey), - tostring(previousNewKey), - tostring(previousNewValue), - tostring(newInKey), - tostring(newInValue) - ) - end - end - - -- copy the stored key/value pair into the new output table - newOutputTable[storedOutKey] = oldOutputTable[storedOutKey] - end - - - -- save dependency values and add to main dependency set - for dependency in pairs(keyData.dependencySet) do - keyData.dependencyValues[dependency] = dependency:get(false) - - self.dependencySet[dependency] = true - dependency.dependentSet[self] = true - end - end - - -- STEP 2: find keys that were removed - for oldOutKey, oldOutValue in pairs(oldOutputTable) do - -- check if this key/value pair is in the new output table - if newOutputTable[oldOutKey] ~= oldOutValue then - -- clean up the old output pair - local oldMetaValue = meta[oldOutKey] - if oldOutValue ~= nil then - local destructOK, err = xpcall(self._destructor or cleanup, parseError, oldOutKey, oldOutValue, oldMetaValue) - if not destructOK then - logErrorNonFatal("forPairsDestructorError", err) - end - end - - -- check if the key was completely removed from the output table - if newOutputTable[oldOutKey] == nil then - meta[oldOutKey] = nil - self._keyData[oldOutKey] = nil - end - - didChange = true - end - end - - for key in pairs(oldInputTable) do - if newInputTable[key] == nil then - oldInputTable[key] = nil - keyIOMap[key] = nil - end - end - - return didChange -end - -local function ForPairs( - inputTable: PubTypes.CanBeState<{ [KI]: VI }>, - processor: (KI, VI) -> (KO, VO, M?), - destructor: (KO, VO, M?) -> ()? -): Types.ForPairs - - local inputIsState = inputTable.type == "State" and typeof(inputTable.get) == "function" - - local self = setmetatable({ - type = "State", - kind = "ForPairs", - dependencySet = {}, - -- if we held strong references to the dependents, then they wouldn't be - -- able to get garbage collected when they fall out of scope - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _oldDependencySet = {}, - - _processor = processor, - _destructor = destructor, - _inputIsState = inputIsState, - - _inputTable = inputTable, - _oldInputTable = {}, - _outputTable = {}, - _oldOutputTable = {}, - _keyIOMap = {}, - _keyData = {}, - _meta = {}, - }, CLASS_METATABLE) - - initDependency(self) - self:update() - - return self -end - -return ForPairs end,Properties={Name="ForPairs"},Reference=65,ClassName="ModuleScript"},{Closure=function() --!nonstrict - ---[[ - Constructs and returns objects which can be used to model independent - reactive state. -]] - -local Package = script.Parent.Parent -local Types = require(Package.Types) -local useDependency = require(Package.Dependencies.useDependency) -local initDependency = require(Package.Dependencies.initDependency) -local updateAll = require(Package.Dependencies.updateAll) -local isSimilar = require(Package.Utility.isSimilar) - -local class = {} - -local CLASS_METATABLE = {__index = class} -local WEAK_KEYS_METATABLE = {__mode = "k"} - ---[[ - Returns the value currently stored in this State object. - The state object will be registered as a dependency unless `asDependency` is - false. -]] -function class:get(asDependency: boolean?): any - if asDependency ~= false then - useDependency(self) - end - return self._value -end - ---[[ - Updates the value stored in this State object. - - If `force` is enabled, this will skip equality checks and always update the - state object and any dependents - use this with care as this can lead to - unnecessary updates. -]] -function class:set(newValue: any, force: boolean?) - local oldValue = self._value - if force or not isSimilar(oldValue, newValue) then - self._value = newValue - updateAll(self) - end -end - -local function Value(initialValue: T): Types.State - local self = setmetatable({ - type = "State", - kind = "Value", - -- if we held strong references to the dependents, then they wouldn't be - -- able to get garbage collected when they fall out of scope - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _value = initialValue - }, CLASS_METATABLE) - - initDependency(self) - - return self -end - -return Value end,Properties={Name="Value"},Reference=68,ClassName="ModuleScript"},{Closure=function() --!nonstrict - ---[[ - Constructs a new ForKeys state object which maps keys of an array using - a `processor` function. - - Optionally, a `destructor` function can be specified for cleaning up - calculated keys. If omitted, the default cleanup function will be used instead. - - Optionally, a `meta` value can be returned in the processor function as the - second value to pass data from the processor to the destructor. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Types = require(Package.Types) -local captureDependencies = require(Package.Dependencies.captureDependencies) -local initDependency = require(Package.Dependencies.initDependency) -local useDependency = require(Package.Dependencies.useDependency) -local parseError = require(Package.Logging.parseError) -local logErrorNonFatal = require(Package.Logging.logErrorNonFatal) -local logError = require(Package.Logging.logError) -local logWarn = require(Package.Logging.logWarn) -local cleanup = require(Package.Utility.cleanup) -local needsDestruction = require(Package.Utility.needsDestruction) - -local class = {} - -local CLASS_METATABLE = { __index = class } -local WEAK_KEYS_METATABLE = { __mode = "k" } - ---[[ - Returns the current value of this ForKeys object. - The object will be registered as a dependency unless `asDependency` is false. -]] -function class:get(asDependency: boolean?): any - if asDependency ~= false then - useDependency(self) - end - return self._outputTable -end - - ---[[ - Called when the original table is changed. - - This will firstly find any keys meeting any of the following criteria: - - - they were not previously present - - a dependency used during generation of this value has changed - - It will recalculate those key pairs, storing information about any - dependencies used in the processor callback during output key generation, - and save the new key to the output array with the same value. If it is - overwriting an older value, that older value will be passed to the - destructor for cleanup. - - Finally, this function will find keys that are no longer present, and remove - their output keys from the output table and pass them to the destructor. -]] - -function class:update(): boolean - local inputIsState = self._inputIsState - local newInputTable = if inputIsState then self._inputTable:get(false) else self._inputTable - local oldInputTable = self._oldInputTable - local outputTable = self._outputTable - - local keyOIMap = self._keyOIMap - local keyIOMap = self._keyIOMap - local meta = self._meta - - local didChange = false - - - -- clean out main dependency set - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = nil - end - - self._oldDependencySet, self.dependencySet = self.dependencySet, self._oldDependencySet - table.clear(self.dependencySet) - - -- if the input table is a state object, add it as a dependency - if inputIsState then - self._inputTable.dependentSet[self] = true - self.dependencySet[self._inputTable] = true - end - - - -- STEP 1: find keys that changed or were not previously present - for newInKey, value in pairs(newInputTable) do - -- get or create key data - local keyData = self._keyData[newInKey] - - if keyData == nil then - keyData = { - dependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - oldDependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - dependencyValues = setmetatable({}, WEAK_KEYS_METATABLE), - } - self._keyData[newInKey] = keyData - end - - -- check if the key is new - local shouldRecalculate = oldInputTable[newInKey] == nil - - -- check if the key's dependencies have changed - if shouldRecalculate == false then - for dependency, oldValue in pairs(keyData.dependencyValues) do - if oldValue ~= dependency:get(false) then - shouldRecalculate = true - break - end - end - end - - - -- recalculate the output key if necessary - if shouldRecalculate then - keyData.oldDependencySet, keyData.dependencySet = keyData.dependencySet, keyData.oldDependencySet - table.clear(keyData.dependencySet) - - local processOK, newOutKey, newMetaValue = captureDependencies( - keyData.dependencySet, - self._processor, - newInKey - ) - - if processOK then - if self._destructor == nil and (needsDestruction(newOutKey) or needsDestruction(newMetaValue)) then - logWarn("destructorNeededForKeys") - end - - local oldInKey = keyOIMap[newOutKey] - local oldOutKey = keyIOMap[newInKey] - - -- check for key collision - if oldInKey ~= newInKey and newInputTable[oldInKey] ~= nil then - logError("forKeysKeyCollision", nil, tostring(newOutKey), tostring(oldInKey), tostring(newOutKey)) - end - - -- check for a changed output key - if oldOutKey ~= newOutKey and keyOIMap[oldOutKey] == newInKey then - -- clean up the old calculated value - local oldMetaValue = meta[oldOutKey] - - local destructOK, err = xpcall(self._destructor or cleanup, parseError, oldOutKey, oldMetaValue) - if not destructOK then - logErrorNonFatal("forKeysDestructorError", err) - end - - keyOIMap[oldOutKey] = nil - outputTable[oldOutKey] = nil - meta[oldOutKey] = nil - end - - -- update the stored data for this key - oldInputTable[newInKey] = value - meta[newOutKey] = newMetaValue - keyOIMap[newOutKey] = newInKey - keyIOMap[newInKey] = newOutKey - outputTable[newOutKey] = value - - -- if we had to recalculate the output, then we did change - didChange = true - else - -- restore old dependencies, because the new dependencies may be corrupt - keyData.oldDependencySet, keyData.dependencySet = keyData.dependencySet, keyData.oldDependencySet - - logErrorNonFatal("forKeysProcessorError", newOutKey) - end - end - - - -- save dependency values and add to main dependency set - for dependency in pairs(keyData.dependencySet) do - keyData.dependencyValues[dependency] = dependency:get(false) - - self.dependencySet[dependency] = true - dependency.dependentSet[self] = true - end - end - - - -- STEP 2: find keys that were removed - for outputKey, inputKey in pairs(keyOIMap) do - if newInputTable[inputKey] == nil then - -- clean up the old calculated value - local oldMetaValue = meta[outputKey] - - local destructOK, err = xpcall(self._destructor or cleanup, parseError, outputKey, oldMetaValue) - if not destructOK then - logErrorNonFatal("forKeysDestructorError", err) - end - - -- remove data - oldInputTable[inputKey] = nil - meta[outputKey] = nil - keyOIMap[outputKey] = nil - keyIOMap[inputKey] = nil - outputTable[outputKey] = nil - self._keyData[inputKey] = nil - - -- if we removed a key, then the table/state changed - didChange = true - end - end - - return didChange -end - -local function ForKeys( - inputTable: PubTypes.CanBeState<{ [KI]: any }>, - processor: (KI) -> (KO, M?), - destructor: (KO, M?) -> ()? -): Types.ForKeys - - local inputIsState = inputTable.type == "State" and typeof(inputTable.get) == "function" - - local self = setmetatable({ - type = "State", - kind = "ForKeys", - dependencySet = {}, - -- if we held strong references to the dependents, then they wouldn't be - -- able to get garbage collected when they fall out of scope - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _oldDependencySet = {}, - - _processor = processor, - _destructor = destructor, - _inputIsState = inputIsState, - - _inputTable = inputTable, - _oldInputTable = {}, - _outputTable = {}, - _keyOIMap = {}, - _keyIOMap = {}, - _keyData = {}, - _meta = {}, - }, CLASS_METATABLE) - - initDependency(self) - self:update() - - return self -end - -return ForKeys end,Properties={Name="ForKeys"},Reference=64,ClassName="ModuleScript"},{Closure=function() --!nonstrict - ---[[ - Constructs a new ForValues object which maps values of a table using - a `processor` function. - - Optionally, a `destructor` function can be specified for cleaning up values. - If omitted, the default cleanup function will be used instead. - - Additionally, a `meta` table/value can optionally be returned to pass data created - when running the processor to the destructor when the created object is cleaned up. -]] -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local Types = require(Package.Types) -local captureDependencies = require(Package.Dependencies.captureDependencies) -local initDependency = require(Package.Dependencies.initDependency) -local useDependency = require(Package.Dependencies.useDependency) -local parseError = require(Package.Logging.parseError) -local logErrorNonFatal = require(Package.Logging.logErrorNonFatal) -local logWarn = require(Package.Logging.logWarn) -local cleanup = require(Package.Utility.cleanup) -local needsDestruction = require(Package.Utility.needsDestruction) - -local class = {} - -local CLASS_METATABLE = { __index = class } -local WEAK_KEYS_METATABLE = { __mode = "k" } - ---[[ - Returns the current value of this ForValues object. - The object will be registered as a dependency unless `asDependency` is false. -]] -function class:get(asDependency: boolean?): any - if asDependency ~= false then - useDependency(self) - end - return self._outputTable -end - ---[[ - Called when the original table is changed. - - This will firstly find any values meeting any of the following criteria: - - - they were not previously present - - a dependency used during generation of this value has changed - - It will recalculate those values, storing information about any dependencies - used in the processor callback during value generation, and save the new value - to the output array with the same key. If it is overwriting an older value, - that older value will be passed to the destructor for cleanup. - - Finally, this function will find values that are no longer present, and remove - their values from the output table and pass them to the destructor. You can re-use - the same value multiple times and this will function will update them as little as - possible; reusing the same values where possible. -]] -function class:update(): boolean - local inputIsState = self._inputIsState - local inputTable = if inputIsState then self._inputTable:get(false) else self._inputTable - local outputValues = {} - - local didChange = false - - -- clean out value cache - self._oldValueCache, self._valueCache = self._valueCache, self._oldValueCache - local newValueCache = self._valueCache - local oldValueCache = self._oldValueCache - table.clear(newValueCache) - - -- clean out main dependency set - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = nil - end - self._oldDependencySet, self.dependencySet = self.dependencySet, self._oldDependencySet - table.clear(self.dependencySet) - - -- if the input table is a state object, add it as a dependency - if inputIsState then - self._inputTable.dependentSet[self] = true - self.dependencySet[self._inputTable] = true - end - - - -- STEP 1: find values that changed or were not previously present - for inKey, inValue in pairs(inputTable) do - -- check if the value is new or changed - local oldCachedValues = oldValueCache[inValue] - local shouldRecalculate = oldCachedValues == nil - - -- get a cached value and its dependency/meta data if available - local value, valueData, meta - - if type(oldCachedValues) == "table" and #oldCachedValues > 0 then - local valueInfo = table.remove(oldCachedValues, #oldCachedValues) - value = valueInfo.value - valueData = valueInfo.valueData - meta = valueInfo.meta - - if #oldCachedValues <= 0 then - oldValueCache[inValue] = nil - end - elseif oldCachedValues ~= nil then - oldValueCache[inValue] = nil - shouldRecalculate = true - end - - if valueData == nil then - valueData = { - dependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - oldDependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - dependencyValues = setmetatable({}, WEAK_KEYS_METATABLE), - } - end - - -- check if the value's dependencies have changed - if shouldRecalculate == false then - for dependency, oldValue in pairs(valueData.dependencyValues) do - if oldValue ~= dependency:get(false) then - shouldRecalculate = true - break - end - end - end - - -- recalculate the output value if necessary - if shouldRecalculate then - valueData.oldDependencySet, valueData.dependencySet = valueData.dependencySet, valueData.oldDependencySet - table.clear(valueData.dependencySet) - - local processOK, newOutValue, newMetaValue = captureDependencies( - valueData.dependencySet, - self._processor, - inValue - ) - - if processOK then - if self._destructor == nil and (needsDestruction(newOutValue) or needsDestruction(newMetaValue)) then - logWarn("destructorNeededForValues") - end - - -- pass the old value to the destructor if it exists - if value ~= nil then - local destructOK, err = xpcall(self._destructor or cleanup, parseError, value, meta) - if not destructOK then - logErrorNonFatal("forValuesDestructorError", err) - end - end - - -- store the new value and meta data - value = newOutValue - meta = newMetaValue - didChange = true - else - -- restore old dependencies, because the new dependencies may be corrupt - valueData.oldDependencySet, valueData.dependencySet = valueData.dependencySet, valueData.oldDependencySet - - logErrorNonFatal("forValuesProcessorError", newOutValue) - end - end - - - -- store the value and its dependency/meta data - local newCachedValues = newValueCache[inValue] - if newCachedValues == nil then - newCachedValues = {} - newValueCache[inValue] = newCachedValues - end - - table.insert(newCachedValues, { - value = value, - valueData = valueData, - meta = meta, - }) - - outputValues[inKey] = value - - - -- save dependency values and add to main dependency set - for dependency in pairs(valueData.dependencySet) do - valueData.dependencyValues[dependency] = dependency:get(false) - - self.dependencySet[dependency] = true - dependency.dependentSet[self] = true - end - end - - - -- STEP 2: find values that were removed - -- for tables of data, we just need to check if it's still in the cache - for _oldInValue, oldCachedValueInfo in pairs(oldValueCache) do - for _, valueInfo in ipairs(oldCachedValueInfo) do - local oldValue = valueInfo.value - local oldMetaValue = valueInfo.meta - - local destructOK, err = xpcall(self._destructor or cleanup, parseError, oldValue, oldMetaValue) - if not destructOK then - logErrorNonFatal("forValuesDestructorError", err) - end - - didChange = true - end - - table.clear(oldCachedValueInfo) - end - - self._outputTable = outputValues - - return didChange -end - -local function ForValues( - inputTable: PubTypes.CanBeState<{ [any]: VI }>, - processor: (VI) -> (VO, M?), - destructor: (VO, M?) -> ()? -): Types.ForValues - - local inputIsState = inputTable.type == "State" and typeof(inputTable.get) == "function" - - local self = setmetatable({ - type = "State", - kind = "ForValues", - dependencySet = {}, - -- if we held strong references to the dependents, then they wouldn't be - -- able to get garbage collected when they fall out of scope - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _oldDependencySet = {}, - - _processor = processor, - _destructor = destructor, - _inputIsState = inputIsState, - - _inputTable = inputTable, - _outputTable = {}, - _valueCache = {}, - _oldValueCache = {}, - }, CLASS_METATABLE) - - initDependency(self) - self:update() - - return self -end - -return ForValues end,Properties={Name="ForValues"},Reference=66,ClassName="ModuleScript"}},Properties={Name="State"},Reference=62,ClassName="Folder"},{Children={{Closure=function() --!strict - ---[[ - Stores shared state for dependency management functions. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) - -type Set = {[T]: any} - --- The set where used dependencies should be saved to. -local dependencySet: Set? = nil - --- A stack of sets where newly created dependencies should be stored. -local initialisedStack: {Set} = {} -local initialisedStackSize = 0 - -return { - dependencySet = dependencySet, - initialisedStack = initialisedStack, - initialisedStackSize = initialisedStackSize -} end,Properties={Name="sharedState"},Reference=41,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Calls the given callback, and stores any used external dependencies. - Arguments can be passed in after the callback. - If the callback completed successfully, returns true and the returned value, - otherwise returns false and the error thrown. - The callback shouldn't yield or run asynchronously. - - NOTE: any calls to useDependency() inside the callback (even if inside any - nested captureDependencies() call) will not be included in the set, to avoid - self-dependencies. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local parseError = require(Package.Logging.parseError) -local sharedState = require(Package.Dependencies.sharedState) - -type Set = {[T]: any} - -local initialisedStack = sharedState.initialisedStack -local initialisedStackCapacity = 0 - -local function captureDependencies( - saveToSet: Set, - callback: (...any) -> any, - ... -): (boolean, any) - - local prevDependencySet = sharedState.dependencySet - sharedState.dependencySet = saveToSet - - sharedState.initialisedStackSize += 1 - local initialisedStackSize = sharedState.initialisedStackSize - - local initialisedSet - if initialisedStackSize > initialisedStackCapacity then - initialisedSet = {} - initialisedStack[initialisedStackSize] = initialisedSet - initialisedStackCapacity = initialisedStackSize - else - initialisedSet = initialisedStack[initialisedStackSize] - table.clear(initialisedSet) - end - - local data = table.pack(xpcall(callback, parseError, ...)) - - sharedState.dependencySet = prevDependencySet - sharedState.initialisedStackSize -= 1 - - return table.unpack(data, 1, data.n) -end - -return captureDependencies - end,Properties={Name="captureDependencies"},Reference=39,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Given a reactive object, updates all dependent reactive objects. - Objects are only ever updated after all of their dependencies are updated, - are only ever updated once, and won't be updated if their dependencies are - unchanged. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) - -type Set = {[T]: any} -type Descendant = (PubTypes.Dependent & PubTypes.Dependency) | PubTypes.Dependent - --- Credit: https://blog.elttob.uk/2022/11/07/sets-efficient-topological-search.html -local function updateAll(root: PubTypes.Dependency) - local counters: {[Descendant]: number} = {} - local flags: {[Descendant]: boolean} = {} - local queue: {Descendant} = {} - local queueSize = 0 - local queuePos = 1 - - for object in root.dependentSet do - queueSize += 1 - queue[queueSize] = object - flags[object] = true - end - - -- Pass 1: counting up - while queuePos <= queueSize do - local next = queue[queuePos] - local counter = counters[next] - counters[next] = if counter == nil then 1 else counter + 1 - if (next :: any).dependentSet ~= nil then - for object in (next :: any).dependentSet do - queueSize += 1 - queue[queueSize] = object - end - end - queuePos += 1 - end - - -- Pass 2: counting down + processing - queuePos = 1 - while queuePos <= queueSize do - local next = queue[queuePos] - local counter = counters[next] - 1 - counters[next] = counter - if counter == 0 and flags[next] and next:update() and (next :: any).dependentSet ~= nil then - for object in (next :: any).dependentSet do - flags[object] = true - end - end - queuePos += 1 - end -end - -return updateAll end,Properties={Name="updateAll"},Reference=42,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - If a target set was specified by captureDependencies(), this will add the - given dependency to the target set. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local sharedState = require(Package.Dependencies.sharedState) - -local initialisedStack = sharedState.initialisedStack - -local function useDependency(dependency: PubTypes.Dependency) - local dependencySet = sharedState.dependencySet - - if dependencySet ~= nil then - local initialisedStackSize = sharedState.initialisedStackSize - if initialisedStackSize > 0 then - local initialisedSet = initialisedStack[initialisedStackSize] - if initialisedSet[dependency] ~= nil then - return - end - end - dependencySet[dependency] = true - end -end - -return useDependency end,Properties={Name="useDependency"},Reference=43,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Registers the creation of an object which can be used as a dependency. - - This is used to make sure objects don't capture dependencies originating - from inside of themselves. -]] - -local Package = script.Parent.Parent -local PubTypes = require(Package.PubTypes) -local sharedState = require(Package.Dependencies.sharedState) - -local initialisedStack = sharedState.initialisedStack - -local function initDependency(dependency: PubTypes.Dependency) - local initialisedStackSize = sharedState.initialisedStackSize - - for index, initialisedSet in ipairs(initialisedStack) do - if index > initialisedStackSize then - return - end - - initialisedSet[dependency] = true - end -end - -return initDependency end,Properties={Name="initDependency"},Reference=40,ClassName="ModuleScript"}},Properties={Name="Dependencies"},Reference=38,ClassName="Folder"},{Children={{Closure=function() --!strict - ---[[ - Restricts the reading of missing members for a table. -]] - -local Package = script.Parent.Parent -local logError = require(Package.Logging.logError) - -type table = {[any]: any} - -local function restrictRead(tableName: string, strictTable: table): table - -- FIXME: Typed Luau doesn't recognise this correctly yet - local metatable = getmetatable(strictTable :: any) - - if metatable == nil then - metatable = {} - setmetatable(strictTable, metatable) - end - - function metatable:__index(memberName) - logError("strictReadError", nil, tostring(memberName), tableName) - end - - return strictTable -end - -return restrictRead end,Properties={Name="restrictRead"},Reference=77,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - A symbol for representing nil values in contexts where nil is not usable. -]] - -local Package = script.Parent.Parent -local Types = require(Package.Types) - -return { - type = "Symbol", - name = "None" -} :: Types.None end,Properties={Name="None"},Reference=72,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Cleans up the tasks passed in as the arguments. - A task can be any of the following: - - - an Instance - will be destroyed - - an RBXScriptConnection - will be disconnected - - a function - will be run - - a table with a `Destroy` or `destroy` function - will be called - - an array - `cleanup` will be called on each item -]] - -local function cleanupOne(task: any) - local taskType = typeof(task) - - -- case 1: Instance - if taskType == "Instance" then - task:Destroy() - - -- case 2: RBXScriptConnection - elseif taskType == "RBXScriptConnection" then - task:Disconnect() - - -- case 3: callback - elseif taskType == "function" then - task() - - elseif taskType == "table" then - -- case 4: destroy() function - if typeof(task.destroy) == "function" then - task:destroy() - - -- case 5: Destroy() function - elseif typeof(task.Destroy) == "function" then - task:Destroy() - - -- case 6: array of tasks - elseif task[1] ~= nil then - for _, subtask in ipairs(task) do - cleanupOne(subtask) - end - end - end -end - -local function cleanup(...: any) - for index = 1, select("#", ...) do - cleanupOne(select(index, ...)) - end -end - -return cleanup end,Properties={Name="cleanup"},Reference=73,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Extended typeof, designed for identifying custom objects. - If given a table with a `type` string, returns that. - Otherwise, returns `typeof()` the argument. -]] - -local function xtypeof(x: any) - local typeString = typeof(x) - - if typeString == "table" and typeof(x.type) == "string" then - return x.type - else - return typeString - end -end - -return xtypeof end,Properties={Name="xtypeof"},Reference=78,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - An empty function. Often used as a destructor to indicate no destruction. -]] - -local function doNothing(...: any) -end - -return doNothing end,Properties={Name="doNothing"},Reference=74,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Returns true if the given value is not automatically memory managed, and - requires manual cleanup. -]] - -local function needsDestruction(x: any): boolean - return typeof(x) == "Instance" -end - -return needsDestruction end,Properties={Name="needsDestruction"},Reference=76,ClassName="ModuleScript"},{Closure=function() --!strict ---[[ - Returns true if A and B are 'similar' - i.e. any user of A would not need - to recompute if it changed to B. -]] - -local function isSimilar(a: any, b: any): boolean - -- HACK: because tables are mutable data structures, don't make assumptions - -- about similarity from equality for now (see issue #44) - if typeof(a) == "table" then - return false - else - return a == b - end -end - -return isSimilar end,Properties={Name="isSimilar"},Reference=75,ClassName="ModuleScript"}},Properties={Name="Utility"},Reference=71,ClassName="Folder"},{Children={{Closure=function() --!strict - ---[[ - Utility function to log a Fusion-specific warning. -]] - -local Package = script.Parent.Parent -local messages = require(Package.Logging.messages) - -local function logWarn(messageID, ...) - local formatString: string - - if messages[messageID] ~= nil then - formatString = messages[messageID] - else - messageID = "unknownMessage" - formatString = messages[messageID] - end - - warn(string.format("[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")", ...)) -end - -return logWarn end,Properties={Name="logWarn"},Reference=58,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Utility function to log a Fusion-specific error. -]] - -local Package = script.Parent.Parent -local Types = require(Package.Types) -local messages = require(Package.Logging.messages) - -local function logError(messageID: string, errObj: Types.Error?, ...) - local formatString: string - - if messages[messageID] ~= nil then - formatString = messages[messageID] - else - messageID = "unknownMessage" - formatString = messages[messageID] - end - - local errorString - if errObj == nil then - errorString = string.format("[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")", ...) - else - formatString = formatString:gsub("ERROR_MESSAGE", errObj.message) - errorString = string.format("[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")\n---- Stack trace ----\n" .. errObj.trace, ...) - end - - error(errorString:gsub("\n", "\n "), 0) -end - -return logError end,Properties={Name="logError"},Reference=56,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - An xpcall() error handler to collect and parse useful information about - errors, such as clean messages and stack traces. - - TODO: this should have a 'type' field for runtime type checking! -]] - -local Package = script.Parent.Parent -local Types = require(Package.Types) - -local function parseError(err: string): Types.Error - return { - type = "Error", - raw = err, - message = err:gsub("^.+:%d+:%s*", ""), - trace = debug.traceback(nil, 2) - } -end - -return parseError end,Properties={Name="parseError"},Reference=60,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Stores templates for different kinds of logging messages. -]] - -return { - cannotAssignProperty = "The class type '%s' has no assignable property '%s'.", - cannotConnectChange = "The %s class doesn't have a property called '%s'.", - cannotConnectEvent = "The %s class doesn't have an event called '%s'.", - cannotCreateClass = "Can't create a new instance of class '%s'.", - computedCallbackError = "Computed callback error: ERROR_MESSAGE", - destructorNeededValue = "To save instances into Values, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", - destructorNeededComputed = "To return instances from Computeds, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", - multiReturnComputed = "Returning multiple values from Computeds is discouraged, as behaviour will change soon - see discussion #189 on GitHub.", - destructorNeededForKeys = "To return instances from ForKeys, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", - destructorNeededForValues = "To return instances from ForValues, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", - destructorNeededForPairs = "To return instances from ForPairs, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", - duplicatePropertyKey = "", - forKeysProcessorError = "ForKeys callback error: ERROR_MESSAGE", - forKeysKeyCollision = "ForKeys should only write to output key '%s' once when processing key changes, but it wrote to it twice. Previously input key: '%s'; New input key: '%s'", - forKeysDestructorError = "ForKeys destructor error: ERROR_MESSAGE", - forPairsDestructorError = "ForPairs destructor error: ERROR_MESSAGE", - forPairsKeyCollision = "ForPairs should only write to output key '%s' once when processing key changes, but it wrote to it twice. Previous input pair: '[%s] = %s'; New input pair: '[%s] = %s'", - forPairsProcessorError = "ForPairs callback error: ERROR_MESSAGE", - forValuesProcessorError = "ForValues callback error: ERROR_MESSAGE", - forValuesDestructorError = "ForValues destructor error: ERROR_MESSAGE", - invalidChangeHandler = "The change handler for the '%s' property must be a function.", - invalidEventHandler = "The handler for the '%s' event must be a function.", - invalidPropertyType = "'%s.%s' expected a '%s' type, but got a '%s' type.", - invalidRefType = "Instance refs must be Value objects.", - invalidOutType = "[Out] properties must be given Value objects.", - invalidOutProperty = "The %s class doesn't have a property called '%s'.", - invalidSpringDamping = "The damping ratio for a spring must be >= 0. (damping was %.2f)", - invalidSpringSpeed = "The speed of a spring must be >= 0. (speed was %.2f)", - mistypedSpringDamping = "The damping ratio for a spring must be a number. (got a %s)", - mistypedSpringSpeed = "The speed of a spring must be a number. (got a %s)", - mistypedTweenInfo = "The tween info of a tween must be a TweenInfo. (got a %s)", - springTypeMismatch = "The type '%s' doesn't match the spring's type '%s'.", - strictReadError = "'%s' is not a valid member of '%s'.", - unknownMessage = "Unknown error: ERROR_MESSAGE", - unrecognisedChildType = "'%s' type children aren't accepted by `[Children]`.", - unrecognisedPropertyKey = "'%s' keys aren't accepted in property tables.", - unrecognisedPropertyStage = "'%s' isn't a valid stage for a special key to be applied at." -} end,Properties={Name="messages"},Reference=59,ClassName="ModuleScript"},{Closure=function() --!strict - ---[[ - Utility function to log a Fusion-specific error, without halting execution. -]] - -local Package = script.Parent.Parent -local Types = require(Package.Types) -local messages = require(Package.Logging.messages) - -local function logErrorNonFatal(messageID: string, errObj: Types.Error?, ...) - local formatString: string - - if messages[messageID] ~= nil then - formatString = messages[messageID] - else - messageID = "unknownMessage" - formatString = messages[messageID] - end - - local errorString - if errObj == nil then - errorString = string.format("[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")", ...) - else - formatString = formatString:gsub("ERROR_MESSAGE", errObj.message) - errorString = string.format("[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")\n---- Stack trace ----\n" .. errObj.trace, ...) - end - - task.spawn(function(...) - error(errorString:gsub("\n", "\n "), 0) - end, ...) -end - -return logErrorNonFatal end,Properties={Name="logErrorNonFatal"},Reference=57,ClassName="ModuleScript"}},Properties={Name="Logging"},Reference=55,ClassName="Folder"}}}},Properties={Name="_Index"},Reference=24,ClassName="Folder"}},Properties={Name="Submodules"},Reference=22,ClassName="Folder"},{Children={{Closure=function() -- This file was @generated by Tarmac. It is not intended for manual editing. -return { - ["MauiLogo-DarkMode"] = "rbxassetid://11915749895", - ["MauiLogo-LightMode"] = "rbxassetid://11915749962", -} end,Properties={Name="Assets"},Reference=15,ClassName="ModuleScript"}},Properties={Name="Tarmac"},Reference=14,ClassName="Folder"},{Properties={Value="0.1.0",Name="Version"},Reference=79,ClassName="StringValue"},{Closure=function() return { - FormatVersion = 1, - Output = { - MinifyTable = true, - UseMinifiedLoader = true, - }, -} end,Properties={Name=".maui"},Reference=16,ClassName="ModuleScript"},{Children={{Closure=function() --[[ - Maui - Roblox Studio Plugin for Packing Modules as Executable Luau Scripts - Licensed Under the LGPLv3 | Copyright (c) 2022-2023 Latte Softworks - https://github.com/latte-soft/maui - - File: /src/Components/Button.lua - Desc: Button component for front-end app -]] - -local Root = script.Parent.Parent -local Submodules = Root.Submodules - -local Fusion = require(Submodules.Fusion) - --- Fusion definitions -local New = Fusion.New -local Children = Fusion.Children -local OnEvent = Fusion.OnEvent -local Value = Fusion.Value -local Computed = Fusion.Computed - -local StudioTheme = settings().Studio.Theme - ---[[ - props = { - Text: string/Value? = "Button" - Enabled: boolean/Value? = true - IsPrimary boolean/Value? = false - AnchorPoint: Vector2/Value? = Vector2.new(0, 0) - Position: UDim2/Value? = UDim2.fromOffset(0, 0) - Size: UDim2/Value? = UDim2.fromOffset(0, 24) - LayoutOrder = number/Value? = 1 - TextWrapped: boolean/Value? = false - OnClick: function? = nil - } -]] -return function(props) - -- Set default value of `props.Enabled` if needed - props.Enabled = if props.Enabled == nil then - Value(true) - elseif type(props.Enabled) == "boolean" then - Value(props.Enabled) - else props.Enabled - - -- Again, set `props.IsPrimary` if needed, which is the "blue" color Studio - -- uses on some buttons they want to highlight out or whatever - props.IsPrimary = if props.IsPrimary == nil then - Value(false) - elseif type(props.IsPrimary) == "boolean" then - Value(props.IsPrimary) - else props.IsPrimary - - -- For bg effects and such - local IsSelected = Value(false) - local IsHovering = Value(false) - - -- Automatically get `StudioStyleGuideModifier` for objs based on state - local function GetStyleGuideModifier() - return if not props.Enabled:get() then - Enum.StudioStyleGuideModifier.Disabled - elseif IsSelected:get() then - Enum.StudioStyleGuideModifier.Pressed - elseif IsHovering:get() then - Enum.StudioStyleGuideModifier.Hover - else - Enum.StudioStyleGuideModifier.Default - end - - return New "TextButton" { - Name = "Button", - LayoutOrder = props.LayoutOrder or 1, - - AutomaticSize = Enum.AutomaticSize.XY, - AnchorPoint = props.AnchorPoint or Vector2.new(0, 0), - Position = props.Position or UDim2.fromOffset(0, 0), - Size = props.Size or UDim2.fromOffset(0, 24), -- AutomaticSize is enabled for X - - BackgroundColor3 = Computed(function() - return StudioTheme:GetColor( - if props.IsPrimary:get() then "MainButton" else "Button", - GetStyleGuideModifier() - ) - end), - BorderSizePixel = 1, - BorderColor3 = Computed(function() - return StudioTheme:GetColor("Border", GetStyleGuideModifier()) - end), - - Text = props.Text or "Button", - Font = Enum.Font.SourceSans, - TextSize = 16, - TextWrapped = props.TextWrapped or false, - TextXAlignment = Enum.TextXAlignment.Center, - TextColor3 = Computed(function() - return StudioTheme:GetColor( - if props.IsPrimary:get() then "BrightText" else "MainText", - GetStyleGuideModifier() - ) - end), - - -- Events - [OnEvent "MouseButton1Click"] = function() - if props.Enabled:get() and props.OnClick then - props.OnClick() - end - end, - - [OnEvent "MouseButton1Down"] = function() - IsSelected:set(true) - end, - - [OnEvent "MouseButton1Up"] = function() - IsSelected:set(false) - end, - - [OnEvent "MouseEnter"] = function() - IsHovering:set(true) - end, - - [OnEvent "MouseLeave"] = function() - IsHovering:set(false) - IsSelected:set(false) - end, - - [Children] = { - New "UIPadding" { - PaddingLeft = UDim.new(0, 16), - PaddingRight = UDim.new(0, 16), - PaddingTop = UDim.new(0, 2), - PaddingBottom = UDim.new(0, 2), - } - } - } -end - end,Properties={Name="Button"},Reference=11,ClassName="ModuleScript"},{Closure=function() --[[ - Maui - Roblox Studio Plugin for Packing Modules as Executable Luau Scripts - Licensed Under the LGPLv3 | Copyright (c) 2022-2023 Latte Softworks - https://github.com/latte-soft/maui - - File: /src/Components/Console.lua - Desc: Console component for front-end app -]] - -local Root = script.Parent.Parent -local Submodules = Root.Submodules - -local Fusion = require(Submodules.Fusion) - --- Fusion definitions -local New = Fusion.New -local Children = Fusion.Children -local OnEvent = Fusion.OnEvent -local OnChange = Fusion.OnChange -local Value = Fusion.Value -local Computed = Fusion.Computed -local Out = Fusion.Out - -local StudioTheme = settings().Studio.Theme - ---[[ - props = { - Text: string/Value = "" - AnchorPoint: Vector2/Value? = Vector2.new(0, 0) - Position: UDim2/Value? = UDim2.fromOffset(0, 0) - Size: UDim2/Value? = UDim2.fromOffset(300, 200) - AutoScrollEnabled: Value? = false - RightClickContextMenu: PluginMenu? = nil - } -]] -return function(props) - -- We need to keep track of these properties later for autoscroll - local CanvasPosition = Value(Vector2.new(0, 0)) - - return New "Frame" { - Name = "Console", - - AnchorPoint = props.AnchorPoint or Vector2.new(0, 0), - Position = props.Position or UDim2.fromOffset(0, 0), - Size = props.Size or UDim2.fromOffset(300, 200), - - BackgroundColor3 = StudioTheme:GetColor("ScrollBarBackground"), - BorderSizePixel = 1, - BorderColor3 = StudioTheme:GetColor("Border"), - - [Children] = { - New "TextLabel" { - Name = "AutoScrollIndicator", - - AnchorPoint = Vector2.new(0, 1), - Position = UDim2.new(0, 0, 1, 0), - Size = UDim2.new(1, 0, 0, 24), - - BackgroundColor3 = StudioTheme:GetColor("Titlebar"), - BorderSizePixel = 1, - BorderColor3 = StudioTheme:GetColor("Border"), - - Text = Computed(function() - return "Autoscrolling Enabled: " .. tostring(if props.AutoScrollEnabled then props.AutoScrollEnabled:get() else false) - end), - Font = Enum.Font.SourceSans, - TextColor3 = StudioTheme:GetColor("MainText"), - TextXAlignment = Enum.TextXAlignment.Right, - - [Children] = { - New "UIPadding" { - PaddingRight = UDim.new(0, 6) - } - } - }, - - New "ScrollingFrame" { - Name = "ScrollWindow", - - Size = UDim2.new(1, 0, 1, -25), -- 1 pixel less so the X scrollbar doesnt overlap border - - BackgroundTransparency = 1, - - AutomaticCanvasSize = Enum.AutomaticSize.XY, - ScrollingDirection = Enum.ScrollingDirection.XY, - CanvasSize = UDim2.fromScale(0, 0), - ScrollBarImageColor3 = StudioTheme:GetColor("ScrollBar"), - ScrollBarThickness = 12, - VerticalScrollBarInset = Enum.ScrollBarInset.ScrollBar, - HorizontalScrollBarInset = Enum.ScrollBarInset.ScrollBar, - ElasticBehavior = Enum.ElasticBehavior.Never, - TopImage = "rbxasset://textures/ui/Scroll/scroll-middle.png", - MidImage = "rbxasset://textures/ui/Scroll/scroll-middle.png", - BottomImage = "rbxasset://textures/ui/Scroll/scroll-middle.png", - - -- Keep track of for auto scroll - CanvasPosition = CanvasPosition, - [Out "CanvasPosition"] = CanvasPosition, - - [OnChange "AbsoluteCanvasSize"] = if not props.AutoScrollEnabled then Fusion.doNothing else function(newCanvasSize) - if props.AutoScrollEnabled:get() then - CanvasPosition:set(Vector2.new(CanvasPosition:get().X, newCanvasSize.Y)) - end - end, - - -- Check if there's a context menu to run on right click end - [OnEvent "InputEnded"] = if not props.RightClickContextMenu then Fusion.doNothing else function(inputObject) - if inputObject.UserInputType == Enum.UserInputType.MouseButton2 then - -- This yields, but it's expected that any `PluginAction.Triggered` events were setup when this prop was passed - props.RightClickContextMenu:ShowAsync() - end - end, - - [Children] = { - New "TextLabel" { - Name = "ConsoleText", - - AutomaticSize = Enum.AutomaticSize.XY, - Size = UDim2.fromScale(1, 0), - - BackgroundTransparency = 1, - - Text = props.Text, - Font = Enum.Font.Code, - LineHeight = 1.1, - TextColor3 = StudioTheme:GetColor("SubText"), - TextXAlignment = Enum.TextXAlignment.Left, - TextYAlignment = Enum.TextYAlignment.Top, - - [Children] = { - New "UIPadding" { - PaddingLeft = UDim.new(0, 8), - PaddingRight = UDim.new(0, 8), - PaddingTop = UDim.new(0, 6), - PaddingBottom = UDim.new(0, 6), - } - } - } - } - } - } - } -end - end,Properties={Name="Console"},Reference=12,ClassName="ModuleScript"}},Properties={Name="Components"},Reference=10,ClassName="Folder"}}}} do local a,b='0.1.0',game:GetService'RunService'local c,d,e,f,g,h,i,j,k=b:IsServer(),b:IsClient(),getfenv(0),{},{},{},{},{},{}local function l(m)local n,o=pcall(Instance.new,m.ClassName)if not n then return end f[m.Reference]=o if m.Closure then h[o]=m.Closure if o:IsA'BaseScript'then table.insert(j,o)end end if m.Properties then for p,q in next,m.Properties do pcall(function()o[p]=q end)end end if m.RefProperties then for p,q in next,m.RefProperties do table.insert(g,{InstanceObject=o,Property=p,ReferenceId=q})end end if m.Attributes then for p,q in next,m.Attributes do pcall(o.SetAttribute,o,p,q)end end if m.Children then for p,q in next,m.Children do local r=l(q)if r then r.Parent=o end end end return o end local m={}do for n,o in next,ModuleRoot do table.insert(m,l(o))end end local function n(o)local p=i[o]if o.ClassName=='ModuleScript'and p then return unpack(p)end local q=h[o]if not q then return end do local r local s={['maui']=table.freeze{Version=a,GetScript=function()return script end,GetShared=function()return k end},['script']=o,['require']=function(s,...)if s and s.ClassName=='ModuleScript'and h[s]then return n(s)end return require(s,...)end,['getfenv']=function(s,...)if type(s)=='number'and s>=0 then if s==0 then return r else local t,u=pcall(getfenv,s)if t and u==e then return r end end end return getfenv(s,...)end,['setfenv']=function(s,t,...)if type(s)=='number'and s>=0 then if s==0 then return setfenv(r,t)else local u,v=pcall(getfenv,s)if u and v==e then return setfenv(r,t)end end end return setfenv(s,t,...)end}r=setmetatable({},{__index=function(t,u)local v=rawget(r,u)if v~=nil then return v end local w=s[u]if w~=nil then return w end return e[u]end,__newindex=function(t,u,v)rawset(r,u,v)end})setfenv(q,r)end local r=coroutine.wrap(q)if o:IsA'BaseScript'then local s,t=if o.Enabled then task.defer(r)else nil t=o:GetPropertyChangedSignal'Enabled':Connect(function(u)t:Disconnect()if u==true then n(o)else pcall(task.cancel,s)end end)return else local s={r()}i[o]=s return unpack(s)end end for o,p in next,g do pcall(function()p.InstanceObject[p.Property]=f[p.ReferenceId]end)end for q,r in next,j do if(c and r.ClassName=='Script')or(d and r.ClassName=='LocalScript')then n(r)end end end diff --git a/examples/reggie_2023-01-07_15-10-46.lua b/examples/reggie_2023-01-07_15-10-46.lua deleted file mode 100644 index 261dd38..0000000 --- a/examples/reggie_2023-01-07_15-10-46.lua +++ /dev/null @@ -1,527 +0,0 @@ --- This script was automatically @generated by Maui, it is not intended for manual editing. - -local ModuleRoot = { - { - Closure = function() for _, Object in script:GetChildren() do - Object.Parent = workspace -end end, - Children = { - { - ClassName = "Model", - RefProperties = { - PrimaryPart = 2 - }, - Properties = { - Name = "jitlua" - }, - Reference = 2, - Children = { - { - Children = { - { - Properties = { - Name = "RightShoulderAttachment", - CFrame = CFrame.new(0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1) - }, - Reference = 16, - ClassName = "Attachment" - } - }, - Properties = { - Name = "Right Arm", - CanCollide = false, - Color = Color3.new(0.9490196108818054, 0.9529411792755127, 0.9529411792755127), - CFrame = CFrame.new(-0.71575927734375, 3, -18.15643310546875, 1, 0, 0, 0, 1, 0, 0, 0, 1), - Size = Vector3.new(1, 2, 1) - }, - Reference = 15, - ClassName = "Part" - }, - { - Children = { - { - RefProperties = { - Part0 = 11, - Part1 = 11 - }, - Properties = { - MaxVelocity = 0.10000000149011612, - C0 = CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, -0), - Name = "RootJoint", - C1 = CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, -0) - }, - Reference = 11, - ClassName = "Motor6D" - } - }, - Properties = { - RightParamA = 0, - BottomSurface = Enum.SurfaceType.Smooth, - CanCollide = false, - CFrame = CFrame.new(-2.21575927734375, 3, -18.15643310546875, 1, 0, 0, 0, 1, 0, 0, 0, 1), - Transparency = 1, - Name = "HumanoidRootPart", - TopSurface = Enum.SurfaceType.Smooth, - LeftParamB = 0, - LeftParamA = 0, - RightParamB = 0, - Size = Vector3.new(2, 2, 1) - }, - Reference = 10, - ClassName = "Part" - }, - { - Children = { - { - Children = { - { - Properties = { - Value = CFrame.new(-1.1220848560333252, 0.41582340002059937, -1.6025032997131348, -0.8191520571708679, 0.1192532554268837, -0.5610424280166626, 0, 0.9781476259231567, 0.20791170001029968, 0.5735764503479004, 0.17031130194664001, -0.8012516498565674), - Name = "ThumbnailCameraValue" - }, - Reference = 42, - ClassName = "CFrameValue" - }, - { - RefProperties = { - Value = 41 - }, - Properties = { - Name = "ThumbnailCameraTarget" - }, - Reference = 41, - ClassName = "ObjectValue" - } - }, - Properties = { - Name = "ThumbnailConfiguration" - }, - Reference = 40, - ClassName = "Configuration" - }, - { - Children = { - { - RefProperties = { - Part0 = 39, - Part1 = 39 - }, - Properties = { - C1 = CFrame.new(0, 0.6000000238418579, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1), - C0 = CFrame.new(0, 0.5999999046325684, 0.00022942014038562775, -1, -7.871375551360416e-09, -3.029981271446139e-15, -7.871375551360416e-09, 1, -4.1444258024012927e-16, 3.029981271446139e-15, -4.1444255377034967e-16, -1), - Name = "AccessoryWeld" - }, - Reference = 39, - ClassName = "Weld" - }, - { - Properties = { - Name = "SpecialMesh", - MeshId = "rbxassetid://10675984346", - MeshType = Enum.MeshType.FileMesh, - TextureId = "rbxassetid://10676064421" - }, - Reference = 36, - ClassName = "SpecialMesh" - }, - { - Properties = { - Name = "HatAttachment", - CFrame = CFrame.new(0, 0.5999999046325684, 0.00022942014038562775, -1, -7.871375551360416e-09, -3.029981271446139e-15, -7.871375551360416e-09, 1, -4.1444258024012927e-16, 3.029981271446139e-15, -4.1444255377034967e-16, -1) - }, - Reference = 38, - ClassName = "Attachment" - }, - { - Properties = { - Value = "Classic", - Name = "AvatarPartScaleType" - }, - Reference = 37, - ClassName = "StringValue" - } - }, - Properties = { - Name = "Handle", - TopSurface = Enum.SurfaceType.Smooth, - BottomSurface = Enum.SurfaceType.Smooth, - CanCollide = false, - Locked = true, - CFrame = CFrame.new(-2.21575927734375, 4.5, -18.156204223632812, -1, -7.871375551360416e-09, 3.029981271446139e-15, -7.871375551360416e-09, 1, -4.1444255377034967e-16, -3.029981271446139e-15, -4.1444258024012927e-16, -1), - CastShadow = false, - Size = Vector3.new(1, 1, 1) - }, - Reference = 35, - ClassName = "Part" - } - }, - Properties = { - AttachmentPoint = CFrame.new(0, 0.5999999046325684, 0.00022942014038562775, -1, -7.871375551360416e-09, -3.029981271446139e-15, -7.871375551360416e-09, 1, -4.1444258024012927e-16, 3.029981271446139e-15, -4.1444255377034967e-16, -1), - Name = "158" - }, - Reference = 34, - ClassName = "Accessory" - }, - { - Children = { - { - Properties = { - Name = "HatAttachment", - CFrame = CFrame.new(0, 0.6000000238418579, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1) - }, - Reference = 5, - ClassName = "Attachment" - }, - { - Properties = { - Scale = Vector3.new(1.25, 1.25, 1.25) - }, - Reference = 4, - ClassName = "SpecialMesh" - }, - { - Properties = { - Name = "FaceFrontAttachment", - CFrame = CFrame.new(0, 0, -0.6000000238418579, 1, 0, 0, 0, 1, 0, 0, 0, 1) - }, - Reference = 7, - ClassName = "Attachment" - }, - { - Properties = { - Name = "face", - Texture = "http://www.roblox.com/asset/?id=8560915" - }, - Reference = 9, - ClassName = "Decal" - }, - { - Properties = { - Name = "HairAttachment", - CFrame = CFrame.new(0, 0.6000000238418579, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1) - }, - Reference = 6, - ClassName = "Attachment" - }, - { - Properties = { - Name = "FaceCenterAttachment" - }, - Reference = 8, - ClassName = "Attachment" - } - }, - Properties = { - Name = "Head", - Color = Color3.new(0.9490196108818054, 0.9529411792755127, 0.9529411792755127), - CFrame = CFrame.new(-2.21575927734375, 4.5, -18.15643310546875, 1, 0, 0, 0, 1, 0, 0, 0, 1), - TopSurface = Enum.SurfaceType.Smooth, - Size = Vector3.new(2, 1, 1) - }, - Reference = 3, - ClassName = "Part" - }, - { - Properties = { - Name = "Pants", - PantsTemplate = "http://www.roblox.com/asset/?id=8891144624" - }, - Reference = 50, - ClassName = "Pants" - }, - { - Properties = { - BottomSurface = Enum.SurfaceType.Smooth, - CanCollide = false, - Color = Color3.new(0.9490196108818054, 0.9529411792755127, 0.9529411792755127), - CFrame = CFrame.new(-2.71575927734375, 1, -18.15643310546875, 1, 0, 0, 0, 1, 0, 0, 0, 1), - Name = "Left Leg", - Size = Vector3.new(1, 2, 1) - }, - Reference = 14, - ClassName = "Part" - }, - { - Properties = { - Name = "Shirt", - ShirtTemplate = "http://www.roblox.com/asset/?id=8891140127" - }, - Reference = 49, - ClassName = "Shirt" - }, - { - Children = { - { - RefProperties = { - Part0 = 24, - Part1 = 24 - }, - Properties = { - MaxVelocity = 0.10000000149011612, - C0 = CFrame.new(-1, -1, 0, 0, 0, -1, 0, 1, 0, 1, 0, 0), - Name = "Left Hip", - C1 = CFrame.new(-0.5, 1, 0, 0, 0, -1, 0, 1, 0, 1, 0, 0) - }, - Reference = 24, - ClassName = "Motor6D" - }, - { - Properties = { - Name = "RightCollarAttachment", - CFrame = CFrame.new(1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1) - }, - Reference = 28, - ClassName = "Attachment" - }, - { - Properties = { - Name = "BodyFrontAttachment", - CFrame = CFrame.new(0, 0, -0.5, 1, 0, 0, 0, 1, 0, 0, 0, 1) - }, - Reference = 31, - ClassName = "Attachment" - }, - { - RefProperties = { - Part0 = 22, - Part1 = 22 - }, - Properties = { - MaxVelocity = 0.10000000149011612, - C0 = CFrame.new(0, 1, 0, -1, 0, 0, 0, 0, 1, 0, 1, -0), - Name = "Neck", - C1 = CFrame.new(0, -0.5, 0, -1, 0, 0, 0, 0, 1, 0, 1, -0) - }, - Reference = 22, - ClassName = "Motor6D" - }, - { - Properties = { - Name = "WaistFrontAttachment", - CFrame = CFrame.new(0, -1, -0.5, 1, 0, 0, 0, 1, 0, 0, 0, 1) - }, - Reference = 25, - ClassName = "Attachment" - }, - { - Properties = { - Name = "BodyBackAttachment", - CFrame = CFrame.new(0, 0, 0.5, 1, 0, 0, 0, 1, 0, 0, 0, 1) - }, - Reference = 32, - ClassName = "Attachment" - }, - { - Properties = { - Name = "WaistCenterAttachment", - CFrame = CFrame.new(0, -1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1) - }, - Reference = 26, - ClassName = "Attachment" - }, - { - Properties = { - Name = "LeftCollarAttachment", - CFrame = CFrame.new(-1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1) - }, - Reference = 30, - ClassName = "Attachment" - }, - { - RefProperties = { - Part0 = 20, - Part1 = 20 - }, - Properties = { - MaxVelocity = 0.10000000149011612, - C0 = CFrame.new(1, 0.5, 0, 0, 0, 1, 0, 1, -0, -1, 0, 0), - Name = "Right Shoulder", - C1 = CFrame.new(-0.5, 0.5, 0, 0, 0, 1, 0, 1, -0, -1, 0, 0) - }, - Reference = 20, - ClassName = "Motor6D" - }, - { - Properties = { - Name = "WaistBackAttachment", - CFrame = CFrame.new(0, -1, 0.5, 1, 0, 0, 0, 1, 0, 0, 0, 1) - }, - Reference = 27, - ClassName = "Attachment" - }, - { - RefProperties = { - Part0 = 21, - Part1 = 21 - }, - Properties = { - MaxVelocity = 0.10000000149011612, - C0 = CFrame.new(1, -1, 0, 0, 0, 1, 0, 1, -0, -1, 0, 0), - Name = "Right Hip", - C1 = CFrame.new(0.5, 1, 0, 0, 0, 1, 0, 1, -0, -1, 0, 0) - }, - Reference = 21, - ClassName = "Motor6D" - }, - { - Properties = { - Name = "roblox" - }, - Reference = 19, - ClassName = "Decal" - }, - { - Properties = { - Name = "NeckAttachment", - CFrame = CFrame.new(0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1) - }, - Reference = 29, - ClassName = "Attachment" - }, - { - RefProperties = { - Part0 = 23, - Part1 = 23 - }, - Properties = { - MaxVelocity = 0.10000000149011612, - C0 = CFrame.new(-1, 0.5, 0, 0, 0, -1, 0, 1, 0, 1, 0, 0), - Name = "Left Shoulder", - C1 = CFrame.new(0.5, 0.5, 0, 0, 0, -1, 0, 1, 0, 1, 0, 0) - }, - Reference = 23, - ClassName = "Motor6D" - } - }, - Properties = { - RightParamA = 0, - Color = Color3.new(0.9490196108818054, 0.9529411792755127, 0.9529411792755127), - CFrame = CFrame.new(-2.21575927734375, 3, -18.15643310546875, 1, 0, 0, 0, 1, 0, 0, 0, 1), - LeftSurface = Enum.SurfaceType.Weld, - Name = "Torso", - RightParamB = 0, - RightSurface = Enum.SurfaceType.Weld, - LeftParamA = 0, - LeftParamB = 0, - Size = Vector3.new(2, 2, 1) - }, - Reference = 18, - ClassName = "Part" - }, - { - Children = { - { - Properties = { - Name = "LeftShoulderAttachment", - CFrame = CFrame.new(0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1) - }, - Reference = 13, - ClassName = "Attachment" - } - }, - Properties = { - Name = "Left Arm", - CanCollide = false, - Color = Color3.new(0.9490196108818054, 0.9529411792755127, 0.9529411792755127), - CFrame = CFrame.new(-3.71575927734375, 3, -18.15643310546875, 1, 0, 0, 0, 1, 0, 0, 0, 1), - Size = Vector3.new(1, 2, 1) - }, - Reference = 12, - ClassName = "Part" - }, - { - RefProperties = { - RootPart = 33 - }, - Properties = {}, - Reference = 33, - ClassName = "Humanoid" - }, - { - Properties = { - LeftLegColor3 = Color3.new(0.9490196704864502, 0.9529412388801575, 0.9529412388801575), - LeftArmColor3 = Color3.new(0.9490196704864502, 0.9529412388801575, 0.9529412388801575), - TorsoColor3 = Color3.new(0.9490196704864502, 0.9529412388801575, 0.9529412388801575), - RightArmColor3 = Color3.new(0.9490196704864502, 0.9529412388801575, 0.9529412388801575), - HeadColor3 = Color3.new(0.9490196704864502, 0.9529412388801575, 0.9529412388801575), - RightLegColor3 = Color3.new(0.9490196704864502, 0.9529412388801575, 0.9529412388801575) - }, - Reference = 48, - ClassName = "BodyColors" - }, - { - Children = { - { - Children = { - { - Properties = { - Name = "HatAttachment", - CFrame = CFrame.new(8.657480066176504e-09, -0.15052366256713867, -0.010221302509307861, 1, 7.871372886825156e-09, 4.241051954068098e-14, -7.832312576283584e-09, 0.9950371980667114, 0.09950371831655502, 7.831886250642128e-10, -0.09950371831655502, 0.9950371980667114) - }, - Reference = 46, - ClassName = "Attachment" - }, - { - RefProperties = { - Part0 = 47, - Part1 = 47 - }, - Properties = { - C1 = CFrame.new(0, 0.6000000238418579, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1), - C0 = CFrame.new(8.657480066176504e-09, -0.15052366256713867, -0.010221302509307861, 1, 7.871372886825156e-09, 4.241051954068098e-14, -7.832312576283584e-09, 0.9950371980667114, 0.09950371831655502, 7.831886250642128e-10, -0.09950371831655502, 0.9950371980667114), - Name = "AccessoryWeld" - }, - Reference = 47, - ClassName = "Weld" - }, - { - Properties = { - MeshId = "http://www.roblox.com/asset/?id=13640868", - MeshType = Enum.MeshType.FileMesh, - TextureId = "http://www.roblox.com/asset/?id=18987684" - }, - Reference = 45, - ClassName = "SpecialMesh" - } - }, - Properties = { - Name = "Handle", - BottomSurface = Enum.SurfaceType.Smooth, - CanCollide = false, - Locked = true, - CFrame = CFrame.new(-2.21575927734375, 5.248759746551514, -18.131284713745117, 1, -7.832312576283584e-09, 7.831886250642128e-10, 7.871372886825156e-09, 0.9950371980667114, -0.09950371831655502, 4.241051954068098e-14, 0.09950371831655502, 0.9950371980667114), - TopSurface = Enum.SurfaceType.Smooth, - Size = Vector3.new(2, 0.800000011920929, 2) - }, - Reference = 44, - ClassName = "Part" - } - }, - Properties = { - AttachmentPoint = CFrame.new(0, -0.25, 0, 1, 0, 0, 0, 0.9950371980667114, 0.09950371831655502, 0, -0.09950371831655502, 0.9950371980667114), - Name = "Fedora" - }, - Reference = 43, - ClassName = "Accessory" - }, - { - Properties = { - BottomSurface = Enum.SurfaceType.Smooth, - CanCollide = false, - Color = Color3.new(0.9490196108818054, 0.9529411792755127, 0.9529411792755127), - CFrame = CFrame.new(-1.71575927734375, 1, -18.15643310546875, 1, 0, 0, 0, 1, 0, 0, 0, 1), - Name = "Right Leg", - Size = Vector3.new(1, 2, 1) - }, - Reference = 17, - ClassName = "Part" - } - } - } - }, - Reference = 1, - ClassName = "LocalScript" - } -} - -do local a,b='0.1.2',game:GetService'RunService'local c,d,e,f,g,h,i,j,k=b:IsServer(),b:IsClient(),getfenv(0),{},{},{},{},{},{}local function l(m)local n,o=pcall(Instance.new,m.ClassName)if not n then return end f[m.Reference]=o if m.Closure then h[o]=m.Closure if o:IsA'BaseScript'then table.insert(j,o)end end if m.Properties then for p,q in next,m.Properties do pcall(function()o[p]=q end)end end if m.RefProperties then for p,q in next,m.RefProperties do table.insert(g,{InstanceObject=o,Property=p,ReferenceId=q})end end if m.Attributes then for p,q in next,m.Attributes do pcall(o.SetAttribute,o,p,q)end end if m.Children then for p,q in next,m.Children do local r=l(q)if r then r.Parent=o end end end return o end local m={}do for n,o in next,ModuleRoot do table.insert(m,l(o))end end local function n(o)local p=i[o]if o.ClassName=='ModuleScript'and p then return unpack(p)end local q=h[o]if not q then return end do local r local s={['maui']=table.freeze{Version=a,GetScript=function()return script end,GetShared=function()return k end},['script']=o,['require']=function(s,...)if s and s.ClassName=='ModuleScript'and h[s]then return n(s)end return require(s,...)end,['getfenv']=function(s,...)if type(s)=='number'and s>=0 then if s==0 then return r else local t,u=pcall(getfenv,s)if t and u==e then return r end end end return getfenv(s,...)end,['setfenv']=function(s,t,...)if type(s)=='number'and s>=0 then if s==0 then return setfenv(r,t)else local u,v=pcall(getfenv,s)if u and v==e then return setfenv(r,t)end end end return setfenv(s,t,...)end}r=setmetatable({},{__index=function(t,u)local v=rawget(r,u)if v~=nil then return v end local w=s[u]if w~=nil then return w end return e[u]end,__newindex=function(t,u,v)rawset(r,u,v)end})setfenv(q,r)end local r=coroutine.wrap(q)if o:IsA'BaseScript'then local s,t=if o.Enabled then task.defer(r)else nil t=o:GetPropertyChangedSignal'Enabled':Connect(function(u)t:Disconnect()if u==true then n(o)else pcall(task.cancel,s)end end)return else local s={r()}i[o]=s return unpack(s)end end for o,p in next,g do pcall(function()p.InstanceObject[p.Property]=f[p.ReferenceId]end)end for q,r in next,j do if(c and r.ClassName=='Script')or(d and r.ClassName=='LocalScript')then n(r)end end end diff --git a/serve.project.json b/serve.project.json index b71abb5..1e0176b 100644 --- a/serve.project.json +++ b/serve.project.json @@ -23,6 +23,10 @@ "TestsMinified": { "$path": "dist/tests" + }, + + "Builds": { + "$path": "build" } } }