diff --git a/0.3/search/search_index.json b/0.3/search/search_index.json index e37def7c4..1ed429af2 100644 --- a/0.3/search/search_index.json +++ b/0.3/search/search_index.json @@ -1 +1 @@ -{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Rediscover the joy of coding. \u00b6 Code is more dynamic, complex and intertwined than ever before. Errors cascade out of control, things update in the wrong order, and it's all connected by difficult, unreadable spaghetti. No longer. Fusion introduces modern 'reactive' concepts for managing code, so you can spend more time getting your logic right, and less time implementing buggy boilerplate code connections. Starting from simple roots, concepts neatly combine and build up with very little learning curve. At every stage, you can robustly guarantee what your code will do, and when you come back in six months, your code is easy to pick back up. Getting started guide Download Scroll down for a quick look at 3 main features. Representing change \u00b6 Fusion introduces \u2018state objects\u2019. They aren\u2019t that complex, but allow you to write dynamic code that\u2019s highly readable, behaves predictably and splits into parts easily. State objects are used to represent changeable or dynamic values in your program. You can peek at their value at any time. -- For example, suppose this function returned a state object. local currentTimeObj = getCurrentTimeStateObject () -- State objects are objects... print ( typeof ( currentTimeObj )) --> table -- ...and you can peek at their value (or \u2018state\u2019) at any time. print ( currentTimeObj : get ()) --> 0.0 task . wait ( 5 ) print ( currentTimeObj : get ()) --> 5.0 You can write out your logic using Fusion's built-in state objects. Here's the two basic ones, Value and Computed: -- This creates a state object that you can set manually. -- You can change its value using myName:set(). local myName = Value ( \"Daniel\" ) -- This creates a state object from a calculation. -- It determines its own value automatically. local myGreeting = Computed ( function () return \"Hello! My name is \" .. myName : get () end ) To watch what a state object does, you can use an Observer. For example, you can run some code when an object changes value. -- This observer watches for when the greeting changes. local myObserver = Observer ( myGreeting ) -- Let\u2019s print out the greeting when there\u2019s a new one. local disconnect = myObserver : onChange ( function () print ( myGreeting : get ()) end ) -- This will run the code above! myName : set ( \"Danny\" ) Building instances \u00b6 Fusion offers comprehensive APIs to build or enrich instances from code, so you can easily integrate with your game scripts. Fusion provides dedicated functions to create and modify instances. They allow you to easily configure your instance in one place. -- This will create a red part in the workspace. local myPart = New \"Part\" { Parent = workspace , BrickColor = BrickColor . Red () } -- This adds on some extras after. Hydrate ( myPart ) { Material = \"Wood\" , Transparency = 0.5 } They offer powerful features to keep all your instance code close together. For example, you can listen for events or add children. -- This will create a rounded button. -- When you click it, it\u2019ll greet you. local myButton = New \"TextButton\" { Text = \"Click me\" , [ OnEvent \"Activated\" ] = function () print ( \"Hello! I\u2019m a button.\" ) end , [ Children ] = New \"UICorner\" { CornerRadius = UDim . new ( 1 , 0 ) } } You can also plug state objects in directly. The instance updates as the state object changes value. -- Creating a state object you can control... local message = Value ( \"Hello!\" ) -- Now you can plug that state object into the Text property. local myLabel = New \"TextLabel\" { Text = message } print ( myLabel . Text ) --> Hello! -- The Text property now responds to changes: message : set ( \"Goodbye!\" ) print ( myLabel . Text ) --> Goodbye! Animating anything \u00b6 Fusion gives you best-in-class tools to animate anything you can think of, completely out of the box. Fusion lets you use tweens or physically based springs to animate any value you want - not just instance properties. -- This could be anything you want, as long as it's a state object. local health = Value ( 100 ) -- Easily make it tween between values... local style = TweenInfo . new ( 0.5 , Enum . EasingStyle . Quad ) local tweenHealth = Tween ( health , style ) -- ...or use spring physics for extra responsiveness. local springHealth = Spring ( health , 30 , 0.9 ) Tween and Spring are state objects, just like anything else that changes in your program. That means it's easy to process them afterwards. -- You can round the animated health to whole numbers. local wholeHealth = Computed ( function () return math . round ( health : get ()) end ) -- You can format it as text and put it in some UI, too. local myText = New \"TextLabel\" { Text = Computed ( function () return \"Health: \" .. wholeHealth : get () end ) } You can even configure your animations using state objects, too. This makes it easy to swap out animations or disable them when needed. -- Define some tweening styles... local TWEEN_FAST = TweenInfo . new ( 0.5 , Enum . EasingStyle . Elastic ) local TWEEN_SLOW = TweenInfo . new ( 2 , Enum . EasingStyle . Sine ) -- Choose more dramatic styles at low health... local style = Computed ( function () return if health : get () < 20 then TWEEN_FAST else TWEEN_SLOW end ) -- Plug it right into your animation! local tweenHealth = Tween ( health , style ) Sparked your curiosity? \u00b6 Those are the core features of Fusion, and they're the foundation of everything - whether it\u2019s complex 3D UI systems, procedural animation, or just a hello world app. It all fits on one page, and that's the magic. You don't have to keep relearning ever-more-complex tools as you scale up from prototype to product. If you'd like to learn in depth, we have a comprehensive beginner's tutorial track , complete with diagrams, examples and code. We would love to welcome you into our warm, vibrant community. Hopefully, we'll see you there :)","title":"Home"},{"location":"#rediscover-the-joy-of-coding","text":"Code is more dynamic, complex and intertwined than ever before. Errors cascade out of control, things update in the wrong order, and it's all connected by difficult, unreadable spaghetti. No longer. Fusion introduces modern 'reactive' concepts for managing code, so you can spend more time getting your logic right, and less time implementing buggy boilerplate code connections. Starting from simple roots, concepts neatly combine and build up with very little learning curve. At every stage, you can robustly guarantee what your code will do, and when you come back in six months, your code is easy to pick back up. Getting started guide Download Scroll down for a quick look at 3 main features.","title":"Rediscover the joy of coding."},{"location":"#representing-change","text":"Fusion introduces \u2018state objects\u2019. They aren\u2019t that complex, but allow you to write dynamic code that\u2019s highly readable, behaves predictably and splits into parts easily. State objects are used to represent changeable or dynamic values in your program. You can peek at their value at any time. -- For example, suppose this function returned a state object. local currentTimeObj = getCurrentTimeStateObject () -- State objects are objects... print ( typeof ( currentTimeObj )) --> table -- ...and you can peek at their value (or \u2018state\u2019) at any time. print ( currentTimeObj : get ()) --> 0.0 task . wait ( 5 ) print ( currentTimeObj : get ()) --> 5.0 You can write out your logic using Fusion's built-in state objects. Here's the two basic ones, Value and Computed: -- This creates a state object that you can set manually. -- You can change its value using myName:set(). local myName = Value ( \"Daniel\" ) -- This creates a state object from a calculation. -- It determines its own value automatically. local myGreeting = Computed ( function () return \"Hello! My name is \" .. myName : get () end ) To watch what a state object does, you can use an Observer. For example, you can run some code when an object changes value. -- This observer watches for when the greeting changes. local myObserver = Observer ( myGreeting ) -- Let\u2019s print out the greeting when there\u2019s a new one. local disconnect = myObserver : onChange ( function () print ( myGreeting : get ()) end ) -- This will run the code above! myName : set ( \"Danny\" )","title":"Representing change"},{"location":"#building-instances","text":"Fusion offers comprehensive APIs to build or enrich instances from code, so you can easily integrate with your game scripts. Fusion provides dedicated functions to create and modify instances. They allow you to easily configure your instance in one place. -- This will create a red part in the workspace. local myPart = New \"Part\" { Parent = workspace , BrickColor = BrickColor . Red () } -- This adds on some extras after. Hydrate ( myPart ) { Material = \"Wood\" , Transparency = 0.5 } They offer powerful features to keep all your instance code close together. For example, you can listen for events or add children. -- This will create a rounded button. -- When you click it, it\u2019ll greet you. local myButton = New \"TextButton\" { Text = \"Click me\" , [ OnEvent \"Activated\" ] = function () print ( \"Hello! I\u2019m a button.\" ) end , [ Children ] = New \"UICorner\" { CornerRadius = UDim . new ( 1 , 0 ) } } You can also plug state objects in directly. The instance updates as the state object changes value. -- Creating a state object you can control... local message = Value ( \"Hello!\" ) -- Now you can plug that state object into the Text property. local myLabel = New \"TextLabel\" { Text = message } print ( myLabel . Text ) --> Hello! -- The Text property now responds to changes: message : set ( \"Goodbye!\" ) print ( myLabel . Text ) --> Goodbye!","title":"Building instances"},{"location":"#animating-anything","text":"Fusion gives you best-in-class tools to animate anything you can think of, completely out of the box. Fusion lets you use tweens or physically based springs to animate any value you want - not just instance properties. -- This could be anything you want, as long as it's a state object. local health = Value ( 100 ) -- Easily make it tween between values... local style = TweenInfo . new ( 0.5 , Enum . EasingStyle . Quad ) local tweenHealth = Tween ( health , style ) -- ...or use spring physics for extra responsiveness. local springHealth = Spring ( health , 30 , 0.9 ) Tween and Spring are state objects, just like anything else that changes in your program. That means it's easy to process them afterwards. -- You can round the animated health to whole numbers. local wholeHealth = Computed ( function () return math . round ( health : get ()) end ) -- You can format it as text and put it in some UI, too. local myText = New \"TextLabel\" { Text = Computed ( function () return \"Health: \" .. wholeHealth : get () end ) } You can even configure your animations using state objects, too. This makes it easy to swap out animations or disable them when needed. -- Define some tweening styles... local TWEEN_FAST = TweenInfo . new ( 0.5 , Enum . EasingStyle . Elastic ) local TWEEN_SLOW = TweenInfo . new ( 2 , Enum . EasingStyle . Sine ) -- Choose more dramatic styles at low health... local style = Computed ( function () return if health : get () < 20 then TWEEN_FAST else TWEEN_SLOW end ) -- Plug it right into your animation! local tweenHealth = Tween ( health , style )","title":"Animating anything"},{"location":"#sparked-your-curiosity","text":"Those are the core features of Fusion, and they're the foundation of everything - whether it\u2019s complex 3D UI systems, procedural animation, or just a hello world app. It all fits on one page, and that's the magic. You don't have to keep relearning ever-more-complex tools as you scale up from prototype to product. If you'd like to learn in depth, we have a comprehensive beginner's tutorial track , complete with diagrams, examples and code. We would love to welcome you into our warm, vibrant community. Hopefully, we'll see you there :)","title":"Sparked your curiosity?"},{"location":"api-reference/","text":"API Reference \u00b6 Welcome to the API Reference! This is where you can find more technical documentation about what the Fusion library provides. For a beginner-friendly experience, try the tutorials. Most Popular \u00b6 General \u00b6 Errors Contextual Memory \u00b6 Scope deriveScope doCleanup scoped State \u00b6 UsedAs Computed Observer peek Value Roblox \u00b6 Child Children Hydrate New Animation \u00b6 Animatable Spring Tween","title":"API Reference"},{"location":"api-reference/#api-reference","text":"Welcome to the API Reference! This is where you can find more technical documentation about what the Fusion library provides. For a beginner-friendly experience, try the tutorials.","title":"API Reference"},{"location":"api-reference/#most-popular","text":"","title":"Most Popular"},{"location":"api-reference/#general","text":"Errors Contextual","title":"General"},{"location":"api-reference/#memory","text":"Scope deriveScope doCleanup scoped","title":"Memory"},{"location":"api-reference/#state","text":"UsedAs Computed Observer peek Value","title":"State"},{"location":"api-reference/#roblox","text":"Child Children Hydrate New","title":"Roblox"},{"location":"api-reference/#animation","text":"Animatable Spring Tween","title":"Animation"},{"location":"api-reference/animation/members/spring/","text":"Animation Members Spring Spring -> Spring \u00b6 function Fusion . Spring < T > ( scope : Scope < unknown > , goal : UsedAs < T > , speed : UsedAs < number > ? , damping : UsedAs < number > ? ) -> Spring < T > Constructs and returns a new spring state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local spring = scope : Spring ( goal , speed , damping ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. goal : UsedAs \u00b6 The goal that this object should follow. For best results, the goal should be animatable . speed : UsedAs ? \u00b6 Multiplies how fast the motion should occur; doubling the speed exactly halves the time it takes for the motion to complete. damping : UsedAs ? \u00b6 The amount of resistance the motion encounters. 0 represents no resistance, 1 is just enough resistance to prevent overshoot (critical damping), and larger values damp out inertia effects and straighten the motion. Returns -> Spring \u00b6 A freshly constructed spring state object. Learn More \u00b6 Springs tutorial","title":"Spring"},{"location":"api-reference/animation/members/spring/#spring-springt","text":"function Fusion . Spring < T > ( scope : Scope < unknown > , goal : UsedAs < T > , speed : UsedAs < number > ? , damping : UsedAs < number > ? ) -> Spring < T > Constructs and returns a new spring state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local spring = scope : Spring ( goal , speed , damping )","title":"Spring -> Spring<T>"},{"location":"api-reference/animation/members/spring/#parameters","text":"","title":"Parameters"},{"location":"api-reference/animation/members/spring/#scope-scopes","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<S>"},{"location":"api-reference/animation/members/spring/#goal-usedast","text":"The goal that this object should follow. For best results, the goal should be animatable .","title":"goal : UsedAs<T>"},{"location":"api-reference/animation/members/spring/#speed-usedast","text":"Multiplies how fast the motion should occur; doubling the speed exactly halves the time it takes for the motion to complete.","title":"speed : UsedAs<T>?"},{"location":"api-reference/animation/members/spring/#damping-usedast","text":"The amount of resistance the motion encounters. 0 represents no resistance, 1 is just enough resistance to prevent overshoot (critical damping), and larger values damp out inertia effects and straighten the motion.","title":"damping : UsedAs<T>?"},{"location":"api-reference/animation/members/spring/#returns-springt","text":"A freshly constructed spring state object.","title":"Returns -> Spring<T>"},{"location":"api-reference/animation/members/spring/#learn-more","text":"Springs tutorial","title":"Learn More"},{"location":"api-reference/animation/members/tween/","text":"Animation Members Tween Tween -> Tween \u00b6 function Fusion . Tween < T > ( scope : Scope < unknown > , goal : StateObject < T > , tweenInfo : UsedAs < TweenInfo > ? ) -> Tween < T > Constructs and returns a new tween state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local tween = scope : Tween ( goal , info ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. goal : UsedAs \u00b6 The goal that this object should follow. For best results, the goal should be animatable . info : UsedAs ? \u00b6 Determines the easing curve that the motion will follow. Returns -> Tween \u00b6 A freshly constructed tween state object. Learn More \u00b6 Tweens tutorial","title":"Tween"},{"location":"api-reference/animation/members/tween/#tween-tweent","text":"function Fusion . Tween < T > ( scope : Scope < unknown > , goal : StateObject < T > , tweenInfo : UsedAs < TweenInfo > ? ) -> Tween < T > Constructs and returns a new tween state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local tween = scope : Tween ( goal , info )","title":"Tween -> Tween<T>"},{"location":"api-reference/animation/members/tween/#parameters","text":"","title":"Parameters"},{"location":"api-reference/animation/members/tween/#scope-scopes","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<S>"},{"location":"api-reference/animation/members/tween/#goal-usedast","text":"The goal that this object should follow. For best results, the goal should be animatable .","title":"goal : UsedAs<T>"},{"location":"api-reference/animation/members/tween/#info-usedastweeninfo","text":"Determines the easing curve that the motion will follow.","title":"info : UsedAs<TweenInfo>?"},{"location":"api-reference/animation/members/tween/#returns-tweent","text":"A freshly constructed tween state object.","title":"Returns -> Tween<T>"},{"location":"api-reference/animation/members/tween/#learn-more","text":"Tweens tutorial","title":"Learn More"},{"location":"api-reference/animation/types/animatable/","text":"Animation Types Animatable Animatable \u00b6 export type Animatable = number | CFrame | Color3 | ColorSequenceKeypoint | DateTime | NumberRange | NumberSequenceKeypoint | PhysicalProperties | Ray | Rect | Region3 | Region3int16 | UDim | UDim2 | Vector2 | Vector2int16 | Vector3 | Vector3int16 Any data type that Fusion can decompose into a tuple of animatable parameters. Passing other types to animation objects Other types can be passed to Tween and Spring objects, however those types will not animate. Instead, non- Animatable types will immediately arrive at their goal value. Learn More \u00b6 Tweens tutorial Springs tutorial","title":"Animatable"},{"location":"api-reference/animation/types/animatable/#animatable","text":"export type Animatable = number | CFrame | Color3 | ColorSequenceKeypoint | DateTime | NumberRange | NumberSequenceKeypoint | PhysicalProperties | Ray | Rect | Region3 | Region3int16 | UDim | UDim2 | Vector2 | Vector2int16 | Vector3 | Vector3int16 Any data type that Fusion can decompose into a tuple of animatable parameters. Passing other types to animation objects Other types can be passed to Tween and Spring objects, however those types will not animate. Instead, non- Animatable types will immediately arrive at their goal value.","title":"Animatable"},{"location":"api-reference/animation/types/animatable/#learn-more","text":"Tweens tutorial Springs tutorial","title":"Learn More"},{"location":"api-reference/animation/types/spring/","text":"Animation Types Spring Spring \u00b6 export type Spring < T > = StateObject < T > & Dependent & { kind : \"Spring\" , setPosition : ( self , newPosition : T ) -> (), setVelocity : ( self , newVelocity : T ) -> (), addVelocity : ( self , deltaVelocity : T ) -> () } A specialised state object for following a goal state smoothly over time, using physics to shape the motion. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from the goal state. The methods on this type allow for direct control over the position and velocity of the motion. Other than that, this type is of limited utility outside of Fusion itself. Members \u00b6 kind : \"Spring\" \u00b6 A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart. Methods \u00b6 setPosition -> () \u00b6 function Spring : setPosition ( newPosition : T ): () Immediately snaps the spring to the given position. The position must have the same typeof() as the goal state. setVelocity -> () \u00b6 function Spring : setVelocity ( newVelocity : T ): () Overwrites the spring's velocity without changing its position. The velocity must have the same typeof() as the goal state. addVelocity -> () \u00b6 function Spring : addVelocity ( deltaVelocity : T ): () Appends to the spring's velocity without changing its position. The velocity must have the same typeof() as the goal state. Learn More \u00b6 Springs tutorial","title":"Spring"},{"location":"api-reference/animation/types/spring/#spring","text":"export type Spring < T > = StateObject < T > & Dependent & { kind : \"Spring\" , setPosition : ( self , newPosition : T ) -> (), setVelocity : ( self , newVelocity : T ) -> (), addVelocity : ( self , deltaVelocity : T ) -> () } A specialised state object for following a goal state smoothly over time, using physics to shape the motion. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from the goal state. The methods on this type allow for direct control over the position and velocity of the motion. Other than that, this type is of limited utility outside of Fusion itself.","title":"Spring"},{"location":"api-reference/animation/types/spring/#members","text":"","title":"Members"},{"location":"api-reference/animation/types/spring/#kind-spring","text":"A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart.","title":"kind : \"Spring\""},{"location":"api-reference/animation/types/spring/#methods","text":"","title":"Methods"},{"location":"api-reference/animation/types/spring/#setposition-","text":"function Spring : setPosition ( newPosition : T ): () Immediately snaps the spring to the given position. The position must have the same typeof() as the goal state.","title":"setPosition -> ()"},{"location":"api-reference/animation/types/spring/#setvelocity-","text":"function Spring : setVelocity ( newVelocity : T ): () Overwrites the spring's velocity without changing its position. The velocity must have the same typeof() as the goal state.","title":"setVelocity -> ()"},{"location":"api-reference/animation/types/spring/#addvelocity-","text":"function Spring : addVelocity ( deltaVelocity : T ): () Appends to the spring's velocity without changing its position. The velocity must have the same typeof() as the goal state.","title":"addVelocity -> ()"},{"location":"api-reference/animation/types/spring/#learn-more","text":"Springs tutorial","title":"Learn More"},{"location":"api-reference/animation/types/tween/","text":"Animation Types Tween Tween \u00b6 export type Tween < T > = StateObject < T > & Dependent & { kind : \"Tween\" } A specialised state object for following a goal state smoothly over time, using a TweenInfo to shape the motion. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from the goal state. This type isn't generally useful outside of Fusion itself. Members \u00b6 kind : \"Tween\" \u00b6 A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart. Learn More \u00b6 Tweens tutorial","title":"Tween"},{"location":"api-reference/animation/types/tween/#tween","text":"export type Tween < T > = StateObject < T > & Dependent & { kind : \"Tween\" } A specialised state object for following a goal state smoothly over time, using a TweenInfo to shape the motion. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from the goal state. This type isn't generally useful outside of Fusion itself.","title":"Tween"},{"location":"api-reference/animation/types/tween/#members","text":"","title":"Members"},{"location":"api-reference/animation/types/tween/#kind-tween","text":"A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart.","title":"kind : \"Tween\""},{"location":"api-reference/animation/types/tween/#learn-more","text":"Tweens tutorial","title":"Learn More"},{"location":"api-reference/general/errors/","text":"General Errors Errors \u00b6 Whenever Fusion outputs any errors or messages to the console, it will have a short error ID at the end. This is used to uniquely identify what kind of error or message you're seeing. Use the search box below to paste in or type an error ID, and it will scroll to the details for you. callbackError \u00b6 Error in callback: attempt to perform arithmetic (add) on number and string Thrown by: Computed , ForKeys , ForValues , ForPairs , Contextual Fusion ran a function you specified, but the function threw an error that Fusion couldn't handle. The error includes a more specific message which can be used to diagnose the issue. cannotAssignProperty \u00b6 The class type 'Foo' has no assignable property 'Bar'. Thrown by: New , Hydrate You tried to set a property on an instance, but the property can't be assigned to for some reason. This could be because the property doesn't exist, or because it's locked by Roblox to prevent edits. Check your privileges Different scripts may have different privileges - for example, plugins will be allowed more privileges than in-game scripts. Make sure you have the necessary privileges to assign to your properties! cannotConnectChange \u00b6 The Frame class doesn't have a property called 'Foo'. Thrown by: OnChange You tried to connect to a property change event, but the property you specify doesn't exist on the instance. cannotConnectEvent \u00b6 The Frame class doesn't have an event called 'Foo'. Thrown by: OnEvent You tried to connect to an event on an instance, but the event you specify doesn't exist on the instance. cannotCreateClass \u00b6 Can't create a new instance of class 'EditableImage'. Thrown by: New You attempted to create a type of instance that Fusion can't create. Beta features Some instances are only creatable when you have certain Studio betas enabled. Check your Beta Features tab to ensure that beta features aren't causing the issue. cleanupWasRenamed \u00b6 `Fusion.cleanup` was renamed to `Fusion.doCleanup`. This will be an error in future versions of Fusion. Thrown by: doCleanup You attempted to use cleanup() in Fusion 0.3, which replaces it with the doCleanup() method. destroyedTwice \u00b6 Attempted to destroy Computed twice; ensure you're not manually calling `:destroy()` while using scopes. See discussion #292 on GitHub for advice. Thrown by: Value , Computed , Observer , ForKeys , ForValues , ForPairs , Spring , Tween Related discussions: #292 The :destroy() method of the object in question was called more than once. This usually means you called :destroy() manually, which is almost never required because Fusion's constructors always link objects to scopes . When that scope is passed to doCleanup() , the :destroy() method is called on every object inside. destructorRedundant \u00b6 Computed destructors no longer do anything. If you wish to run code on destroy, `table.insert` a function into the `scope` argument. See discussion #292 on GitHub for advice. Thrown by: Computed , ForKeys , ForValues , ForPairs Related discussions: #292 You passed an extra parameter to the constructor, which has historically been interpreted as a function that runs when a value is cleaned up. This mechanism has been replaced by scopes . forKeyCollision \u00b6 The key '6' was returned multiple times simultaneously, which is not allowed in `For` objects. Thrown by: ForKeys , ForPairs When called with different items from the table, the same key was returned for both of them. This is not allowed, because keys have to be unique in a table. invalidAttributeChangeHandler \u00b6 The change handler for the 'Active' attribute must be a function. Thrown by: AttributeChange AttributeChange expected you to provide a function for it to run when the attribute changes, but you provided something other than a function. For example, you might have accidentally provided nil . invalidAttributeOutType \u00b6 [AttributeOut] properties must be given Value objects. Thrown by: AttributeOut AttributeOut expected you to give it a value , but you gave it something else. invalidChangeHandler \u00b6 The change handler for the 'AbsoluteSize' property must be a function. Thrown by: OnChange OnChange expected you to provide a function for it to run when the property changes, but you provided something other than a function. For example, you might have accidentally provided nil . invalidEventHandler \u00b6 The handler for the 'MouseEnter' event must be a function. Thrown by: OnEvent OnEvent expected you to provide a function for it to run when the event is fired, but you provided something other than a function. For example, you might have accidentally provided nil . invalidOutProperty \u00b6 The Frame class doesn't have a property called 'MouseButton1Down'. Thrown by: Out The property that you tried to output doesn't exist on the instance that Out was used with. invalidOutType \u00b6 [Out] properties must be given Value objects. Thrown by: Out Out expected you to give it a value , but you gave it something else. invalidPropertyType \u00b6 'Frame.BackgroundColor3' expected a 'Color3' type, but got a 'Vector3' type. Thrown by: New , Hydrate You attempted to assign a value to a Roblox instance's property, but the assignment threw an error because that property doesn't accept values of that type. invalidRefType \u00b6 Instance refs must be Value objects. Thrown by: Ref Ref expected you to give it a value , but you gave it something else. invalidSpringDamping \u00b6 The damping ratio for a spring must be >= 0. (damping was -1.00) Thrown by: Spring You provided a damping ratio that the spring doesn't support, for example NaN , or a negative damping implying negative friction. invalidSpringSpeed \u00b6 The speed of a spring must be >= 0. (speed was NaN) Thrown by: Spring You provided a speed multiplier that the spring doesn't support, for example NaN or a negative speed implying the spring moves backwards through time. mergeConflict \u00b6 Multiple definitions for 'Observer' found while merging. Thrown by: scoped Fusion tried to merge together multiple tables, but a key was found in more than one of the tables, and it's unclear which one you intended to have in the final merged result. This can happen subtly with methods such as scoped() which automatically merge together all of their arguments. mistypedSpringDamping \u00b6 The damping ratio for a spring must be a number. (got a string) Thrown by: Spring You provided a damping ratio that the spring couldn't understand. Damping ratio has to be a number. mistypedSpringSpeed \u00b6 The speed of a spring must be a number. (got a string) Thrown by: Spring You provided a speed multiplier that the spring couldn't understand. Speed has to be a number. mistypedTweenInfo \u00b6 The tween info of a tween must be a TweenInfo. (got a table) Thrown by: Tween You provided an easing curve that the tween couldn't understand. The easing curve has to be specified using Roblox's TweenInfo data type. noTaskScheduler \u00b6 Fusion is not connected to an external task scheduler. Fusion depends on a task scheduler being present to perform certain time-related tasks such as deferral, delays, or updating animations. You'll need to define a set of standard task scheduler functions that Fusion can use for those purposes. Roblox users should never see this error, as Fusion automatically connects to Roblox's task scheduling APIs. possiblyOutlives \u00b6 The Value object could be destroyed before the Computed that is use()-ing it; review the order they're created in, and what scopes they belong to. See discussion #292 on GitHub for advice. Thrown by: Spring , Tween , New , Hydrate , Attribute , AttributeOut , Out , Ref , Computed , Observer Related discussions: #292 If you use an object after it's been destroyed, then your code can break. This mainly happens when one object 'outlives' another object that it's using. Because scopes clean up the newest objects first, this can happen when an old object depends on something much newer that itself. During cleanup, a situation could arise where the newer object is destroyed, then the older object runs code of some kind that needed the newer object to be there. Fusion can check for situations like this by analysing the scopes. This message is shown when Fusion can prove one of these situations will occur. There are two typical solutions: If the objects should always be created and destroyed at the exact same time, then ensure they're created in the correct order. Otherwise, move the objects into separate scopes, and ensure that both scopes can exist without the other scope. propertySetError \u00b6 Error setting property: UIAspectRatioConstraint.AspectRatio set to a non-positive value. Value must be a positive. Thrown by: New , Hydrate You attempted to set a property, but Roblox threw an error in response. The error includes a more specific message which can be used to diagnose the issue. scopeMissing \u00b6 To create Observers, provide a scope. (e.g. `myScope:Observer(watching)`). See discussion #292 on GitHub for advice. Thrown by: New , Hydrate , Value , Computed , Observer , ForKeys , ForValues , ForPairs , Spring , Tween Related discussions: #292 You attempted to create an object without providing a scope as the first parameter. Scopes are mandatory for all Fusion constructors so that Fusion knows when the object should be destroyed. springNanGoal \u00b6 A spring was given a NaN goal, so some simulation has been skipped. Ensure no springs have NaN goals. Thrown by: Spring The goal parameter given to the spring during construction contained one or more NaN values. This typically occurs when zero is accidentally divided by zero, or some other invalid mathematical operation has occurred. Check that your code is free of maths errors, and handles all edge cases. springNanMotion \u00b6 A spring encountered NaN during motion, so has snapped to the goal position. Ensure no springs have NaN positions or velocities. Thrown by: Spring While calculating updated position and velocity, one or both of those values ended up as NaN. This typically occurs when zero is accidentally divided by zero, or some other invalid mathematical operation has occurred. Check that your code is free of maths errors, and handles all edge cases. springTypeMismatch \u00b6 The type 'Vector3' doesn't match the spring's type 'Color3'. Thrown by: Spring The spring expected you to provide a type matching the data type that the spring is currently outputting. However, you provided a different data type. stateGetWasRemoved \u00b6 `StateObject:get()` has been replaced by `use()` and `peek()` - see discussion #217 on GitHub. Thrown by: Value , Computed , ForKeys , ForValues , ForPairs , Spring , Tween Related discussions: #217 Older versions of Fusion let you call :get() directly on state objects to read their current value and attempt to infer dependencies. This has been replaced by use functions in Fusion 0.3 for more predictable behaviour and better support for constant values. unknownMessage \u00b6 Unknown error: attempt to call a nil value Fusion ran into a problem, but couldn't associate it with a valid type of error. This is a fallback error type which shouldn't be seen by end users, because it indicates that Fusion code isn't reporting errors correctly. unrecognisedChildType \u00b6 'string' type children aren't accepted by `[Children]`. Thrown by: Children You provided a value inside of [Children] which didn't meet the definition of a child value. Check that you're only passing instances, arrays and state objects. unrecognisedPropertyKey \u00b6 'number' keys aren't accepted in property tables. Thrown by: New , Hydrate You provided something other than a property assignment ( Property = Value ) or special key in your property table. Most commonly, this means you tried to add child instances directly into the property table, rather than passing them into the [Children] special key. unrecognisedPropertyStage \u00b6 'children' isn't a valid stage for a special key to be applied at. Thrown by: New , Hydrate You attempted to use a special key which has a misconfigured stage , so Fusion didn't know when to apply it during instance construction. useAfterDestroy \u00b6 The Value object is no longer valid - it was destroyed before the Computed that is use()-ing. See discussion #292 on GitHub for advice. Thrown by: Spring , Tween , New , Hydrate , Attribute , AttributeOut , Out , Ref , Computed , Observer Related discussions: #292 Your code attempted to access an object after that object was destroyed, either because its :destroy() method was called manually, or because the object's scope was cleaned up. Make sure your objects are being added to the correct scopes according to when you expect them to be destroyed. Additionally, make sure your code can detect and deal with situations where other objects are no longer available.","title":"Errors"},{"location":"api-reference/general/errors/#errors","text":"Whenever Fusion outputs any errors or messages to the console, it will have a short error ID at the end. This is used to uniquely identify what kind of error or message you're seeing. Use the search box below to paste in or type an error ID, and it will scroll to the details for you.","title":"Errors"},{"location":"api-reference/general/errors/#callbackerror","text":"Error in callback: attempt to perform arithmetic (add) on number and string Thrown by: Computed , ForKeys , ForValues , ForPairs , Contextual Fusion ran a function you specified, but the function threw an error that Fusion couldn't handle. The error includes a more specific message which can be used to diagnose the issue.","title":"callbackError"},{"location":"api-reference/general/errors/#cannotassignproperty","text":"The class type 'Foo' has no assignable property 'Bar'. Thrown by: New , Hydrate You tried to set a property on an instance, but the property can't be assigned to for some reason. This could be because the property doesn't exist, or because it's locked by Roblox to prevent edits. Check your privileges Different scripts may have different privileges - for example, plugins will be allowed more privileges than in-game scripts. Make sure you have the necessary privileges to assign to your properties!","title":"cannotAssignProperty"},{"location":"api-reference/general/errors/#cannotconnectchange","text":"The Frame class doesn't have a property called 'Foo'. Thrown by: OnChange You tried to connect to a property change event, but the property you specify doesn't exist on the instance.","title":"cannotConnectChange"},{"location":"api-reference/general/errors/#cannotconnectevent","text":"The Frame class doesn't have an event called 'Foo'. Thrown by: OnEvent You tried to connect to an event on an instance, but the event you specify doesn't exist on the instance.","title":"cannotConnectEvent"},{"location":"api-reference/general/errors/#cannotcreateclass","text":"Can't create a new instance of class 'EditableImage'. Thrown by: New You attempted to create a type of instance that Fusion can't create. Beta features Some instances are only creatable when you have certain Studio betas enabled. Check your Beta Features tab to ensure that beta features aren't causing the issue.","title":"cannotCreateClass"},{"location":"api-reference/general/errors/#cleanupwasrenamed","text":"`Fusion.cleanup` was renamed to `Fusion.doCleanup`. This will be an error in future versions of Fusion. Thrown by: doCleanup You attempted to use cleanup() in Fusion 0.3, which replaces it with the doCleanup() method.","title":"cleanupWasRenamed"},{"location":"api-reference/general/errors/#destroyedtwice","text":"Attempted to destroy Computed twice; ensure you're not manually calling `:destroy()` while using scopes. See discussion #292 on GitHub for advice. Thrown by: Value , Computed , Observer , ForKeys , ForValues , ForPairs , Spring , Tween Related discussions: #292 The :destroy() method of the object in question was called more than once. This usually means you called :destroy() manually, which is almost never required because Fusion's constructors always link objects to scopes . When that scope is passed to doCleanup() , the :destroy() method is called on every object inside.","title":"destroyedTwice"},{"location":"api-reference/general/errors/#destructorredundant","text":"Computed destructors no longer do anything. If you wish to run code on destroy, `table.insert` a function into the `scope` argument. See discussion #292 on GitHub for advice. Thrown by: Computed , ForKeys , ForValues , ForPairs Related discussions: #292 You passed an extra parameter to the constructor, which has historically been interpreted as a function that runs when a value is cleaned up. This mechanism has been replaced by scopes .","title":"destructorRedundant"},{"location":"api-reference/general/errors/#forkeycollision","text":"The key '6' was returned multiple times simultaneously, which is not allowed in `For` objects. Thrown by: ForKeys , ForPairs When called with different items from the table, the same key was returned for both of them. This is not allowed, because keys have to be unique in a table.","title":"forKeyCollision"},{"location":"api-reference/general/errors/#invalidattributechangehandler","text":"The change handler for the 'Active' attribute must be a function. Thrown by: AttributeChange AttributeChange expected you to provide a function for it to run when the attribute changes, but you provided something other than a function. For example, you might have accidentally provided nil .","title":"invalidAttributeChangeHandler"},{"location":"api-reference/general/errors/#invalidattributeouttype","text":"[AttributeOut] properties must be given Value objects. Thrown by: AttributeOut AttributeOut expected you to give it a value , but you gave it something else.","title":"invalidAttributeOutType"},{"location":"api-reference/general/errors/#invalidchangehandler","text":"The change handler for the 'AbsoluteSize' property must be a function. Thrown by: OnChange OnChange expected you to provide a function for it to run when the property changes, but you provided something other than a function. For example, you might have accidentally provided nil .","title":"invalidChangeHandler"},{"location":"api-reference/general/errors/#invalideventhandler","text":"The handler for the 'MouseEnter' event must be a function. Thrown by: OnEvent OnEvent expected you to provide a function for it to run when the event is fired, but you provided something other than a function. For example, you might have accidentally provided nil .","title":"invalidEventHandler"},{"location":"api-reference/general/errors/#invalidoutproperty","text":"The Frame class doesn't have a property called 'MouseButton1Down'. Thrown by: Out The property that you tried to output doesn't exist on the instance that Out was used with.","title":"invalidOutProperty"},{"location":"api-reference/general/errors/#invalidouttype","text":"[Out] properties must be given Value objects. Thrown by: Out Out expected you to give it a value , but you gave it something else.","title":"invalidOutType"},{"location":"api-reference/general/errors/#invalidpropertytype","text":"'Frame.BackgroundColor3' expected a 'Color3' type, but got a 'Vector3' type. Thrown by: New , Hydrate You attempted to assign a value to a Roblox instance's property, but the assignment threw an error because that property doesn't accept values of that type.","title":"invalidPropertyType"},{"location":"api-reference/general/errors/#invalidreftype","text":"Instance refs must be Value objects. Thrown by: Ref Ref expected you to give it a value , but you gave it something else.","title":"invalidRefType"},{"location":"api-reference/general/errors/#invalidspringdamping","text":"The damping ratio for a spring must be >= 0. (damping was -1.00) Thrown by: Spring You provided a damping ratio that the spring doesn't support, for example NaN , or a negative damping implying negative friction.","title":"invalidSpringDamping"},{"location":"api-reference/general/errors/#invalidspringspeed","text":"The speed of a spring must be >= 0. (speed was NaN) Thrown by: Spring You provided a speed multiplier that the spring doesn't support, for example NaN or a negative speed implying the spring moves backwards through time.","title":"invalidSpringSpeed"},{"location":"api-reference/general/errors/#mergeconflict","text":"Multiple definitions for 'Observer' found while merging. Thrown by: scoped Fusion tried to merge together multiple tables, but a key was found in more than one of the tables, and it's unclear which one you intended to have in the final merged result. This can happen subtly with methods such as scoped() which automatically merge together all of their arguments.","title":"mergeConflict"},{"location":"api-reference/general/errors/#mistypedspringdamping","text":"The damping ratio for a spring must be a number. (got a string) Thrown by: Spring You provided a damping ratio that the spring couldn't understand. Damping ratio has to be a number.","title":"mistypedSpringDamping"},{"location":"api-reference/general/errors/#mistypedspringspeed","text":"The speed of a spring must be a number. (got a string) Thrown by: Spring You provided a speed multiplier that the spring couldn't understand. Speed has to be a number.","title":"mistypedSpringSpeed"},{"location":"api-reference/general/errors/#mistypedtweeninfo","text":"The tween info of a tween must be a TweenInfo. (got a table) Thrown by: Tween You provided an easing curve that the tween couldn't understand. The easing curve has to be specified using Roblox's TweenInfo data type.","title":"mistypedTweenInfo"},{"location":"api-reference/general/errors/#notaskscheduler","text":"Fusion is not connected to an external task scheduler. Fusion depends on a task scheduler being present to perform certain time-related tasks such as deferral, delays, or updating animations. You'll need to define a set of standard task scheduler functions that Fusion can use for those purposes. Roblox users should never see this error, as Fusion automatically connects to Roblox's task scheduling APIs.","title":"noTaskScheduler"},{"location":"api-reference/general/errors/#possiblyoutlives","text":"The Value object could be destroyed before the Computed that is use()-ing it; review the order they're created in, and what scopes they belong to. See discussion #292 on GitHub for advice. Thrown by: Spring , Tween , New , Hydrate , Attribute , AttributeOut , Out , Ref , Computed , Observer Related discussions: #292 If you use an object after it's been destroyed, then your code can break. This mainly happens when one object 'outlives' another object that it's using. Because scopes clean up the newest objects first, this can happen when an old object depends on something much newer that itself. During cleanup, a situation could arise where the newer object is destroyed, then the older object runs code of some kind that needed the newer object to be there. Fusion can check for situations like this by analysing the scopes. This message is shown when Fusion can prove one of these situations will occur. There are two typical solutions: If the objects should always be created and destroyed at the exact same time, then ensure they're created in the correct order. Otherwise, move the objects into separate scopes, and ensure that both scopes can exist without the other scope.","title":"possiblyOutlives"},{"location":"api-reference/general/errors/#propertyseterror","text":"Error setting property: UIAspectRatioConstraint.AspectRatio set to a non-positive value. Value must be a positive. Thrown by: New , Hydrate You attempted to set a property, but Roblox threw an error in response. The error includes a more specific message which can be used to diagnose the issue.","title":"propertySetError"},{"location":"api-reference/general/errors/#scopemissing","text":"To create Observers, provide a scope. (e.g. `myScope:Observer(watching)`). See discussion #292 on GitHub for advice. Thrown by: New , Hydrate , Value , Computed , Observer , ForKeys , ForValues , ForPairs , Spring , Tween Related discussions: #292 You attempted to create an object without providing a scope as the first parameter. Scopes are mandatory for all Fusion constructors so that Fusion knows when the object should be destroyed.","title":"scopeMissing"},{"location":"api-reference/general/errors/#springnangoal","text":"A spring was given a NaN goal, so some simulation has been skipped. Ensure no springs have NaN goals. Thrown by: Spring The goal parameter given to the spring during construction contained one or more NaN values. This typically occurs when zero is accidentally divided by zero, or some other invalid mathematical operation has occurred. Check that your code is free of maths errors, and handles all edge cases.","title":"springNanGoal"},{"location":"api-reference/general/errors/#springnanmotion","text":"A spring encountered NaN during motion, so has snapped to the goal position. Ensure no springs have NaN positions or velocities. Thrown by: Spring While calculating updated position and velocity, one or both of those values ended up as NaN. This typically occurs when zero is accidentally divided by zero, or some other invalid mathematical operation has occurred. Check that your code is free of maths errors, and handles all edge cases.","title":"springNanMotion"},{"location":"api-reference/general/errors/#springtypemismatch","text":"The type 'Vector3' doesn't match the spring's type 'Color3'. Thrown by: Spring The spring expected you to provide a type matching the data type that the spring is currently outputting. However, you provided a different data type.","title":"springTypeMismatch"},{"location":"api-reference/general/errors/#stategetwasremoved","text":"`StateObject:get()` has been replaced by `use()` and `peek()` - see discussion #217 on GitHub. Thrown by: Value , Computed , ForKeys , ForValues , ForPairs , Spring , Tween Related discussions: #217 Older versions of Fusion let you call :get() directly on state objects to read their current value and attempt to infer dependencies. This has been replaced by use functions in Fusion 0.3 for more predictable behaviour and better support for constant values.","title":"stateGetWasRemoved"},{"location":"api-reference/general/errors/#unknownmessage","text":"Unknown error: attempt to call a nil value Fusion ran into a problem, but couldn't associate it with a valid type of error. This is a fallback error type which shouldn't be seen by end users, because it indicates that Fusion code isn't reporting errors correctly.","title":"unknownMessage"},{"location":"api-reference/general/errors/#unrecognisedchildtype","text":"'string' type children aren't accepted by `[Children]`. Thrown by: Children You provided a value inside of [Children] which didn't meet the definition of a child value. Check that you're only passing instances, arrays and state objects.","title":"unrecognisedChildType"},{"location":"api-reference/general/errors/#unrecognisedpropertykey","text":"'number' keys aren't accepted in property tables. Thrown by: New , Hydrate You provided something other than a property assignment ( Property = Value ) or special key in your property table. Most commonly, this means you tried to add child instances directly into the property table, rather than passing them into the [Children] special key.","title":"unrecognisedPropertyKey"},{"location":"api-reference/general/errors/#unrecognisedpropertystage","text":"'children' isn't a valid stage for a special key to be applied at. Thrown by: New , Hydrate You attempted to use a special key which has a misconfigured stage , so Fusion didn't know when to apply it during instance construction.","title":"unrecognisedPropertyStage"},{"location":"api-reference/general/errors/#useafterdestroy","text":"The Value object is no longer valid - it was destroyed before the Computed that is use()-ing. See discussion #292 on GitHub for advice. Thrown by: Spring , Tween , New , Hydrate , Attribute , AttributeOut , Out , Ref , Computed , Observer Related discussions: #292 Your code attempted to access an object after that object was destroyed, either because its :destroy() method was called manually, or because the object's scope was cleaned up. Make sure your objects are being added to the correct scopes according to when you expect them to be destroyed. Additionally, make sure your code can detect and deal with situations where other objects are no longer available.","title":"useAfterDestroy"},{"location":"api-reference/general/members/contextual/","text":"General Members Contextual Contextual -> Contextual \u00b6 function Fusion . Contextual < T > ( defaultValue : T ): Contextual < T > Constructs and returns a new contextual . Parameters \u00b6 defaultValue : T \u00b6 The value which Contextual:now() should return if no value has been specified by Contextual:is():during() . Returns -> Contextual \u00b6 A freshly constructed contextual. Learn More \u00b6 Sharing Values tutorial","title":"Contextual"},{"location":"api-reference/general/members/contextual/#contextual-contextualt","text":"function Fusion . Contextual < T > ( defaultValue : T ): Contextual < T > Constructs and returns a new contextual .","title":"Contextual -> Contextual<T>"},{"location":"api-reference/general/members/contextual/#parameters","text":"","title":"Parameters"},{"location":"api-reference/general/members/contextual/#defaultvalue-t","text":"The value which Contextual:now() should return if no value has been specified by Contextual:is():during() .","title":"defaultValue : T"},{"location":"api-reference/general/members/contextual/#returns-contextualt","text":"A freshly constructed contextual.","title":"Returns -> Contextual<T>"},{"location":"api-reference/general/members/contextual/#learn-more","text":"Sharing Values tutorial","title":"Learn More"},{"location":"api-reference/general/members/version/","text":"General Members version version : Version \u00b6 Fusion . version : Version The version of the Fusion source code. isRelease is only true when using a version of Fusion downloaded from the Releases page .","title":"version"},{"location":"api-reference/general/members/version/#version-version","text":"Fusion . version : Version The version of the Fusion source code. isRelease is only true when using a version of Fusion downloaded from the Releases page .","title":"version : Version"},{"location":"api-reference/general/types/contextual/","text":"General Types Contextual Contextual \u00b6 export type Contextual < T > = { type : \"Contextual\" , now : ( self ) -> T , is : ( self , newValue : T ) -> { during : < R , A ... > ( self , callback : ( A ...) -> R , A ...) -> R } } An object representing a widely-accessible value, which can take on different values at different times in different coroutines. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially. Fields \u00b6 type : \"Contextual\" \u00b6 A type string which can be used for runtime type checking. Methods \u00b6 now -> T \u00b6 function Contextual : now (): T Returns the current value of this contextual. This varies based on when the function is called, and in what coroutine it was called. is/during -> R \u00b6 function Contextual : is ( newValue : T ): { during : < R , A ... > ( self , callback : ( A ...) -> R , A ... ) -> R } Runs the callback with the arguments A... and returns the value the callback returns ( R ). The Contextual will appear to be newValue in the callback, unless it's overridden by another :is():during() call. Learn More \u00b6 Sharing Values tutorial","title":"Contextual"},{"location":"api-reference/general/types/contextual/#contextual","text":"export type Contextual < T > = { type : \"Contextual\" , now : ( self ) -> T , is : ( self , newValue : T ) -> { during : < R , A ... > ( self , callback : ( A ...) -> R , A ...) -> R } } An object representing a widely-accessible value, which can take on different values at different times in different coroutines. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially.","title":"Contextual"},{"location":"api-reference/general/types/contextual/#fields","text":"","title":"Fields"},{"location":"api-reference/general/types/contextual/#type-contextual","text":"A type string which can be used for runtime type checking.","title":"type : \"Contextual\""},{"location":"api-reference/general/types/contextual/#methods","text":"","title":"Methods"},{"location":"api-reference/general/types/contextual/#now-t","text":"function Contextual : now (): T Returns the current value of this contextual. This varies based on when the function is called, and in what coroutine it was called.","title":"now -> T"},{"location":"api-reference/general/types/contextual/#isduring-r","text":"function Contextual : is ( newValue : T ): { during : < R , A ... > ( self , callback : ( A ...) -> R , A ... ) -> R } Runs the callback with the arguments A... and returns the value the callback returns ( R ). The Contextual will appear to be newValue in the callback, unless it's overridden by another :is():during() call.","title":"is/during -> R"},{"location":"api-reference/general/types/contextual/#learn-more","text":"Sharing Values tutorial","title":"Learn More"},{"location":"api-reference/general/types/version/","text":"General Types Version Version \u00b6 export type Version = { major : number , minor : number , isRelease : boolean } Describes a version of Fusion's source code. Members \u00b6 major : number \u00b6 The major version number. If this is greater than 0 , then two versions sharing the same major version number are not expected to be incompatible or have breaking changes. minor : number \u00b6 The minor version number. Describes version updates that are not enumerated by the major version number, such as versions prior to 1.0, or versions which are non-breaking. isRelease : boolean \u00b6 Describes whether the version was sourced from an official release package.","title":"Version"},{"location":"api-reference/general/types/version/#version","text":"export type Version = { major : number , minor : number , isRelease : boolean } Describes a version of Fusion's source code.","title":"Version"},{"location":"api-reference/general/types/version/#members","text":"","title":"Members"},{"location":"api-reference/general/types/version/#major-number","text":"The major version number. If this is greater than 0 , then two versions sharing the same major version number are not expected to be incompatible or have breaking changes.","title":"major : number"},{"location":"api-reference/general/types/version/#minor-number","text":"The minor version number. Describes version updates that are not enumerated by the major version number, such as versions prior to 1.0, or versions which are non-breaking.","title":"minor : number"},{"location":"api-reference/general/types/version/#isrelease-boolean","text":"Describes whether the version was sourced from an official release package.","title":"isRelease : boolean"},{"location":"api-reference/memory/members/derivescope/","text":"Memory Members deriveScope deriveScope -> Scope \u00b6 function Fusion . deriveScope < T > ( existing : Scope < T > ): Scope < T > Returns a blank scope with the same methods as an existing scope. Scopes are not unique Fusion can recycle old unused scopes. This helps make scopes more lightweight, but it also means they don't uniquely belong to any part of your program. As a result, you shouldn't hold on to scopes after they've been cleaned up, and you shouldn't use them as unique identifiers anywhere. Parameters \u00b6 existing : Scope \u00b6 An existing scope, whose methods should be re-used for the new scope. Returns -> Scope \u00b6 A blank scope with the same methods as the existing scope. Learn More \u00b6 Scopes tutorial","title":"deriveScope"},{"location":"api-reference/memory/members/derivescope/#derivescope-scopet","text":"function Fusion . deriveScope < T > ( existing : Scope < T > ): Scope < T > Returns a blank scope with the same methods as an existing scope. Scopes are not unique Fusion can recycle old unused scopes. This helps make scopes more lightweight, but it also means they don't uniquely belong to any part of your program. As a result, you shouldn't hold on to scopes after they've been cleaned up, and you shouldn't use them as unique identifiers anywhere.","title":"deriveScope -> Scope<T>"},{"location":"api-reference/memory/members/derivescope/#parameters","text":"","title":"Parameters"},{"location":"api-reference/memory/members/derivescope/#existing-scopet","text":"An existing scope, whose methods should be re-used for the new scope.","title":"existing : Scope<T>"},{"location":"api-reference/memory/members/derivescope/#returns-scopet","text":"A blank scope with the same methods as the existing scope.","title":"Returns -> Scope<T>"},{"location":"api-reference/memory/members/derivescope/#learn-more","text":"Scopes tutorial","title":"Learn More"},{"location":"api-reference/memory/members/docleanup/","text":"Memory Members doCleanup doCleanup -> () \u00b6 function Fusion . doCleanup ( ...: unknown ): () Attempts to destroy all arguments based on their runtime type. This is a black hole! Any values you pass into doCleanup should be treated as completely gone. Make sure you remove all references to those values, and ensure your code never uses them again. Parameters \u00b6 ... : unknown \u00b6 A value which should be disposed of; the value's runtime type will be inspected to determine what should happen. if function , it is called ...else if {destroy: (self) -> ()} , :destroy() is called ...else if {Destroy: (self) -> ()} , :Destroy() is called ...else if {any} , doCleanup is called on all members When Fusion is running inside of Roblox: if Instance , :Destroy() is called ...else if RBXScriptConnection , :Disconnect() is called If none of these conditions match, the value is ignored. Learn More \u00b6 Scopes tutorial","title":"doCleanup"},{"location":"api-reference/memory/members/docleanup/#docleanup-","text":"function Fusion . doCleanup ( ...: unknown ): () Attempts to destroy all arguments based on their runtime type. This is a black hole! Any values you pass into doCleanup should be treated as completely gone. Make sure you remove all references to those values, and ensure your code never uses them again.","title":"doCleanup -> ()"},{"location":"api-reference/memory/members/docleanup/#parameters","text":"","title":"Parameters"},{"location":"api-reference/memory/members/docleanup/#unknown","text":"A value which should be disposed of; the value's runtime type will be inspected to determine what should happen. if function , it is called ...else if {destroy: (self) -> ()} , :destroy() is called ...else if {Destroy: (self) -> ()} , :Destroy() is called ...else if {any} , doCleanup is called on all members When Fusion is running inside of Roblox: if Instance , :Destroy() is called ...else if RBXScriptConnection , :Disconnect() is called If none of these conditions match, the value is ignored.","title":"... : unknown"},{"location":"api-reference/memory/members/docleanup/#learn-more","text":"Scopes tutorial","title":"Learn More"},{"location":"api-reference/memory/members/scoped/","text":"Memory Members scoped scoped -> Scope \u00b6 function Fusion . scoped < T > ( constructors : T ): Scope < T > Returns a blank scope , with the __index metatable pointing at the given list of constructors for syntax convenience. Scopes are not unique Fusion can recycle old unused scopes. This helps make scopes more lightweight, but it also means they don't uniquely belong to any part of your program. As a result, you shouldn't hold on to scopes after they've been cleaned up, and you shouldn't use them as unique identifiers anywhere. Parameters \u00b6 constructors : T \u00b6 A table, ideally including functions which take a scope as their first parameter. Those functions will turn into methods. Returns -> Scope \u00b6 A blank scope with the specified methods. Learn More \u00b6 Scopes tutorial","title":"scoped"},{"location":"api-reference/memory/members/scoped/#scoped-scopet","text":"function Fusion . scoped < T > ( constructors : T ): Scope < T > Returns a blank scope , with the __index metatable pointing at the given list of constructors for syntax convenience. Scopes are not unique Fusion can recycle old unused scopes. This helps make scopes more lightweight, but it also means they don't uniquely belong to any part of your program. As a result, you shouldn't hold on to scopes after they've been cleaned up, and you shouldn't use them as unique identifiers anywhere.","title":"scoped -> Scope<T>"},{"location":"api-reference/memory/members/scoped/#parameters","text":"","title":"Parameters"},{"location":"api-reference/memory/members/scoped/#constructors-t","text":"A table, ideally including functions which take a scope as their first parameter. Those functions will turn into methods.","title":"constructors : T"},{"location":"api-reference/memory/members/scoped/#returns-scopet","text":"A blank scope with the specified methods.","title":"Returns -> Scope<T>"},{"location":"api-reference/memory/members/scoped/#learn-more","text":"Scopes tutorial","title":"Learn More"},{"location":"api-reference/memory/types/scope/","text":"Memory Types Scope Scope \u00b6 export type Scope < Constructors > = { unknown } & Constructors A table collecting all objects created as part of an independent unit of code, with optional Constructors as methods which can be called. Scopes are not unique Fusion can recycle old unused scopes. This helps make scopes more lightweight, but it also means they don't uniquely belong to any part of your program. As a result, you shouldn't hold on to scopes after they've been cleaned up, and you shouldn't use them as unique identifiers anywhere. Learn More \u00b6 Scopes tutorial","title":"Scope"},{"location":"api-reference/memory/types/scope/#scope","text":"export type Scope < Constructors > = { unknown } & Constructors A table collecting all objects created as part of an independent unit of code, with optional Constructors as methods which can be called. Scopes are not unique Fusion can recycle old unused scopes. This helps make scopes more lightweight, but it also means they don't uniquely belong to any part of your program. As a result, you shouldn't hold on to scopes after they've been cleaned up, and you shouldn't use them as unique identifiers anywhere.","title":"Scope"},{"location":"api-reference/memory/types/scope/#learn-more","text":"Scopes tutorial","title":"Learn More"},{"location":"api-reference/memory/types/scopedobject/","text":"Memory Types ScopedObject ScopedObject \u00b6 export type ScopedObject = { scope : Scope < unknown > ? , destroy : () -> () } An object designed for use with scopes . Objects satisfying this interface can be probed for information about their lifetime and how long they live relative to other objects satisfying this interface. These objects are also recognised by doCleanup . Members \u00b6 scope : Scope ? \u00b6 The scope which this object was constructed with, or nil if the object has been destroyed. Unchanged until destruction The scope is expected to be set once upon construction. It should not be assigned to again, except when the scope is destroyed - at which point it should be set to nil to indicate that it no longer exists inside of a scope. This is typically done inside the :destroy() method, if it exists. Methods \u00b6 destroy -> () \u00b6 function ScopedObject : destroy (): () Called by doCleanup to destroy this object. User code should generally not call this; instead, destroy the scope as a whole. Double-destruction prevention Fusion's objects throw destroyedTwice if they detect a nil scope during :destroy() . It's strongly recommended that you emulate this behaviour if you're implementing your own objects, as this protects against double-destruction and exposes potential scoping issues further ahead of time. Learn More \u00b6 Scopes tutorial","title":"ScopedObject"},{"location":"api-reference/memory/types/scopedobject/#scopedobject","text":"export type ScopedObject = { scope : Scope < unknown > ? , destroy : () -> () } An object designed for use with scopes . Objects satisfying this interface can be probed for information about their lifetime and how long they live relative to other objects satisfying this interface. These objects are also recognised by doCleanup .","title":"ScopedObject"},{"location":"api-reference/memory/types/scopedobject/#members","text":"","title":"Members"},{"location":"api-reference/memory/types/scopedobject/#scope-scopeunknown","text":"The scope which this object was constructed with, or nil if the object has been destroyed. Unchanged until destruction The scope is expected to be set once upon construction. It should not be assigned to again, except when the scope is destroyed - at which point it should be set to nil to indicate that it no longer exists inside of a scope. This is typically done inside the :destroy() method, if it exists.","title":"scope : Scope<unknown>?"},{"location":"api-reference/memory/types/scopedobject/#methods","text":"","title":"Methods"},{"location":"api-reference/memory/types/scopedobject/#destroy-","text":"function ScopedObject : destroy (): () Called by doCleanup to destroy this object. User code should generally not call this; instead, destroy the scope as a whole. Double-destruction prevention Fusion's objects throw destroyedTwice if they detect a nil scope during :destroy() . It's strongly recommended that you emulate this behaviour if you're implementing your own objects, as this protects against double-destruction and exposes potential scoping issues further ahead of time.","title":"destroy -> ()"},{"location":"api-reference/memory/types/scopedobject/#learn-more","text":"Scopes tutorial","title":"Learn More"},{"location":"api-reference/memory/types/task/","text":"Memory Types Task Task \u00b6 export type Task = Instance | RBXScriptConnection | () -> () | { destroy : ( self ) -> ()} | { Destroy : ( self ) -> ()} | { Task } Types which doCleanup has defined behaviour for. Not enforced Fusion does not use static types to enforce that doCleanup is given a type which it can process. This type is only exposed for your own use. Learn More \u00b6 Scopes tutorial","title":"Task"},{"location":"api-reference/memory/types/task/#task","text":"export type Task = Instance | RBXScriptConnection | () -> () | { destroy : ( self ) -> ()} | { Destroy : ( self ) -> ()} | { Task } Types which doCleanup has defined behaviour for. Not enforced Fusion does not use static types to enforce that doCleanup is given a type which it can process. This type is only exposed for your own use.","title":"Task"},{"location":"api-reference/memory/types/task/#learn-more","text":"Scopes tutorial","title":"Learn More"},{"location":"api-reference/roblox/members/attribute/","text":"Memory Members Attribute Attribute -> SpecialKey \u00b6 function Fusion . Attribute ( attributeName : string ): SpecialKey Given an attribute name, returns a special key which can modify attributes of that name. When paired with a value in a property table , the special key sets the attribute to that value. Parameters \u00b6 attributeName : string \u00b6 The name of the attribute that the special key should target. Returns -> SpecialKey \u00b6 A special key for modifying attributes of that name.","title":"Attribute"},{"location":"api-reference/roblox/members/attribute/#attribute-specialkey","text":"function Fusion . Attribute ( attributeName : string ): SpecialKey Given an attribute name, returns a special key which can modify attributes of that name. When paired with a value in a property table , the special key sets the attribute to that value.","title":"Attribute -> SpecialKey"},{"location":"api-reference/roblox/members/attribute/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/attribute/#attributename-string","text":"The name of the attribute that the special key should target.","title":"attributeName : string"},{"location":"api-reference/roblox/members/attribute/#returns-specialkey","text":"A special key for modifying attributes of that name.","title":"Returns -> SpecialKey"},{"location":"api-reference/roblox/members/attributechange/","text":"Memory Members AttributeChange AttributeChange -> SpecialKey \u00b6 function Fusion . AttributeChange ( attributeName : string ): SpecialKey Given an attribute name, returns a special key which can listen to changes for attributes of that name. When paired with a callback in a property table , the special key connects the callback to the attribute's change event. Parameters \u00b6 attributeName : string \u00b6 The name of the attribute that the special key should target. Returns -> SpecialKey \u00b6 A special key for listening to changes for attributes of that name.","title":"AttributeChange"},{"location":"api-reference/roblox/members/attributechange/#attributechange-specialkey","text":"function Fusion . AttributeChange ( attributeName : string ): SpecialKey Given an attribute name, returns a special key which can listen to changes for attributes of that name. When paired with a callback in a property table , the special key connects the callback to the attribute's change event.","title":"AttributeChange -> SpecialKey"},{"location":"api-reference/roblox/members/attributechange/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/attributechange/#attributename-string","text":"The name of the attribute that the special key should target.","title":"attributeName : string"},{"location":"api-reference/roblox/members/attributechange/#returns-specialkey","text":"A special key for listening to changes for attributes of that name.","title":"Returns -> SpecialKey"},{"location":"api-reference/roblox/members/attributeout/","text":"Memory Members AttributeOut AttributeOut -> SpecialKey \u00b6 function Fusion . AttributeOut ( attributeName : string ): SpecialKey Given an attribute name, returns a special key which can output values from attributes of that name. When paired with a value object in a property table , the special key sets the value when the attribute changes. Parameters \u00b6 attributeName : string \u00b6 The name of the attribute that the special key should target. Returns -> SpecialKey \u00b6 A special key for outputting values from attributes of that name.","title":"AttributeOut"},{"location":"api-reference/roblox/members/attributeout/#attributeout-specialkey","text":"function Fusion . AttributeOut ( attributeName : string ): SpecialKey Given an attribute name, returns a special key which can output values from attributes of that name. When paired with a value object in a property table , the special key sets the value when the attribute changes.","title":"AttributeOut -> SpecialKey"},{"location":"api-reference/roblox/members/attributeout/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/attributeout/#attributename-string","text":"The name of the attribute that the special key should target.","title":"attributeName : string"},{"location":"api-reference/roblox/members/attributeout/#returns-specialkey","text":"A special key for outputting values from attributes of that name.","title":"Returns -> SpecialKey"},{"location":"api-reference/roblox/members/children/","text":"Memory Members Children Children : SpecialKey \u00b6 Fusion . Children : SpecialKey A special key which parents other instances into this instance. When paired with a Child in a property table , the special key explores the Child to find every Instance nested inside. It then parents those instances under the instance which the special key was applied to. In particular, this special key will recursively explore arrays and bind to any state objects . Learn More \u00b6 Parenting tutorial","title":"Children"},{"location":"api-reference/roblox/members/children/#children-specialkey","text":"Fusion . Children : SpecialKey A special key which parents other instances into this instance. When paired with a Child in a property table , the special key explores the Child to find every Instance nested inside. It then parents those instances under the instance which the special key was applied to. In particular, this special key will recursively explore arrays and bind to any state objects .","title":"Children : SpecialKey"},{"location":"api-reference/roblox/members/children/#learn-more","text":"Parenting tutorial","title":"Learn More"},{"location":"api-reference/roblox/members/hydrate/","text":"Memory Members Hydrate Hydrate -> ( PropertyTable ) -> Instance \u00b6 function Fusion . Hydrate ( target : Instance ): ( props : PropertyTable ) -> Instance Given an instance, returns a component for binding extra functionality to that instance. In the property table, string keys are assigned as properties on the instance. If the value is a state object , it is re-assigned every time the value of the state object changes. Any special keys present in the property table are applied to the instance after string keys are processed, in the order specified by their stage . A special exception is made for assigning Parent , which is only assigned after the descendants stage. Do not overwrite properties If the instance was previously created with New or previously hydrated, do not assign to any properties that were previously specified in those prior calls. Duplicated assignments can interfere with each other in unpredictable ways. Parameters \u00b6 target : Instance \u00b6 The instance which should be modified. Returns -> ( PropertyTable ) -> Instance \u00b6 A component that hydrates that instance, accepting various properties to build up bindings and operations applied to the instance. Learn More \u00b6 Hydration tutorial","title":"Hydrate"},{"location":"api-reference/roblox/members/hydrate/#hydrate-propertytable-instance","text":"function Fusion . Hydrate ( target : Instance ): ( props : PropertyTable ) -> Instance Given an instance, returns a component for binding extra functionality to that instance. In the property table, string keys are assigned as properties on the instance. If the value is a state object , it is re-assigned every time the value of the state object changes. Any special keys present in the property table are applied to the instance after string keys are processed, in the order specified by their stage . A special exception is made for assigning Parent , which is only assigned after the descendants stage. Do not overwrite properties If the instance was previously created with New or previously hydrated, do not assign to any properties that were previously specified in those prior calls. Duplicated assignments can interfere with each other in unpredictable ways.","title":"Hydrate -> (PropertyTable) -> Instance"},{"location":"api-reference/roblox/members/hydrate/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/hydrate/#target-instance","text":"The instance which should be modified.","title":"target : Instance"},{"location":"api-reference/roblox/members/hydrate/#returns-propertytable-instance","text":"A component that hydrates that instance, accepting various properties to build up bindings and operations applied to the instance.","title":"Returns -> (PropertyTable) -> Instance"},{"location":"api-reference/roblox/members/hydrate/#learn-more","text":"Hydration tutorial","title":"Learn More"},{"location":"api-reference/roblox/members/new/","text":"Memory Members New New -> ( PropertyTable ) -> Instance \u00b6 function Fusion . New ( className : string ): ( props : PropertyTable ) -> Instance Given a class name, returns a component for constructing instances of that class. In the property table, string keys are assigned as properties on the instance. If the value is a state object , it is re-assigned every time the value of the state object changes. Any special keys present in the property table are applied to the instance after string keys are processed, in the order specified by their stage . A special exception is made for assigning Parent , which is only assigned after the descendants stage. Parameters \u00b6 className : string \u00b6 The kind of instance that should be constructed. Returns -> ( PropertyTable ) -> Instance \u00b6 A component that constructs instances of that type, accepting various properties to customise each instance uniquely. Learn More \u00b6 New Instances tutorial","title":"New"},{"location":"api-reference/roblox/members/new/#new-propertytable-instance","text":"function Fusion . New ( className : string ): ( props : PropertyTable ) -> Instance Given a class name, returns a component for constructing instances of that class. In the property table, string keys are assigned as properties on the instance. If the value is a state object , it is re-assigned every time the value of the state object changes. Any special keys present in the property table are applied to the instance after string keys are processed, in the order specified by their stage . A special exception is made for assigning Parent , which is only assigned after the descendants stage.","title":"New -> (PropertyTable) -> Instance"},{"location":"api-reference/roblox/members/new/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/new/#classname-string","text":"The kind of instance that should be constructed.","title":"className : string"},{"location":"api-reference/roblox/members/new/#returns-propertytable-instance","text":"A component that constructs instances of that type, accepting various properties to customise each instance uniquely.","title":"Returns -> (PropertyTable) -> Instance"},{"location":"api-reference/roblox/members/new/#learn-more","text":"New Instances tutorial","title":"Learn More"},{"location":"api-reference/roblox/members/onchange/","text":"Memory Members OnChange OnChange -> SpecialKey \u00b6 function Fusion . OnChange ( propertyName : string ): SpecialKey Given an property name, returns a special key which can listen to changes for properties of that name. When paired with a callback in a property table , the special key connects the callback to the property's change event. Parameters \u00b6 propertyName : string \u00b6 The name of the property that the special key should target. Returns -> SpecialKey \u00b6 A special key for listening to changes for properties of that name. Learn More \u00b6 Change Events tutorial","title":"OnChange"},{"location":"api-reference/roblox/members/onchange/#onchange-specialkey","text":"function Fusion . OnChange ( propertyName : string ): SpecialKey Given an property name, returns a special key which can listen to changes for properties of that name. When paired with a callback in a property table , the special key connects the callback to the property's change event.","title":"OnChange -> SpecialKey"},{"location":"api-reference/roblox/members/onchange/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/onchange/#propertyname-string","text":"The name of the property that the special key should target.","title":"propertyName : string"},{"location":"api-reference/roblox/members/onchange/#returns-specialkey","text":"A special key for listening to changes for properties of that name.","title":"Returns -> SpecialKey"},{"location":"api-reference/roblox/members/onchange/#learn-more","text":"Change Events tutorial","title":"Learn More"},{"location":"api-reference/roblox/members/onevent/","text":"Memory Members OnEvent OnEvent -> SpecialKey \u00b6 function Fusion . OnEvent ( eventName : string ): SpecialKey Given an event name, returns a special key which can listen for events of that name. When paired with a callback in a property table , the special key connects the callback to the event. Parameters \u00b6 eventName : string \u00b6 The name of the event that the special key should target. Returns -> SpecialKey \u00b6 A special key for listening to events of that name. Learn More \u00b6 Events tutorial","title":"OnEvent"},{"location":"api-reference/roblox/members/onevent/#onevent-specialkey","text":"function Fusion . OnEvent ( eventName : string ): SpecialKey Given an event name, returns a special key which can listen for events of that name. When paired with a callback in a property table , the special key connects the callback to the event.","title":"OnEvent -> SpecialKey"},{"location":"api-reference/roblox/members/onevent/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/onevent/#eventname-string","text":"The name of the event that the special key should target.","title":"eventName : string"},{"location":"api-reference/roblox/members/onevent/#returns-specialkey","text":"A special key for listening to events of that name.","title":"Returns -> SpecialKey"},{"location":"api-reference/roblox/members/onevent/#learn-more","text":"Events tutorial","title":"Learn More"},{"location":"api-reference/roblox/members/out/","text":"Memory Members Out Out -> SpecialKey \u00b6 function Fusion . Out ( propertyName : string ): SpecialKey Given an property name, returns a special key which can output values from properties of that name. When paired with a value object in a property table , the special key sets the value when the property changes. Parameters \u00b6 propertyName : string \u00b6 The name of the property that the special key should target. Returns -> SpecialKey \u00b6 A special key for outputting values from properties of that name. Learn More \u00b6 Outputs tutorial","title":"Out"},{"location":"api-reference/roblox/members/out/#out-specialkey","text":"function Fusion . Out ( propertyName : string ): SpecialKey Given an property name, returns a special key which can output values from properties of that name. When paired with a value object in a property table , the special key sets the value when the property changes.","title":"Out -> SpecialKey"},{"location":"api-reference/roblox/members/out/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/out/#propertyname-string","text":"The name of the property that the special key should target.","title":"propertyName : string"},{"location":"api-reference/roblox/members/out/#returns-specialkey","text":"A special key for outputting values from properties of that name.","title":"Returns -> SpecialKey"},{"location":"api-reference/roblox/members/out/#learn-more","text":"Outputs tutorial","title":"Learn More"},{"location":"api-reference/roblox/members/ref/","text":"Memory Members Ref Ref : SpecialKey \u00b6 Fusion . Ref : SpecialKey A special key which outputs the instance it's being applied to. When paired with a value object in a property table , the special key sets the value to the instance. Learn More \u00b6 References tutorial","title":"Ref"},{"location":"api-reference/roblox/members/ref/#ref-specialkey","text":"Fusion . Ref : SpecialKey A special key which outputs the instance it's being applied to. When paired with a value object in a property table , the special key sets the value to the instance.","title":"Ref : SpecialKey"},{"location":"api-reference/roblox/members/ref/#learn-more","text":"References tutorial","title":"Learn More"},{"location":"api-reference/roblox/types/child/","text":"Roblox Types Child Child \u00b6 export type Child = Instance | StateObject < Child > | {[ unknown ]: Child } All of the types understood by the [Children] special key. Learn More \u00b6 Parenting tutorial Instance Handling tutorial","title":"Child"},{"location":"api-reference/roblox/types/child/#child","text":"export type Child = Instance | StateObject < Child > | {[ unknown ]: Child } All of the types understood by the [Children] special key.","title":"Child"},{"location":"api-reference/roblox/types/child/#learn-more","text":"Parenting tutorial Instance Handling tutorial","title":"Learn More"},{"location":"api-reference/roblox/types/propertytable/","text":"Roblox Types PropertyTable PropertyTable \u00b6 export type PropertyTable = {[ string | SpecialKey ]: unknown } A table of named instance properties and special keys , which can be passed to New to create an instance. This type can be overly generic In most cases, you should know what properties your code is looking for. In those cases, you should prefer to list out the properties explicitly, to document what your code needs. You should only use this type if you don't know what properties your code will accept.","title":"PropertyTable"},{"location":"api-reference/roblox/types/propertytable/#propertytable","text":"export type PropertyTable = {[ string | SpecialKey ]: unknown } A table of named instance properties and special keys , which can be passed to New to create an instance. This type can be overly generic In most cases, you should know what properties your code is looking for. In those cases, you should prefer to list out the properties explicitly, to document what your code needs. You should only use this type if you don't know what properties your code will accept.","title":"PropertyTable"},{"location":"api-reference/roblox/types/specialkey/","text":"General Types SpecialKey SpecialKey \u00b6 export type SpecialKey = { type : \"SpecialKey\" , kind : string , stage : \"self\" | \"descendants\" | \"ancestor\" | \"observer\" , apply : ( self , scope : Scope < unknown > , value : unknown , applyTo : Instance ) -> () } When used as the key in a property table , defines a custom operation to apply to the created Roblox instance. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially. Members \u00b6 type : \"SpecialKey\" \u00b6 A type string which can be used for runtime type checking. kind : string \u00b6 A more specific type string which can be used for runtime type checking. This can be used to tell types of special key apart. stage : \"self\" | \"descendants\" | \"ancestor\" | \"observer\" \u00b6 Describes the type of operation, which subsequently determines when it's applied relative to other operations. self runs before parenting any instances descendants runs once descendants are parented, but before this instance is parented to its ancestor ancestor runs after all parenting operations are complete observer runs after all other operations, so the final state of the instance can be observed Methods \u00b6 apply -> () \u00b6 function SpecialKey : apply ( self , scope : Scope < unknown > , value : unknown , applyTo : Instance ): () Called to apply this operation to an instance. value is the value from the property table, and applyTo is the instance to apply the operation to. The given scope is cleaned up when the operation is being unapplied, including when the instance is destroyed. Operations should use the scope to clean up any connections or undo any changes they cause.","title":"SpecialKey"},{"location":"api-reference/roblox/types/specialkey/#specialkey","text":"export type SpecialKey = { type : \"SpecialKey\" , kind : string , stage : \"self\" | \"descendants\" | \"ancestor\" | \"observer\" , apply : ( self , scope : Scope < unknown > , value : unknown , applyTo : Instance ) -> () } When used as the key in a property table , defines a custom operation to apply to the created Roblox instance. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially.","title":"SpecialKey"},{"location":"api-reference/roblox/types/specialkey/#members","text":"","title":"Members"},{"location":"api-reference/roblox/types/specialkey/#type-specialkey","text":"A type string which can be used for runtime type checking.","title":"type : \"SpecialKey\""},{"location":"api-reference/roblox/types/specialkey/#kind-string","text":"A more specific type string which can be used for runtime type checking. This can be used to tell types of special key apart.","title":"kind : string"},{"location":"api-reference/roblox/types/specialkey/#stage-self-descendants-ancestor-observer","text":"Describes the type of operation, which subsequently determines when it's applied relative to other operations. self runs before parenting any instances descendants runs once descendants are parented, but before this instance is parented to its ancestor ancestor runs after all parenting operations are complete observer runs after all other operations, so the final state of the instance can be observed","title":"stage : \"self\" | \"descendants\" | \"ancestor\" | \"observer\""},{"location":"api-reference/roblox/types/specialkey/#methods","text":"","title":"Methods"},{"location":"api-reference/roblox/types/specialkey/#apply-","text":"function SpecialKey : apply ( self , scope : Scope < unknown > , value : unknown , applyTo : Instance ): () Called to apply this operation to an instance. value is the value from the property table, and applyTo is the instance to apply the operation to. The given scope is cleaned up when the operation is being unapplied, including when the instance is destroyed. Operations should use the scope to clean up any connections or undo any changes they cause.","title":"apply -> ()"},{"location":"api-reference/state/members/computed/","text":"State Members Computed Computed -> Computed \u00b6 function Fusion . Computed < T , S > ( scope : Scope < S > , processor : ( Use , Scope < S > ) -> T ) -> Computed < T > Constructs and returns a new computed state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local computed = scope : Computed ( processor ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. processor : ( Use , Scope ) -> T \u00b6 Computes the value that will be used by the computed. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the computed. Returns -> Computed \u00b6 A freshly constructed computed state object. Learn More \u00b6 Computeds tutorial","title":"Computed"},{"location":"api-reference/state/members/computed/#computed-computedt","text":"function Fusion . Computed < T , S > ( scope : Scope < S > , processor : ( Use , Scope < S > ) -> T ) -> Computed < T > Constructs and returns a new computed state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local computed = scope : Computed ( processor )","title":"Computed -> Computed<T>"},{"location":"api-reference/state/members/computed/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/members/computed/#scope-scopes","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<S>"},{"location":"api-reference/state/members/computed/#processor-use-scopes-t","text":"Computes the value that will be used by the computed. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the computed.","title":"processor : (Use, Scope<S>) -> T"},{"location":"api-reference/state/members/computed/#returns-computedt","text":"A freshly constructed computed state object.","title":"Returns -> Computed<T>"},{"location":"api-reference/state/members/computed/#learn-more","text":"Computeds tutorial","title":"Learn More"},{"location":"api-reference/state/members/forkeys/","text":"State Members ForKeys ForKeys -> For \u00b6 function Fusion . ForKeys < KI , KO , V , S > ( scope : Scope < S > , inputTable : UsedAs < {[ KI ]: V } > , processor : ( Use , Scope < S > , key : KI ) -> KO ) -> For < KO , V > Constructs and returns a new For state object which processes keys and preserves values. Use scoped() method syntax This function is intended to be accessed as a method on a scope: local forObj = scope : ForKeys ( inputTable , processor ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. inputTable : UsedAs <{[KI]: V}> \u00b6 The table which will provide the input keys and input values for this object. If it is a state object, this object will respond to changes in that state. processor : ( Use , Scope , key: KI) -> KO \u00b6 Accepts a KI key from the input table, and returns the KO key that should appear in the output table. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the whole object. Returns -> For \u00b6 A freshly constructed For state object. Learn More \u00b6 ForKeys tutorial","title":"ForKeys"},{"location":"api-reference/state/members/forkeys/#forkeys-forko-v","text":"function Fusion . ForKeys < KI , KO , V , S > ( scope : Scope < S > , inputTable : UsedAs < {[ KI ]: V } > , processor : ( Use , Scope < S > , key : KI ) -> KO ) -> For < KO , V > Constructs and returns a new For state object which processes keys and preserves values. Use scoped() method syntax This function is intended to be accessed as a method on a scope: local forObj = scope : ForKeys ( inputTable , processor )","title":"ForKeys -> For<KO, V>"},{"location":"api-reference/state/members/forkeys/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/members/forkeys/#scope-scopes","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<S>"},{"location":"api-reference/state/members/forkeys/#inputtable-usedaski-v","text":"The table which will provide the input keys and input values for this object. If it is a state object, this object will respond to changes in that state.","title":"inputTable : UsedAs<{[KI]: V}>"},{"location":"api-reference/state/members/forkeys/#processor-use-scopes-key-ki-ko","text":"Accepts a KI key from the input table, and returns the KO key that should appear in the output table. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the whole object.","title":"processor : (Use, Scope<S>, key: KI) -> KO"},{"location":"api-reference/state/members/forkeys/#returns-forko-v","text":"A freshly constructed For state object.","title":"Returns -> For<KO, V>"},{"location":"api-reference/state/members/forkeys/#learn-more","text":"ForKeys tutorial","title":"Learn More"},{"location":"api-reference/state/members/forpairs/","text":"State Members ForPairs ForPairs -> For \u00b6 function Fusion . ForPairs < KI , KO , VI , VO , S > ( scope : Scope < S > , inputTable : UsedAs < {[ KI ]: VI } > , processor : ( Use , Scope < S > , key : KI , value : VI ) -> ( KO , VO ) ) -> For < KO , VO > Constructs and returns a new For state object which processes keys and values in pairs. Use scoped() method syntax This function is intended to be accessed as a method on a scope: local forObj = scope : ForPairs ( inputTable , processor ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. inputTable : UsedAs <{[KI]: VI}> \u00b6 The table which will provide the input keys and input values for this object. If it is a state object, this object will respond to changes in that state. processor : ( Use , Scope , key: KI, value: VI) -> (KO, VO) \u00b6 Accepts a KI key and VI value pair from the input table, and returns the KO key and VO value pair that should appear in the output table. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the whole object. Returns -> For \u00b6 A freshly constructed For state object. Learn More \u00b6 ForPairs tutorial","title":"ForPairs"},{"location":"api-reference/state/members/forpairs/#forpairs-forko-vo","text":"function Fusion . ForPairs < KI , KO , VI , VO , S > ( scope : Scope < S > , inputTable : UsedAs < {[ KI ]: VI } > , processor : ( Use , Scope < S > , key : KI , value : VI ) -> ( KO , VO ) ) -> For < KO , VO > Constructs and returns a new For state object which processes keys and values in pairs. Use scoped() method syntax This function is intended to be accessed as a method on a scope: local forObj = scope : ForPairs ( inputTable , processor )","title":"ForPairs -> For<KO, VO>"},{"location":"api-reference/state/members/forpairs/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/members/forpairs/#scope-scopes","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<S>"},{"location":"api-reference/state/members/forpairs/#inputtable-usedaski-vi","text":"The table which will provide the input keys and input values for this object. If it is a state object, this object will respond to changes in that state.","title":"inputTable : UsedAs<{[KI]: VI}>"},{"location":"api-reference/state/members/forpairs/#processor-use-scopes-key-ki-value-vi-ko-vo","text":"Accepts a KI key and VI value pair from the input table, and returns the KO key and VO value pair that should appear in the output table. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the whole object.","title":"processor : (Use, Scope<S>, key: KI, value: VI) -> (KO, VO)"},{"location":"api-reference/state/members/forpairs/#returns-forko-vo","text":"A freshly constructed For state object.","title":"Returns -> For<KO, VO>"},{"location":"api-reference/state/members/forpairs/#learn-more","text":"ForPairs tutorial","title":"Learn More"},{"location":"api-reference/state/members/forvalues/","text":"State Members ForValues ForValues -> For \u00b6 function Fusion . ForValues < K , VI , VO , S > ( scope : Scope < S > , inputTable : UsedAs < {[ K ]: VI } > , processor : ( Use , Scope < S > , value : VI ) -> VO ) -> For < K , VO > Constructs and returns a new For state object which processes values and preserves keys. Use scoped() method syntax This function is intended to be accessed as a method on a scope: local forObj = scope : ForValues ( inputTable , processor ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. inputTable : UsedAs <{[K]: VI}> \u00b6 The table which will provide the input keys and input values for this object. If it is a state object, this object will respond to changes in that state. processor : ( Use , Scope , value: VI) -> VO \u00b6 Accepts a VI value from the input table, and returns the VO value that should appear in the output table. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the whole object. Returns -> For \u00b6 A freshly constructed For state object. Learn More \u00b6 ForValues tutorial","title":"ForValues"},{"location":"api-reference/state/members/forvalues/#forvalues-fork-vo","text":"function Fusion . ForValues < K , VI , VO , S > ( scope : Scope < S > , inputTable : UsedAs < {[ K ]: VI } > , processor : ( Use , Scope < S > , value : VI ) -> VO ) -> For < K , VO > Constructs and returns a new For state object which processes values and preserves keys. Use scoped() method syntax This function is intended to be accessed as a method on a scope: local forObj = scope : ForValues ( inputTable , processor )","title":"ForValues -> For<K, VO>"},{"location":"api-reference/state/members/forvalues/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/members/forvalues/#scope-scopes","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<S>"},{"location":"api-reference/state/members/forvalues/#inputtable-usedask-vi","text":"The table which will provide the input keys and input values for this object. If it is a state object, this object will respond to changes in that state.","title":"inputTable : UsedAs<{[K]: VI}>"},{"location":"api-reference/state/members/forvalues/#processor-use-scopes-value-vi-vo","text":"Accepts a VI value from the input table, and returns the VO value that should appear in the output table. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the whole object.","title":"processor : (Use, Scope<S>, value: VI) -> VO"},{"location":"api-reference/state/members/forvalues/#returns-fork-vo","text":"A freshly constructed For state object.","title":"Returns -> For<K, VO>"},{"location":"api-reference/state/members/forvalues/#learn-more","text":"ForValues tutorial","title":"Learn More"},{"location":"api-reference/state/members/observer/","text":"State Members Observer Observer -> Observer \u00b6 function Fusion . Observer ( scope : Scope < unknown > , watching : unknown ) -> Observer Constructs and returns a new observer . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local observer = scope : Observer ( watching ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. watching : unknown \u00b6 The target that the observer should watch for changes. Works best with state objects While non- state object values are accepted for compatibility, they won't be able to trigger updates. Returns -> Observer \u00b6 A freshly constructed observer. Learn More \u00b6 Observers tutorial","title":"Observer"},{"location":"api-reference/state/members/observer/#observer-observer","text":"function Fusion . Observer ( scope : Scope < unknown > , watching : unknown ) -> Observer Constructs and returns a new observer . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local observer = scope : Observer ( watching )","title":"Observer -> Observer"},{"location":"api-reference/state/members/observer/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/members/observer/#scope-scopeunknown","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<unknown>"},{"location":"api-reference/state/members/observer/#watching-unknown","text":"The target that the observer should watch for changes. Works best with state objects While non- state object values are accepted for compatibility, they won't be able to trigger updates.","title":"watching : unknown"},{"location":"api-reference/state/members/observer/#returns-observer","text":"A freshly constructed observer.","title":"Returns -> Observer"},{"location":"api-reference/state/members/observer/#learn-more","text":"Observers tutorial","title":"Learn More"},{"location":"api-reference/state/members/peek/","text":"State Members peek peek : Use \u00b6 function Fusion . peek < T > ( target : UsedAs < T > ): T Extract a value of type T from its input. This is a general-purpose implementation of Use . It does not do any extra processing or book-keeping beyond what is required to determine the returned value. Specific implementations If you're given a specific implementation of Use by an API, it's highly likely that you are expected to use that implementation instead of peek() . This applies to reusable code too. It's often best to ask for a Use callback if your code needs to extract values, so an appropriate implementation can be passed in. Alternatively for reusable code, you can avoid extracting values entirely, and expect the user to do it prior to calling your code. This can work well if you unconditionally use all inputs, but beware that you may end up extracting more values than you need - this can have performance implications. Parameters \u00b6 target : UsedAs \u00b6 The abstract representation of T to extract a value from. Returns -> T \u00b6 The current value of T , derived from target . Learn More \u00b6 Values tutorial","title":"peek"},{"location":"api-reference/state/members/peek/#peek-use","text":"function Fusion . peek < T > ( target : UsedAs < T > ): T Extract a value of type T from its input. This is a general-purpose implementation of Use . It does not do any extra processing or book-keeping beyond what is required to determine the returned value. Specific implementations If you're given a specific implementation of Use by an API, it's highly likely that you are expected to use that implementation instead of peek() . This applies to reusable code too. It's often best to ask for a Use callback if your code needs to extract values, so an appropriate implementation can be passed in. Alternatively for reusable code, you can avoid extracting values entirely, and expect the user to do it prior to calling your code. This can work well if you unconditionally use all inputs, but beware that you may end up extracting more values than you need - this can have performance implications.","title":"peek : Use"},{"location":"api-reference/state/members/peek/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/members/peek/#target-usedast","text":"The abstract representation of T to extract a value from.","title":"target : UsedAs<T>"},{"location":"api-reference/state/members/peek/#returns-t","text":"The current value of T , derived from target .","title":"Returns -> T"},{"location":"api-reference/state/members/peek/#learn-more","text":"Values tutorial","title":"Learn More"},{"location":"api-reference/state/members/value/","text":"State Members Value Value -> Value \u00b6 function Fusion . Value < T > ( scope : Scope < unknown > , initialValue : T ) -> Value < T > Constructs and returns a new value state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local computed = scope : Computed ( processor ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. initialValue : T \u00b6 The initial value that will be stored until the next value is :set() . Returns -> Value \u00b6 A freshly constructed value state object. Learn More \u00b6 Values tutorial","title":"Value"},{"location":"api-reference/state/members/value/#value-valuet","text":"function Fusion . Value < T > ( scope : Scope < unknown > , initialValue : T ) -> Value < T > Constructs and returns a new value state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local computed = scope : Computed ( processor )","title":"Value -> Value<T>"},{"location":"api-reference/state/members/value/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/members/value/#scope-scopes","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<S>"},{"location":"api-reference/state/members/value/#initialvalue-t","text":"The initial value that will be stored until the next value is :set() .","title":"initialValue : T"},{"location":"api-reference/state/members/value/#returns-valuet","text":"A freshly constructed value state object.","title":"Returns -> Value<T>"},{"location":"api-reference/state/members/value/#learn-more","text":"Values tutorial","title":"Learn More"},{"location":"api-reference/state/types/computed/","text":"State Types Computed Computed \u00b6 export type Computed < T > = StateObject < T > & Dependent & { kind : \"Computed\" } A specialised state object for tracking single values computed from a user-defined computation. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from the objects used as part of the computation. This type isn't generally useful outside of Fusion itself. Members \u00b6 kind : \"Computed\" \u00b6 A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart. Learn More \u00b6 Computeds tutorial","title":"Computed"},{"location":"api-reference/state/types/computed/#computed","text":"export type Computed < T > = StateObject < T > & Dependent & { kind : \"Computed\" } A specialised state object for tracking single values computed from a user-defined computation. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from the objects used as part of the computation. This type isn't generally useful outside of Fusion itself.","title":"Computed"},{"location":"api-reference/state/types/computed/#members","text":"","title":"Members"},{"location":"api-reference/state/types/computed/#kind-computed","text":"A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart.","title":"kind : \"Computed\""},{"location":"api-reference/state/types/computed/#learn-more","text":"Computeds tutorial","title":"Learn More"},{"location":"api-reference/state/types/dependency/","text":"State Types Dependency Dependency \u00b6 export type Dependency = ScopedObject & { dependentSet : {[ Dependent ]: unknown } } A reactive graph object which can broadcast updates. Other graph objects can declare themselves as dependent upon this object to receive updates. This type includes ScopedObject , which allows the lifetime and destruction order of the reactive graph to be analysed. Members \u00b6 dependentSet : {[ Dependent ]: unknown} \u00b6 The reactive graph objects which declare themselves as dependent upon this object.","title":"Dependency"},{"location":"api-reference/state/types/dependency/#dependency","text":"export type Dependency = ScopedObject & { dependentSet : {[ Dependent ]: unknown } } A reactive graph object which can broadcast updates. Other graph objects can declare themselves as dependent upon this object to receive updates. This type includes ScopedObject , which allows the lifetime and destruction order of the reactive graph to be analysed.","title":"Dependency"},{"location":"api-reference/state/types/dependency/#members","text":"","title":"Members"},{"location":"api-reference/state/types/dependency/#dependentset-dependent-unknown","text":"The reactive graph objects which declare themselves as dependent upon this object.","title":"dependentSet : {[Dependent]: unknown}"},{"location":"api-reference/state/types/dependent/","text":"State Types Dependent Dependent \u00b6 export type Dependent = ScopedObject & { update : ( self ) -> boolean , dependencySet : {[ Dependency ]: unknown } } A reactive graph object which can add itself to dependencies and receive updates. This type includes ScopedObject , which allows the lifetime and destruction order of the reactive graph to be analysed. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially. Members \u00b6 dependencySet : {[ Dependency ]: unknown} \u00b6 Everything this reactive graph object currently declares itself as dependent upon. Methods \u00b6 update -> boolean \u00b6 function Dependent : update (): boolean Called from a dependency when a change occurs. Returns true if the update should propagate transitively through this object, or false if the update should not continue through this object specifically. Return value ignored for non-dependencies If this Dependent is not also a Dependency , the return value does nothing, as an object must be declarable as a dependency for other objects to receive updates from it.","title":"Dependent"},{"location":"api-reference/state/types/dependent/#dependent","text":"export type Dependent = ScopedObject & { update : ( self ) -> boolean , dependencySet : {[ Dependency ]: unknown } } A reactive graph object which can add itself to dependencies and receive updates. This type includes ScopedObject , which allows the lifetime and destruction order of the reactive graph to be analysed. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially.","title":"Dependent"},{"location":"api-reference/state/types/dependent/#members","text":"","title":"Members"},{"location":"api-reference/state/types/dependent/#dependencyset-dependency-unknown","text":"Everything this reactive graph object currently declares itself as dependent upon.","title":"dependencySet : {[Dependency]: unknown}"},{"location":"api-reference/state/types/dependent/#methods","text":"","title":"Methods"},{"location":"api-reference/state/types/dependent/#update-boolean","text":"function Dependent : update (): boolean Called from a dependency when a change occurs. Returns true if the update should propagate transitively through this object, or false if the update should not continue through this object specifically. Return value ignored for non-dependencies If this Dependent is not also a Dependency , the return value does nothing, as an object must be declarable as a dependency for other objects to receive updates from it.","title":"update -> boolean"},{"location":"api-reference/state/types/for/","text":"State Types For For \u00b6 export type For < KO , VO > = StateObject < {[ KO ]: VO } > & Dependent & { kind : \"For\" } A specialised state object for tracking multiple values computed from user-defined computations, which are merged into an output table. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from objects used as part of any of the computations. This type isn't generally useful outside of Fusion itself. Members \u00b6 kind : \"For\" \u00b6 A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart. Learn More \u00b6 ForValues tutorial ForKeys tutorial ForPairs tutorial","title":"For"},{"location":"api-reference/state/types/for/#for","text":"export type For < KO , VO > = StateObject < {[ KO ]: VO } > & Dependent & { kind : \"For\" } A specialised state object for tracking multiple values computed from user-defined computations, which are merged into an output table. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from objects used as part of any of the computations. This type isn't generally useful outside of Fusion itself.","title":"For"},{"location":"api-reference/state/types/for/#members","text":"","title":"Members"},{"location":"api-reference/state/types/for/#kind-for","text":"A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart.","title":"kind : \"For\""},{"location":"api-reference/state/types/for/#learn-more","text":"ForValues tutorial ForKeys tutorial ForPairs tutorial","title":"Learn More"},{"location":"api-reference/state/types/observer/","text":"State Types Observer Observer \u00b6 export type Observer = Dependent & { type : \"Observer\" , onChange : ( self , callback : () -> ()) -> (() -> ()), onBind : ( self , callback : () -> ()) -> (() -> ()) } A user-constructed dependent that runs user code when its dependency is updated. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially. Members \u00b6 type : \"Observer\" \u00b6 A type string which can be used for runtime type checking. Methods \u00b6 onChange -> (() -> ()) \u00b6 function Observer : onChange ( callback : () -> () ): (() -> ()) Registers the callback to run when an update is received. The returned function will unregister the callback. onBind -> (() -> ()) \u00b6 function Observer : onBind ( callback : () -> () ): (() -> ()) Runs the callback immediately, and registers the callback to run when an update is received. The returned function will unregister the callback. Learn More \u00b6 Observers tutorial","title":"Observer"},{"location":"api-reference/state/types/observer/#observer","text":"export type Observer = Dependent & { type : \"Observer\" , onChange : ( self , callback : () -> ()) -> (() -> ()), onBind : ( self , callback : () -> ()) -> (() -> ()) } A user-constructed dependent that runs user code when its dependency is updated. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially.","title":"Observer"},{"location":"api-reference/state/types/observer/#members","text":"","title":"Members"},{"location":"api-reference/state/types/observer/#type-observer","text":"A type string which can be used for runtime type checking.","title":"type : \"Observer\""},{"location":"api-reference/state/types/observer/#methods","text":"","title":"Methods"},{"location":"api-reference/state/types/observer/#onchange-","text":"function Observer : onChange ( callback : () -> () ): (() -> ()) Registers the callback to run when an update is received. The returned function will unregister the callback.","title":"onChange -> (() -> ())"},{"location":"api-reference/state/types/observer/#onbind-","text":"function Observer : onBind ( callback : () -> () ): (() -> ()) Runs the callback immediately, and registers the callback to run when an update is received. The returned function will unregister the callback.","title":"onBind -> (() -> ())"},{"location":"api-reference/state/types/observer/#learn-more","text":"Observers tutorial","title":"Learn More"},{"location":"api-reference/state/types/stateobject/","text":"State Types StateObject StateObject \u00b6 export type StateObject < T > = Dependency & { type : \"State\" , kind : string } Stores a value of T which can change over time. As a dependency , it can broadcast updates when its value changes. This type isn't generally useful outside of Fusion itself; you should prefer to work with UsedAs in your own code. Members \u00b6 type : \"State\" \u00b6 A type string which can be used for runtime type checking. kind : string \u00b6 A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart.","title":"StateObject"},{"location":"api-reference/state/types/stateobject/#stateobject","text":"export type StateObject < T > = Dependency & { type : \"State\" , kind : string } Stores a value of T which can change over time. As a dependency , it can broadcast updates when its value changes. This type isn't generally useful outside of Fusion itself; you should prefer to work with UsedAs in your own code.","title":"StateObject"},{"location":"api-reference/state/types/stateobject/#members","text":"","title":"Members"},{"location":"api-reference/state/types/stateobject/#type-state","text":"A type string which can be used for runtime type checking.","title":"type : \"State\""},{"location":"api-reference/state/types/stateobject/#kind-string","text":"A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart.","title":"kind : string"},{"location":"api-reference/state/types/use/","text":"State Types Use Use \u00b6 export type Use = < T > ( target : UsedAs < T > ) -> T A function which extracts a value of T from something that can be used as T . The most generic implementation of this is the peek() function , which performs this extraction with no additional steps. However, certain APIs may provide their own implementation, so they can perform additional processing for certain representations. Most notably, computeds provide their own use() function which adds inputs to a watchlist, which allows them to re-calculate as inputs change. Parameters \u00b6 target : UsedAs \u00b6 The representation of T to extract a value from. Returns -> T \u00b6 The current value of T , derived from target . Learn More \u00b6 Values tutorial Computeds tutorial","title":"Use"},{"location":"api-reference/state/types/use/#use","text":"export type Use = < T > ( target : UsedAs < T > ) -> T A function which extracts a value of T from something that can be used as T . The most generic implementation of this is the peek() function , which performs this extraction with no additional steps. However, certain APIs may provide their own implementation, so they can perform additional processing for certain representations. Most notably, computeds provide their own use() function which adds inputs to a watchlist, which allows them to re-calculate as inputs change.","title":"Use"},{"location":"api-reference/state/types/use/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/types/use/#target-usedast","text":"The representation of T to extract a value from.","title":"target : UsedAs<T>"},{"location":"api-reference/state/types/use/#returns-t","text":"The current value of T , derived from target .","title":"Returns -> T"},{"location":"api-reference/state/types/use/#learn-more","text":"Values tutorial Computeds tutorial","title":"Learn More"},{"location":"api-reference/state/types/usedas/","text":"State Types UsedAs UsedAs \u00b6 export type UsedAs < T > = T | StateObject < T > Something which describes a value of type T . When it is used in a calculation, it becomes that value. Recommended Instead of using one of the more specific variants, your code should aim to use this type as often as possible. It allows your logic to deal with many representations of values at once, Variants \u00b6 T - represents unchanging constant values StateObject - represents dynamically updating values Learn More \u00b6 Components tutorial","title":"UsedAs"},{"location":"api-reference/state/types/usedas/#usedas","text":"export type UsedAs < T > = T | StateObject < T > Something which describes a value of type T . When it is used in a calculation, it becomes that value. Recommended Instead of using one of the more specific variants, your code should aim to use this type as often as possible. It allows your logic to deal with many representations of values at once,","title":"UsedAs"},{"location":"api-reference/state/types/usedas/#variants","text":"T - represents unchanging constant values StateObject - represents dynamically updating values","title":"Variants"},{"location":"api-reference/state/types/usedas/#learn-more","text":"Components tutorial","title":"Learn More"},{"location":"api-reference/state/types/value/","text":"State Types Value Value \u00b6 export type Value < T > = StateObject < T > & { kind : \"State\" , set : ( self , newValue : T ) -> () } A specialised state object which allows regular Luau code to control its value. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially. Members \u00b6 kind : \"Value\" \u00b6 A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart. Methods \u00b6 set -> () \u00b6 function Value : set ( newValue : T ): () Updates the value of this state object. Other objects using the value are notified immediately of the change. Learn More \u00b6 Values tutorial","title":"Value"},{"location":"api-reference/state/types/value/#value","text":"export type Value < T > = StateObject < T > & { kind : \"State\" , set : ( self , newValue : T ) -> () } A specialised state object which allows regular Luau code to control its value. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially.","title":"Value"},{"location":"api-reference/state/types/value/#members","text":"","title":"Members"},{"location":"api-reference/state/types/value/#kind-value","text":"A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart.","title":"kind : \"Value\""},{"location":"api-reference/state/types/value/#methods","text":"","title":"Methods"},{"location":"api-reference/state/types/value/#set-","text":"function Value : set ( newValue : T ): () Updates the value of this state object. Other objects using the value are notified immediately of the change.","title":"set -> ()"},{"location":"api-reference/state/types/value/#learn-more","text":"Values tutorial","title":"Learn More"},{"location":"examples/","text":"Examples \u00b6 Welcome to the Examples section! Here, you can find various open-source examples and projects, so you can see how Fusion works in a real setting. The Cookbook \u00b6 Oftentimes, you might be stuck on a small problem. You want to create something specific, but don't know how to do it with Fusion's tools. The cookbook can help with that! It's a collection of snippets which show you how to do various small tasks with Fusion, like processing arrays, applying animations and responding to different events. Visit the cookbook to see what's available. Open-Source Projects \u00b6 Fusion Wordle (for Fusion 0.2) \u00b6 See how Fusion can be used to build a mobile-first UI-centric game, with server validation, spring animations and sounds. Play and edit the game on Roblox. Fusion Obby (for Fusion 0.1) \u00b6 See how Fusion can be used to build a minimal interface for an obby, with an animated checkpoint counter and simulated confetti. Play and edit the game on Roblox.","title":"Home"},{"location":"examples/#examples","text":"Welcome to the Examples section! Here, you can find various open-source examples and projects, so you can see how Fusion works in a real setting.","title":"Examples"},{"location":"examples/#the-cookbook","text":"Oftentimes, you might be stuck on a small problem. You want to create something specific, but don't know how to do it with Fusion's tools. The cookbook can help with that! It's a collection of snippets which show you how to do various small tasks with Fusion, like processing arrays, applying animations and responding to different events. Visit the cookbook to see what's available.","title":"The Cookbook"},{"location":"examples/#open-source-projects","text":"","title":"Open-Source Projects"},{"location":"examples/#fusion-wordle-for-fusion-02","text":"See how Fusion can be used to build a mobile-first UI-centric game, with server validation, spring animations and sounds. Play and edit the game on Roblox.","title":"Fusion Wordle (for Fusion 0.2)"},{"location":"examples/#fusion-obby-for-fusion-01","text":"See how Fusion can be used to build a minimal interface for an obby, with an animated checkpoint counter and simulated confetti. Play and edit the game on Roblox.","title":"Fusion Obby (for Fusion 0.1)"},{"location":"examples/cookbook/","text":"Cookbook \u00b6 Oftentimes, you might be stuck on a small problem. You want to create something specific, but don't know how to do it with Fusion's tools. The cookbook can help with that! It's a collection of snippets which show you how to do various small tasks with Fusion, like processing arrays, applying animations and responding to different events. Navigation \u00b6 Using the sidebar to the left, you can browse all of the cookbook examples by name.","title":"Cookbook"},{"location":"examples/cookbook/#cookbook","text":"Oftentimes, you might be stuck on a small problem. You want to create something specific, but don't know how to do it with Fusion's tools. The cookbook can help with that! It's a collection of snippets which show you how to do various small tasks with Fusion, like processing arrays, applying animations and responding to different events.","title":"Cookbook"},{"location":"examples/cookbook/#navigation","text":"Using the sidebar to the left, you can browse all of the cookbook examples by name.","title":"Navigation"},{"location":"examples/cookbook/animated-computed/","text":"This example shows you how to animate a single value with an animation curve of your preference. For demonstration, the example uses Roblox API members. Overview \u00b6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 local Players = game : GetService ( \"Players\" ) local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children = Fusion . Children local TWEEN_INFO = TweenInfo . new ( 0.5 , Enum . EasingStyle . Sine , Enum . EasingDirection . InOut ) -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion ) -- You can set this at any time to indicate where The Thing should be. local showTheThing = scope : Value ( false ) local exampleUI = scope : New \"ScreenGui\" { Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ), Name = \"Example UI\" , [ Children ] = scope : New \"Frame\" { Name = \"The Thing\" , Position = scope : Tween ( scope : Computed ( function ( use ) local CENTRE = UDim2 . fromScale ( 0.5 , 0.5 ) local OFFSCREEN = UDim2 . fromScale ( - 0.5 , 0.5 ) return if use ( showTheThing ) then CENTRE else OFFSCREEN end ), TWEEN_INFO ), Size = UDim2 . fromOffset ( 200 , 200 ) } } -- Without toggling the value, you won't see it animate. task . defer ( function () while true do task . wait ( 1 ) showTheThing : set ( not peek ( showTheThing )) end end ) Explanation \u00b6 There's three key components to the above code snippet. Firstly, there's showTheThing . When this is true , The Thing should be in the centre of the screen. Otherwise, The Thing should be off-screen. 13 14 -- You can set this at any time to indicate where The Thing should be. local showTheThing = scope : Value ( false ) Next, there's the computed object on line 26. This takes that boolean value, and turns it into a UDim2 position for The Thing to use. You can imagine this as the 'non-animated' version of what you want The Thing to do, if it were to instantly teleport around. 26 27 28 29 30 scope : Computed ( function ( use ) local CENTRE = UDim2 . fromScale ( 0.5 , 0.5 ) local OFFSCREEN = UDim2 . fromScale ( - 0.5 , 0.5 ) return if use ( showTheThing ) then CENTRE else OFFSCREEN end ), Finally, there's the tween object that the computed is being passed into. The tween object will smoothly move towards the computed over time. If needed, you could separate the computed into a dedicated variable to access it independently. 25 26 27 28 29 30 31 32 Position = scope : Tween ( scope : Computed ( function ( use ) local CENTRE = UDim2 . fromScale ( 0.5 , 0.5 ) local OFFSCREEN = UDim2 . fromScale ( - 0.5 , 0.5 ) return if use ( showTheThing ) then CENTRE else OFFSCREEN end ), TWEEN_INFO ), The 'shape' of the animation is saved in a TWEEN_INFO constant defined earlier in the code. The Tween tutorial explains how each parameter shapes the motion. 7 8 9 10 11 local TWEEN_INFO = TweenInfo . new ( 0.5 , Enum . EasingStyle . Sine , Enum . EasingDirection . InOut ) Fluid animations with springs For extra smooth animation shapes that preserve velocity, consider trying spring objects . They're very similar in usage and can help improve the responsiveness of the motion.","title":"Animated Computed"},{"location":"examples/cookbook/animated-computed/#overview","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 local Players = game : GetService ( \"Players\" ) local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children = Fusion . Children local TWEEN_INFO = TweenInfo . new ( 0.5 , Enum . EasingStyle . Sine , Enum . EasingDirection . InOut ) -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion ) -- You can set this at any time to indicate where The Thing should be. local showTheThing = scope : Value ( false ) local exampleUI = scope : New \"ScreenGui\" { Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ), Name = \"Example UI\" , [ Children ] = scope : New \"Frame\" { Name = \"The Thing\" , Position = scope : Tween ( scope : Computed ( function ( use ) local CENTRE = UDim2 . fromScale ( 0.5 , 0.5 ) local OFFSCREEN = UDim2 . fromScale ( - 0.5 , 0.5 ) return if use ( showTheThing ) then CENTRE else OFFSCREEN end ), TWEEN_INFO ), Size = UDim2 . fromOffset ( 200 , 200 ) } } -- Without toggling the value, you won't see it animate. task . defer ( function () while true do task . wait ( 1 ) showTheThing : set ( not peek ( showTheThing )) end end )","title":"Overview"},{"location":"examples/cookbook/animated-computed/#explanation","text":"There's three key components to the above code snippet. Firstly, there's showTheThing . When this is true , The Thing should be in the centre of the screen. Otherwise, The Thing should be off-screen. 13 14 -- You can set this at any time to indicate where The Thing should be. local showTheThing = scope : Value ( false ) Next, there's the computed object on line 26. This takes that boolean value, and turns it into a UDim2 position for The Thing to use. You can imagine this as the 'non-animated' version of what you want The Thing to do, if it were to instantly teleport around. 26 27 28 29 30 scope : Computed ( function ( use ) local CENTRE = UDim2 . fromScale ( 0.5 , 0.5 ) local OFFSCREEN = UDim2 . fromScale ( - 0.5 , 0.5 ) return if use ( showTheThing ) then CENTRE else OFFSCREEN end ), Finally, there's the tween object that the computed is being passed into. The tween object will smoothly move towards the computed over time. If needed, you could separate the computed into a dedicated variable to access it independently. 25 26 27 28 29 30 31 32 Position = scope : Tween ( scope : Computed ( function ( use ) local CENTRE = UDim2 . fromScale ( 0.5 , 0.5 ) local OFFSCREEN = UDim2 . fromScale ( - 0.5 , 0.5 ) return if use ( showTheThing ) then CENTRE else OFFSCREEN end ), TWEEN_INFO ), The 'shape' of the animation is saved in a TWEEN_INFO constant defined earlier in the code. The Tween tutorial explains how each parameter shapes the motion. 7 8 9 10 11 local TWEEN_INFO = TweenInfo . new ( 0.5 , Enum . EasingStyle . Sine , Enum . EasingDirection . InOut ) Fluid animations with springs For extra smooth animation shapes that preserve velocity, consider trying spring objects . They're very similar in usage and can help improve the responsiveness of the motion.","title":"Explanation"},{"location":"examples/cookbook/button-component/","text":"This example is a relatively complete button component implemented using Fusion's Roblox API. It handles many common interactions such as hovering and clicking. This should be a generally useful template for assembling components of your own. For further ideas and best practices for building components, see the Components tutorial . Overview \u00b6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children , OnEvent = Fusion . Children , Fusion . OnEvent type UsedAs < T > = Fusion . UsedAs < T > local COLOUR_BLACK = Color3 . new ( 0 , 0 , 0 ) local COLOUR_WHITE = Color3 . new ( 1 , 1 , 1 ) local COLOUR_TEXT = COLOUR_WHITE local COLOUR_BG_REST = Color3 . fromHex ( \"0085FF\" ) local COLOUR_BG_HOVER = COLOUR_BG_REST : Lerp ( COLOUR_WHITE , 0.25 ) local COLOUR_BG_HELD = COLOUR_BG_REST : Lerp ( COLOUR_BLACK , 0.25 ) local COLOUR_BG_DISABLED = Color3 . fromHex ( \"CCCCCC\" ) local BG_FADE_SPEED = 20 -- spring speed units local ROUNDED_CORNERS = UDim . new ( 0 , 4 ) local PADDING = UDim2 . fromOffset ( 6 , 4 ) local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Name : UsedAs < string > ? , Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , AutomaticSize : UsedAs < Enum . AutomaticSize > ? }, Text : UsedAs < string > ? , Disabled : UsedAs < boolean > ? , OnClick : (() -> ()) ? } ): Fusion . Child local isHovering = scope : Value ( false ) local isHeldDown = scope : Value ( false ) return scope : New \"TextButton\" { Name = props . Name , LayoutOrder = props . Layout . LayoutOrder , Position = props . Layout . Position , AnchorPoint = props . Layout . AnchorPoint , ZIndex = props . Layout . ZIndex , Size = props . Layout . Size , AutomaticSize = props . Layout . AutomaticSize , Text = props . Text , TextColor3 = COLOUR_TEXT , BackgroundColor3 = scope : Spring ( scope : Computed ( function ( use ) -- The order of conditions matter here; it defines which states -- visually override other states, with earlier states being -- more important. return if use ( props . Disabled ) then COLOUR_BG_DISABLED elseif use ( isHeldDown ) then COLOUR_BG_HELD elseif use ( isHovering ) then COLOUR_BG_HOVER else return COLOUR_BG_REST end end ), BG_FADE_SPEED ), [ OnEvent \"Activated\" ] = function () if props . OnClick ~= nil and not peek ( props . Disabled ) then -- Explicitly called with no arguments to match the typedef. -- If passed straight to `OnEvent`, the function might receive -- arguments from the event. If the function secretly *does* -- take arguments (despite the type) this would cause problems. props . OnClick () end end , [ OnEvent \"MouseButton1Down\" ] = function () isHeldDown : set ( true ) end , [ OnEvent \"MouseButton1Up\" ] = function () isHeldDown : set ( false ) end , [ OnEvent \"MouseEnter\" ] = function () -- Roblox calls this event even if the button is being covered by -- other UI. For simplicity, this does not account for that. isHovering : set ( true ) end , [ OnEvent \"MouseLeave\" ] = function () -- If the button is being held down, but the cursor moves off the -- button, then we won't receive the mouse up event. To make sure -- the button doesn't get stuck held down, we'll release it if the -- cursor leaves the button. isHeldDown : set ( false ) isHovering : set ( false ) end , [ Children ] = { New \"UICorner\" { CornerRadius = ROUNDED_CORNERS }, New \"UIPadding\" { PaddingTop = PADDING . Y , PaddingBottom = PADDING . Y , PaddingLeft = PADDING . X , PaddingRight = PADDING . X } } } end return Button Explanation \u00b6 The main part of note is the function signature. It's highly recommended that you statically type the function signature for components, because it not only improves autocomplete and error checking, but also acts as up-to-date, machine readable documentation. 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Name : UsedAs < string > ? , Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , AutomaticSize : UsedAs < Enum . AutomaticSize > ? }, Text : UsedAs < string > ? , Disabled : UsedAs < boolean > ? , OnClick : (() -> ()) ? } ): Fusion . Child The scope parameter specifies that the component depends on Fusion's methods. If you're not sure how to write type definitions for scopes, the 'Scopes' section of the Components tutorial goes into further detail. The property table is laid out with each property on a new line, so it's easy to scan the list and see what properties are available. Most are typed with UsedAs , which allows the user to use state objects if they desire. They're also ? (optional), which can reduce boilerplate when using the component. Not all properties have to be that way, but usually it's better to have the flexibility. Property grouping You can group properties together in nested tables, like the Layout table above, to avoid long mixed lists of properties. In addition to being more readable, this can sometimes help with passing around lots of properties at once, because you can pass the whole nested table as one value if you'd like to. The return type of the function is Fusion.Child , which tells the user that the component is compatible with Fusion's [Children] API, without exposing what children it's returning specifically. This helps ensure the user doesn't accidentally depend on the internal structure of the component.","title":"Button Component"},{"location":"examples/cookbook/button-component/#overview","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children , OnEvent = Fusion . Children , Fusion . OnEvent type UsedAs < T > = Fusion . UsedAs < T > local COLOUR_BLACK = Color3 . new ( 0 , 0 , 0 ) local COLOUR_WHITE = Color3 . new ( 1 , 1 , 1 ) local COLOUR_TEXT = COLOUR_WHITE local COLOUR_BG_REST = Color3 . fromHex ( \"0085FF\" ) local COLOUR_BG_HOVER = COLOUR_BG_REST : Lerp ( COLOUR_WHITE , 0.25 ) local COLOUR_BG_HELD = COLOUR_BG_REST : Lerp ( COLOUR_BLACK , 0.25 ) local COLOUR_BG_DISABLED = Color3 . fromHex ( \"CCCCCC\" ) local BG_FADE_SPEED = 20 -- spring speed units local ROUNDED_CORNERS = UDim . new ( 0 , 4 ) local PADDING = UDim2 . fromOffset ( 6 , 4 ) local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Name : UsedAs < string > ? , Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , AutomaticSize : UsedAs < Enum . AutomaticSize > ? }, Text : UsedAs < string > ? , Disabled : UsedAs < boolean > ? , OnClick : (() -> ()) ? } ): Fusion . Child local isHovering = scope : Value ( false ) local isHeldDown = scope : Value ( false ) return scope : New \"TextButton\" { Name = props . Name , LayoutOrder = props . Layout . LayoutOrder , Position = props . Layout . Position , AnchorPoint = props . Layout . AnchorPoint , ZIndex = props . Layout . ZIndex , Size = props . Layout . Size , AutomaticSize = props . Layout . AutomaticSize , Text = props . Text , TextColor3 = COLOUR_TEXT , BackgroundColor3 = scope : Spring ( scope : Computed ( function ( use ) -- The order of conditions matter here; it defines which states -- visually override other states, with earlier states being -- more important. return if use ( props . Disabled ) then COLOUR_BG_DISABLED elseif use ( isHeldDown ) then COLOUR_BG_HELD elseif use ( isHovering ) then COLOUR_BG_HOVER else return COLOUR_BG_REST end end ), BG_FADE_SPEED ), [ OnEvent \"Activated\" ] = function () if props . OnClick ~= nil and not peek ( props . Disabled ) then -- Explicitly called with no arguments to match the typedef. -- If passed straight to `OnEvent`, the function might receive -- arguments from the event. If the function secretly *does* -- take arguments (despite the type) this would cause problems. props . OnClick () end end , [ OnEvent \"MouseButton1Down\" ] = function () isHeldDown : set ( true ) end , [ OnEvent \"MouseButton1Up\" ] = function () isHeldDown : set ( false ) end , [ OnEvent \"MouseEnter\" ] = function () -- Roblox calls this event even if the button is being covered by -- other UI. For simplicity, this does not account for that. isHovering : set ( true ) end , [ OnEvent \"MouseLeave\" ] = function () -- If the button is being held down, but the cursor moves off the -- button, then we won't receive the mouse up event. To make sure -- the button doesn't get stuck held down, we'll release it if the -- cursor leaves the button. isHeldDown : set ( false ) isHovering : set ( false ) end , [ Children ] = { New \"UICorner\" { CornerRadius = ROUNDED_CORNERS }, New \"UIPadding\" { PaddingTop = PADDING . Y , PaddingBottom = PADDING . Y , PaddingLeft = PADDING . X , PaddingRight = PADDING . X } } } end return Button","title":"Overview"},{"location":"examples/cookbook/button-component/#explanation","text":"The main part of note is the function signature. It's highly recommended that you statically type the function signature for components, because it not only improves autocomplete and error checking, but also acts as up-to-date, machine readable documentation. 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Name : UsedAs < string > ? , Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , AutomaticSize : UsedAs < Enum . AutomaticSize > ? }, Text : UsedAs < string > ? , Disabled : UsedAs < boolean > ? , OnClick : (() -> ()) ? } ): Fusion . Child The scope parameter specifies that the component depends on Fusion's methods. If you're not sure how to write type definitions for scopes, the 'Scopes' section of the Components tutorial goes into further detail. The property table is laid out with each property on a new line, so it's easy to scan the list and see what properties are available. Most are typed with UsedAs , which allows the user to use state objects if they desire. They're also ? (optional), which can reduce boilerplate when using the component. Not all properties have to be that way, but usually it's better to have the flexibility. Property grouping You can group properties together in nested tables, like the Layout table above, to avoid long mixed lists of properties. In addition to being more readable, this can sometimes help with passing around lots of properties at once, because you can pass the whole nested table as one value if you'd like to. The return type of the function is Fusion.Child , which tells the user that the component is compatible with Fusion's [Children] API, without exposing what children it's returning specifically. This helps ensure the user doesn't accidentally depend on the internal structure of the component.","title":"Explanation"},{"location":"examples/cookbook/drag-and-drop/","text":"This example shows a full drag-and-drop implementation for mouse input only, using Fusion's Roblox API. To ensure best accessibility, any interactions you implement shouldn't force you to hold the mouse button down. Either allow drag-and-drop with single clicks, or provide a non-dragging alternative. This ensures people with reduced motor ability aren't locked out of UI functions. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 local Players = game : GetService ( \"Players\" ) local UserInputService = game : GetService ( \"UserInputService\" ) local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children , OnEvent = Fusion . Children , Fusion . OnEvent type UsedAs < T > = Fusion . UsedAs < T > type DragInfo = { id : string , mouseOffset : Vector2 -- relative to the dragged item } local function Draggable ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { ID : string , Name : UsedAs < string > ? , Parent : Fusion . StateObject < Instance ? > , -- StateObject so it's observable Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , OutAbsolutePosition : Fusion . Value < Vector2 > ? , }, Dragging : { MousePosition : UsedAs < Vector2 > , SelfDragInfo : UsedAs < DragInfo ? > , OverlayFrame : UsedAs < Instance ? > } [ typeof ( Children )]: Fusion . Child } ): Fusion . Child -- When `nil`, the parent can't be measured for some reason. local parentSize = scope : Value ( nil ) do local function measureParentNow () local parent = peek ( props . Parent ) parentSize : set ( if parent ~= nil and parent : IsA ( \"GuiObject\" ) then parent . AbsoluteSize else nil ) end local resizeConn = nil local function stopMeasuring () if resizeConn ~= nil then resizeConn : Disconnect () resizeConn = nil end end scope : Observer ( props . Parent ): onBind ( function () stopMeasuring () measureParentNow () if peek ( parentSize ) ~= nil then resizeConn = parent : GetPropertyChangedSignal ( \"AbsoluteSize\" ) : Connect ( measureParentNow ) end end ) table.insert ( scope , stopMeasuring ) end return New \"Frame\" { Name = props . Name or \"Draggable\" , Parent = scope : Computed ( function ( use ) return if use ( props . Dragging . SelfDragInfo ) ~= nil then use ( props . Dragging . OverlayFrame ) else use ( props . Parent ) end ), LayoutOrder = props . Layout . LayoutOrder , AnchorPoint = props . Layout . AnchorPoint , ZIndex = props . Layout . ZIndex , AutomaticSize = props . Layout . AutomaticSize , BackgroundTransparency = 1 , Position = scope : Computed ( function ( use ) local dragInfo = use ( props . Dragging . SelfDragInfo ) if dragInfo == nil then return use ( props . Layout . Position ) or UDim2 . fromOffset ( 0 , 0 ) else local mousePos = use ( props . Dragging . MousePosition ) local topLeftCorner = mousePos - dragInfo . mouseOffset return UDim2 . fromOffset ( topLeftCorner . X , topLeftCorner . Y ) end end ), -- Calculated manually so the Scale can be set relative to -- `props.Parent` at all times, rather than the `Parent` of this Frame. Size = scope : Computed ( function ( use ) local udim2 = use ( props . Layout . Size ) or UDim2 . fromOffset ( 0 , 0 ) local parentSize = use ( parentSize ) or Vector2 . zero return UDim2 . fromOffset ( udim2 . X . Scale * parentSize . X + udim2 . X . Offset , udim2 . Y . Scale * parentSize . Y + udim2 . Y . Offset ) end ), [ Out \"AbsolutePosition\" ] = props . OutAbsolutePosition , [ Children ] = props [ Children ] } end local COLOUR_COMPLETED = Color3 . new ( 0 , 1 , 0 ) local COLOUR_NOT_COMPLETED = Color3 . new ( 1 , 1 , 1 ) local TODO_ITEM_SIZE = UDim2 . new ( 1 , 0 , 0 , 50 ) local function newUniqueID () -- You can replace this with a better method for generating unique IDs. return game : GetService ( \"HttpService\" ): GenerateGUID () end type TodoItem = { id : string , text : string , completed : Fusion . Value < boolean > } local todoItems : Fusion . Value < TodoItem > = { { id = newUniqueID (), text = \"Wake up today\" , completed = Value ( true ) }, { id = newUniqueID (), text = \"Read the Fusion docs\" , completed = Value ( true ) }, { id = newUniqueID (), text = \"Take over the universe\" , completed = Value ( false ) } } local function getTodoItemForID ( id : string ): TodoItem ? for _ , item in todoItems do if item . id == id then return item end end return nil end local function TodoEntry ( outerScope : Fusion . Scope < {} > , props : { Item : TodoItem , Parent : Fusion . StateObject < Instance ? > , Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , OutAbsolutePosition : Fusion . Value < Vector2 > ? , }, Dragging : { MousePosition : UsedAs < Vector2 > , SelfDragInfo : UsedAs < CurrentlyDragging ? > , OverlayFrame : UsedAs < Instance > ? }, OnMouseDown : () -> () ? } ): Fusion . Child local scope = scoped ( Fusion , { Draggable = Draggable }) table.insert ( outerScope , scope ) local itemPosition = scope : Value ( nil ) local itemIsDragging = scope : Computed ( function ( use ) local dragInfo = use ( props . CurrentlyDragging ) return dragInfo ~= nil and dragInfo . id == props . Item . id end ) return scope : Draggable { ID = props . Item . id , Name = props . Item . text , Parent = props . Parent , Layout = props . Layout , Dragging = props . Dragging , [ Children ] = scope : New \"TextButton\" { Name = \"TodoEntry\" , Size = UDim2 . fromScale ( 1 , 1 ), BackgroundColor3 = scope : Computed ( function ( use ) return if use ( props . Item . completed ) then COLOUR_COMPLETED else COLOUR_NOT_COMPLETED end end ), Text = props . Item . text , TextSize = 28 , [ OnEvent \"MouseButton1Down\" ] = props . OnMouseDown -- Don't detect mouse up here, because in some rare cases, the event -- could be missed due to lag between the item's position and the -- cursor position. } } end -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion ) local mousePos = scope : Value ( UserInputService : GetMouseLocation ()) table.insert ( scope , UserInputService . InputChanged : Connect ( function ( inputObject ) if inputObject . UserInputType == Enum . UserInputType . MouseMovement then -- If this code did not read coordinates from the same method, it -- might inconsistently handle UI insets. So, keep it simple! mousePos : set ( UserInputService : GetMouseLocation ()) end end ) ) local dropAction = scope : Value ( nil ) local taskLists = scope : ForPairs ( { incomplete = \"mark-as-incomplete\" , completed = \"mark-as-completed\" }, function ( use , scope , listName , listDropAction ) return listName , scope : New \"ScrollingFrame\" { Name = ` TaskList ({ listName }) ` , Position = UDim2 . fromScale ( 0.1 , 0.1 ), Size = UDim2 . fromScale ( 0.35 , 0.9 ), BackgroundTransparency = 0.75 , BackgroundColor3 = Color3 . new ( 1 , 0 , 0 ), [ OnEvent \"MouseEnter\" ] = function () dropAction : set ( listDropAction ) end , [ OnEvent \"MouseLeave\" ] = function () -- A different item might have overwritten this already. if peek ( dropAction ) == listDropAction then dropAction : set ( nil ) end end , [ Children ] = { New \"UIListLayout\" { SortOrder = \"Name\" , Padding = UDim . new ( 0 , 5 ) } } } end ) local overlayFrame = scope : New \"Frame\" { Size = UDim2 . fromScale ( 1 , 1 ), ZIndex = 10 , BackgroundTransparency = 1 } local currentlyDragging : Fusion . Value < DragInfo ? > = scope : Value ( nil ) local allEntries = scope : ForValues ( todoItems , function ( use , scope , item ) local itemPosition = scope : Value ( nil ) return scope : TodoEntry { Item = item , Parent = scope : Computed ( function ( use ) return if use ( item . completed ) then use ( taskLists ). completed else use ( taskLists ). incomplete end ), Layout = { Size = TODO_ITEM_SIZE , OutAbsolutePosition = itemPosition }, Dragging = { MousePosition = mousePos , SelfDragInfo = scope : Computed ( function ( use ) local dragInfo = use ( currentlyDragging ) return if dragInfo == nil or dragInfo . id ~= item . id then nil else dragInfo end ) OverlayFrame = overlayFrame }, OnMouseDown = function () if peek ( currentlyDragging ) == nil then local itemPos = peek ( itemPosition ) or Vector2 . zero local mouseOffset = peek ( mousePos ) - itemPos currentlyDragging : set ({ id = item . id , mouseOffset = mouseOffset }) end end } end ) table.insert ( scope , UserInputService . InputEnded : Connect ( function ( inputObject ) if inputObject . UserInputType ~= Enum . UserInputType . MouseButton1 then return end local dragInfo = peek ( currentlyDragging ) if dragInfo == nil then return end local item = getTodoItemForID ( dragInfo . id ) local action = peek ( dropAction ) if item ~= nil then if action == \"mark-as-incomplete\" then item . completed : set ( false ) elseif action == \"mark-as-completed\" then item . completed : set ( true ) end end currentlyDragging : set ( nil ) end ) ) local ui = scope : New \"ScreenGui\" { Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ) [ Children ] = { overlayFrame , taskLists , -- Don't pass `allEntries` in here - they manage their own parent! } } Explanation \u00b6 The basic idea is to create a container which stores the UI you want to drag. This container then reparents itself as it gets dragged around between different containers. The Draggable component implements everything necessary to make a seamlessly re-parentable container. 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 type DragInfo = { id : string , mouseOffset : Vector2 -- relative to the dragged item } local function Draggable ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { ID : string , Name : UsedAs < string > ? , Parent : Fusion . StateObject < Instance ? > , -- StateObject so it's observable Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , OutAbsolutePosition : Fusion . Value < Vector2 > ? , }, Dragging : { MousePosition : UsedAs < Vector2 > , SelfDragInfo : UsedAs < DragInfo ? > , OverlayFrame : UsedAs < Instance ? > } [ typeof ( Children )]: Fusion . Child } ): Fusion . Child By default, Draggable behaves like a regular Frame, parenting itself to the Parent property and applying its Layout properties. It only behaves specially when Dragging.SelfDragInfo is provided. Firstly, it reparents itself to Dragging.OverlayFrame , so it can be seen in front of other UI. 67 68 69 70 71 72 Parent = scope : Computed ( function ( use ) return if use ( props . Dragging . SelfDragInfo ) ~= nil then use ( props . Dragging . OverlayFrame ) else use ( props . Parent ) end ), Because of this reparenting, Draggable has to do some extra work to keep the size consistent; it manually calculates the size based on the size of Parent , so it doesn't change size when moved to Dragging.OverlayFrame . 90 91 92 93 94 95 96 97 98 99 -- Calculated manually so the Scale can be set relative to -- `props.Parent` at all times, rather than the `Parent` of this Frame. Size = scope : Computed ( function ( use ) local udim2 = use ( props . Layout . Size ) or UDim2 . fromOffset ( 0 , 0 ) local parentSize = use ( parentSize ) or Vector2 . zero return UDim2 . fromOffset ( udim2 . X . Scale * parentSize . X + udim2 . X . Offset , udim2 . Y . Scale * parentSize . Y + udim2 . Y . Offset ) end ), The Draggable also needs to snap to the mouse cursor, so it can be moved by the user. Ideally, the mouse would stay fixed in position relative to the Draggable , so there are no abrupt changes in the position of any elements. As part of Dragging.SelfDragInfo , a mouseOffset is provided, which describes how far the mouse should stay from the top-left corner. So, when setting the position of the Draggable , that offset can be applied to keep the UI fixed in position relative to the mouse. 81 82 83 84 85 86 87 88 89 90 Position = scope : Computed ( function ( use ) local dragInfo = use ( props . Dragging . SelfDragInfo ) if dragInfo == nil then return use ( props . Layout . Position ) or UDim2 . fromOffset ( 0 , 0 ) else local mousePos = use ( props . Dragging . MousePosition ) local topLeftCorner = mousePos - dragInfo . mouseOffset return UDim2 . fromOffset ( topLeftCorner . X , topLeftCorner . Y ) end end ), This is all that's needed to make a generic container that can seamlessly move between distinct parts of the UI. The rest of the example demonstrates how this can be integrated into real world UI. The example creates a list of TodoItem objects, each with a unique ID, text message, and completion status. Because we don't expect the ID or text to change, they're just constant values. However, the completion status is expected to change, so that's specified to be a Value object. 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 type TodoItem = { id : string , text : string , completed : Fusion . Value < boolean > } local todoItems : Fusion . Value < TodoItem > = { { id = newUniqueID (), text = \"Wake up today\" , completed = Value ( true ) }, { id = newUniqueID (), text = \"Read the Fusion docs\" , completed = Value ( true ) }, { id = newUniqueID (), text = \"Take over the universe\" , completed = Value ( false ) } } The TodoEntry component is meant to represent one individual TodoItem . 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 local function TodoEntry ( outerScope : Fusion . Scope < {} > , props : { Item : TodoItem , Parent : Fusion . StateObject < Instance ? > , Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , OutAbsolutePosition : Fusion . Value < Vector2 > ? , }, Dragging : { MousePosition : UsedAs < Vector2 > , SelfDragInfo : UsedAs < CurrentlyDragging ? > , OverlayFrame : UsedAs < Instance > ? }, OnMouseDown : () -> () ? } ): Fusion . Child Notice that it shares many of the same property groups as Draggable - these can be passed directly through. 182 183 184 185 186 187 return scope : Draggable { ID = props . Item . id , Name = props . Item . text , Parent = props . Parent , Layout = props . Layout , Dragging = props . Dragging , It also provides an OnMouseDown callback, which can be used to pick up the entry if the mouse is pressed down above the entry. Note the comment about why it is not desirable to detect mouse-up here; the UI should unconditionally respond to mouse-up, even if the mouse happens to briefly leave this element. 202 203 204 205 206 [ OnEvent \"MouseButton1Down\" ] = props . OnMouseDown -- Don't detect mouse up here, because in some rare cases, the event -- could be missed due to lag between the item's position and the -- cursor position. Now, the destinations for these entries can be created. To help decide where to drop items later, the dropAction tracks which destination the mouse is hovered over. 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 local dropAction = scope : Value ( nil ) local taskLists = scope : ForPairs ( { incomplete = \"mark-as-incomplete\" , completed = \"mark-as-completed\" }, function ( use , scope , listName , listDropAction ) return listName , scope : New \"ScrollingFrame\" { Name = ` TaskList ({ listName }) ` , Position = UDim2 . fromScale ( 0.1 , 0.1 ), Size = UDim2 . fromScale ( 0.35 , 0.9 ), BackgroundTransparency = 0.75 , BackgroundColor3 = Color3 . new ( 1 , 0 , 0 ), [ OnEvent \"MouseEnter\" ] = function () dropAction : set ( listDropAction ) end , [ OnEvent \"MouseLeave\" ] = function () -- A different item might have overwritten this already. if peek ( dropAction ) == listDropAction then dropAction : set ( nil ) end end , [ Children ] = { New \"UIListLayout\" { SortOrder = \"Name\" , Padding = UDim . new ( 0 , 5 ) } } } end ) This is also where the 'overlay frame' is created, which gives currently-dragged UI a dedicated layer above all other UI to freely move around. 265 266 267 268 269 local overlayFrame = scope : New \"Frame\" { Size = UDim2 . fromScale ( 1 , 1 ), ZIndex = 10 , BackgroundTransparency = 1 } Finally, each TodoItem is created as a TodoEntry . Some state is also created to track which entry is being dragged at the moment. 271 272 273 274 275 276 277 278 local currentlyDragging : Fusion . Value < DragInfo ? > = scope : Value ( nil ) local allEntries = scope : ForValues ( todoItems , function ( use , scope , item ) local itemPosition = scope : Value ( nil ) return scope : TodoEntry { Item = item , Each entry dynamically picks one of the two destinations based on its completion status. 279 280 281 282 283 284 Parent = scope : Computed ( function ( use ) return if use ( item . completed ) then use ( taskLists ). completed else use ( taskLists ). incomplete end ), It also provides the information needed by the Draggable . Note that the current drag information is filtered from the currentlyDragging state so the Draggable won't see information about other entries being dragged. 289 290 291 292 293 294 295 296 297 298 299 Dragging = { MousePosition = mousePos , SelfDragInfo = scope : Computed ( function ( use ) local dragInfo = use ( currentlyDragging ) return if dragInfo == nil or dragInfo . id ~= item . id then nil else dragInfo end ) OverlayFrame = overlayFrame }, Now it's time to handle starting and stopping the drag. To begin the drag, this code makes use of the OnMouseDown callback. If nothing else is being dragged right now, the position of the mouse relative to the item is captured. Then, that mouseOffset and the id of the item are passed into the currentlyDragging state to indicate this entry is being dragged. 300 301 302 303 304 305 306 307 308 309 OnMouseDown = function () if peek ( currentlyDragging ) == nil then local itemPos = peek ( itemPosition ) or Vector2 . zero local mouseOffset = peek ( mousePos ) - itemPos currentlyDragging : set ({ id = item . id , mouseOffset = mouseOffset }) end end To end the drag, a global InputEnded listener is created, which should reliably fire no matter where or when the event occurs. If there's a dropAction to take, for example mark-as-completed , then that action is executed here. In all cases, currentlyDragging is cleared, so the entry is no longer dragged. 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 table.insert ( scope , UserInputService . InputEnded : Connect ( function ( inputObject ) if inputObject . UserInputType ~= Enum . UserInputType . MouseButton1 then return end local dragInfo = peek ( currentlyDragging ) if dragInfo == nil then return end local item = getTodoItemForID ( dragInfo . id ) local action = peek ( dropAction ) if item ~= nil then if action == \"mark-as-incomplete\" then item . completed : set ( false ) elseif action == \"mark-as-completed\" then item . completed : set ( true ) end end currentlyDragging : set ( nil ) end ) ) All that remains is to parent the task lists and overlay frames to a UI, so they can be seen. Because the TodoEntry component manages their own parent, this code shouldn't pass in allEntries as a child here. 336 337 338 339 340 341 342 343 344 local ui = scope : New \"ScreenGui\" { Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ) [ Children ] = { overlayFrame , taskLists , -- Don't pass `allEntries` in here - they manage their own parent! } }","title":"Drag & Drop"},{"location":"examples/cookbook/drag-and-drop/#explanation","text":"The basic idea is to create a container which stores the UI you want to drag. This container then reparents itself as it gets dragged around between different containers. The Draggable component implements everything necessary to make a seamlessly re-parentable container. 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 type DragInfo = { id : string , mouseOffset : Vector2 -- relative to the dragged item } local function Draggable ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { ID : string , Name : UsedAs < string > ? , Parent : Fusion . StateObject < Instance ? > , -- StateObject so it's observable Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , OutAbsolutePosition : Fusion . Value < Vector2 > ? , }, Dragging : { MousePosition : UsedAs < Vector2 > , SelfDragInfo : UsedAs < DragInfo ? > , OverlayFrame : UsedAs < Instance ? > } [ typeof ( Children )]: Fusion . Child } ): Fusion . Child By default, Draggable behaves like a regular Frame, parenting itself to the Parent property and applying its Layout properties. It only behaves specially when Dragging.SelfDragInfo is provided. Firstly, it reparents itself to Dragging.OverlayFrame , so it can be seen in front of other UI. 67 68 69 70 71 72 Parent = scope : Computed ( function ( use ) return if use ( props . Dragging . SelfDragInfo ) ~= nil then use ( props . Dragging . OverlayFrame ) else use ( props . Parent ) end ), Because of this reparenting, Draggable has to do some extra work to keep the size consistent; it manually calculates the size based on the size of Parent , so it doesn't change size when moved to Dragging.OverlayFrame . 90 91 92 93 94 95 96 97 98 99 -- Calculated manually so the Scale can be set relative to -- `props.Parent` at all times, rather than the `Parent` of this Frame. Size = scope : Computed ( function ( use ) local udim2 = use ( props . Layout . Size ) or UDim2 . fromOffset ( 0 , 0 ) local parentSize = use ( parentSize ) or Vector2 . zero return UDim2 . fromOffset ( udim2 . X . Scale * parentSize . X + udim2 . X . Offset , udim2 . Y . Scale * parentSize . Y + udim2 . Y . Offset ) end ), The Draggable also needs to snap to the mouse cursor, so it can be moved by the user. Ideally, the mouse would stay fixed in position relative to the Draggable , so there are no abrupt changes in the position of any elements. As part of Dragging.SelfDragInfo , a mouseOffset is provided, which describes how far the mouse should stay from the top-left corner. So, when setting the position of the Draggable , that offset can be applied to keep the UI fixed in position relative to the mouse. 81 82 83 84 85 86 87 88 89 90 Position = scope : Computed ( function ( use ) local dragInfo = use ( props . Dragging . SelfDragInfo ) if dragInfo == nil then return use ( props . Layout . Position ) or UDim2 . fromOffset ( 0 , 0 ) else local mousePos = use ( props . Dragging . MousePosition ) local topLeftCorner = mousePos - dragInfo . mouseOffset return UDim2 . fromOffset ( topLeftCorner . X , topLeftCorner . Y ) end end ), This is all that's needed to make a generic container that can seamlessly move between distinct parts of the UI. The rest of the example demonstrates how this can be integrated into real world UI. The example creates a list of TodoItem objects, each with a unique ID, text message, and completion status. Because we don't expect the ID or text to change, they're just constant values. However, the completion status is expected to change, so that's specified to be a Value object. 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 type TodoItem = { id : string , text : string , completed : Fusion . Value < boolean > } local todoItems : Fusion . Value < TodoItem > = { { id = newUniqueID (), text = \"Wake up today\" , completed = Value ( true ) }, { id = newUniqueID (), text = \"Read the Fusion docs\" , completed = Value ( true ) }, { id = newUniqueID (), text = \"Take over the universe\" , completed = Value ( false ) } } The TodoEntry component is meant to represent one individual TodoItem . 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 local function TodoEntry ( outerScope : Fusion . Scope < {} > , props : { Item : TodoItem , Parent : Fusion . StateObject < Instance ? > , Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , OutAbsolutePosition : Fusion . Value < Vector2 > ? , }, Dragging : { MousePosition : UsedAs < Vector2 > , SelfDragInfo : UsedAs < CurrentlyDragging ? > , OverlayFrame : UsedAs < Instance > ? }, OnMouseDown : () -> () ? } ): Fusion . Child Notice that it shares many of the same property groups as Draggable - these can be passed directly through. 182 183 184 185 186 187 return scope : Draggable { ID = props . Item . id , Name = props . Item . text , Parent = props . Parent , Layout = props . Layout , Dragging = props . Dragging , It also provides an OnMouseDown callback, which can be used to pick up the entry if the mouse is pressed down above the entry. Note the comment about why it is not desirable to detect mouse-up here; the UI should unconditionally respond to mouse-up, even if the mouse happens to briefly leave this element. 202 203 204 205 206 [ OnEvent \"MouseButton1Down\" ] = props . OnMouseDown -- Don't detect mouse up here, because in some rare cases, the event -- could be missed due to lag between the item's position and the -- cursor position. Now, the destinations for these entries can be created. To help decide where to drop items later, the dropAction tracks which destination the mouse is hovered over. 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 local dropAction = scope : Value ( nil ) local taskLists = scope : ForPairs ( { incomplete = \"mark-as-incomplete\" , completed = \"mark-as-completed\" }, function ( use , scope , listName , listDropAction ) return listName , scope : New \"ScrollingFrame\" { Name = ` TaskList ({ listName }) ` , Position = UDim2 . fromScale ( 0.1 , 0.1 ), Size = UDim2 . fromScale ( 0.35 , 0.9 ), BackgroundTransparency = 0.75 , BackgroundColor3 = Color3 . new ( 1 , 0 , 0 ), [ OnEvent \"MouseEnter\" ] = function () dropAction : set ( listDropAction ) end , [ OnEvent \"MouseLeave\" ] = function () -- A different item might have overwritten this already. if peek ( dropAction ) == listDropAction then dropAction : set ( nil ) end end , [ Children ] = { New \"UIListLayout\" { SortOrder = \"Name\" , Padding = UDim . new ( 0 , 5 ) } } } end ) This is also where the 'overlay frame' is created, which gives currently-dragged UI a dedicated layer above all other UI to freely move around. 265 266 267 268 269 local overlayFrame = scope : New \"Frame\" { Size = UDim2 . fromScale ( 1 , 1 ), ZIndex = 10 , BackgroundTransparency = 1 } Finally, each TodoItem is created as a TodoEntry . Some state is also created to track which entry is being dragged at the moment. 271 272 273 274 275 276 277 278 local currentlyDragging : Fusion . Value < DragInfo ? > = scope : Value ( nil ) local allEntries = scope : ForValues ( todoItems , function ( use , scope , item ) local itemPosition = scope : Value ( nil ) return scope : TodoEntry { Item = item , Each entry dynamically picks one of the two destinations based on its completion status. 279 280 281 282 283 284 Parent = scope : Computed ( function ( use ) return if use ( item . completed ) then use ( taskLists ). completed else use ( taskLists ). incomplete end ), It also provides the information needed by the Draggable . Note that the current drag information is filtered from the currentlyDragging state so the Draggable won't see information about other entries being dragged. 289 290 291 292 293 294 295 296 297 298 299 Dragging = { MousePosition = mousePos , SelfDragInfo = scope : Computed ( function ( use ) local dragInfo = use ( currentlyDragging ) return if dragInfo == nil or dragInfo . id ~= item . id then nil else dragInfo end ) OverlayFrame = overlayFrame }, Now it's time to handle starting and stopping the drag. To begin the drag, this code makes use of the OnMouseDown callback. If nothing else is being dragged right now, the position of the mouse relative to the item is captured. Then, that mouseOffset and the id of the item are passed into the currentlyDragging state to indicate this entry is being dragged. 300 301 302 303 304 305 306 307 308 309 OnMouseDown = function () if peek ( currentlyDragging ) == nil then local itemPos = peek ( itemPosition ) or Vector2 . zero local mouseOffset = peek ( mousePos ) - itemPos currentlyDragging : set ({ id = item . id , mouseOffset = mouseOffset }) end end To end the drag, a global InputEnded listener is created, which should reliably fire no matter where or when the event occurs. If there's a dropAction to take, for example mark-as-completed , then that action is executed here. In all cases, currentlyDragging is cleared, so the entry is no longer dragged. 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 table.insert ( scope , UserInputService . InputEnded : Connect ( function ( inputObject ) if inputObject . UserInputType ~= Enum . UserInputType . MouseButton1 then return end local dragInfo = peek ( currentlyDragging ) if dragInfo == nil then return end local item = getTodoItemForID ( dragInfo . id ) local action = peek ( dropAction ) if item ~= nil then if action == \"mark-as-incomplete\" then item . completed : set ( false ) elseif action == \"mark-as-completed\" then item . completed : set ( true ) end end currentlyDragging : set ( nil ) end ) ) All that remains is to parent the task lists and overlay frames to a UI, so they can be seen. Because the TodoEntry component manages their own parent, this code shouldn't pass in allEntries as a child here. 336 337 338 339 340 341 342 343 344 local ui = scope : New \"ScreenGui\" { Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ) [ Children ] = { overlayFrame , taskLists , -- Don't pass `allEntries` in here - they manage their own parent! } }","title":"Explanation"},{"location":"examples/cookbook/fetch-data-from-server/","text":"This code shows how to deal with yielding/blocking code, such as fetching data from a server. Because these tasks don't complete immediately, they can't be directly run inside of a Computed , so this example provides a robust framework for handling this in a way that doesn't corrupt your code. This example assumes the presence of a Roblox-like task scheduler. Overview \u00b6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local function fetchUserBio ( userID : number ): string -- pretend this calls out to a server somewhere, causing this code to yield task . wait ( 1 ) return \"This is the bio for user \" .. userID .. \"!\" end -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion ) -- This doesn't have to be a `Value` - any kind of state object works too. local currentUserID = scope : Value ( 1670764 ) -- While the bio is loading, this is `nil` instead of a string. local currentUserBio : Fusion . Value < string ? > = scope : Value ( nil ) do local fetchInProgress = nil local function performFetch () local userID = peek ( currentUserID ) currentUserBio : set ( nil ) if fetchInProgress ~= nil then task . cancel ( fetchInProgress ) end fetchInProgress = task . spawn ( function () currentUserBio : set ( fetchUserBio ()) fetchInProgress = nil end ) end scope : Observer ( currentUserID ): onBind ( performFetch ) end scope : Observer ( currentUserBio ): onBind ( function () local bio = peek ( currentUserBio ) if bio == nil then print ( \"User bio is loading...\" ) else print ( \"Loaded user bio:\" , bio ) end end ) Explanation \u00b6 If you yield or wait inside of a Computed , you can easily corrupt your entire program. However, this example has a function, fetchUserBio , that yields. 5 6 7 8 9 10 11 local function fetchUserBio ( userID : number ): string -- pretend this calls out to a server somewhere, causing this code to yield task . wait ( 1 ) return \"This is the bio for user \" .. userID .. \"!\" end It also has some arbitrary state object, currentUserID , that it needs to convert into a bio somehow. 15 16 -- This doesn't have to be a `Value` - any kind of state object works too. local currentUserID = scope : Value ( 1670764 ) Because Computed can't yield, this code has to manually manage a currentUserBio object, which will store the output of the code in a way that can be used by other Fusion objects later. Notice that the 'loading' state is explicitly documented. It's a good idea to be clear and honest when you have no data to show, because it allows other code to respond to that case flexibly. 18 19 -- While the bio is loading, this is `nil` instead of a string. local currentUserBio : Fusion . Value < string ? > = scope : Value ( nil ) To perform the actual fetch, a simple function can be written which calls fetchUserBio in a separate task. Once it returns a bio, the currentUserBio can be updated. To avoid two fetches overwriting each other, any existing fetch task is canceled before the new task is created. 22 23 24 25 26 27 28 29 30 31 32 33 local fetchInProgress = nil local function performFetch () local userID = peek ( currentUserID ) currentUserBio : set ( nil ) if fetchInProgress ~= nil then task . cancel ( fetchInProgress ) end fetchInProgress = task . spawn ( function () currentUserBio : set ( fetchUserBio ()) fetchInProgress = nil end ) end Finally, to run this function when the currentUserID changes, performFetch can be added to an Observer . The onBind method also runs performFetch once at the start of the program, so the request is sent out automatically. 34 scope : Observer ( currentUserID ): onBind ( performFetch ) That's all you need - now, any other Fusion code can read and depend upon currentUserBio as if it were any other kind of state object. Just remember to handle the 'loading' state as well as the successful state. 37 38 39 40 41 42 43 44 scope : Observer ( currentUserBio ): onBind ( function () local bio = peek ( currentUserBio ) if bio == nil then print ( \"User bio is loading...\" ) else print ( \"Loaded user bio:\" , bio ) end end ) You may wish to expand this code with error handling if fetchUserBio() can throw errors.","title":"Fetch Data From Server"},{"location":"examples/cookbook/fetch-data-from-server/#overview","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local function fetchUserBio ( userID : number ): string -- pretend this calls out to a server somewhere, causing this code to yield task . wait ( 1 ) return \"This is the bio for user \" .. userID .. \"!\" end -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion ) -- This doesn't have to be a `Value` - any kind of state object works too. local currentUserID = scope : Value ( 1670764 ) -- While the bio is loading, this is `nil` instead of a string. local currentUserBio : Fusion . Value < string ? > = scope : Value ( nil ) do local fetchInProgress = nil local function performFetch () local userID = peek ( currentUserID ) currentUserBio : set ( nil ) if fetchInProgress ~= nil then task . cancel ( fetchInProgress ) end fetchInProgress = task . spawn ( function () currentUserBio : set ( fetchUserBio ()) fetchInProgress = nil end ) end scope : Observer ( currentUserID ): onBind ( performFetch ) end scope : Observer ( currentUserBio ): onBind ( function () local bio = peek ( currentUserBio ) if bio == nil then print ( \"User bio is loading...\" ) else print ( \"Loaded user bio:\" , bio ) end end )","title":"Overview"},{"location":"examples/cookbook/fetch-data-from-server/#explanation","text":"If you yield or wait inside of a Computed , you can easily corrupt your entire program. However, this example has a function, fetchUserBio , that yields. 5 6 7 8 9 10 11 local function fetchUserBio ( userID : number ): string -- pretend this calls out to a server somewhere, causing this code to yield task . wait ( 1 ) return \"This is the bio for user \" .. userID .. \"!\" end It also has some arbitrary state object, currentUserID , that it needs to convert into a bio somehow. 15 16 -- This doesn't have to be a `Value` - any kind of state object works too. local currentUserID = scope : Value ( 1670764 ) Because Computed can't yield, this code has to manually manage a currentUserBio object, which will store the output of the code in a way that can be used by other Fusion objects later. Notice that the 'loading' state is explicitly documented. It's a good idea to be clear and honest when you have no data to show, because it allows other code to respond to that case flexibly. 18 19 -- While the bio is loading, this is `nil` instead of a string. local currentUserBio : Fusion . Value < string ? > = scope : Value ( nil ) To perform the actual fetch, a simple function can be written which calls fetchUserBio in a separate task. Once it returns a bio, the currentUserBio can be updated. To avoid two fetches overwriting each other, any existing fetch task is canceled before the new task is created. 22 23 24 25 26 27 28 29 30 31 32 33 local fetchInProgress = nil local function performFetch () local userID = peek ( currentUserID ) currentUserBio : set ( nil ) if fetchInProgress ~= nil then task . cancel ( fetchInProgress ) end fetchInProgress = task . spawn ( function () currentUserBio : set ( fetchUserBio ()) fetchInProgress = nil end ) end Finally, to run this function when the currentUserID changes, performFetch can be added to an Observer . The onBind method also runs performFetch once at the start of the program, so the request is sent out automatically. 34 scope : Observer ( currentUserID ): onBind ( performFetch ) That's all you need - now, any other Fusion code can read and depend upon currentUserBio as if it were any other kind of state object. Just remember to handle the 'loading' state as well as the successful state. 37 38 39 40 41 42 43 44 scope : Observer ( currentUserBio ): onBind ( function () local bio = peek ( currentUserBio ) if bio == nil then print ( \"User bio is loading...\" ) else print ( \"Loaded user bio:\" , bio ) end end ) You may wish to expand this code with error handling if fetchUserBio() can throw errors.","title":"Explanation"},{"location":"examples/cookbook/light-and-dark-theme/","text":"This example demonstrates how to create dynamic theme colours using Fusion's state objects. Overview \u00b6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 local Fusion = --initialise Fusion here however you please! local scoped = Fusion . scoped local Theme = {} Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"222222\" ), dark = Color3 . fromHex ( \"FFFFFF\" ) } } -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion ) Theme . current = scope : Value ( \"light\" ) Theme . dynamic = {} for colour , variants in Theme . colours do Theme . dynamic [ colour ] = scope : Computed ( function ( use ) return variants [ use ( Theme . current )] end ) end Theme . current : set ( \"light\" ) print ( peek ( Theme . dynamic . background )) --> 255, 255, 255 Theme . current : set ( \"dark\" ) print ( peek ( Theme . dynamic . background )) --> 34, 34, 34 Explanation \u00b6 To begin, this example defines a set of colours with light and dark variants. 6 7 8 9 10 11 12 13 14 15 Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"222222\" ), dark = Color3 . fromHex ( \"FFFFFF\" ) } } A Value object stores which variant is in use right now. 20 Theme . current = scope : Value ( \"light\" ) Finally, each colour is turned into a Computed , which dynamically pulls the desired variant from the list. 21 22 23 24 25 26 Theme . dynamic = {} for colour , variants in Theme . colours do Theme . dynamic [ colour ] = scope : Computed ( function ( use ) return variants [ use ( Theme . current )] end ) end This allows other code to easily access theme colours from Theme.dynamic . 28 29 30 31 32 Theme . current : set ( \"light\" ) print ( peek ( Theme . dynamic . background )) --> 255, 255, 255 Theme . current : set ( \"dark\" ) print ( peek ( Theme . dynamic . background )) --> 34, 34, 34","title":"Light & Dark Theme"},{"location":"examples/cookbook/light-and-dark-theme/#overview","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 local Fusion = --initialise Fusion here however you please! local scoped = Fusion . scoped local Theme = {} Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"222222\" ), dark = Color3 . fromHex ( \"FFFFFF\" ) } } -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion ) Theme . current = scope : Value ( \"light\" ) Theme . dynamic = {} for colour , variants in Theme . colours do Theme . dynamic [ colour ] = scope : Computed ( function ( use ) return variants [ use ( Theme . current )] end ) end Theme . current : set ( \"light\" ) print ( peek ( Theme . dynamic . background )) --> 255, 255, 255 Theme . current : set ( \"dark\" ) print ( peek ( Theme . dynamic . background )) --> 34, 34, 34","title":"Overview"},{"location":"examples/cookbook/light-and-dark-theme/#explanation","text":"To begin, this example defines a set of colours with light and dark variants. 6 7 8 9 10 11 12 13 14 15 Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"222222\" ), dark = Color3 . fromHex ( \"FFFFFF\" ) } } A Value object stores which variant is in use right now. 20 Theme . current = scope : Value ( \"light\" ) Finally, each colour is turned into a Computed , which dynamically pulls the desired variant from the list. 21 22 23 24 25 26 Theme . dynamic = {} for colour , variants in Theme . colours do Theme . dynamic [ colour ] = scope : Computed ( function ( use ) return variants [ use ( Theme . current )] end ) end This allows other code to easily access theme colours from Theme.dynamic . 28 29 30 31 32 Theme . current : set ( \"light\" ) print ( peek ( Theme . dynamic . background )) --> 255, 255, 255 Theme . current : set ( \"dark\" ) print ( peek ( Theme . dynamic . background )) --> 34, 34, 34","title":"Explanation"},{"location":"examples/cookbook/loading-spinner/","text":"This example implements a procedural spinning animation using Fusion's Roblox APIs. Overview \u00b6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 local RunService = game : GetService ( \"RunService\" ) local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children = Fusion . Children type UsedAs < T > = Fusion . UsedAs < T > local SPIN_DEGREES_PER_SECOND = 180 local SPIN_SIZE = 50 local function Spinner ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? }, CurrentTime : UsedAs < number > , } ): Fusion . Child return scope : New \"ImageLabel\" { Name = \"Spinner\" , LayoutOrder = props . Layout . LayoutOrder , Position = props . Layout . Position , AnchorPoint = props . Layout . AnchorPoint , ZIndex = props . Layout . ZIndex , Size = UDim2 . fromOffset ( SPIN_SIZE , SPIN_SIZE ), BackgroundTransparency = 1 , Image = \"rbxassetid://your-loading-spinner-image\" , -- replace this! Rotation = scope : Computed ( function ( use ) return ( use ( props . CurrentTime ) * SPIN_DEGREES_PER_SECOND ) % 360 end ) } end -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion , { Spinner = Spinner }) local currentTime = scope : Value ( os.clock ()) table.insert ( scope , RunService . RenderStepped : Connect ( function () currentTime : set ( os.clock ()) end ) ) local spinner = scope : Spinner { Layout = { Position = UDim2 . fromScale ( 0.5 , 0.5 ), AnchorPoint = Vector2 . new ( 0.5 , 0.5 ), Size = UDim2 . fromOffset ( 50 , 50 ) }, CurrentTime = currentTime } Explanation \u00b6 The Spinner components implements the animation for the loading spinner. It's largely a standard Fusion component definition. The main thing to note is that it asks for a CurrentTime property. 11 12 13 14 15 16 17 18 19 20 21 22 local function Spinner ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? }, CurrentTime : UsedAs < number > , } ): Fusion . Child The CurrentTime is used to drive the rotation of the loading spinner. 36 37 38 Rotation = scope : Computed ( function ( use ) return ( use ( props . CurrentTime ) * SPIN_DEGREES_PER_SECOND ) % 360 end ) That's all that's required for the Spinner component. Later on, the example creates a Value object that will store the current time, and starts a process to keep it up to date. 47 48 49 50 51 52 local currentTime = scope : Value ( os.clock ()) table.insert ( scope , RunService . RenderStepped : Connect ( function () currentTime : set ( os.clock ()) end ) ) This can then be passed in as CurrentTime when the Spinner is created. 54 55 56 57 58 59 60 61 local spinner = scope : Spinner { Layout = { Position = UDim2 . fromScale ( 0.5 , 0.5 ), AnchorPoint = Vector2 . new ( 0.5 , 0.5 ), Size = UDim2 . fromOffset ( 50 , 50 ) }, CurrentTime = currentTime }","title":"Loading Spinner"},{"location":"examples/cookbook/loading-spinner/#overview","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 local RunService = game : GetService ( \"RunService\" ) local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children = Fusion . Children type UsedAs < T > = Fusion . UsedAs < T > local SPIN_DEGREES_PER_SECOND = 180 local SPIN_SIZE = 50 local function Spinner ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? }, CurrentTime : UsedAs < number > , } ): Fusion . Child return scope : New \"ImageLabel\" { Name = \"Spinner\" , LayoutOrder = props . Layout . LayoutOrder , Position = props . Layout . Position , AnchorPoint = props . Layout . AnchorPoint , ZIndex = props . Layout . ZIndex , Size = UDim2 . fromOffset ( SPIN_SIZE , SPIN_SIZE ), BackgroundTransparency = 1 , Image = \"rbxassetid://your-loading-spinner-image\" , -- replace this! Rotation = scope : Computed ( function ( use ) return ( use ( props . CurrentTime ) * SPIN_DEGREES_PER_SECOND ) % 360 end ) } end -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion , { Spinner = Spinner }) local currentTime = scope : Value ( os.clock ()) table.insert ( scope , RunService . RenderStepped : Connect ( function () currentTime : set ( os.clock ()) end ) ) local spinner = scope : Spinner { Layout = { Position = UDim2 . fromScale ( 0.5 , 0.5 ), AnchorPoint = Vector2 . new ( 0.5 , 0.5 ), Size = UDim2 . fromOffset ( 50 , 50 ) }, CurrentTime = currentTime }","title":"Overview"},{"location":"examples/cookbook/loading-spinner/#explanation","text":"The Spinner components implements the animation for the loading spinner. It's largely a standard Fusion component definition. The main thing to note is that it asks for a CurrentTime property. 11 12 13 14 15 16 17 18 19 20 21 22 local function Spinner ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? }, CurrentTime : UsedAs < number > , } ): Fusion . Child The CurrentTime is used to drive the rotation of the loading spinner. 36 37 38 Rotation = scope : Computed ( function ( use ) return ( use ( props . CurrentTime ) * SPIN_DEGREES_PER_SECOND ) % 360 end ) That's all that's required for the Spinner component. Later on, the example creates a Value object that will store the current time, and starts a process to keep it up to date. 47 48 49 50 51 52 local currentTime = scope : Value ( os.clock ()) table.insert ( scope , RunService . RenderStepped : Connect ( function () currentTime : set ( os.clock ()) end ) ) This can then be passed in as CurrentTime when the Spinner is created. 54 55 56 57 58 59 60 61 local spinner = scope : Spinner { Layout = { Position = UDim2 . fromScale ( 0.5 , 0.5 ), AnchorPoint = Vector2 . new ( 0.5 , 0.5 ), Size = UDim2 . fromOffset ( 50 , 50 ) }, CurrentTime = currentTime }","title":"Explanation"},{"location":"examples/cookbook/player-list/","text":"This shows how to use Fusion's Roblox API to create a simple, dynamically updating player list. Overview \u00b6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 local Players = game : GetService ( \"Players\" ) local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children = Fusion . Children type UsedAs < T > = Fusion . UsedAs < T > local function PlayerList ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Players : UsedAs < { Player } > } ): Fusion . Child return scope : New \"Frame\" { Name = \"PlayerList\" , Position = UDim2 . fromScale ( 1 , 0 ), AnchorPoint = Vector2 . new ( 1 , 0 ), Size = UDim2 . fromOffset ( 300 , 0 ), AutomaticSize = \"Y\" , BackgroundTransparency = 0.5 , BackgroundColor3 = Color3 . new ( 0 , 0 , 0 ), [ Children ] = { scope : New \"UICorner\" { CornerRadius = UDim . new ( 0 , 8 ) }, scope : New \"UIListLayout\" { SortOrder = \"Name\" , FillDirection = \"Vertical\" }, scope : ForValues ( props . Players , function ( use , scope , player ) return scope : New \"TextLabel\" { Name = \"PlayerListRow: \" .. player . DisplayName , Size = UDim2 . new ( 1 , 0 , 0 , 25 ), BackgroundTransparency = 1 , Text = player . DisplayName , TextColor3 = Color3 . new ( 1 , 1 , 1 ), Font = Enum . Font . GothamMedium , TextSize = 16 , TextXAlignment = \"Right\" , TextTruncate = \"AtEnd\" , [ Children ] = scope : New \"UIPadding\" { PaddingLeft = UDim . new ( 0 , 10 ), PaddingRight = UDim . new ( 0 , 10 ) } } end ) } } end -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion , { PlayerList = PlayerList }) local players = scope : Value ( Players : GetPlayers ()) local function updatePlayers () players : set ( Players : GetPlayers ()) end table.insert ( scope , { Players . PlayerAdded : Connect ( updatePlayers ), Players . PlayerRemoving : Connect ( updatePlayers ) }) local gui = scope : New \"ScreenGui\" { Name = \"PlayerListGui\" , Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ), [ Children ] = scope : PlayerList { Players = players } } Explanation \u00b6 The PlayerList component is designed to be simple and self-contained. The only thing it needs is a Players list - it handles everything else, including its position, size, appearance and behaviour. 8 9 10 11 12 13 local function PlayerList ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Players : UsedAs < { Player } > } ): Fusion . Child After creating a vertically expanding Frame with some style and layout added, it turns the Players into a series of text labels using ForValues , which will automatically create and remove them as the Players list changes. 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 scope : ForValues ( props . Players , function ( use , scope , player ) return scope : New \"TextLabel\" { Name = \"PlayerListRow: \" .. player . DisplayName , Size = UDim2 . new ( 1 , 0 , 0 , 25 ), BackgroundTransparency = 1 , Text = player . DisplayName , TextColor3 = Color3 . new ( 1 , 1 , 1 ), Font = Enum . Font . GothamMedium , TextSize = 16 , TextXAlignment = \"Right\" , TextTruncate = \"AtEnd\" , [ Children ] = scope : New \"UIPadding\" { PaddingLeft = UDim . new ( 0 , 10 ), PaddingRight = UDim . new ( 0 , 10 ) } } end ) That's all that the PlayerList component has to do. Later on, the code creates a Value object to store a list of players, and update it every time a player joins or leaves the game. 63 64 65 66 67 68 69 70 local players = scope : Value ( Players : GetPlayers ()) local function updatePlayers () players : set ( Players : GetPlayers ()) end table.insert ( scope , { Players . PlayerAdded : Connect ( updatePlayers ), Players . PlayerRemoving : Connect ( updatePlayers ) }) That object can then be passed in as Players when creating the PlayerList . 72 73 74 75 76 77 78 79 local gui = scope : New \"ScreenGui\" { Name = \"PlayerListGui\" , Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ), [ Children ] = scope : PlayerList { Players = players } }","title":"Player List"},{"location":"examples/cookbook/player-list/#overview","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 local Players = game : GetService ( \"Players\" ) local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children = Fusion . Children type UsedAs < T > = Fusion . UsedAs < T > local function PlayerList ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Players : UsedAs < { Player } > } ): Fusion . Child return scope : New \"Frame\" { Name = \"PlayerList\" , Position = UDim2 . fromScale ( 1 , 0 ), AnchorPoint = Vector2 . new ( 1 , 0 ), Size = UDim2 . fromOffset ( 300 , 0 ), AutomaticSize = \"Y\" , BackgroundTransparency = 0.5 , BackgroundColor3 = Color3 . new ( 0 , 0 , 0 ), [ Children ] = { scope : New \"UICorner\" { CornerRadius = UDim . new ( 0 , 8 ) }, scope : New \"UIListLayout\" { SortOrder = \"Name\" , FillDirection = \"Vertical\" }, scope : ForValues ( props . Players , function ( use , scope , player ) return scope : New \"TextLabel\" { Name = \"PlayerListRow: \" .. player . DisplayName , Size = UDim2 . new ( 1 , 0 , 0 , 25 ), BackgroundTransparency = 1 , Text = player . DisplayName , TextColor3 = Color3 . new ( 1 , 1 , 1 ), Font = Enum . Font . GothamMedium , TextSize = 16 , TextXAlignment = \"Right\" , TextTruncate = \"AtEnd\" , [ Children ] = scope : New \"UIPadding\" { PaddingLeft = UDim . new ( 0 , 10 ), PaddingRight = UDim . new ( 0 , 10 ) } } end ) } } end -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion , { PlayerList = PlayerList }) local players = scope : Value ( Players : GetPlayers ()) local function updatePlayers () players : set ( Players : GetPlayers ()) end table.insert ( scope , { Players . PlayerAdded : Connect ( updatePlayers ), Players . PlayerRemoving : Connect ( updatePlayers ) }) local gui = scope : New \"ScreenGui\" { Name = \"PlayerListGui\" , Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ), [ Children ] = scope : PlayerList { Players = players } }","title":"Overview"},{"location":"examples/cookbook/player-list/#explanation","text":"The PlayerList component is designed to be simple and self-contained. The only thing it needs is a Players list - it handles everything else, including its position, size, appearance and behaviour. 8 9 10 11 12 13 local function PlayerList ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Players : UsedAs < { Player } > } ): Fusion . Child After creating a vertically expanding Frame with some style and layout added, it turns the Players into a series of text labels using ForValues , which will automatically create and remove them as the Players list changes. 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 scope : ForValues ( props . Players , function ( use , scope , player ) return scope : New \"TextLabel\" { Name = \"PlayerListRow: \" .. player . DisplayName , Size = UDim2 . new ( 1 , 0 , 0 , 25 ), BackgroundTransparency = 1 , Text = player . DisplayName , TextColor3 = Color3 . new ( 1 , 1 , 1 ), Font = Enum . Font . GothamMedium , TextSize = 16 , TextXAlignment = \"Right\" , TextTruncate = \"AtEnd\" , [ Children ] = scope : New \"UIPadding\" { PaddingLeft = UDim . new ( 0 , 10 ), PaddingRight = UDim . new ( 0 , 10 ) } } end ) That's all that the PlayerList component has to do. Later on, the code creates a Value object to store a list of players, and update it every time a player joins or leaves the game. 63 64 65 66 67 68 69 70 local players = scope : Value ( Players : GetPlayers ()) local function updatePlayers () players : set ( Players : GetPlayers ()) end table.insert ( scope , { Players . PlayerAdded : Connect ( updatePlayers ), Players . PlayerRemoving : Connect ( updatePlayers ) }) That object can then be passed in as Players when creating the PlayerList . 72 73 74 75 76 77 78 79 local gui = scope : New \"ScreenGui\" { Name = \"PlayerListGui\" , Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ), [ Children ] = scope : PlayerList { Players = players } }","title":"Explanation"},{"location":"extras/","text":"Extras \u00b6 Welcome to the Extras section! Here, you can find guidelines and assets for Fusion branding, download backgrounds and wallpapers for your devices, and more! Commonly Used \u00b6 Backgrounds Brand Guidelines","title":"Home"},{"location":"extras/#extras","text":"Welcome to the Extras section! Here, you can find guidelines and assets for Fusion branding, download backgrounds and wallpapers for your devices, and more!","title":"Extras"},{"location":"extras/#commonly-used","text":"Backgrounds Brand Guidelines","title":"Commonly Used"},{"location":"extras/backgrounds/","text":"All backgrounds are PNG format, and have been optimised for these resolutions: Ultrawide (7680 x 1440) Widescreen (2560 x 1440) 3:2 (2256 x 1504) Mobile (1125 x 2436) These backgrounds are intended for personal use only! These backgrounds are, and remain, the copyright of Elttob. You may not use these, commercially or otherwise, without explicit written consent. Isosceles \u00b6 A pattern of isosceles triangles distributed along the bottom, with the Fusion gradient. Background is hex colour #1D1D1F, and so might not be ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile Extrusion \u00b6 A Fusion logo, with extruded fill lines coming out of the logo. Background is hex colour #0D0D0F, and so might not be ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile Construction \u00b6 The Fusion logo, with construction lines shown and other geometric patterns. Background is 100% black, ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile Glow \u00b6 A centred Fusion logo emitting light on a dark background. Background is 100% black, ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile Glow (Alternate) \u00b6 A centred Fusion logo emitting light on a dark background. Uses an alternate design of the logo, which is now used for livestreams. Background is 100% black, ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile","title":"Backgrounds"},{"location":"extras/backgrounds/#isosceles","text":"A pattern of isosceles triangles distributed along the bottom, with the Fusion gradient. Background is hex colour #1D1D1F, and so might not be ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile","title":"Isosceles"},{"location":"extras/backgrounds/#extrusion","text":"A Fusion logo, with extruded fill lines coming out of the logo. Background is hex colour #0D0D0F, and so might not be ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile","title":"Extrusion"},{"location":"extras/backgrounds/#construction","text":"The Fusion logo, with construction lines shown and other geometric patterns. Background is 100% black, ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile","title":"Construction"},{"location":"extras/backgrounds/#glow","text":"A centred Fusion logo emitting light on a dark background. Background is 100% black, ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile","title":"Glow"},{"location":"extras/backgrounds/#glow-alternate","text":"A centred Fusion logo emitting light on a dark background. Uses an alternate design of the logo, which is now used for livestreams. Background is 100% black, ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile","title":"Glow (Alternate)"},{"location":"extras/brand-guidelines/","text":"The Fusion branding is designed to be simplistic, modern, easy to recognise and distinctive. Colours \u00b6 Primaries \u00b6 These colours are used in the Fusion logo and most illustrations. They might not be suitable for text or all backgrounds. FusionDoc greys \u00b6 These colours are used by the FusionDoc theme on this website for all grey tones used on pages. FusionDoc accents \u00b6 These colours are used by the FusionDoc theme on this website for accent colours on links and interactive elements. Best Practices \u00b6 We would love you to use the Fusion branding in your own work, but please be mindful that you use it appropriately. If you're not sure, feel free to reach out over Discord or Twitter - it's always better to ask first to be secure! These aren't hard and fast rules, and we can't and don't want to police how people use our stuff. Instead, these are provided as best practices to follow. We'll add any common examples or questions to this list over time. Brand Confusion \u00b6 Fusion's logo and name are designed to represent Fusion's official projects. Please don't use them to represent things that are not Fusion; for example, if you build your own UI library, it's better to design a new logo. In general, prefer to keep some distance between your project branding and Fusion's branding, enough distance that people don't get confused about who makes what, or whether a project is officially supported or not. It's a good litmus test to imagine a first-time user who knows nothing about Fusion, and how they will perceive your project. Don't do this In this example, someone made their own UI library and named it Fusion 2. Note that this is an extreme example for demonstration purposes - most violations of this principle are probably more subtle. This is bad because people might mistakenly think the official Fusion project approves or provides support for this unrelated project, which would cause many headaches and is dishonest about the relationship between the two libraries. Plus, in this example, we reserve the right to update Fusion to version 2 at any time, which would immediately cause a naming conflict. Instead, do this This logo makes it more clear that the project is not a port of, update to or bindings for the Fusion library. Instead, it's a completely distinct project which only takes inspiration from Fusion, but is otherwise unrelated. It's okay for the logo to remind people of Fusion's design. Remember - you don't have to be completely original, just distinct enough that it isn't confusing for people. Acceptable, but be careful Here, this plugin is using the Fusion logo to represent a 'Convert to Fusion' action. This is fine, because users will understand the Fusion logo represents the thing being converted to. However, be careful, as free-standing uses of the Fusion icon like this can easily become confusing. Make sure people understand the logo represents Fusion, and not the plugin, so confusion between the two is minimised.","title":"Brand Guidelines"},{"location":"extras/brand-guidelines/#colours","text":"","title":"Colours"},{"location":"extras/brand-guidelines/#primaries","text":"These colours are used in the Fusion logo and most illustrations. They might not be suitable for text or all backgrounds.","title":"Primaries"},{"location":"extras/brand-guidelines/#fusiondoc-greys","text":"These colours are used by the FusionDoc theme on this website for all grey tones used on pages.","title":"FusionDoc greys"},{"location":"extras/brand-guidelines/#fusiondoc-accents","text":"These colours are used by the FusionDoc theme on this website for accent colours on links and interactive elements.","title":"FusionDoc accents"},{"location":"extras/brand-guidelines/#best-practices","text":"We would love you to use the Fusion branding in your own work, but please be mindful that you use it appropriately. If you're not sure, feel free to reach out over Discord or Twitter - it's always better to ask first to be secure! These aren't hard and fast rules, and we can't and don't want to police how people use our stuff. Instead, these are provided as best practices to follow. We'll add any common examples or questions to this list over time.","title":"Best Practices"},{"location":"extras/brand-guidelines/#brand-confusion","text":"Fusion's logo and name are designed to represent Fusion's official projects. Please don't use them to represent things that are not Fusion; for example, if you build your own UI library, it's better to design a new logo. In general, prefer to keep some distance between your project branding and Fusion's branding, enough distance that people don't get confused about who makes what, or whether a project is officially supported or not. It's a good litmus test to imagine a first-time user who knows nothing about Fusion, and how they will perceive your project. Don't do this In this example, someone made their own UI library and named it Fusion 2. Note that this is an extreme example for demonstration purposes - most violations of this principle are probably more subtle. This is bad because people might mistakenly think the official Fusion project approves or provides support for this unrelated project, which would cause many headaches and is dishonest about the relationship between the two libraries. Plus, in this example, we reserve the right to update Fusion to version 2 at any time, which would immediately cause a naming conflict. Instead, do this This logo makes it more clear that the project is not a port of, update to or bindings for the Fusion library. Instead, it's a completely distinct project which only takes inspiration from Fusion, but is otherwise unrelated. It's okay for the logo to remind people of Fusion's design. Remember - you don't have to be completely original, just distinct enough that it isn't confusing for people. Acceptable, but be careful Here, this plugin is using the Fusion logo to represent a 'Convert to Fusion' action. This is fine, because users will understand the Fusion logo represents the thing being converted to. However, be careful, as free-standing uses of the Fusion icon like this can easily become confusing. Make sure people understand the logo represents Fusion, and not the plugin, so confusion between the two is minimised.","title":"Brand Confusion"},{"location":"tutorials/","text":"Welcome to the Fusion tutorial section! Here, you'll learn how to build great things with Fusion, even if you're a complete newcomer to the library. You'll not only learn how Fusion's features work, but you'll also be presented with wisdom from those who've worked with some of the largest Fusion codebases today. But first, some advice from the maintainers... Fusion is pre-1.0 software. We (the maintainers and contributors) work hard to keep releases bug-free and relatively complete, so it should be safe to use in production. Many people already do, and report fantastic results! However, we mark Fusion as pre-1.0 because we are working on the design of the library itself. We strive for the best library design we can deliver, which means breaking changes are common and sweeping. With Fusion, you should expect: upgrades to be frictionful, requiring code to be rethought features to be superseded or removed across versions advice or best practices to change over time You should also expect: careful consideration around breakage, even though we reserve the right to do it clear communication ahead of any major changes helpful advice to answer your questions and ease your porting process We hope you enjoy using Fusion! What You Need To Know \u00b6 These tutorials assume: That you're comfortable with the Luau scripting language. These tutorials aren't an introduction to Luau! If you'd like to learn, check out the Roblox documentation . That - if you're using Roblox features - you're familiar with how Roblox works. You don't have to be an expert! Knowing about basic instances, events and data types will be good enough. Based on your existing knowledge, you may find some tutorials easier or harder. Don't be discouraged - Fusion's built to be easy to learn, but it may still take a bit of time to absorb some concepts. Learn at a pace which is right for you. Installing Fusion \u00b6 There are two ways of installing Fusion, dependent on your use case. If you are creating Luau experiences in Roblox Studio, then you can import a Roblox model file containing Fusion. Steps (click to expand) Head over to Fusion's 'Releases' page . Click the 'Assets' dropdown to view the downloadable files: Now, click on the Fusion.rbxm file to download it. This model contains Fusion. Head into Roblox Studio to import the model; if you're just following the tutorials, an empty baseplate will do. Right-click on ReplicatedStorage , and select 'Insert from File': Select the Fusion.rbxm file you just downloaded. You should see a 'Fusion' module script appear in ReplicatedStorage ! Now, you can create a script for testing: Create a LocalScript in StarterGui or StarterPlayerScripts . Remove the default code, and paste the following code in: 1 2 local ReplicatedStorage = game : GetService ( \"ReplicatedStorage\" ) local Fusion = require ( ReplicatedStorage . Fusion ) Press 'Play' - if there are no errors, everything was set up correctly!. If you're using pure Luau, or if you're synchronising external files into Roblox Studio, then you can use Fusion's source code directly. Steps (click to expand) Head over to Fusion's 'Releases' page . Under 'Assets', download Source code (zip) . Inside is a copy of the Fusion GitHub repository. Inside the zip, copy the src folder - it may be inside another folder. Paste the src folder into your local project, wherever you keep your libraries (e.g. inside a lib or shared folder) Rename the pasted folder from src to Fusion . Once everything is set up, you should be able to require() Fusion in one of the following ways: -- Rojo local Fusion = require ( ReplicatedStorage . Fusion ) -- darklua local Fusion = require ( \"../shared/Fusion\" ) -- vanilla Luau local Fusion = require ( \"../shared/Fusion/init.lua\" ) Getting Help \u00b6 Fusion is built to be easy to use, and this website strives to be as useful and comprehensive as possible. However, you might need targeted help on a specific issue, or you might want to grow your understanding of Fusion in other ways. The best place to get help is the #fusion channel over on the Roblox OSS Discord server . Maintainers and contributors drop in frequently, alongside many eager Fusion users. For bugs and feature requests, open an issue on GitHub.","title":"Get Started"},{"location":"tutorials/#what-you-need-to-know","text":"These tutorials assume: That you're comfortable with the Luau scripting language. These tutorials aren't an introduction to Luau! If you'd like to learn, check out the Roblox documentation . That - if you're using Roblox features - you're familiar with how Roblox works. You don't have to be an expert! Knowing about basic instances, events and data types will be good enough. Based on your existing knowledge, you may find some tutorials easier or harder. Don't be discouraged - Fusion's built to be easy to learn, but it may still take a bit of time to absorb some concepts. Learn at a pace which is right for you.","title":"What You Need To Know"},{"location":"tutorials/#installing-fusion","text":"There are two ways of installing Fusion, dependent on your use case. If you are creating Luau experiences in Roblox Studio, then you can import a Roblox model file containing Fusion. Steps (click to expand) Head over to Fusion's 'Releases' page . Click the 'Assets' dropdown to view the downloadable files: Now, click on the Fusion.rbxm file to download it. This model contains Fusion. Head into Roblox Studio to import the model; if you're just following the tutorials, an empty baseplate will do. Right-click on ReplicatedStorage , and select 'Insert from File': Select the Fusion.rbxm file you just downloaded. You should see a 'Fusion' module script appear in ReplicatedStorage ! Now, you can create a script for testing: Create a LocalScript in StarterGui or StarterPlayerScripts . Remove the default code, and paste the following code in: 1 2 local ReplicatedStorage = game : GetService ( \"ReplicatedStorage\" ) local Fusion = require ( ReplicatedStorage . Fusion ) Press 'Play' - if there are no errors, everything was set up correctly!. If you're using pure Luau, or if you're synchronising external files into Roblox Studio, then you can use Fusion's source code directly. Steps (click to expand) Head over to Fusion's 'Releases' page . Under 'Assets', download Source code (zip) . Inside is a copy of the Fusion GitHub repository. Inside the zip, copy the src folder - it may be inside another folder. Paste the src folder into your local project, wherever you keep your libraries (e.g. inside a lib or shared folder) Rename the pasted folder from src to Fusion . Once everything is set up, you should be able to require() Fusion in one of the following ways: -- Rojo local Fusion = require ( ReplicatedStorage . Fusion ) -- darklua local Fusion = require ( \"../shared/Fusion\" ) -- vanilla Luau local Fusion = require ( \"../shared/Fusion/init.lua\" )","title":"Installing Fusion"},{"location":"tutorials/#getting-help","text":"Fusion is built to be easy to use, and this website strives to be as useful and comprehensive as possible. However, you might need targeted help on a specific issue, or you might want to grow your understanding of Fusion in other ways. The best place to get help is the #fusion channel over on the Roblox OSS Discord server . Maintainers and contributors drop in frequently, alongside many eager Fusion users. For bugs and feature requests, open an issue on GitHub.","title":"Getting Help"},{"location":"tutorials/animation/springs/","text":"Springs follow the value of other state objects using a physical spring simulation. This can be used for 'springy' effects, or for smoothing out movement naturally without abrupt changes in direction. Usage \u00b6 To create a new spring object, call scope:Spring() and pass it a state object to move towards: local goal = scope : Value ( 0 ) local animated = scope : Spring ( goal ) The spring will smoothly follow the 'goal' state object over time. As with other state objects, you can peek() at its value at any time: print ( peek ( animated )) --> 0.26425... To configure how the spring moves, you can provide a speed and damping ratio to use. Both are optional, and both can be state objects if desired: local goal = scope : Value ( 0 ) local speed = 25 local damping = scope : Value ( 0.5 ) local animated = scope : Spring ( goal , speed , damping ) You can also set the position and velocity of the spring at any time. animated : setPosition ( 5 ) -- teleport the spring to 5 animated : setVelocity ( 2 ) -- from here, move 2 units/second You can use many different kinds of values with springs, not just numbers. Vectors, CFrames, Color3s, UDim2s and other number-based types are supported; each number inside the type is animated individually. local goalPosition = scope : Value ( UDim2 . new ( 0.5 , 0 , 0 , 0 )) local animated = scope : Spring ( goalPosition , 25 , 0.5 ) Damping Ratio \u00b6 The damping ratio (a.k.a damping) of the spring changes the friction in the physics simulation. Lower values allow the spring to move freely and oscillate up and down, while higher values restrict movement. Zero damping \u00b6 Zero damping means no friction is applied, so the spring will oscillate forever without losing energy. This is generally not useful. Underdamping \u00b6 A damping between 0 and 1 means some friction is applied. The spring will still oscillate, but it will lose energy and eventually settle at the goal. Critical damping \u00b6 A damping of exactly 1 means just enough friction is applied to stop the spring from oscillating. It reaches its goal as quickly as possible without going past. This is also commonly known as critical damping. Overdamping \u00b6 A damping above 1 applies excessive friction to the spring. The spring behaves like it's moving through honey, glue or some other viscous fluid. Overdamping reduces the effect of velocity changes, and makes movement more rigid. Speed \u00b6 The speed of the spring scales how much time it takes for the spring to move. Doubling the speed makes it move twice as fast; halving the speed makes it move twice as slow. Interruption \u00b6 Springs do not share the same interruption problems as tweens. When the goal changes, springs are guaranteed to preserve both position and velocity, reducing jank: This also means springs are suitable for following rapidly changing values:","title":"Springs"},{"location":"tutorials/animation/springs/#usage","text":"To create a new spring object, call scope:Spring() and pass it a state object to move towards: local goal = scope : Value ( 0 ) local animated = scope : Spring ( goal ) The spring will smoothly follow the 'goal' state object over time. As with other state objects, you can peek() at its value at any time: print ( peek ( animated )) --> 0.26425... To configure how the spring moves, you can provide a speed and damping ratio to use. Both are optional, and both can be state objects if desired: local goal = scope : Value ( 0 ) local speed = 25 local damping = scope : Value ( 0.5 ) local animated = scope : Spring ( goal , speed , damping ) You can also set the position and velocity of the spring at any time. animated : setPosition ( 5 ) -- teleport the spring to 5 animated : setVelocity ( 2 ) -- from here, move 2 units/second You can use many different kinds of values with springs, not just numbers. Vectors, CFrames, Color3s, UDim2s and other number-based types are supported; each number inside the type is animated individually. local goalPosition = scope : Value ( UDim2 . new ( 0.5 , 0 , 0 , 0 )) local animated = scope : Spring ( goalPosition , 25 , 0.5 )","title":"Usage"},{"location":"tutorials/animation/springs/#damping-ratio","text":"The damping ratio (a.k.a damping) of the spring changes the friction in the physics simulation. Lower values allow the spring to move freely and oscillate up and down, while higher values restrict movement.","title":"Damping Ratio"},{"location":"tutorials/animation/springs/#zero-damping","text":"Zero damping means no friction is applied, so the spring will oscillate forever without losing energy. This is generally not useful.","title":"Zero damping"},{"location":"tutorials/animation/springs/#underdamping","text":"A damping between 0 and 1 means some friction is applied. The spring will still oscillate, but it will lose energy and eventually settle at the goal.","title":"Underdamping"},{"location":"tutorials/animation/springs/#critical-damping","text":"A damping of exactly 1 means just enough friction is applied to stop the spring from oscillating. It reaches its goal as quickly as possible without going past. This is also commonly known as critical damping.","title":"Critical damping"},{"location":"tutorials/animation/springs/#overdamping","text":"A damping above 1 applies excessive friction to the spring. The spring behaves like it's moving through honey, glue or some other viscous fluid. Overdamping reduces the effect of velocity changes, and makes movement more rigid.","title":"Overdamping"},{"location":"tutorials/animation/springs/#speed","text":"The speed of the spring scales how much time it takes for the spring to move. Doubling the speed makes it move twice as fast; halving the speed makes it move twice as slow.","title":"Speed"},{"location":"tutorials/animation/springs/#interruption","text":"Springs do not share the same interruption problems as tweens. When the goal changes, springs are guaranteed to preserve both position and velocity, reducing jank: This also means springs are suitable for following rapidly changing values:","title":"Interruption"},{"location":"tutorials/animation/tweens/","text":"Tweens follow the value of other state objects using a pre-made animation curve. This can be used for basic, predictable animations. Usage \u00b6 To create a new tween object, call scope:Tween() and pass it a state object to move towards: local goal = scope : Value ( 0 ) local animated = scope : Tween ( goal ) The tween will smoothly follow the 'goal' state object over time. As with other state objects, you can peek() at its value at any time: print ( peek ( animated )) --> 0.26425... To configure how the tween moves, you can provide a TweenInfo to change the shape of the animation curve. It's optional, and it can be a state object if desired: local goal = scope : Value ( 0 ) local style = TweenInfo . new ( 0.5 , Enum . EasingStyle . Quad ) local animated = scope : Tween ( goal , style ) You can use many different kinds of values with tweens, not just numbers. Vectors, CFrames, Color3s, UDim2s and other number-based types are supported; each number inside the type is animated individually. local goalPosition = scope : Value ( UDim2 . new ( 0.5 , 0 , 0 , 0 )) local animated = scope : Tween ( goalPosition , TweenInfo . new ( 0.5 , Enum . EasingStyle . Quad )) Time \u00b6 The first parameter of TweenInfo is time. This specifies how long it should take for the value to animate to the goal, in seconds. Easing Style \u00b6 The second parameter of TweenInfo is easing style. By setting this to various Enum.EasingStyle values, you can select different pre-made animation curves. Easing Direction \u00b6 The third parameter of TweenInfo is easing direction. This can be set to one of three values to control how the tween starts and stops: Enum.EasingDirection.Out makes the tween animate out smoothly. Enum.EasingDirection.In makes the tween animate in smoothly. Enum.EasingDirection.InOut makes the tween animate in and out smoothly. Repeats \u00b6 The fourth parameter of TweenInfo is repeat count. This can be used to loop the animation a number of times. Setting the repeat count to a negative number causes it to loop infinitely. This is not generally useful for transition animations. Reversing \u00b6 The fifth parameter of TweenInfo is a reversing option. When enabled, the animation will return to the starting point. This is not typically useful because the animation doesn't end at the goal value, and might not end at the start value either if the animation is interrupted. Delay \u00b6 The sixth and final parameter of TweenInfo is delay. Increasing this delay adds empty space before the beginning of the animation curve. It's important to note this is not the same as a true delay. This option does not delay the input signal - it only makes the tween animation longer. Interruption \u00b6 Because tweens are built from pre-made, fixed animation curves, you should avoid interrupting those animation curves before they're finished. Interrupting a tween halfway through leads to abrupt changes in velocity, which can cause your animation to feel janky: Tweens also can't track constantly changing targets very well. That's because the tween is always getting interrupted as it gets started, so it never has time to play out much of its animation. These issues arise because tweens don't 'remember' their previous velocity when they start animating towards a new goal. If you need velocity to be remembered, it's a much better idea to use springs, which can preserve their momentum.","title":"Tweens"},{"location":"tutorials/animation/tweens/#usage","text":"To create a new tween object, call scope:Tween() and pass it a state object to move towards: local goal = scope : Value ( 0 ) local animated = scope : Tween ( goal ) The tween will smoothly follow the 'goal' state object over time. As with other state objects, you can peek() at its value at any time: print ( peek ( animated )) --> 0.26425... To configure how the tween moves, you can provide a TweenInfo to change the shape of the animation curve. It's optional, and it can be a state object if desired: local goal = scope : Value ( 0 ) local style = TweenInfo . new ( 0.5 , Enum . EasingStyle . Quad ) local animated = scope : Tween ( goal , style ) You can use many different kinds of values with tweens, not just numbers. Vectors, CFrames, Color3s, UDim2s and other number-based types are supported; each number inside the type is animated individually. local goalPosition = scope : Value ( UDim2 . new ( 0.5 , 0 , 0 , 0 )) local animated = scope : Tween ( goalPosition , TweenInfo . new ( 0.5 , Enum . EasingStyle . Quad ))","title":"Usage"},{"location":"tutorials/animation/tweens/#time","text":"The first parameter of TweenInfo is time. This specifies how long it should take for the value to animate to the goal, in seconds.","title":"Time"},{"location":"tutorials/animation/tweens/#easing-style","text":"The second parameter of TweenInfo is easing style. By setting this to various Enum.EasingStyle values, you can select different pre-made animation curves.","title":"Easing Style"},{"location":"tutorials/animation/tweens/#easing-direction","text":"The third parameter of TweenInfo is easing direction. This can be set to one of three values to control how the tween starts and stops: Enum.EasingDirection.Out makes the tween animate out smoothly. Enum.EasingDirection.In makes the tween animate in smoothly. Enum.EasingDirection.InOut makes the tween animate in and out smoothly.","title":"Easing Direction"},{"location":"tutorials/animation/tweens/#repeats","text":"The fourth parameter of TweenInfo is repeat count. This can be used to loop the animation a number of times. Setting the repeat count to a negative number causes it to loop infinitely. This is not generally useful for transition animations.","title":"Repeats"},{"location":"tutorials/animation/tweens/#reversing","text":"The fifth parameter of TweenInfo is a reversing option. When enabled, the animation will return to the starting point. This is not typically useful because the animation doesn't end at the goal value, and might not end at the start value either if the animation is interrupted.","title":"Reversing"},{"location":"tutorials/animation/tweens/#delay","text":"The sixth and final parameter of TweenInfo is delay. Increasing this delay adds empty space before the beginning of the animation curve. It's important to note this is not the same as a true delay. This option does not delay the input signal - it only makes the tween animation longer.","title":"Delay"},{"location":"tutorials/animation/tweens/#interruption","text":"Because tweens are built from pre-made, fixed animation curves, you should avoid interrupting those animation curves before they're finished. Interrupting a tween halfway through leads to abrupt changes in velocity, which can cause your animation to feel janky: Tweens also can't track constantly changing targets very well. That's because the tween is always getting interrupted as it gets started, so it never has time to play out much of its animation. These issues arise because tweens don't 'remember' their previous velocity when they start animating towards a new goal. If you need velocity to be remembered, it's a much better idea to use springs, which can preserve their momentum.","title":"Interruption"},{"location":"tutorials/best-practices/callbacks/","text":"Normally, components are controlled by the code creating them. This is called top-down control, and is the primary flow of control in Fusion. However, sometimes components need to talk back to their controlling code, for example to report when button clicks occur. In Luau \u00b6 Callbacks are functions which you pass into other functions. They're useful because they allow the function to 'call back' into your code, so your code can do something in response: local function printMessage () print ( \"Hello, world!\" ) end -- Here, we're passing `printMessage` as a callback -- `task.delay` will call it after 5 seconds task . delay ( 5 , printMessage ) If your function accepts a callback, then you can call it like any other function. Luau will execute the function, then return to your code. In this example, the fiveTimes function calls a callback five times: Luau code Output local function fiveTimes ( callback : ( number ) -> () ) for x = 1 , 5 do callback ( x ) end end fiveTimes ( function ( num ) print ( \"The number is\" , num ) end ) The number is 1 The number is 2 The number is 3 The number is 4 The number is 5 In Fusion \u00b6 Components can use callbacks the same way. Consider this button component; when the button is clicked, the button needs to run some external code: local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > props : { Position : UsedAs < UDim2 > ? , Size : UsedAs < UDim2 > ? , Text : UsedAs < string > ? } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), Position = props . Position , Size = props . Size , Text = props . Text , TextColor3 = Color3 . new ( 1 , 1 , 1 ), [ OnEvent \"Activated\" ] = -- ??? } end It can ask the controlling code to provide an OnClick callback in props . local button = scope : Button { Text = \"Hello, world!\" , OnClick = function () print ( \"The button was clicked\" ) end } Assuming that callback is passed in, the callback can be passed directly into [OnEvent] , because [OnEvent] accepts functions. It can even be optional - Luau won't add the key to the table if the value is nil . local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Position : UsedAs < UDim2 > ? , Size : UsedAs < UDim2 > ? , Text : UsedAs < string > ? , OnClick : (() -> ()) ? } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), Position = props . Position , Size = props . Size , Text = props . Text , TextColor3 = Color3 . new ( 1 , 1 , 1 ), [ OnEvent \"Activated\" ] = props . OnClick } end Alternatively, we can call props.OnClick manually, which is useful if you want to do your own processing first: local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Position : UsedAs < UDim2 > ? , Size : UsedAs < UDim2 > ? , Text : UsedAs < string > ? , Disabled : UsedAs < boolean > ? , OnClick : (() -> ()) ? } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), Position = props . Position , Size = props . Size , Text = props . Text , TextColor3 = Color3 . new ( 1 , 1 , 1 ), [ OnEvent \"Activated\" ] = function () if props . OnClick ~= nil and not peek ( props . Disabled ) then props . OnClick () end end } end This is the primary way components talk to their controlling code in Fusion.","title":"Callbacks"},{"location":"tutorials/best-practices/callbacks/#in-luau","text":"Callbacks are functions which you pass into other functions. They're useful because they allow the function to 'call back' into your code, so your code can do something in response: local function printMessage () print ( \"Hello, world!\" ) end -- Here, we're passing `printMessage` as a callback -- `task.delay` will call it after 5 seconds task . delay ( 5 , printMessage ) If your function accepts a callback, then you can call it like any other function. Luau will execute the function, then return to your code. In this example, the fiveTimes function calls a callback five times: Luau code Output local function fiveTimes ( callback : ( number ) -> () ) for x = 1 , 5 do callback ( x ) end end fiveTimes ( function ( num ) print ( \"The number is\" , num ) end ) The number is 1 The number is 2 The number is 3 The number is 4 The number is 5","title":"In Luau"},{"location":"tutorials/best-practices/callbacks/#in-fusion","text":"Components can use callbacks the same way. Consider this button component; when the button is clicked, the button needs to run some external code: local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > props : { Position : UsedAs < UDim2 > ? , Size : UsedAs < UDim2 > ? , Text : UsedAs < string > ? } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), Position = props . Position , Size = props . Size , Text = props . Text , TextColor3 = Color3 . new ( 1 , 1 , 1 ), [ OnEvent \"Activated\" ] = -- ??? } end It can ask the controlling code to provide an OnClick callback in props . local button = scope : Button { Text = \"Hello, world!\" , OnClick = function () print ( \"The button was clicked\" ) end } Assuming that callback is passed in, the callback can be passed directly into [OnEvent] , because [OnEvent] accepts functions. It can even be optional - Luau won't add the key to the table if the value is nil . local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Position : UsedAs < UDim2 > ? , Size : UsedAs < UDim2 > ? , Text : UsedAs < string > ? , OnClick : (() -> ()) ? } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), Position = props . Position , Size = props . Size , Text = props . Text , TextColor3 = Color3 . new ( 1 , 1 , 1 ), [ OnEvent \"Activated\" ] = props . OnClick } end Alternatively, we can call props.OnClick manually, which is useful if you want to do your own processing first: local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Position : UsedAs < UDim2 > ? , Size : UsedAs < UDim2 > ? , Text : UsedAs < string > ? , Disabled : UsedAs < boolean > ? , OnClick : (() -> ()) ? } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), Position = props . Position , Size = props . Size , Text = props . Text , TextColor3 = Color3 . new ( 1 , 1 , 1 ), [ OnEvent \"Activated\" ] = function () if props . OnClick ~= nil and not peek ( props . Disabled ) then props . OnClick () end end } end This is the primary way components talk to their controlling code in Fusion.","title":"In Fusion"},{"location":"tutorials/best-practices/components/","text":"You can use functions to create self-contained, reusable blocks of code. In the world of UI, you may think of them as components - though they can be used for much more than just UI. For example, consider this function, which generates a button based on some props the user passes in: type UsedAs < T > = Fusion . UsedAs < T > local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , Size : UsedAs < UDim2 > ? , LayoutOrder : UsedAs < number > ? , ButtonText : UsedAs < string > } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0 , 0.25 , 1 ), Position = props . Position , AnchorPoint = props . AnchorPoint , Size = props . Size , LayoutOrder = props . LayoutOrder , Text = props . ButtonText , TextSize = 28 , TextColor3 = Color3 . new ( 1 , 1 , 1 ), [ Children ] = UICorner { CornerRadius = UDim2 . new ( 0 , 8 ) } } end You can call this function later to generate as many buttons as you need. local helloBtn = Button ( scope , { ButtonText = \"Hello\" , Size = UDim2 . fromOffset ( 200 , 50 ) }) helloBtn . Parent = Players . LocalPlayer . PlayerGui . ScreenGui Since the scope is the first parameter, it can even be used with scoped() syntax. local scope = scoped ( Fusion , { Button = Button }) local helloBtn = scope : Button { ButtonText = \"Hello\" , Size = UDim2 . fromOffset ( 200 , 50 ) } helloBtn . Parent = Players . LocalPlayer . PlayerGui . ScreenGui This is the primary way of writing components in Fusion. You create functions that accept scope and props , then return some content from them. Properties \u00b6 If you don't say what props should contain, it might be hard to figure out how to use it. You can specify your list of properties by adding a type to props , which gives you useful autocomplete and type checking. local function Cake ( -- ... some stuff here ... props : { Size : Vector3 , Colour : Color3 , IsTasty : boolean } ) -- ... some other stuff here ... end Note that the above code only accepts constant values, not state objects. If you want to accept either a constant or a state object, you can use the UsedAs type. type UsedAs < T > = Fusion . UsedAs < T > local function Cake ( -- ... some stuff here ... props : { Size : UsedAs < Vector3 > , Colour : UsedAs < Color3 > , IsTasty : UsedAs < boolean > } ) -- ... some other stuff here ... end This is usually what you want, because it means the user can easily switch a property to dynamically change over time, while still writing properties normally when they don't change over time. You can mostly treat UsedAs properties like they're state objects, because functions like peek() and use() automatically choose the right behaviour for you. You can use the rest of Luau's type checking features to do more complex things, like making certain properties optional, or restricting that values are valid for a given property. Go wild! Be mindful of the angle brackets Remember that, when working with UsedAs , you should be mindful of whether you're putting things inside the angled brackets, or outside of them. Putting some things inside of the angle brackets can change their meaning, compared to putting them outside of the angle brackets. Consider these two type definitions carefully: -- A Vector3, or a state object storing Vector3, or nil. UsedAs < Vector3 > ? -- A Vector3?, or a state object storing Vector3? UsedAs < Vector3 ? > The first type is best for optional properties , where you provide a default value if it isn't specified by the user. If the user does specify it, they're forced to always give a valid value for it. The second type is best if the property understands nil as a valid value. This means the user can set it to nil at any time. Scopes \u00b6 In addition to props , you should also ask for a scope . The scope parameter should come first, so that your users can use scoped() syntax to create it. -- barebones syntax local thing = Component ( scope , { -- ... some properties here ... }) -- scoped() syntax local thing = scope : Component { -- ... some properties here ... } It's a good idea to provide a type for scope . This lets you specify what methods you need the scope to have. scope : Fusion . Scope < YourMethodsHere > If you don't know what methods to ask for, consider these two strategies. If you only use common methods (like Fusion's constructors) then it's a safe assumption that the user will also have those methods. You can ask for a scope with those methods pre-defined. local function Component ( scope : Fusion . Scope < typeof ( Fusion ) > , props : {} ) return scope : New \"Thing\" { -- ... rest of code here ... } end If you need more specific or niche things that the user likely won't have (for example, components you use internally), then you should not ask for those. Instead, create a new inner scope with the methods you need. local function Component ( outerScope : Fusion . Scope < {} > , props : {} ) local scope = scoped ( Fusion , { SpecialThing1 = require ( script . SpecialThing1 ), SpecialThing2 = require ( script . SpecialThing2 ), }) table.insert ( outerScope , scope ) return scope : SpecialThing1 { -- ... rest of code here ... } end If you're not sure which strategy to pick, the second is always a safe fallback, because it assumes less about your users and helps hide implementation details. Modules \u00b6 It's common to save different components inside of different files. There's a number of advantages to this: it's easier to find the source code for a specific component it keep each file shorter and simpler it makes sure components are properly independent, and can't interfere it encourages reusing components everywhere, not just in one file Here's an example of how you could split up some components into modules: Main file PopUp Message Button 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) local scoped , doCleanup = Fusion . scoped , Fusion . doCleanup local scope = scoped ( Fusion , { PopUp = require ( script . Parent . PopUp ) }) local ui = scope : New \"ScreenGui\" { -- ...some properties... [ Children ] = scope : PopUp { Message = \"Hello, world!\" , DismissText = \"Close\" } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) type UsedAs < T > = Fusion . UsedAs < T > local function PopUp ( outerScope : Fusion . Scope < {} > , props : { Message : UsedAs < string > , DismissText : UsedAs < string > } ) local scope = scoped ( Fusion , { Message = require ( script . Parent . Message ), Button = require ( script . Parent . Button ) }) table.insert ( outerScope , scope ) return scope : New \"Frame\" { -- ...some properties... [ Children ] = { scope : Message { Scope = scope , Text = props . Message } scope : Button { Scope = scope , Text = props . DismissText } } } end return PopUp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) type UsedAs < T > = Fusion . UsedAs < T > local function Message ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Text : UsedAs < string > } ) return scope : New \"TextLabel\" { AutomaticSize = \"XY\" , BackgroundTransparency = 1 , -- ...some properties... Text = props . Text } end return Message 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) type UsedAs < T > = Fusion . UsedAs < T > local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Text : UsedAs < string > } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), AutoButtonColor = true , -- ...some properties... Text = props . Text } end return Button","title":"Components"},{"location":"tutorials/best-practices/components/#properties","text":"If you don't say what props should contain, it might be hard to figure out how to use it. You can specify your list of properties by adding a type to props , which gives you useful autocomplete and type checking. local function Cake ( -- ... some stuff here ... props : { Size : Vector3 , Colour : Color3 , IsTasty : boolean } ) -- ... some other stuff here ... end Note that the above code only accepts constant values, not state objects. If you want to accept either a constant or a state object, you can use the UsedAs type. type UsedAs < T > = Fusion . UsedAs < T > local function Cake ( -- ... some stuff here ... props : { Size : UsedAs < Vector3 > , Colour : UsedAs < Color3 > , IsTasty : UsedAs < boolean > } ) -- ... some other stuff here ... end This is usually what you want, because it means the user can easily switch a property to dynamically change over time, while still writing properties normally when they don't change over time. You can mostly treat UsedAs properties like they're state objects, because functions like peek() and use() automatically choose the right behaviour for you. You can use the rest of Luau's type checking features to do more complex things, like making certain properties optional, or restricting that values are valid for a given property. Go wild! Be mindful of the angle brackets Remember that, when working with UsedAs , you should be mindful of whether you're putting things inside the angled brackets, or outside of them. Putting some things inside of the angle brackets can change their meaning, compared to putting them outside of the angle brackets. Consider these two type definitions carefully: -- A Vector3, or a state object storing Vector3, or nil. UsedAs < Vector3 > ? -- A Vector3?, or a state object storing Vector3? UsedAs < Vector3 ? > The first type is best for optional properties , where you provide a default value if it isn't specified by the user. If the user does specify it, they're forced to always give a valid value for it. The second type is best if the property understands nil as a valid value. This means the user can set it to nil at any time.","title":"Properties"},{"location":"tutorials/best-practices/components/#scopes","text":"In addition to props , you should also ask for a scope . The scope parameter should come first, so that your users can use scoped() syntax to create it. -- barebones syntax local thing = Component ( scope , { -- ... some properties here ... }) -- scoped() syntax local thing = scope : Component { -- ... some properties here ... } It's a good idea to provide a type for scope . This lets you specify what methods you need the scope to have. scope : Fusion . Scope < YourMethodsHere > If you don't know what methods to ask for, consider these two strategies. If you only use common methods (like Fusion's constructors) then it's a safe assumption that the user will also have those methods. You can ask for a scope with those methods pre-defined. local function Component ( scope : Fusion . Scope < typeof ( Fusion ) > , props : {} ) return scope : New \"Thing\" { -- ... rest of code here ... } end If you need more specific or niche things that the user likely won't have (for example, components you use internally), then you should not ask for those. Instead, create a new inner scope with the methods you need. local function Component ( outerScope : Fusion . Scope < {} > , props : {} ) local scope = scoped ( Fusion , { SpecialThing1 = require ( script . SpecialThing1 ), SpecialThing2 = require ( script . SpecialThing2 ), }) table.insert ( outerScope , scope ) return scope : SpecialThing1 { -- ... rest of code here ... } end If you're not sure which strategy to pick, the second is always a safe fallback, because it assumes less about your users and helps hide implementation details.","title":"Scopes"},{"location":"tutorials/best-practices/components/#modules","text":"It's common to save different components inside of different files. There's a number of advantages to this: it's easier to find the source code for a specific component it keep each file shorter and simpler it makes sure components are properly independent, and can't interfere it encourages reusing components everywhere, not just in one file Here's an example of how you could split up some components into modules: Main file PopUp Message Button 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) local scoped , doCleanup = Fusion . scoped , Fusion . doCleanup local scope = scoped ( Fusion , { PopUp = require ( script . Parent . PopUp ) }) local ui = scope : New \"ScreenGui\" { -- ...some properties... [ Children ] = scope : PopUp { Message = \"Hello, world!\" , DismissText = \"Close\" } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) type UsedAs < T > = Fusion . UsedAs < T > local function PopUp ( outerScope : Fusion . Scope < {} > , props : { Message : UsedAs < string > , DismissText : UsedAs < string > } ) local scope = scoped ( Fusion , { Message = require ( script . Parent . Message ), Button = require ( script . Parent . Button ) }) table.insert ( outerScope , scope ) return scope : New \"Frame\" { -- ...some properties... [ Children ] = { scope : Message { Scope = scope , Text = props . Message } scope : Button { Scope = scope , Text = props . DismissText } } } end return PopUp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) type UsedAs < T > = Fusion . UsedAs < T > local function Message ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Text : UsedAs < string > } ) return scope : New \"TextLabel\" { AutomaticSize = \"XY\" , BackgroundTransparency = 1 , -- ...some properties... Text = props . Text } end return Message 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) type UsedAs < T > = Fusion . UsedAs < T > local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Text : UsedAs < string > } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), AutoButtonColor = true , -- ...some properties... Text = props . Text } end return Button","title":"Modules"},{"location":"tutorials/best-practices/instance-handling/","text":"Components are a good fit for Roblox instances. You can assemble complex groups of instances by combining simpler, self-contained parts. To ensure maximum compatibility, there are a few best practices to consider. Returns \u00b6 Anything you return from a component should be supported by [Children] . -- returns an instance return scope : New \"Frame\" {} -- returns an array of instances return { scope : New \"Frame\" {}, scope : New \"Frame\" {}, scope : New \"Frame\" {} } -- returns a state object containing instances return scope : ForValues ({ 1 , 2 , 3 }, function ( use , scope , number ) return scope : New \"Frame\" {} end ) -- mix of arrays, instances and state objects return { scope : New \"Frame\" {}, { scope : New \"Frame\" {}, scope : ForValues ( ... ) } scope : ForValues ( ... ) } Returning multiple values is fragile Don't return multiple values directly from your function. When a function returns multiple values directly, the extra returned values can easily get lost. local function BadThing ( scope , props ) -- returns *multiple* instances (not surrounded by curly braces!) return scope : New \"Frame\" {}, scope : New \"Frame\" {}, scope : New \"Frame\" {} end local things = { -- Luau doesn't let you add multiple returns to a list like this. -- Only the first Frame will be added. scope : BadThing {}, scope : New \"TextButton\" {} } print ( things ) --> { Frame, TextButton } Instead, you should return them inside of an array. Because the array is a single return value, it won't get lost. If your returns are compatible with [Children] like above, you can insert a component anywhere you'd normally insert an instance. You can pass in one component on its own... local ui = scope : New \"ScreenGui\" { [ Children ] = scope : Button { Text = \"Hello, world!\" } } ...you can include components as part of an array.. local ui = scope : New \"ScreenGui\" { [ Children ] = { scope : New \"UIListLayout\" {}, scope : Button { Text = \"Hello, world!\" }, scope : Button { Text = \"Hello, again!\" } } } ...and you can return them from state objects, too. local stuff = { \"Hello\" , \"world\" , \"from\" , \"Fusion\" } local ui = scope : New \"ScreenGui\" { [ Children ] = { scope : New \"UIListLayout\" {}, scope : ForValues ( stuff , function ( use , scope , text ) return scope : Button { Text = text } end ) } } Containers \u00b6 Some components, for example pop-ups, might contain lots of different content: Ideally, you would be able to reuse the pop-up 'container', while placing your own content inside. The simplest way to do this is to pass instances through to [Children] . For example, if you accept a table of props , you can add a [Children] key: local function PopUp ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { [ typeof ( Children )]: Fusion . Children } ) return scope : New \"Frame\" { [ Children ] = props [ Children ] } end Accepting multiple instances If you have multiple 'slots' where you want to pass through instances, you can make other properties and give them the Fusion.Children type. Later on, when a pop-up is created, instances can now be parented into that pop-up: scope : PopUp { [ Children ] = { scope : Label { Text = \"New item collected\" }, scope : ItemPreview { Item = Items . BRICK }, scope : Button { Text = \"Add to inventory\" } } } If you need to add other instances, you can still use arrays and state objects as normal. You can include instances you're given, in exactly the same way you would include any other instances. scope : New \"Frame\" { -- ... some other properties ... [ Children ] = { -- the component provides some children here scope : New \"UICorner\" { CornerRadius = UDim . new ( 0 , 8 ) }, -- include children from outside the component here props [ Children ] } }","title":"Instance Handling"},{"location":"tutorials/best-practices/instance-handling/#returns","text":"Anything you return from a component should be supported by [Children] . -- returns an instance return scope : New \"Frame\" {} -- returns an array of instances return { scope : New \"Frame\" {}, scope : New \"Frame\" {}, scope : New \"Frame\" {} } -- returns a state object containing instances return scope : ForValues ({ 1 , 2 , 3 }, function ( use , scope , number ) return scope : New \"Frame\" {} end ) -- mix of arrays, instances and state objects return { scope : New \"Frame\" {}, { scope : New \"Frame\" {}, scope : ForValues ( ... ) } scope : ForValues ( ... ) } Returning multiple values is fragile Don't return multiple values directly from your function. When a function returns multiple values directly, the extra returned values can easily get lost. local function BadThing ( scope , props ) -- returns *multiple* instances (not surrounded by curly braces!) return scope : New \"Frame\" {}, scope : New \"Frame\" {}, scope : New \"Frame\" {} end local things = { -- Luau doesn't let you add multiple returns to a list like this. -- Only the first Frame will be added. scope : BadThing {}, scope : New \"TextButton\" {} } print ( things ) --> { Frame, TextButton } Instead, you should return them inside of an array. Because the array is a single return value, it won't get lost. If your returns are compatible with [Children] like above, you can insert a component anywhere you'd normally insert an instance. You can pass in one component on its own... local ui = scope : New \"ScreenGui\" { [ Children ] = scope : Button { Text = \"Hello, world!\" } } ...you can include components as part of an array.. local ui = scope : New \"ScreenGui\" { [ Children ] = { scope : New \"UIListLayout\" {}, scope : Button { Text = \"Hello, world!\" }, scope : Button { Text = \"Hello, again!\" } } } ...and you can return them from state objects, too. local stuff = { \"Hello\" , \"world\" , \"from\" , \"Fusion\" } local ui = scope : New \"ScreenGui\" { [ Children ] = { scope : New \"UIListLayout\" {}, scope : ForValues ( stuff , function ( use , scope , text ) return scope : Button { Text = text } end ) } }","title":"Returns"},{"location":"tutorials/best-practices/instance-handling/#containers","text":"Some components, for example pop-ups, might contain lots of different content: Ideally, you would be able to reuse the pop-up 'container', while placing your own content inside. The simplest way to do this is to pass instances through to [Children] . For example, if you accept a table of props , you can add a [Children] key: local function PopUp ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { [ typeof ( Children )]: Fusion . Children } ) return scope : New \"Frame\" { [ Children ] = props [ Children ] } end Accepting multiple instances If you have multiple 'slots' where you want to pass through instances, you can make other properties and give them the Fusion.Children type. Later on, when a pop-up is created, instances can now be parented into that pop-up: scope : PopUp { [ Children ] = { scope : Label { Text = \"New item collected\" }, scope : ItemPreview { Item = Items . BRICK }, scope : Button { Text = \"Add to inventory\" } } } If you need to add other instances, you can still use arrays and state objects as normal. You can include instances you're given, in exactly the same way you would include any other instances. scope : New \"Frame\" { -- ... some other properties ... [ Children ] = { -- the component provides some children here scope : New \"UICorner\" { CornerRadius = UDim . new ( 0 , 8 ) }, -- include children from outside the component here props [ Children ] } }","title":"Containers"},{"location":"tutorials/best-practices/optimisation/","text":"Fusion tries to handle your code in the smartest way it can. To help achieve the best performance, you can give Fusion more information about what you're trying to do, or avoid a few problematic scenarios that slow Fusion down. Update Skipping \u00b6 Fusion tries to skip updates when they result in 'meaningless changes'. TL;DR When your computations return values that aren't meaningfully different, Fusion doesn't bother to perform further updates. However, Fusion can't automatically do this for tables. So, you should freeze every table you create, unless you need to change what's inside the table later (for example, if it's a list that changes over time). This allows Fusion to apply more aggressive optimisations for free. Example \u00b6 Imagine you have a number, and you're using a computed to calculate whether it's even or odd. An observer is used to see how often this results in other code being run. Luau code Output local number = scope : Value ( 1 ) local isEven = scope : Computed ( function ( use ) return use ( number ) % 2 == 0 end ) scope : Observer ( isEven ): onChange ( function () print ( \"-> isEven has changed to \" .. peek ( isEven )) end ) print ( \"Number becomes 2\" ) number : set ( 2 ) print ( \"Number becomes 3\" ) number : set ( 3 ) print ( \"Number becomes 13\" ) number : set ( 13 ) print ( \"Number becomes 14\" ) number : set ( 14 ) print ( \"Number becomes 24\" ) number : set ( 24 ) Number becomes 2 -> isEven has changed to true Number becomes 3 -> isEven has changed to false Number becomes 13 Number becomes 14 -> isEven has changed to true Number becomes 24 Notice that the observer only runs when isEven returns a meaningfully different value: When the number changed from 2 to 3, isEven returned false . This is meaningfully different from the previous value of isEven , which was true . As a result, the observer is run and the printed message is seen. When the number changed from 3 to 13, isEven returned false . This is not meaningfully different from the previous value of isEven , which was false . As a result, the observer does not run, and no printed message is seen. Similarity \u00b6 When trying to determine if a change is 'meaningless', Fusion compares the old and new values, using what's called the similarity test . The similarity test is a fast, approximate test that Fusion uses to guess which updates it can safely discard. If two values pass the similarity test, then you should be able to use them interchangeably without affecting most Luau code. In Fusion's case, if the values before and after a change are similar, then Fusion won't continue updating other code beyond that change, because those updates aren't likely to have an effect on the outcome of computations. Here's what the similarity test looks for: Different types: Two values of different types are never similar to each other. Tables: Frozen tables are similar to other values when they're == to each other. Tables with a metatable are similar to other values when when they're == to each other. Other kinds of table are never similar to anything. Userdatas: Userdatas are similar to other values when they're == to each other. NaN: If each value does not == itself, then the two values are similar to each other. This doesn't apply to tables or userdatas. Any other values: Two values are similar to each other when they're == to each other. Roblox data types Roblox data types are not considered to be userdatas. Instead, the similarity test follows typeof() rules when determining type. Optimising For Similarity \u00b6 With this knowledge about the similarity test, you can experiment with how Fusion optimises different changes, and what breaks that optimisation. Tables \u00b6 Imagine you're setting a value object to a table of theme colours. You attach an observer object to see when Fusion thinks the theme meaningfully changed. Luau code Output local LIGHT_THEME = { name = \"light\" , -- imagine theme colours in here } local DARK_THEME = { name = \"dark\" , -- imagine theme colours in here } local currentTheme = scope : Value ( LIGHT_THEME ) scope : Observer ( currentTheme ): onChange ( function () print ( \"-> currentTheme changed to \" .. peek ( currentTheme ). name ) end ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) Set to DARK_THEME -> currentTheme changed to dark Set to DARK_THEME -> currentTheme changed to dark Set to LIGHT_THEME -> currentTheme changed to light Set to LIGHT_THEME -> currentTheme changed to light Because the LIGHT_THEME and DARK_THEME tables aren't frozen, and they don't have any metatables, Fusion will never skip over updates that change to or from those values. Why won't Fusion skip those updates? In Fusion, it's common to update arrays without creating a new array. This is known as mutating the array. local drinks = scope : Value ({ \"beer\" , \"pepsi\" }) do -- add tea local array = peek ( drinks ) table.insert ( array , \"tea\" ) -- mutation occurs here drinks : set ( array ) -- still the same array, so it's == end If Fusion skipped updates when the old and new values were == , then these mutating changes wouldn't cause an update. For that reason, Fusion doesn't skip updates for tables unless you do one of two things: You disable the ability to mutate the table (via table.freeze ). You indicate to Fusion that this isn't plain data by adding a metatable. Metatables are almost always used for OOP, where == is a sensible way of determining if two objects are similar. You can also use metatables to define how equality should work, which Fusion will respect. According to the similarity test (and the question section above), one way to skip these updates is by freezing the original tables. Luau code Output local LIGHT_THEME = table . freeze { name = \"light\" , -- imagine theme colours in here } local DARK_THEME = table . freeze { name = \"dark\" , -- imagine theme colours in here } local currentTheme = scope : Value ( LIGHT_THEME ) scope : Observer ( currentTheme ): onChange ( function () print ( \"-> currentTheme changed to \" .. peek ( currentTheme ). name ) end ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) Set to DARK_THEME -> currentTheme changed to dark Set to DARK_THEME Set to LIGHT_THEME -> currentTheme changed to light Set to LIGHT_THEME Now, Fusion is confident enough to skip over the updates. In general, you should freeze all of your tables when working with Fusion, unless you have a reason for modifying them later on. There's almost zero cost to freezing a table, making this modification essentially free. Plus, this lets Fusion optimise your updates more aggressively, which means you spend less time running computations on average.","title":"Optimisation"},{"location":"tutorials/best-practices/optimisation/#update-skipping","text":"Fusion tries to skip updates when they result in 'meaningless changes'. TL;DR When your computations return values that aren't meaningfully different, Fusion doesn't bother to perform further updates. However, Fusion can't automatically do this for tables. So, you should freeze every table you create, unless you need to change what's inside the table later (for example, if it's a list that changes over time). This allows Fusion to apply more aggressive optimisations for free.","title":"Update Skipping"},{"location":"tutorials/best-practices/optimisation/#example","text":"Imagine you have a number, and you're using a computed to calculate whether it's even or odd. An observer is used to see how often this results in other code being run. Luau code Output local number = scope : Value ( 1 ) local isEven = scope : Computed ( function ( use ) return use ( number ) % 2 == 0 end ) scope : Observer ( isEven ): onChange ( function () print ( \"-> isEven has changed to \" .. peek ( isEven )) end ) print ( \"Number becomes 2\" ) number : set ( 2 ) print ( \"Number becomes 3\" ) number : set ( 3 ) print ( \"Number becomes 13\" ) number : set ( 13 ) print ( \"Number becomes 14\" ) number : set ( 14 ) print ( \"Number becomes 24\" ) number : set ( 24 ) Number becomes 2 -> isEven has changed to true Number becomes 3 -> isEven has changed to false Number becomes 13 Number becomes 14 -> isEven has changed to true Number becomes 24 Notice that the observer only runs when isEven returns a meaningfully different value: When the number changed from 2 to 3, isEven returned false . This is meaningfully different from the previous value of isEven , which was true . As a result, the observer is run and the printed message is seen. When the number changed from 3 to 13, isEven returned false . This is not meaningfully different from the previous value of isEven , which was false . As a result, the observer does not run, and no printed message is seen.","title":"Example"},{"location":"tutorials/best-practices/optimisation/#similarity","text":"When trying to determine if a change is 'meaningless', Fusion compares the old and new values, using what's called the similarity test . The similarity test is a fast, approximate test that Fusion uses to guess which updates it can safely discard. If two values pass the similarity test, then you should be able to use them interchangeably without affecting most Luau code. In Fusion's case, if the values before and after a change are similar, then Fusion won't continue updating other code beyond that change, because those updates aren't likely to have an effect on the outcome of computations. Here's what the similarity test looks for: Different types: Two values of different types are never similar to each other. Tables: Frozen tables are similar to other values when they're == to each other. Tables with a metatable are similar to other values when when they're == to each other. Other kinds of table are never similar to anything. Userdatas: Userdatas are similar to other values when they're == to each other. NaN: If each value does not == itself, then the two values are similar to each other. This doesn't apply to tables or userdatas. Any other values: Two values are similar to each other when they're == to each other. Roblox data types Roblox data types are not considered to be userdatas. Instead, the similarity test follows typeof() rules when determining type.","title":"Similarity"},{"location":"tutorials/best-practices/optimisation/#optimising-for-similarity","text":"With this knowledge about the similarity test, you can experiment with how Fusion optimises different changes, and what breaks that optimisation.","title":"Optimising For Similarity"},{"location":"tutorials/best-practices/optimisation/#tables","text":"Imagine you're setting a value object to a table of theme colours. You attach an observer object to see when Fusion thinks the theme meaningfully changed. Luau code Output local LIGHT_THEME = { name = \"light\" , -- imagine theme colours in here } local DARK_THEME = { name = \"dark\" , -- imagine theme colours in here } local currentTheme = scope : Value ( LIGHT_THEME ) scope : Observer ( currentTheme ): onChange ( function () print ( \"-> currentTheme changed to \" .. peek ( currentTheme ). name ) end ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) Set to DARK_THEME -> currentTheme changed to dark Set to DARK_THEME -> currentTheme changed to dark Set to LIGHT_THEME -> currentTheme changed to light Set to LIGHT_THEME -> currentTheme changed to light Because the LIGHT_THEME and DARK_THEME tables aren't frozen, and they don't have any metatables, Fusion will never skip over updates that change to or from those values. Why won't Fusion skip those updates? In Fusion, it's common to update arrays without creating a new array. This is known as mutating the array. local drinks = scope : Value ({ \"beer\" , \"pepsi\" }) do -- add tea local array = peek ( drinks ) table.insert ( array , \"tea\" ) -- mutation occurs here drinks : set ( array ) -- still the same array, so it's == end If Fusion skipped updates when the old and new values were == , then these mutating changes wouldn't cause an update. For that reason, Fusion doesn't skip updates for tables unless you do one of two things: You disable the ability to mutate the table (via table.freeze ). You indicate to Fusion that this isn't plain data by adding a metatable. Metatables are almost always used for OOP, where == is a sensible way of determining if two objects are similar. You can also use metatables to define how equality should work, which Fusion will respect. According to the similarity test (and the question section above), one way to skip these updates is by freezing the original tables. Luau code Output local LIGHT_THEME = table . freeze { name = \"light\" , -- imagine theme colours in here } local DARK_THEME = table . freeze { name = \"dark\" , -- imagine theme colours in here } local currentTheme = scope : Value ( LIGHT_THEME ) scope : Observer ( currentTheme ): onChange ( function () print ( \"-> currentTheme changed to \" .. peek ( currentTheme ). name ) end ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) Set to DARK_THEME -> currentTheme changed to dark Set to DARK_THEME Set to LIGHT_THEME -> currentTheme changed to light Set to LIGHT_THEME Now, Fusion is confident enough to skip over the updates. In general, you should freeze all of your tables when working with Fusion, unless you have a reason for modifying them later on. There's almost zero cost to freezing a table, making this modification essentially free. Plus, this lets Fusion optimise your updates more aggressively, which means you spend less time running computations on average.","title":"Tables"},{"location":"tutorials/best-practices/sharing-values/","text":"Sometimes values are used in far-away parts of the codebase. For example, many UI elements might share theme colours for light and dark theme. Globals \u00b6 Typically, values are shared by placing them in modules. These modules can be required from anywhere in the codebase, and their values can be imported into any code. Values shared in this way are known as globals . Theme.luau Somewhere else 1 2 3 4 5 6 7 8 9 local Theme = {} Theme . colours = { background = Color3 . fromHex ( \"FFFFFF\" ), text = Color3 . fromHex ( \"222222\" ), -- etc. } return Theme 1 2 3 4 local Theme = require ( \"path/to/Theme.luau\" ) local textColour = Theme . colours . text print ( textColour ) --> 34, 34, 34 In particular, you can share state objects this way, and every part of the codebase will be able to see and interact with those state objects. Theme.luau Somewhere else 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 local Fusion = require ( \"path/to/Fusion.luau\" ) local Theme = {} Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, -- etc. } function Theme . init ( scope : Fusion . Scope < typeof ( Fusion ) > ) Theme . currentTheme = scope : Value ( \"light\" ) end return Theme 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 local Fusion = require ( \"path/to/Fusion.luau\" ) local scoped , peek = Fusion . scoped , Fusion . peek local Theme = require ( \"path/to/Theme.luau\" ) local function printTheme () local theme = Theme . currentTheme print ( peek ( theme ), if typeof ( theme ) == \"string\" then \"constant\" else \"state object\" ) end local scope = scoped ( Fusion ) Theme . init ( scope ) printTheme () --> light state object Theme . currentTheme : set ( \"dark\" ) printTheme () --> dark state object Globals are very straightforward to implement and can be useful, but they can quickly cause problems if not used carefully. Hidden dependencies \u00b6 When you use a global inside a block of reusable code such as a component, you are making your code dependent on another code file without declaring it to the outside world. To some extent, this is entirely why using globals is desirable. While it's more 'correct' to accept the Theme via the parameters of your function, it often means the Theme has to be passed down through multiple layers of functions. This is known as prop drilling and is widely considered bad practice, because it clutters up unrelated code with parameters that are only passed through functions. To avoid prop drilling, globals are often used, which 'hides' the dependency on that external code file. You no longer have to pass it down through parameters. However, relying too heavily on these hidden dependencies can cause your code to behave in surprising, unintuitive ways, or it can obscure what functionality is available to developers using your code. Hard-to-locate writes \u00b6 If you write into globals from deep inside your code base, it becomes very hard to figure out where the global is being changed from, which significantly hurts debugging. Generally, it's best to treat globals as read-only . If you're writing to a global, it should be coming from a single well-signposted, easy-to-find place. You should also keep the principles of top-down control in mind; think of globals as 'flowing down' from the root of the program. Globals are best managed from high up in the program, because they have widespread effects, so consider using callbacks to pass control up the chain, rather than managing globals directly from every part of the code base. Memory management \u00b6 In addition, globals can complicate memory management. Because every part of your code base can access them, you can't destroy globals until the very end of your program. In the above example, this is solved with an init() method which passes the main scope to Theme . Because init() is called before anything else that uses Theme , the objects that Theme creates will be added to the scope first. When the main scope is cleaned up, doCleanup() will destroy things in reverse order. This means the Theme objects will be cleaned up last, after everything else in the program has been cleaned up. This only works if you know that the main script is the only entry point in your program. If you have two scripts running concurrently which try to init() the Theme module, they will overwrite each other. Non-replaceable for testing \u00b6 When your code uses a global, you're hard-coding a connection between your code and that specific global. This is problematic for testing; unless you're using an advanced testing framework with code injection, it's pretty much impossible to separate your code from that global code, which makes it impossible to replace global values for testing purposes. For example, if you wanted to write automated tests that verify light theme and dark theme are correctly applied throughout your UI, you can't replace any values stored in Theme . You might be able to write to the Theme by going through the normal process, but this fundamentally limits how you can test. For example, you couldn't run a test for light theme and dark theme at the same time. Contextuals \u00b6 The main drawback of globals is that they hold one value for all code. To solve this, Fusion introduces contextual values , which can be temporarily changed for the duration of a code block. To create a contextual, call the Contextual function from Fusion. It asks for a default value. local myContextual = Contextual ( \"foo\" ) At any time, you can query its current value using the :now() method. local myContextual = Contextual ( \"foo\" ) print ( myContextual : now ()) --> foo You can override the value for a limited span of time using :is():during() . Pass the temporary value to :is() , and pass a callback to :during() . While the callback is running, the contextual will adopt the temporary value. local myContextual = Contextual ( \"foo\" ) print ( myContextual : now ()) --> foo myContextual : is ( \"bar\" ): during ( function () print ( myContextual : now ()) --> bar end ) print ( myContextual : now ()) --> foo By storing widely-used values inside contextuals, you can isolate different code paths from each other, while retaining the easy, hidden referencing that globals offer. This makes testing and memory management significantly easier, and helps you locate which code is modifying any shared values. To demonstrate, the Theme example can be rewritten to use contextuals. Theme.luau Somewhere else 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 local Fusion = require ( \"path/to/Fusion.luau\" ) local Contextual = Fusion . Contextual local Theme = {} Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, -- etc. } Theme . currentTheme = Contextual ( \"light\" ) return Theme 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 local Fusion = require ( \"path/to/Fusion.luau\" ) local scoped , peek = Fusion . scoped , Fusion . peek local Theme = require ( \"path/to/Theme.luau\" ) local function printTheme () local theme = Theme . currentTheme : now () print ( peek ( theme ), if typeof ( theme ) == \"string\" then \"constant\" else \"state object\" ) end printTheme () --> light constant local scope = scoped ( Fusion ) local override = scope : Value ( \"light\" ) Theme . currentTheme : is ( override ): during ( function () printTheme () --> light state object override : set ( \"dark\" ) printTheme () --> dark state object end ) printTheme () --> light constant In this rewritten example, Theme no longer requires an init() function, because - instead of defining a state object globally - Theme only defines \"light\" as the default value. You're expected to replace the default value with a state object when you want to make the theme dynamic. This has a number of benefits: Because the override is time-limited to one span of your code, you can have multiple scripts running at the same time with completely different overrides. It also explicitly places your code in charge of memory management, because you're creating the object yourself. It's easy to locate where changes are coming from, because you can look for the nearest :is():during() call. Optionally, you could share a limited, read-only version of the value, while retaining private access to write to the value wherever you're overriding the contextual from. Testing becomes much simpler; you can override the contextual for different parts of your testing, without ever having to inject code, and without altering how you read and override the contextual in your production code. It's still possible to run into issues with contextuals, though. You're still hiding a dependency of your code, which can still lead to confusion and obscuring available features, just the same as globals. Unlike globals, contextuals are time-limited. If you connect to an event or start a delayed task, you won't be able to access the value anymore. Instead, capture the value at the start of the code block, so you can use it in delayed tasks.","title":"Sharing Values"},{"location":"tutorials/best-practices/sharing-values/#globals","text":"Typically, values are shared by placing them in modules. These modules can be required from anywhere in the codebase, and their values can be imported into any code. Values shared in this way are known as globals . Theme.luau Somewhere else 1 2 3 4 5 6 7 8 9 local Theme = {} Theme . colours = { background = Color3 . fromHex ( \"FFFFFF\" ), text = Color3 . fromHex ( \"222222\" ), -- etc. } return Theme 1 2 3 4 local Theme = require ( \"path/to/Theme.luau\" ) local textColour = Theme . colours . text print ( textColour ) --> 34, 34, 34 In particular, you can share state objects this way, and every part of the codebase will be able to see and interact with those state objects. Theme.luau Somewhere else 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 local Fusion = require ( \"path/to/Fusion.luau\" ) local Theme = {} Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, -- etc. } function Theme . init ( scope : Fusion . Scope < typeof ( Fusion ) > ) Theme . currentTheme = scope : Value ( \"light\" ) end return Theme 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 local Fusion = require ( \"path/to/Fusion.luau\" ) local scoped , peek = Fusion . scoped , Fusion . peek local Theme = require ( \"path/to/Theme.luau\" ) local function printTheme () local theme = Theme . currentTheme print ( peek ( theme ), if typeof ( theme ) == \"string\" then \"constant\" else \"state object\" ) end local scope = scoped ( Fusion ) Theme . init ( scope ) printTheme () --> light state object Theme . currentTheme : set ( \"dark\" ) printTheme () --> dark state object Globals are very straightforward to implement and can be useful, but they can quickly cause problems if not used carefully.","title":"Globals"},{"location":"tutorials/best-practices/sharing-values/#hidden-dependencies","text":"When you use a global inside a block of reusable code such as a component, you are making your code dependent on another code file without declaring it to the outside world. To some extent, this is entirely why using globals is desirable. While it's more 'correct' to accept the Theme via the parameters of your function, it often means the Theme has to be passed down through multiple layers of functions. This is known as prop drilling and is widely considered bad practice, because it clutters up unrelated code with parameters that are only passed through functions. To avoid prop drilling, globals are often used, which 'hides' the dependency on that external code file. You no longer have to pass it down through parameters. However, relying too heavily on these hidden dependencies can cause your code to behave in surprising, unintuitive ways, or it can obscure what functionality is available to developers using your code.","title":"Hidden dependencies"},{"location":"tutorials/best-practices/sharing-values/#hard-to-locate-writes","text":"If you write into globals from deep inside your code base, it becomes very hard to figure out where the global is being changed from, which significantly hurts debugging. Generally, it's best to treat globals as read-only . If you're writing to a global, it should be coming from a single well-signposted, easy-to-find place. You should also keep the principles of top-down control in mind; think of globals as 'flowing down' from the root of the program. Globals are best managed from high up in the program, because they have widespread effects, so consider using callbacks to pass control up the chain, rather than managing globals directly from every part of the code base.","title":"Hard-to-locate writes"},{"location":"tutorials/best-practices/sharing-values/#memory-management","text":"In addition, globals can complicate memory management. Because every part of your code base can access them, you can't destroy globals until the very end of your program. In the above example, this is solved with an init() method which passes the main scope to Theme . Because init() is called before anything else that uses Theme , the objects that Theme creates will be added to the scope first. When the main scope is cleaned up, doCleanup() will destroy things in reverse order. This means the Theme objects will be cleaned up last, after everything else in the program has been cleaned up. This only works if you know that the main script is the only entry point in your program. If you have two scripts running concurrently which try to init() the Theme module, they will overwrite each other.","title":"Memory management"},{"location":"tutorials/best-practices/sharing-values/#non-replaceable-for-testing","text":"When your code uses a global, you're hard-coding a connection between your code and that specific global. This is problematic for testing; unless you're using an advanced testing framework with code injection, it's pretty much impossible to separate your code from that global code, which makes it impossible to replace global values for testing purposes. For example, if you wanted to write automated tests that verify light theme and dark theme are correctly applied throughout your UI, you can't replace any values stored in Theme . You might be able to write to the Theme by going through the normal process, but this fundamentally limits how you can test. For example, you couldn't run a test for light theme and dark theme at the same time.","title":"Non-replaceable for testing"},{"location":"tutorials/best-practices/sharing-values/#contextuals","text":"The main drawback of globals is that they hold one value for all code. To solve this, Fusion introduces contextual values , which can be temporarily changed for the duration of a code block. To create a contextual, call the Contextual function from Fusion. It asks for a default value. local myContextual = Contextual ( \"foo\" ) At any time, you can query its current value using the :now() method. local myContextual = Contextual ( \"foo\" ) print ( myContextual : now ()) --> foo You can override the value for a limited span of time using :is():during() . Pass the temporary value to :is() , and pass a callback to :during() . While the callback is running, the contextual will adopt the temporary value. local myContextual = Contextual ( \"foo\" ) print ( myContextual : now ()) --> foo myContextual : is ( \"bar\" ): during ( function () print ( myContextual : now ()) --> bar end ) print ( myContextual : now ()) --> foo By storing widely-used values inside contextuals, you can isolate different code paths from each other, while retaining the easy, hidden referencing that globals offer. This makes testing and memory management significantly easier, and helps you locate which code is modifying any shared values. To demonstrate, the Theme example can be rewritten to use contextuals. Theme.luau Somewhere else 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 local Fusion = require ( \"path/to/Fusion.luau\" ) local Contextual = Fusion . Contextual local Theme = {} Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, -- etc. } Theme . currentTheme = Contextual ( \"light\" ) return Theme 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 local Fusion = require ( \"path/to/Fusion.luau\" ) local scoped , peek = Fusion . scoped , Fusion . peek local Theme = require ( \"path/to/Theme.luau\" ) local function printTheme () local theme = Theme . currentTheme : now () print ( peek ( theme ), if typeof ( theme ) == \"string\" then \"constant\" else \"state object\" ) end printTheme () --> light constant local scope = scoped ( Fusion ) local override = scope : Value ( \"light\" ) Theme . currentTheme : is ( override ): during ( function () printTheme () --> light state object override : set ( \"dark\" ) printTheme () --> dark state object end ) printTheme () --> light constant In this rewritten example, Theme no longer requires an init() function, because - instead of defining a state object globally - Theme only defines \"light\" as the default value. You're expected to replace the default value with a state object when you want to make the theme dynamic. This has a number of benefits: Because the override is time-limited to one span of your code, you can have multiple scripts running at the same time with completely different overrides. It also explicitly places your code in charge of memory management, because you're creating the object yourself. It's easy to locate where changes are coming from, because you can look for the nearest :is():during() call. Optionally, you could share a limited, read-only version of the value, while retaining private access to write to the value wherever you're overriding the contextual from. Testing becomes much simpler; you can override the contextual for different parts of your testing, without ever having to inject code, and without altering how you read and override the contextual in your production code. It's still possible to run into issues with contextuals, though. You're still hiding a dependency of your code, which can still lead to confusion and obscuring available features, just the same as globals. Unlike globals, contextuals are time-limited. If you connect to an event or start a delayed task, you won't be able to access the value anymore. Instead, capture the value at the start of the code block, so you can use it in delayed tasks.","title":"Contextuals"},{"location":"tutorials/best-practices/state/","text":"Components can hold their own data privately using state objects. This can be useful, but you should be careful when adding state. Creation \u00b6 You can create state objects inside components as you would anywhere else. local HOVER_COLOUR = Color3 . new ( 0.5 , 0.75 , 1 ) local REST_COLOUR = Color3 . new ( 0.25 , 0.5 , 1 ) local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { -- ... some properties ... } ) local isHovering = scope : Value ( false ) return scope : New \"TextButton\" { BackgroundColor3 = scope : Computed ( function ( use ) return if use ( isHovering ) then HOVER_COLOUR else REST_COLOUR end ), -- ... ... some more code ... } end Because these state objects are made with the same scope as the rest of the component, they're destroyed alongside the rest of the component. Top-Down Control \u00b6 Remember that Fusion mainly works with a top-down flow of control. It's a good idea to keep that in mind when adding state to components. When you're making reusable components, it's more flexible if your component can be controlled externally. Components that control themselves entirely are hard to use and customise. Consider the example of a check box. Each check box often reflects a state object under the hood: It might seem logical to store the state object inside the check box, but this causes a few problems: because the state is hidden, it's awkward to read and write from outside often, the user already has a state object representing the same setting, so now there's two state objects where one would have sufficed local function CheckBox ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { -- ... some properties ... } ) local isChecked = scope : Value ( false ) -- problematic return scope : New \"ImageButton\" { [ OnEvent \"Activated\" ] = function () isChecked : set ( not peek ( isChecked )) end , -- ... some more code ... } end A slightly better solution is to pass the state object in. This ensures the controlling code has easy access to the state if it needs it. However, this is not a complete solution: the user is forced to store the state in a Value object, but they might be computing the value dynamically with other state objects instead the behaviour of clicking the check box is hardcoded; the user cannot intercept the click or toggle a different state local function CheckBox ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { IsChecked : Fusion . Value < boolean > -- slightly better } ) return scope : New \"ImageButton\" { [ OnEvent \"Activated\" ] = function () props . IsChecked : set ( not peek ( props . IsChecked )) end , -- ... some more code ... } end That's why the best solution is to use UsedAs to create read-only properties, and add callbacks for signalling actions and events. because UsedAs is read-only, it lets the user plug in any data source, including dynamic computations because the callback is provided by the user, the behaviour of clicking the check box is completely customisable local function CheckBox ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { IsChecked : UsedAs < boolean > , -- best OnClick : () -> () } ) return scope : New \"ImageButton\" { [ OnEvent \"Activated\" ] = function () props . OnClick () end , -- ... some more code ... } end The control is always top-down here; the check box's appearance is fully controlled by the creator. The creator of the check box decides to switch the setting when the check box is clicked. In Practice \u00b6 Setting up your components in this way makes extending their behaviour incredibly straightforward. Consider a scenario where you wish to group multiple options under a 'main' check box, so you can turn them all on/off at once. The appearance of that check box would not be controlled by a single state, but instead reflects the combination of multiple states. Because the code uses UsedAs , you can represent this with a Computed object. local playMusic = scope : Value ( true ) local playSFX = scope : Value ( false ) local playNarration = scope : Value ( true ) local checkBox = scope : CheckBox { Text = \"Play sounds\" , IsChecked = scope : Computed ( function ( use ) local anyChecked = use ( playMusic ) or use ( playSFX ) or use ( playNarration ) local allChecked = use ( playMusic ) and use ( playSFX ) and use ( playNarration ) if not anyChecked then return \"unchecked\" elseif not allChecked then return \"partially-checked\" else return \"checked\" end end ) } You can then implement the 'check all'/'uncheck all' behaviour inside OnClick : local playMusic = scope : Value ( true ) local playSFX = scope : Value ( false ) local playNarration = scope : Value ( true ) local checkBox = scope : CheckBox { -- ... same properties as before ... OnClick = function () local allChecked = peek ( playMusic ) and peek ( playSFX ) and peek ( playNarration ) playMusic : set ( not allChecked ) playSFX : set ( not allChecked ) playNarration : set ( not allChecked ) end } Because the check box was written to be flexible, it can handle complex usage easily. Best Practices \u00b6 Those examples lead us to the golden rule of reusable components: Golden Rule Reusable components should reflect program state. They should not control program state. At the bottom of the chain of control, components shouldn't be massively responsible. At these levels, reflective components are easier to work with. As you go up the chain of control, components get broader in scope and less reusable; those places are often suitable for controlling components. A well-balanced codebase places controlling components at key, strategic locations. They allow higher-up components to operate without special knowledge about what goes on below. At first, this might be difficult to do well, but with experience you'll have a better intuition for it. Remember that you can always rewrite your code if it becomes a problem!","title":"State"},{"location":"tutorials/best-practices/state/#creation","text":"You can create state objects inside components as you would anywhere else. local HOVER_COLOUR = Color3 . new ( 0.5 , 0.75 , 1 ) local REST_COLOUR = Color3 . new ( 0.25 , 0.5 , 1 ) local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { -- ... some properties ... } ) local isHovering = scope : Value ( false ) return scope : New \"TextButton\" { BackgroundColor3 = scope : Computed ( function ( use ) return if use ( isHovering ) then HOVER_COLOUR else REST_COLOUR end ), -- ... ... some more code ... } end Because these state objects are made with the same scope as the rest of the component, they're destroyed alongside the rest of the component.","title":"Creation"},{"location":"tutorials/best-practices/state/#top-down-control","text":"Remember that Fusion mainly works with a top-down flow of control. It's a good idea to keep that in mind when adding state to components. When you're making reusable components, it's more flexible if your component can be controlled externally. Components that control themselves entirely are hard to use and customise. Consider the example of a check box. Each check box often reflects a state object under the hood: It might seem logical to store the state object inside the check box, but this causes a few problems: because the state is hidden, it's awkward to read and write from outside often, the user already has a state object representing the same setting, so now there's two state objects where one would have sufficed local function CheckBox ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { -- ... some properties ... } ) local isChecked = scope : Value ( false ) -- problematic return scope : New \"ImageButton\" { [ OnEvent \"Activated\" ] = function () isChecked : set ( not peek ( isChecked )) end , -- ... some more code ... } end A slightly better solution is to pass the state object in. This ensures the controlling code has easy access to the state if it needs it. However, this is not a complete solution: the user is forced to store the state in a Value object, but they might be computing the value dynamically with other state objects instead the behaviour of clicking the check box is hardcoded; the user cannot intercept the click or toggle a different state local function CheckBox ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { IsChecked : Fusion . Value < boolean > -- slightly better } ) return scope : New \"ImageButton\" { [ OnEvent \"Activated\" ] = function () props . IsChecked : set ( not peek ( props . IsChecked )) end , -- ... some more code ... } end That's why the best solution is to use UsedAs to create read-only properties, and add callbacks for signalling actions and events. because UsedAs is read-only, it lets the user plug in any data source, including dynamic computations because the callback is provided by the user, the behaviour of clicking the check box is completely customisable local function CheckBox ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { IsChecked : UsedAs < boolean > , -- best OnClick : () -> () } ) return scope : New \"ImageButton\" { [ OnEvent \"Activated\" ] = function () props . OnClick () end , -- ... some more code ... } end The control is always top-down here; the check box's appearance is fully controlled by the creator. The creator of the check box decides to switch the setting when the check box is clicked.","title":"Top-Down Control"},{"location":"tutorials/best-practices/state/#in-practice","text":"Setting up your components in this way makes extending their behaviour incredibly straightforward. Consider a scenario where you wish to group multiple options under a 'main' check box, so you can turn them all on/off at once. The appearance of that check box would not be controlled by a single state, but instead reflects the combination of multiple states. Because the code uses UsedAs , you can represent this with a Computed object. local playMusic = scope : Value ( true ) local playSFX = scope : Value ( false ) local playNarration = scope : Value ( true ) local checkBox = scope : CheckBox { Text = \"Play sounds\" , IsChecked = scope : Computed ( function ( use ) local anyChecked = use ( playMusic ) or use ( playSFX ) or use ( playNarration ) local allChecked = use ( playMusic ) and use ( playSFX ) and use ( playNarration ) if not anyChecked then return \"unchecked\" elseif not allChecked then return \"partially-checked\" else return \"checked\" end end ) } You can then implement the 'check all'/'uncheck all' behaviour inside OnClick : local playMusic = scope : Value ( true ) local playSFX = scope : Value ( false ) local playNarration = scope : Value ( true ) local checkBox = scope : CheckBox { -- ... same properties as before ... OnClick = function () local allChecked = peek ( playMusic ) and peek ( playSFX ) and peek ( playNarration ) playMusic : set ( not allChecked ) playSFX : set ( not allChecked ) playNarration : set ( not allChecked ) end } Because the check box was written to be flexible, it can handle complex usage easily.","title":"In Practice"},{"location":"tutorials/best-practices/state/#best-practices","text":"Those examples lead us to the golden rule of reusable components: Golden Rule Reusable components should reflect program state. They should not control program state. At the bottom of the chain of control, components shouldn't be massively responsible. At these levels, reflective components are easier to work with. As you go up the chain of control, components get broader in scope and less reusable; those places are often suitable for controlling components. A well-balanced codebase places controlling components at key, strategic locations. They allow higher-up components to operate without special knowledge about what goes on below. At first, this might be difficult to do well, but with experience you'll have a better intuition for it. Remember that you can always rewrite your code if it becomes a problem!","title":"Best Practices"},{"location":"tutorials/fundamentals/computeds/","text":"Computeds are state objects that immediately process values from other state objects. You pass in a callback to define a calculation. Then, you can use peek() to read the result of the calculation at any time. local numCoins = scope : Value ( 50 ) local itemPrice = scope : Value ( 10 ) local finalCoins = scope : Computed ( function ( use , scope ) return use ( numCoins ) - use ( itemPrice ) end ) print ( peek ( finalCoins )) --> 40 numCoins : set ( 25 ) itemPrice : set ( 15 ) print ( peek ( finalCoins )) --> 10 Usage \u00b6 To create a new computed object, call scope:Computed() and give it a function that performs your calculation. It takes two parameters which will be explained later; for the first part of this tutorial, they'll be left unnamed. 6 7 8 9 local scope = scoped ( Fusion ) local hardMaths = scope : Computed ( function ( _ , _ ) return 1 + 1 end ) The value the callback returns will be stored as the computed's value. You can get the computed's current value using peek() : 6 7 8 9 10 11 local scope = scoped ( Fusion ) local hardMaths = scope : Computed ( function ( _ , _ ) return 1 + 1 end ) print ( peek ( hardMaths )) --> 2 The calculation should be immediate - that is, it should never delay. That means you should not use computed objects when you need to wait for something to occur (e.g. waiting for a server to respond to a request). Using State Objects \u00b6 The calculation is only run once by default. If you try to peek() at state objects inside the calculation, your code breaks quickly: 6 7 8 9 10 11 12 13 14 15 16 local scope = scoped ( Fusion ) local number = scope : Value ( 2 ) local double = scope : Computed ( function ( _ , _ ) return peek ( number ) * 2 end ) print ( peek ( number ), peek ( double )) --> 2 4 -- The calculation won't re-run! Oh no! number : set ( 10 ) print ( peek ( number ), peek ( double )) --> 10 4 Instead, the computed object provides a use function as the first argument. As your logic runs, you can call this function with different state objects. If any of them changes, then the computed throws everything away and recalculates. 6 7 8 9 10 11 12 13 14 15 16 17 local scope = scoped ( Fusion ) local number = scope : Value ( 2 ) local double = scope : Computed ( function ( use , _ ) use ( number ) -- the calculation will re-run when `number` changes value return peek ( number ) * 2 end ) print ( peek ( number ), peek ( double )) --> 2 4 -- Now it re-runs! number : set ( 10 ) print ( peek ( number ), peek ( double )) --> 10 20 For convenience, use() will also read the value, just like peek() , so you can easily replace peek() calls with use() calls. This keeps your logic concise, readable and easily copyable. 6 7 8 9 10 11 12 13 14 15 local scope = scoped ( Fusion ) local number = scope : Value ( 2 ) local double = scope : Computed ( function ( use , _ ) return use ( number ) * 2 end ) print ( peek ( number ), peek ( double )) --> 2 4 number : set ( 10 ) print ( peek ( number ), peek ( double )) --> 10 20 It's recommended you always give the first parameter the name use , even if it already exists. This helps prevent you from using the wrong parameter if you have multiple computed objects at the same time. scope : Computed ( function ( use , _ ) -- ... scope : Computed ( function ( use , _ ) -- ... scope : Computed ( function ( use , _ ) return use ( number ) * 2 end ) -- ... end ) -- ... end ) Help! Using the same name gives me a warning. Depending on your setup, Luau might be configured to warn when you use the same variable name multiple times. In many cases, using the same variable name can be a mistake, but in this case we actually find it useful. So, to turn off the warning, try adding --!nolint LocalShadow to the top of your file. Keep in mind that Fusion sometimes applies optimisations; recalculations might be postponed or cancelled if the value of the computed isn't being used. This is why you should not use computed objects for things like playing sound effects. You will learn more about how Fusion does this later. Inner Scopes \u00b6 Sometimes, you'll need to create things inside computed objects temporarily. In these cases, you want the temporary things to be destroyed when you're done. You might try and reuse the scope you already have, to construct objects and add cleanup tasks. Luau code Output 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 local scope = scoped ( Fusion ) local number = scope : Value ( 5 ) local double = scope : Computed ( function ( use , _ ) local current = use ( number ) print ( \"Creating\" , current ) -- suppose we want to run some cleanup code for stuff in here table.insert ( scope , function () print ( \"Destroying\" , current ) end ) return current * 2 end ) print ( \"...setting to 25...\" ) number : set ( 25 ) print ( \"...setting to 2...\" ) number : set ( 2 ) print ( \"...cleaning up...\" ) doCleanup ( scope ) Creating 5 ...setting to 25... Creating 25 ...setting to 2... Creating 2 ...cleaning up... Destroying 2 Destroying 25 Destroying 5 However, this doesn't work the way you'd want it to. All of the tasks pile up at the end of the program, instead of being thrown away with the rest of the calculation. That's why the second argument is a different scope for you to use while inside the computed object. This scope argument is automatically cleaned up for you when the computed object recalculates. Luau code Output 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 local scope = scoped ( Fusion ) local number = scope : Value ( 5 ) local double = scope : Computed ( function ( use , myBrandNewScope ) local current = use ( number ) print ( \"Creating\" , current ) table.insert ( myBrandNewScope , function () print ( \"Destroying\" , current ) end ) return current * 2 end ) print ( \"...setting to 25...\" ) number : set ( 25 ) print ( \"...setting to 2...\" ) number : set ( 2 ) print ( \"...cleaning up...\" ) doCleanup ( scope ) Creating 5 ...setting to 25... Creating 25 Destroying 5 ...setting to 2... Creating 2 Destroying 25 ...cleaning up... Destroying 2 When using this new 'inner' scope, the tasks no longer pile up at the end of the program. Instead, they're cleaned up as soon as possible, when the computed object throws away the old calculation. It can help to give this parameter the same name as the original scope. This stops you from accidentally using the original scope inside the computed, and makes your code more easily copyable and movable. local scope = scoped ( Fusion ) scope : Computed ( function ( use , scope ) -- ... scope : Computed ( function ( use , scope ) -- ... scope : Computed ( function ( use , scope ) local innerValue = scope : Value ( 5 ) end ) -- ... end ) -- ... end ) Help! Using the same name gives me a warning. Depending on your setup, Luau might be configured to warn when you use the same variable name multiple times. In many cases, using the same variable name can be a mistake, but in this case we actually find it useful. So, to turn off the warning, try adding --!nolint LocalShadow to the top of your file. Once you understand computeds, as well as the previously discussed scopes, values and observers, you're well positioned to explore the rest of Fusion.","title":"Computeds"},{"location":"tutorials/fundamentals/computeds/#usage","text":"To create a new computed object, call scope:Computed() and give it a function that performs your calculation. It takes two parameters which will be explained later; for the first part of this tutorial, they'll be left unnamed. 6 7 8 9 local scope = scoped ( Fusion ) local hardMaths = scope : Computed ( function ( _ , _ ) return 1 + 1 end ) The value the callback returns will be stored as the computed's value. You can get the computed's current value using peek() : 6 7 8 9 10 11 local scope = scoped ( Fusion ) local hardMaths = scope : Computed ( function ( _ , _ ) return 1 + 1 end ) print ( peek ( hardMaths )) --> 2 The calculation should be immediate - that is, it should never delay. That means you should not use computed objects when you need to wait for something to occur (e.g. waiting for a server to respond to a request).","title":"Usage"},{"location":"tutorials/fundamentals/computeds/#using-state-objects","text":"The calculation is only run once by default. If you try to peek() at state objects inside the calculation, your code breaks quickly: 6 7 8 9 10 11 12 13 14 15 16 local scope = scoped ( Fusion ) local number = scope : Value ( 2 ) local double = scope : Computed ( function ( _ , _ ) return peek ( number ) * 2 end ) print ( peek ( number ), peek ( double )) --> 2 4 -- The calculation won't re-run! Oh no! number : set ( 10 ) print ( peek ( number ), peek ( double )) --> 10 4 Instead, the computed object provides a use function as the first argument. As your logic runs, you can call this function with different state objects. If any of them changes, then the computed throws everything away and recalculates. 6 7 8 9 10 11 12 13 14 15 16 17 local scope = scoped ( Fusion ) local number = scope : Value ( 2 ) local double = scope : Computed ( function ( use , _ ) use ( number ) -- the calculation will re-run when `number` changes value return peek ( number ) * 2 end ) print ( peek ( number ), peek ( double )) --> 2 4 -- Now it re-runs! number : set ( 10 ) print ( peek ( number ), peek ( double )) --> 10 20 For convenience, use() will also read the value, just like peek() , so you can easily replace peek() calls with use() calls. This keeps your logic concise, readable and easily copyable. 6 7 8 9 10 11 12 13 14 15 local scope = scoped ( Fusion ) local number = scope : Value ( 2 ) local double = scope : Computed ( function ( use , _ ) return use ( number ) * 2 end ) print ( peek ( number ), peek ( double )) --> 2 4 number : set ( 10 ) print ( peek ( number ), peek ( double )) --> 10 20 It's recommended you always give the first parameter the name use , even if it already exists. This helps prevent you from using the wrong parameter if you have multiple computed objects at the same time. scope : Computed ( function ( use , _ ) -- ... scope : Computed ( function ( use , _ ) -- ... scope : Computed ( function ( use , _ ) return use ( number ) * 2 end ) -- ... end ) -- ... end ) Help! Using the same name gives me a warning. Depending on your setup, Luau might be configured to warn when you use the same variable name multiple times. In many cases, using the same variable name can be a mistake, but in this case we actually find it useful. So, to turn off the warning, try adding --!nolint LocalShadow to the top of your file. Keep in mind that Fusion sometimes applies optimisations; recalculations might be postponed or cancelled if the value of the computed isn't being used. This is why you should not use computed objects for things like playing sound effects. You will learn more about how Fusion does this later.","title":"Using State Objects"},{"location":"tutorials/fundamentals/computeds/#inner-scopes","text":"Sometimes, you'll need to create things inside computed objects temporarily. In these cases, you want the temporary things to be destroyed when you're done. You might try and reuse the scope you already have, to construct objects and add cleanup tasks. Luau code Output 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 local scope = scoped ( Fusion ) local number = scope : Value ( 5 ) local double = scope : Computed ( function ( use , _ ) local current = use ( number ) print ( \"Creating\" , current ) -- suppose we want to run some cleanup code for stuff in here table.insert ( scope , function () print ( \"Destroying\" , current ) end ) return current * 2 end ) print ( \"...setting to 25...\" ) number : set ( 25 ) print ( \"...setting to 2...\" ) number : set ( 2 ) print ( \"...cleaning up...\" ) doCleanup ( scope ) Creating 5 ...setting to 25... Creating 25 ...setting to 2... Creating 2 ...cleaning up... Destroying 2 Destroying 25 Destroying 5 However, this doesn't work the way you'd want it to. All of the tasks pile up at the end of the program, instead of being thrown away with the rest of the calculation. That's why the second argument is a different scope for you to use while inside the computed object. This scope argument is automatically cleaned up for you when the computed object recalculates. Luau code Output 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 local scope = scoped ( Fusion ) local number = scope : Value ( 5 ) local double = scope : Computed ( function ( use , myBrandNewScope ) local current = use ( number ) print ( \"Creating\" , current ) table.insert ( myBrandNewScope , function () print ( \"Destroying\" , current ) end ) return current * 2 end ) print ( \"...setting to 25...\" ) number : set ( 25 ) print ( \"...setting to 2...\" ) number : set ( 2 ) print ( \"...cleaning up...\" ) doCleanup ( scope ) Creating 5 ...setting to 25... Creating 25 Destroying 5 ...setting to 2... Creating 2 Destroying 25 ...cleaning up... Destroying 2 When using this new 'inner' scope, the tasks no longer pile up at the end of the program. Instead, they're cleaned up as soon as possible, when the computed object throws away the old calculation. It can help to give this parameter the same name as the original scope. This stops you from accidentally using the original scope inside the computed, and makes your code more easily copyable and movable. local scope = scoped ( Fusion ) scope : Computed ( function ( use , scope ) -- ... scope : Computed ( function ( use , scope ) -- ... scope : Computed ( function ( use , scope ) local innerValue = scope : Value ( 5 ) end ) -- ... end ) -- ... end ) Help! Using the same name gives me a warning. Depending on your setup, Luau might be configured to warn when you use the same variable name multiple times. In many cases, using the same variable name can be a mistake, but in this case we actually find it useful. So, to turn off the warning, try adding --!nolint LocalShadow to the top of your file. Once you understand computeds, as well as the previously discussed scopes, values and observers, you're well positioned to explore the rest of Fusion.","title":"Inner Scopes"},{"location":"tutorials/fundamentals/observers/","text":"When you're working with state objects, it can be useful to detect various changes that happen to them. Observers allow you to detect those changes. Create one with a state object to 'watch', then connect code to run using :onChange() or :onBind() . local observer = scope : Observer ( health ) local disconnect = observer : onChange ( function () print ( \"The new value is: \" , peek ( health )) end ) task . wait ( 5 ) disconnect () Usage \u00b6 To create a new observer object, call scope:Observer() and give it a state object you want to detect changes on. 6 7 8 local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) local observer = scope : Observer ( health ) The observer will watch the state object for changes until it's destroyed. You can take advantage of this by connecting your own code using the observer's different methods. The first method is :onChange() , which runs your code when the state object changes value. Luau code Output 8 9 10 11 12 13 14 15 16 local observer = scope : Observer ( health ) print ( \"...connecting...\" ) observer : onChange ( function () print ( \"Observed a change to: \" , peek ( health )) end ) print ( \"...setting health to 25...\" ) health : set ( 25 ) ...connecting... ...setting health to 25... Observed a change to: 25 By default, the :onChange() connection is disconnected when the observer object is destroyed. However, if you want to disconnect it earlier, the :onChange() method returns an optional disconnect function. Calling it will disconnect that specific :onChange() handler early. 8 9 10 11 12 13 14 local disconnect = observer : onChange ( function () print ( \"The new value is: \" , peek ( health )) end ) -- disconnect the above handler after 5 seconds task . wait ( 5 ) disconnect () The second method is :onBind() . It works identically to :onChange() , but it also runs your code right away, which can often be useful. Luau code Output 8 9 10 11 12 13 14 15 16 local observer = scope : Observer ( health ) print ( \"...connecting...\" ) observer : onBind ( function () print ( \"Observed a change to: \" , peek ( health )) end ) print ( \"...setting health to 25...\" ) health : set ( 25 ) ...connecting... Observed a change to: 5 ...setting health to 25... Observed a change to: 25 What Counts As A Change? \u00b6 If you set the health to the same value multiple times in a row, you might notice your observer only runs the first time. Luau code Output 8 9 10 11 12 13 14 15 16 17 local observer = scope : Observer ( health ) observer : onChange ( function () print ( \"Observed a change to: \" , peek ( health )) end ) print ( \"...setting health to 25 three times...\" ) health : set ( 25 ) health : set ( 25 ) health : set ( 25 ) ...setting health to 25 three times... Observed a change to: 25 This is because the health object sees that it isn't actually changing value, so it doesn't broadcast any updates. Therefore, our observer doesn't run. This leads to improved performance because your code runs less often. Fusion applies these kinds of optimisations generously throughout your program.","title":"Observers"},{"location":"tutorials/fundamentals/observers/#usage","text":"To create a new observer object, call scope:Observer() and give it a state object you want to detect changes on. 6 7 8 local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) local observer = scope : Observer ( health ) The observer will watch the state object for changes until it's destroyed. You can take advantage of this by connecting your own code using the observer's different methods. The first method is :onChange() , which runs your code when the state object changes value. Luau code Output 8 9 10 11 12 13 14 15 16 local observer = scope : Observer ( health ) print ( \"...connecting...\" ) observer : onChange ( function () print ( \"Observed a change to: \" , peek ( health )) end ) print ( \"...setting health to 25...\" ) health : set ( 25 ) ...connecting... ...setting health to 25... Observed a change to: 25 By default, the :onChange() connection is disconnected when the observer object is destroyed. However, if you want to disconnect it earlier, the :onChange() method returns an optional disconnect function. Calling it will disconnect that specific :onChange() handler early. 8 9 10 11 12 13 14 local disconnect = observer : onChange ( function () print ( \"The new value is: \" , peek ( health )) end ) -- disconnect the above handler after 5 seconds task . wait ( 5 ) disconnect () The second method is :onBind() . It works identically to :onChange() , but it also runs your code right away, which can often be useful. Luau code Output 8 9 10 11 12 13 14 15 16 local observer = scope : Observer ( health ) print ( \"...connecting...\" ) observer : onBind ( function () print ( \"Observed a change to: \" , peek ( health )) end ) print ( \"...setting health to 25...\" ) health : set ( 25 ) ...connecting... Observed a change to: 5 ...setting health to 25... Observed a change to: 25","title":"Usage"},{"location":"tutorials/fundamentals/observers/#what-counts-as-a-change","text":"If you set the health to the same value multiple times in a row, you might notice your observer only runs the first time. Luau code Output 8 9 10 11 12 13 14 15 16 17 local observer = scope : Observer ( health ) observer : onChange ( function () print ( \"Observed a change to: \" , peek ( health )) end ) print ( \"...setting health to 25 three times...\" ) health : set ( 25 ) health : set ( 25 ) health : set ( 25 ) ...setting health to 25 three times... Observed a change to: 25 This is because the health object sees that it isn't actually changing value, so it doesn't broadcast any updates. Therefore, our observer doesn't run. This leads to improved performance because your code runs less often. Fusion applies these kinds of optimisations generously throughout your program.","title":"What Counts As A Change?"},{"location":"tutorials/fundamentals/scopes/","text":"In Fusion, you create a lot of objects. These objects need to be destroyed when you're done with them. Fusion has some coding conventions to make large quantities of objects easier to manage. Scopes \u00b6 When you create many objects at once, you often want to :destroy() them together later. To make this easier, some people add their objects to an array. Arrays that group together objects like this are given a special name: scopes . To create a new scope, create an empty array. 2 3 4 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} Later, when you create objects, they will ask for a scope as the first argument. 2 3 4 5 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} local thing = Fusion . Value ( scope , \"i am a thing\" ) That object will add itself to the scope. 2 3 4 5 6 7 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} local thing = Fusion . Value ( scope , \"i am a thing\" ) print ( scope [ 1 ] == thing ) --> true Repeat as many times as you like. Objects appear in order of creation. 2 3 4 5 6 7 8 9 10 11 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) print ( scope [ 1 ] == thing1 ) --> true print ( scope [ 2 ] == thing2 ) --> true print ( scope [ 3 ] == thing3 ) --> true Later, destroy the scope by using the doCleanup() function. The contents are destroyed in reverse order. 2 3 4 5 6 7 8 9 10 11 12 13 14 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup = Fusion . doCleanup local scope = {} local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) doCleanup ( scope ) -- Using `doCleanup` is the same as: -- thing3:destroy() -- thing2:destroy() -- thing1:destroy() Scopes passed to doCleanup can contain: Objects with :destroy() or :Destroy() methods to be called Functions to be run Roblox instances to destroy Roblox event connections to disconnect Other nested scopes to be cleaned up You can add these manually using table.insert if you need custom behaviour, or if you are working with objects that don't add themselves to scopes. That's all there is to scopes. They are arrays of objects which later get passed to a cleanup function. Improved Scopes \u00b6 This syntax is recommended From now on, you'll see this syntax used throughout the tutorials. Fusion can help manage your scopes for you. This unlocks convenient syntax, and allows Fusion to optimise your code. You can call scoped() to obtain a new scope. 2 3 4 5 6 7 8 9 10 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped () local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) doCleanup ( scope ) Unlike {} (which always creates a new array), scoped can re-use old arrays. This helps keep your program running smoothly. Beyond making your code more efficient, you can also use scoped for convenient syntax. You can pass a table of constructor functions into scoped : 2 3 4 5 6 7 8 9 10 11 12 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ({ Value = Fusion . Value }) local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) doCleanup ( scope ) You can now use those constructors as methods on scope . 2 3 4 5 6 7 8 9 10 11 12 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ({ Value = Fusion . Value }) local thing1 = scope : Value ( \"i am thing 1\" ) local thing2 = scope : Value ( \"i am thing 2\" ) local thing3 = scope : Value ( \"i am thing 3\" ) doCleanup ( scope ) This makes it harder to mess up writing scopes. Your code reads more naturally, too. Adding Methods In Bulk \u00b6 Try passing Fusion to scoped() - it's a table with functions. 2 3 4 5 6 7 8 9 10 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ( Fusion ) local thing1 = scope : Value ( \"i am thing 1\" ) local thing2 = scope : Value ( \"i am thing 2\" ) local thing3 = scope : Value ( \"i am thing 3\" ) doCleanup ( scope ) This gives you access to all of Fusion's constructors without having to import each one manually. If you need to mix in other things, you can pass in another table. local scope = scoped ( Fusion , { Foo = ..., Bar = ... }) You can do this for as many tables as you need. Conflicting names If you pass in two tables that contain things with the same name, scoped() will error. Reusing Methods From Other Scopes \u00b6 Sometimes, you'll want to make a new scope with the same methods as an existing scope. local foo = scoped ({ Foo = Foo , Bar = Bar , Baz = Baz }) -- it'd be nice to define this once only... local bar = scoped ({ Foo = Foo , Bar = Bar , Baz = Baz }) To do this, Fusion provides a deriveScope function. It behaves like scoped but lets you skip defining the methods. Instead, you give it an example of what the scope should look like. 2 3 4 5 6 7 8 9 10 11 12 13 14 15 local Fusion = require ( ReplicatedStorage . Fusion ) local scoped , deriveScope = Fusion . scoped , Fusion . deriveScope local doCleanup = Fusion . doCleanup local foo = scoped ({ Foo = Foo , Bar = Bar , Baz = Baz }) local bar = deriveScope ( foo ) doCleanup ( bar ) doCleanup ( foo ) Deriving scopes like this is highly efficient because Fusion can re-use the same information for both scopes. It also helps keep your definitions all in one place. When You'll Use This \u00b6 Scopes might sound like a lot of upfront work. However, you'll find in practice that Fusion manages a lot of this for you. You'll need to create and destroy your own scopes manually sometimes. For example, you'll need to create a scope in your main code file to start using Fusion, and you might want to make a few more in other parts of your code. However, Fusion manages most of your scopes for you, so for large parts of your codebase, you won't have to consider scopes and destruction at all.","title":"Scopes"},{"location":"tutorials/fundamentals/scopes/#scopes","text":"When you create many objects at once, you often want to :destroy() them together later. To make this easier, some people add their objects to an array. Arrays that group together objects like this are given a special name: scopes . To create a new scope, create an empty array. 2 3 4 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} Later, when you create objects, they will ask for a scope as the first argument. 2 3 4 5 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} local thing = Fusion . Value ( scope , \"i am a thing\" ) That object will add itself to the scope. 2 3 4 5 6 7 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} local thing = Fusion . Value ( scope , \"i am a thing\" ) print ( scope [ 1 ] == thing ) --> true Repeat as many times as you like. Objects appear in order of creation. 2 3 4 5 6 7 8 9 10 11 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) print ( scope [ 1 ] == thing1 ) --> true print ( scope [ 2 ] == thing2 ) --> true print ( scope [ 3 ] == thing3 ) --> true Later, destroy the scope by using the doCleanup() function. The contents are destroyed in reverse order. 2 3 4 5 6 7 8 9 10 11 12 13 14 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup = Fusion . doCleanup local scope = {} local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) doCleanup ( scope ) -- Using `doCleanup` is the same as: -- thing3:destroy() -- thing2:destroy() -- thing1:destroy() Scopes passed to doCleanup can contain: Objects with :destroy() or :Destroy() methods to be called Functions to be run Roblox instances to destroy Roblox event connections to disconnect Other nested scopes to be cleaned up You can add these manually using table.insert if you need custom behaviour, or if you are working with objects that don't add themselves to scopes. That's all there is to scopes. They are arrays of objects which later get passed to a cleanup function.","title":"Scopes"},{"location":"tutorials/fundamentals/scopes/#improved-scopes","text":"This syntax is recommended From now on, you'll see this syntax used throughout the tutorials. Fusion can help manage your scopes for you. This unlocks convenient syntax, and allows Fusion to optimise your code. You can call scoped() to obtain a new scope. 2 3 4 5 6 7 8 9 10 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped () local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) doCleanup ( scope ) Unlike {} (which always creates a new array), scoped can re-use old arrays. This helps keep your program running smoothly. Beyond making your code more efficient, you can also use scoped for convenient syntax. You can pass a table of constructor functions into scoped : 2 3 4 5 6 7 8 9 10 11 12 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ({ Value = Fusion . Value }) local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) doCleanup ( scope ) You can now use those constructors as methods on scope . 2 3 4 5 6 7 8 9 10 11 12 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ({ Value = Fusion . Value }) local thing1 = scope : Value ( \"i am thing 1\" ) local thing2 = scope : Value ( \"i am thing 2\" ) local thing3 = scope : Value ( \"i am thing 3\" ) doCleanup ( scope ) This makes it harder to mess up writing scopes. Your code reads more naturally, too.","title":"Improved Scopes"},{"location":"tutorials/fundamentals/scopes/#adding-methods-in-bulk","text":"Try passing Fusion to scoped() - it's a table with functions. 2 3 4 5 6 7 8 9 10 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ( Fusion ) local thing1 = scope : Value ( \"i am thing 1\" ) local thing2 = scope : Value ( \"i am thing 2\" ) local thing3 = scope : Value ( \"i am thing 3\" ) doCleanup ( scope ) This gives you access to all of Fusion's constructors without having to import each one manually. If you need to mix in other things, you can pass in another table. local scope = scoped ( Fusion , { Foo = ..., Bar = ... }) You can do this for as many tables as you need. Conflicting names If you pass in two tables that contain things with the same name, scoped() will error.","title":"Adding Methods In Bulk"},{"location":"tutorials/fundamentals/scopes/#reusing-methods-from-other-scopes","text":"Sometimes, you'll want to make a new scope with the same methods as an existing scope. local foo = scoped ({ Foo = Foo , Bar = Bar , Baz = Baz }) -- it'd be nice to define this once only... local bar = scoped ({ Foo = Foo , Bar = Bar , Baz = Baz }) To do this, Fusion provides a deriveScope function. It behaves like scoped but lets you skip defining the methods. Instead, you give it an example of what the scope should look like. 2 3 4 5 6 7 8 9 10 11 12 13 14 15 local Fusion = require ( ReplicatedStorage . Fusion ) local scoped , deriveScope = Fusion . scoped , Fusion . deriveScope local doCleanup = Fusion . doCleanup local foo = scoped ({ Foo = Foo , Bar = Bar , Baz = Baz }) local bar = deriveScope ( foo ) doCleanup ( bar ) doCleanup ( foo ) Deriving scopes like this is highly efficient because Fusion can re-use the same information for both scopes. It also helps keep your definitions all in one place.","title":"Reusing Methods From Other Scopes"},{"location":"tutorials/fundamentals/scopes/#when-youll-use-this","text":"Scopes might sound like a lot of upfront work. However, you'll find in practice that Fusion manages a lot of this for you. You'll need to create and destroy your own scopes manually sometimes. For example, you'll need to create a scope in your main code file to start using Fusion, and you might want to make a few more in other parts of your code. However, Fusion manages most of your scopes for you, so for large parts of your codebase, you won't have to consider scopes and destruction at all.","title":"When You'll Use This"},{"location":"tutorials/fundamentals/values/","text":"Now that you understand how Fusion works with objects, you can create Fusion's simplest object. Values are objects which store single values. You can write to them with their :set() method, and read from them with the peek() function. local health = scope : Value ( 100 ) print ( peek ( health )) --> 100 health : set ( 25 ) print ( peek ( health )) --> 25 Usage \u00b6 To create a new value object, call scope:Value() and give it a value you want to store. 2 3 4 5 6 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) Fusion provides a global peek() function. It will read the value of whatever you give it. You'll use peek() to read the value of lots of things; for now, it's useful for printing health back out. 2 3 4 5 6 7 8 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local peek = Fusion . peek local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) print ( peek ( health )) --> 5 You can change the value using the :set() method. Unlike peek() , this is specific to value objects, so it's done on the object itself. 6 7 8 9 10 11 local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) print ( peek ( health )) --> 5 health : set ( 25 ) print ( peek ( health )) --> 25 Value objects are Fusion's simplest 'state object'. State objects contain a single value - their state , you might say - and that single value can be read out at any time using peek() . Later on, you'll discover more advanced state objects that can calculate their value in more interesting ways.","title":"Values"},{"location":"tutorials/fundamentals/values/#usage","text":"To create a new value object, call scope:Value() and give it a value you want to store. 2 3 4 5 6 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) Fusion provides a global peek() function. It will read the value of whatever you give it. You'll use peek() to read the value of lots of things; for now, it's useful for printing health back out. 2 3 4 5 6 7 8 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local peek = Fusion . peek local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) print ( peek ( health )) --> 5 You can change the value using the :set() method. Unlike peek() , this is specific to value objects, so it's done on the object itself. 6 7 8 9 10 11 local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) print ( peek ( health )) --> 5 health : set ( 25 ) print ( peek ( health )) --> 25 Value objects are Fusion's simplest 'state object'. State objects contain a single value - their state , you might say - and that single value can be read out at any time using peek() . Later on, you'll discover more advanced state objects that can calculate their value in more interesting ways.","title":"Usage"},{"location":"tutorials/roblox/change-events/","text":"OnChange is a function that returns keys to use when hydrating or creating an instance. Those keys let you connect functions to property changed events on the instance. local input = scope : New \"TextBox\" { [ OnChange \"Text\" ] = function ( newText ) print ( \"You typed:\" , newText ) end } Usage \u00b6 OnChange doesn't need a scope - import it into your code from Fusion directly. local OnChange = Fusion . OnChange When you call OnChange with a property name, it will return a special key: local key = OnChange ( \"Text\" ) When used in a property table, you can pass in a handler and it will be run when that property changes. Arguments are different to Roblox API Normally in the Roblox API, when using :GetPropertyChangedSignal() on an instance, the callback will not receive any arguments. To make working with change events easier, OnChange will pass the new value of the property to the callback. local input = scope : New \"TextBox\" { [ OnChange ( \"Text\" )] = function ( newText ) print ( \"You typed:\" , newText ) end } If you're using quotes '' \"\" for the event name, the extra parentheses () are optional: local input = scope : New \"TextBox\" { [ OnChange \"Text\" ] = function ( newText ) print ( \"You typed:\" , newText ) end }","title":"Change Events"},{"location":"tutorials/roblox/change-events/#usage","text":"OnChange doesn't need a scope - import it into your code from Fusion directly. local OnChange = Fusion . OnChange When you call OnChange with a property name, it will return a special key: local key = OnChange ( \"Text\" ) When used in a property table, you can pass in a handler and it will be run when that property changes. Arguments are different to Roblox API Normally in the Roblox API, when using :GetPropertyChangedSignal() on an instance, the callback will not receive any arguments. To make working with change events easier, OnChange will pass the new value of the property to the callback. local input = scope : New \"TextBox\" { [ OnChange ( \"Text\" )] = function ( newText ) print ( \"You typed:\" , newText ) end } If you're using quotes '' \"\" for the event name, the extra parentheses () are optional: local input = scope : New \"TextBox\" { [ OnChange \"Text\" ] = function ( newText ) print ( \"You typed:\" , newText ) end }","title":"Usage"},{"location":"tutorials/roblox/events/","text":"OnEvent is a function that returns keys to use when hydrating or creating an instance. Those keys let you connect functions to events on the instance. local button = scope : New \"TextButton\" { [ OnEvent \"Activated\" ] = function ( _ , numClicks ) print ( \"The button was pressed\" , numClicks , \"time(s)!\" ) end } Usage \u00b6 OnEvent doesn't need a scope - import it into your code from Fusion directly. local OnEvent = Fusion . OnEvent When you call OnEvent with an event name, it will return a special key: local key = OnEvent ( \"Activated\" ) When that key is used in a property table, you can pass in a handler and it will be connected to the event for you: local button = scope : New \"TextButton\" { [ OnEvent ( \"Activated\" )] = function ( _ , numClicks ) print ( \"The button was pressed\" , numClicks , \"time(s)!\" ) end } If you're using quotes '' \"\" for the event name, the extra parentheses () are optional: local button = scope : New \"TextButton\" { [ OnEvent \"Activated\" ] = function ( _ , numClicks ) print ( \"The button was pressed\" , numClicks , \"time(s)!\" ) end }","title":"Events"},{"location":"tutorials/roblox/events/#usage","text":"OnEvent doesn't need a scope - import it into your code from Fusion directly. local OnEvent = Fusion . OnEvent When you call OnEvent with an event name, it will return a special key: local key = OnEvent ( \"Activated\" ) When that key is used in a property table, you can pass in a handler and it will be connected to the event for you: local button = scope : New \"TextButton\" { [ OnEvent ( \"Activated\" )] = function ( _ , numClicks ) print ( \"The button was pressed\" , numClicks , \"time(s)!\" ) end } If you're using quotes '' \"\" for the event name, the extra parentheses () are optional: local button = scope : New \"TextButton\" { [ OnEvent \"Activated\" ] = function ( _ , numClicks ) print ( \"The button was pressed\" , numClicks , \"time(s)!\" ) end }","title":"Usage"},{"location":"tutorials/roblox/hydration/","text":"Intent to replace While the contents of this page still apply (and are useful for explaining other features), Hydrate itself will be replaced by other primitives in the near future. See this issue on GitHub for further details. The process of connecting your scripts to a pre-made UI template is known as hydration . This is where logic in your scripts translate into UI effects, for example setting a message inside a TextLabel, moving menus around, or showing and hiding buttons. Screenshot: GameUIDatabase (Halo Infinite) Fusion provides a Hydrate function for hydrating an instance using a table of properties. If you pass in Fusion objects, changes will be applied immediately: local showUI = scope : Value ( false ) local ui = scope : Hydrate ( StarterGui . Template : Clone ()) { Name = \"MainGui\" , Enabled = showUI } print ( ui . Name ) --> MainGui print ( ui . Enabled ) --> false showUI : set ( true ) task . wait () -- important: changes are applied on the next frame! print ( ui . Enabled ) --> true Usage \u00b6 The Hydrate function is called in two parts. First, call the function with the instance you want to hydrate, then pass in the property table: local instance = workspace . Part scope : Hydrate ( instance )({ Color = Color3 . new ( 1 , 0 , 0 ) }) If you're using curly braces {} to pass your properties in, the extra parentheses () are optional: local instance = workspace . Part -- This only works when you're using curly braces {}! scope : Hydrate ( instance ) { Color = Color3 . new ( 1 , 0 , 0 ) } Hydrate returns the instance you give it, so you can use it in declarations: local instance = scope : Hydrate ( workspace . Part ) { Color = Color3 . new ( 1 , 0 , 0 ) } If you pass in constant values for properties, they'll be applied to the instance directly. However, if you pass in a Fusion object (like Value ), then changes will be applied immediately: local message = scope : Value ( \"Loading...\" ) scope : Hydrate ( PlayerGui . LoadingText ) { Text = message } print ( PlayerGui . Message . Text ) --> Loading... message : set ( \"All done!\" ) task . wait () -- important: changes are applied on the next frame! print ( PlayerGui . Message . Text ) --> All done!","title":"Hydration"},{"location":"tutorials/roblox/hydration/#usage","text":"The Hydrate function is called in two parts. First, call the function with the instance you want to hydrate, then pass in the property table: local instance = workspace . Part scope : Hydrate ( instance )({ Color = Color3 . new ( 1 , 0 , 0 ) }) If you're using curly braces {} to pass your properties in, the extra parentheses () are optional: local instance = workspace . Part -- This only works when you're using curly braces {}! scope : Hydrate ( instance ) { Color = Color3 . new ( 1 , 0 , 0 ) } Hydrate returns the instance you give it, so you can use it in declarations: local instance = scope : Hydrate ( workspace . Part ) { Color = Color3 . new ( 1 , 0 , 0 ) } If you pass in constant values for properties, they'll be applied to the instance directly. However, if you pass in a Fusion object (like Value ), then changes will be applied immediately: local message = scope : Value ( \"Loading...\" ) scope : Hydrate ( PlayerGui . LoadingText ) { Text = message } print ( PlayerGui . Message . Text ) --> Loading... message : set ( \"All done!\" ) task . wait () -- important: changes are applied on the next frame! print ( PlayerGui . Message . Text ) --> All done!","title":"Usage"},{"location":"tutorials/roblox/new-instances/","text":"Fusion provides a New function when you're hydrating newly-made instances. It creates a new instance, applies some default properties, then hydrates it with a property table. local message = scope : Value ( \"Hello there!\" ) local ui = scope : New \"TextLabel\" { Name = \"Greeting\" , Parent = PlayerGui . ScreenGui , Text = message } print ( ui . Name ) --> Greeting print ( ui . Text ) --> Hello there! message : set ( \"Goodbye friend!\" ) task . wait () -- important: changes are applied on the next frame! print ( ui . Text ) --> Goodbye friend! Usage \u00b6 The New function is called in two parts. First, call the function with the type of instance, then pass in the property table: local instance = scope : New ( \"Part\" )({ Parent = workspace , Color = Color3 . new ( 1 , 0 , 0 ) }) If you're using curly braces {} for your properties, and quotes '' \"\" for your class type, the extra parentheses () are optional: -- This only works when you're using curly braces {} and quotes '' \"\"! local instance = scope : New \"Part\" { Parent = workspace , Color = Color3 . new ( 1 , 0 , 0 ) } By design, New works just like Hydrate - it will apply properties the same way. See the Hydrate tutorial to learn more. Default Properties \u00b6 When you create an instance using Instance.new() , Roblox will give it some default properties. However, these tend to be outdated and aren't useful for most people, leading to repetitive boilerplate needed to disable features that nobody wants to use. The New function will apply some of it's own default properties to fix this. For example, by default borders on UI are disabled, automatic colouring is turned off and default content is removed. For a complete list, take a look at Fusion's default properties file.","title":"New Instances"},{"location":"tutorials/roblox/new-instances/#usage","text":"The New function is called in two parts. First, call the function with the type of instance, then pass in the property table: local instance = scope : New ( \"Part\" )({ Parent = workspace , Color = Color3 . new ( 1 , 0 , 0 ) }) If you're using curly braces {} for your properties, and quotes '' \"\" for your class type, the extra parentheses () are optional: -- This only works when you're using curly braces {} and quotes '' \"\"! local instance = scope : New \"Part\" { Parent = workspace , Color = Color3 . new ( 1 , 0 , 0 ) } By design, New works just like Hydrate - it will apply properties the same way. See the Hydrate tutorial to learn more.","title":"Usage"},{"location":"tutorials/roblox/new-instances/#default-properties","text":"When you create an instance using Instance.new() , Roblox will give it some default properties. However, these tend to be outdated and aren't useful for most people, leading to repetitive boilerplate needed to disable features that nobody wants to use. The New function will apply some of it's own default properties to fix this. For example, by default borders on UI are disabled, automatic colouring is turned off and default content is removed. For a complete list, take a look at Fusion's default properties file.","title":"Default Properties"},{"location":"tutorials/roblox/outputs/","text":"Out is a function that returns keys to use when hydrating or creating an instance. Those keys let you output a property's value to a Value object. local name = scope : Value () local thing = scope : New \"Part\" { [ Out \"Name\" ] = name } print ( peek ( name )) --> Part thing . Name = \"Jimmy\" print ( peek ( name )) --> Jimmy Usage \u00b6 Out doesn't need a scope - import it into your code from Fusion directly. local Out = Fusion . Out When you call Out with a property name, it will return a special key: local key = Out ( \"Activated\" ) When used in a property table, you can pass in a Value object. It will be set to the value of the property, and when the property changes, it will be set to the new value: local name = scope : Value () local thing = scope : New \"Part\" { [ Out ( \"Name\" )] = name } print ( peek ( name )) --> Part thing . Name = \"Jimmy\" print ( peek ( name )) --> Jimmy If you're using quotes '' \"\" for the event name, the extra parentheses () are optional: local thing = scope : New \"Part\" { [ Out \"Name\" ] = name } Two-Way Binding \u00b6 By default, Out only outputs changes to the property. If you set the value to something else, the property remains the same: local name = scope : Value () local thing = scope : New \"Part\" { [ Out \"Name\" ] = name -- When `thing.Name` changes, set `name` } print ( thing . Name , peek ( name )) --> Part Part name : set ( \"NewName\" ) task . wait () print ( thing . Name , peek ( name )) --> Part NewName If you want the value to both change and be changed by the property, you need to explicitly say so: local name = scope : Value () local thing = scope : New \"Part\" { Name = name -- When `name` changes, set `thing.Name` [ Out \"Name\" ] = name -- When `thing.Name` changes, set `name` } print ( thing . Name , peek ( name )) --> Part Part name : set ( \"NewName\" ) task . wait () print ( thing . Name , peek ( name )) --> NewName NewName This is known as two-way binding. Most of the time you won't need it, but it can come in handy when working with some kinds of UI - for example, a text box that users can write into, but which can also be modified by your scripts.","title":"Outputs"},{"location":"tutorials/roblox/outputs/#usage","text":"Out doesn't need a scope - import it into your code from Fusion directly. local Out = Fusion . Out When you call Out with a property name, it will return a special key: local key = Out ( \"Activated\" ) When used in a property table, you can pass in a Value object. It will be set to the value of the property, and when the property changes, it will be set to the new value: local name = scope : Value () local thing = scope : New \"Part\" { [ Out ( \"Name\" )] = name } print ( peek ( name )) --> Part thing . Name = \"Jimmy\" print ( peek ( name )) --> Jimmy If you're using quotes '' \"\" for the event name, the extra parentheses () are optional: local thing = scope : New \"Part\" { [ Out \"Name\" ] = name }","title":"Usage"},{"location":"tutorials/roblox/outputs/#two-way-binding","text":"By default, Out only outputs changes to the property. If you set the value to something else, the property remains the same: local name = scope : Value () local thing = scope : New \"Part\" { [ Out \"Name\" ] = name -- When `thing.Name` changes, set `name` } print ( thing . Name , peek ( name )) --> Part Part name : set ( \"NewName\" ) task . wait () print ( thing . Name , peek ( name )) --> Part NewName If you want the value to both change and be changed by the property, you need to explicitly say so: local name = scope : Value () local thing = scope : New \"Part\" { Name = name -- When `name` changes, set `thing.Name` [ Out \"Name\" ] = name -- When `thing.Name` changes, set `name` } print ( thing . Name , peek ( name )) --> Part Part name : set ( \"NewName\" ) task . wait () print ( thing . Name , peek ( name )) --> NewName NewName This is known as two-way binding. Most of the time you won't need it, but it can come in handy when working with some kinds of UI - for example, a text box that users can write into, but which can also be modified by your scripts.","title":"Two-Way Binding"},{"location":"tutorials/roblox/parenting/","text":"The [Children] key allows you to add children when hydrating or creating an instance. It accepts instances, arrays of children, and state objects containing children or nil . local folder = scope : New \"Folder\" { [ Children ] = { New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) }, New \"Part\" { Name = \"Sammy\" , Material = \"Glass\" } } } Usage \u00b6 Children doesn't need a scope - import it into your code from Fusion directly. local Children = Fusion . Children When using New or Hydrate , you can use [Children] as a key in the property table. Any instance you pass in will be parented: local folder = scope : New \"Folder\" { -- The part will be moved inside of the folder [ Children ] = workspace . Part } Since New and Hydrate both return their instances, you can nest them: -- Makes a Folder, containing a part called Gregory local folder = scope : New \"Folder\" { [ Children ] = scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) } } If you need to parent multiple children, arrays of children are accepted: -- Makes a Folder, containing parts called Gregory and Sammy local folder = scope : New \"Folder\" { [ Children ] = { scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) }, scope : New \"Part\" { Name = \"Sammy\" , Material = \"Glass\" } } } Arrays can be nested to any depth; all children will still be parented: local folder = scope : New \"Folder\" { [ Children ] = { { { { scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) } } } } } } State objects containing children or nil are also allowed: local value = scope : Value () local folder = scope : New \"Folder\" { [ Children ] = value } value : set ( scope : New \"Part\" { Name = \"Clyde\" , Transparency = 0.5 } ) You may use any combination of these to parent whichever children you need: local modelChildren = workspace . Model : GetChildren () local includeModel = scope : Value ( true ) local folder = scope : New \"Folder\" { -- array of children [ Children ] = { -- single instance scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) }, -- state object containing children (or nil) scope : Computed ( function ( use ) return if use ( includeModel ) then modelChildren : GetChildren () -- array of children else nil end ) } }","title":"Parenting"},{"location":"tutorials/roblox/parenting/#usage","text":"Children doesn't need a scope - import it into your code from Fusion directly. local Children = Fusion . Children When using New or Hydrate , you can use [Children] as a key in the property table. Any instance you pass in will be parented: local folder = scope : New \"Folder\" { -- The part will be moved inside of the folder [ Children ] = workspace . Part } Since New and Hydrate both return their instances, you can nest them: -- Makes a Folder, containing a part called Gregory local folder = scope : New \"Folder\" { [ Children ] = scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) } } If you need to parent multiple children, arrays of children are accepted: -- Makes a Folder, containing parts called Gregory and Sammy local folder = scope : New \"Folder\" { [ Children ] = { scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) }, scope : New \"Part\" { Name = \"Sammy\" , Material = \"Glass\" } } } Arrays can be nested to any depth; all children will still be parented: local folder = scope : New \"Folder\" { [ Children ] = { { { { scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) } } } } } } State objects containing children or nil are also allowed: local value = scope : Value () local folder = scope : New \"Folder\" { [ Children ] = value } value : set ( scope : New \"Part\" { Name = \"Clyde\" , Transparency = 0.5 } ) You may use any combination of these to parent whichever children you need: local modelChildren = workspace . Model : GetChildren () local includeModel = scope : Value ( true ) local folder = scope : New \"Folder\" { -- array of children [ Children ] = { -- single instance scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) }, -- state object containing children (or nil) scope : Computed ( function ( use ) return if use ( includeModel ) then modelChildren : GetChildren () -- array of children else nil end ) } }","title":"Usage"},{"location":"tutorials/roblox/references/","text":"The [Ref] key allows you to save a reference to an instance you're hydrating or creating. local myRef = scope : Value () local thing = scope : New \"Part\" { [ Ref ] = myRef } print ( peek ( myRef )) --> Part print ( peek ( myRef ) == thing ) --> true Usage \u00b6 Ref doesn't need a scope - import it into your code from Fusion directly. 1 2 local Fusion = require ( ReplicatedStorage . Fusion ) local Ref = Fusion . Ref When creating an instance with New , [Ref] will save that instance to a value object. local myRef = scope : Value () scope : New \"Part\" { [ Ref ] = myRef } print ( peek ( myRef )) --> Part Among other things, this allows you to refer to instances from other instances. local myPart = scope : Value () New \"SelectionBox\" { -- the selection box should adorn to the part Adornee = myPart } New \"Part\" { -- sets `myPart` to this part, which sets the adornee to this part [ Ref ] = myPart } You can also get references to instances from deep inside function calls. -- this will refer to the part, once we create it local myPart = scope : Value () scope : New \"Folder\" { [ Children ] = scope : New \"Folder\" { [ Children ] = scope : New \"Part\" { -- save a reference into the value object [ Ref ] = myPart } } } Nil hazard Before the part is created, the myPart value object will be nil . Be careful not to use it before it's created. If you need to know about the instance ahead of time, you should create the instance early, and parent it in later, when you create the rest of the instances. -- build the part elsewhere, so it can be saved to a variable local myPart = scope : New \"Part\" {} local folders = scope : New \"Folder\" { [ Children ] = scope : New \"Folder\" { -- parent the part into the folder here [ Children ] = myPart } }","title":"References"},{"location":"tutorials/roblox/references/#usage","text":"Ref doesn't need a scope - import it into your code from Fusion directly. 1 2 local Fusion = require ( ReplicatedStorage . Fusion ) local Ref = Fusion . Ref When creating an instance with New , [Ref] will save that instance to a value object. local myRef = scope : Value () scope : New \"Part\" { [ Ref ] = myRef } print ( peek ( myRef )) --> Part Among other things, this allows you to refer to instances from other instances. local myPart = scope : Value () New \"SelectionBox\" { -- the selection box should adorn to the part Adornee = myPart } New \"Part\" { -- sets `myPart` to this part, which sets the adornee to this part [ Ref ] = myPart } You can also get references to instances from deep inside function calls. -- this will refer to the part, once we create it local myPart = scope : Value () scope : New \"Folder\" { [ Children ] = scope : New \"Folder\" { [ Children ] = scope : New \"Part\" { -- save a reference into the value object [ Ref ] = myPart } } } Nil hazard Before the part is created, the myPart value object will be nil . Be careful not to use it before it's created. If you need to know about the instance ahead of time, you should create the instance early, and parent it in later, when you create the rest of the instances. -- build the part elsewhere, so it can be saved to a variable local myPart = scope : New \"Part\" {} local folders = scope : New \"Folder\" { [ Children ] = scope : New \"Folder\" { -- parent the part into the folder here [ Children ] = myPart } }","title":"Usage"},{"location":"tutorials/tables/forkeys/","text":"ForKeys is a state object that processes keys from another table. It supports both constants and state objects. local data = { Red = \"foo\" , Blue = \"bar\" } local prefix = scope : Value ( \"Key_\" ) local renamed = scope : ForKeys ( data , function ( use , key ) return use ( prefix ) .. key end ) print ( peek ( renamed )) --> {Key_Red = \"foo\", Key_Blue = \"bar\"} prefix : set ( \"colour\" ) print ( peek ( renamed )) --> {colourRed = \"foo\", colourBlue = \"bar\"} Usage \u00b6 To create a new ForKeys object, call the constructor with an input table and a processor function. The first two arguments are use and scope , just like computed objects . The third argument is one of the keys read from the input table. local data = { red = \"foo\" , blue = \"bar\" } local renamed = scope : ForKeys ( data , function ( use , scope , key ) return string.upper ( key ) end ) You can read the table of processed keys using peek() : local data = { red = \"foo\" , blue = \"bar\" } local renamed = scope : ForKeys ( data , function ( use , scope , key ) return string.upper ( key ) end ) print ( peek ( renamed )) --> {RED = \"foo\", BLUE = \"bar\"} The input table can be a state object. When the input table changes, the output will update. local foodSet = scope : Value ({}) local prefixes = { pie = \"tasty\" , chocolate = \"yummy\" , broccoli = \"gross\" } local renamedFoodSet = scope : ForKeys ( foodSet , function ( use , scope , food ) return prefixes [ food ] .. food end ) foodSet : set ({ pie = true }) print ( peek ( renamedFoodSet )) --> { tasty_pie = true } foodSet : set ({ broccoli = true , chocolate = true }) print ( peek ( renamedFoodSet )) --> { gross_broccoli = true, yummy_chocolate = true } You can also use() state objects in your calculations, just like a computed. local foodSet = scope : Value ({ broccoli = true , chocolate = true }) local prefixes = { chocolate = \"yummy\" , broccoli = scope : Value ( \"gross\" ) } local renamedFoodSet = scope : ForKeys ( foodSet , function ( use , scope , food ) return use ( prefixes [ food ]) .. food end ) print ( peek ( renamedFoodSet )) --> { gross_broccoli = true, yummy_chocolate = true } prefixes . broccoli : set ( \"scrumptious\" ) print ( peek ( renamedFoodSet )) --> { scrumptious_broccoli = true, yummy_chocolate = true } Anything added to the scope is cleaned up for you when the processed key is removed. local foodSet = scope : Value ({ broccoli = true , chocolate = true }) local shoutingFoodSet = scope : ForKeys ( names , function ( use , scope , food ) table.insert ( scope , function () print ( \"I ate the \" .. food .. \"!\" ) end ) return string.upper ( food ) end ) names : set ({ chocolate = true }) --> I ate the broccoli! How ForKeys optimises your code Rather than creating a new output table from scratch every time the input table is changed, ForKeys will try and reuse as much as possible to improve performance. Say you're converting an array to a dictionary: local array = scope : Value ({ \"Fusion\" , \"Knit\" , \"Matter\" }) local dict = scope : ForKeys ( array , function ( use , scope , index ) return \"Value\" .. index end ) print ( peek ( dict )) --> {Value1 = \"Fusion\", Value2 = \"Knit\", Value3 = \"Matter\"} Because ForKeys only operates on the keys, changing the values in the array doesn't affect the keys. Keys are only added or removed as needed: local array = scope : Value ({ \"Fusion\" , \"Knit\" , \"Matter\" }) local dict = scope : ForKeys ( array , function ( use , scope , index ) return \"Value\" .. index end ) print ( peek ( dict )) --> {Value1 = \"Fusion\", Value2 = \"Knit\", Value3 = \"Matter\"} array : set ({ \"Roact\" , \"Rodux\" , \"Promise\" }) print ( peek ( dict )) --> {Value1 = \"Roact\", Value2 = \"Rodux\", Value3 = \"Promise\"} ForKeys takes advantage of this - when a value changes, it's copied into the output table without recalculating the key. Keys are only calculated when a value is assigned to a new key.","title":"ForKeys"},{"location":"tutorials/tables/forkeys/#usage","text":"To create a new ForKeys object, call the constructor with an input table and a processor function. The first two arguments are use and scope , just like computed objects . The third argument is one of the keys read from the input table. local data = { red = \"foo\" , blue = \"bar\" } local renamed = scope : ForKeys ( data , function ( use , scope , key ) return string.upper ( key ) end ) You can read the table of processed keys using peek() : local data = { red = \"foo\" , blue = \"bar\" } local renamed = scope : ForKeys ( data , function ( use , scope , key ) return string.upper ( key ) end ) print ( peek ( renamed )) --> {RED = \"foo\", BLUE = \"bar\"} The input table can be a state object. When the input table changes, the output will update. local foodSet = scope : Value ({}) local prefixes = { pie = \"tasty\" , chocolate = \"yummy\" , broccoli = \"gross\" } local renamedFoodSet = scope : ForKeys ( foodSet , function ( use , scope , food ) return prefixes [ food ] .. food end ) foodSet : set ({ pie = true }) print ( peek ( renamedFoodSet )) --> { tasty_pie = true } foodSet : set ({ broccoli = true , chocolate = true }) print ( peek ( renamedFoodSet )) --> { gross_broccoli = true, yummy_chocolate = true } You can also use() state objects in your calculations, just like a computed. local foodSet = scope : Value ({ broccoli = true , chocolate = true }) local prefixes = { chocolate = \"yummy\" , broccoli = scope : Value ( \"gross\" ) } local renamedFoodSet = scope : ForKeys ( foodSet , function ( use , scope , food ) return use ( prefixes [ food ]) .. food end ) print ( peek ( renamedFoodSet )) --> { gross_broccoli = true, yummy_chocolate = true } prefixes . broccoli : set ( \"scrumptious\" ) print ( peek ( renamedFoodSet )) --> { scrumptious_broccoli = true, yummy_chocolate = true } Anything added to the scope is cleaned up for you when the processed key is removed. local foodSet = scope : Value ({ broccoli = true , chocolate = true }) local shoutingFoodSet = scope : ForKeys ( names , function ( use , scope , food ) table.insert ( scope , function () print ( \"I ate the \" .. food .. \"!\" ) end ) return string.upper ( food ) end ) names : set ({ chocolate = true }) --> I ate the broccoli! How ForKeys optimises your code Rather than creating a new output table from scratch every time the input table is changed, ForKeys will try and reuse as much as possible to improve performance. Say you're converting an array to a dictionary: local array = scope : Value ({ \"Fusion\" , \"Knit\" , \"Matter\" }) local dict = scope : ForKeys ( array , function ( use , scope , index ) return \"Value\" .. index end ) print ( peek ( dict )) --> {Value1 = \"Fusion\", Value2 = \"Knit\", Value3 = \"Matter\"} Because ForKeys only operates on the keys, changing the values in the array doesn't affect the keys. Keys are only added or removed as needed: local array = scope : Value ({ \"Fusion\" , \"Knit\" , \"Matter\" }) local dict = scope : ForKeys ( array , function ( use , scope , index ) return \"Value\" .. index end ) print ( peek ( dict )) --> {Value1 = \"Fusion\", Value2 = \"Knit\", Value3 = \"Matter\"} array : set ({ \"Roact\" , \"Rodux\" , \"Promise\" }) print ( peek ( dict )) --> {Value1 = \"Roact\", Value2 = \"Rodux\", Value3 = \"Promise\"} ForKeys takes advantage of this - when a value changes, it's copied into the output table without recalculating the key. Keys are only calculated when a value is assigned to a new key.","title":"Usage"},{"location":"tutorials/tables/forpairs/","text":"ForPairs is like ForValues and ForKeys in one object. It can process pairs of keys and values at the same time. It supports both constants and state objects. local itemColours = { shoes = \"red\" , socks = \"blue\" } local owner = scope : Value ( \"Janet\" ) local manipulated = scope : ForPairs ( itemColours , function ( use , scope , thing , colour ) local newKey = colour local newValue = use ( owner ) .. \"'s \" .. thing return newKey , newValue end ) print ( peek ( manipulated )) --> {red = \"Janet's shoes\", blue = \"Janet's socks\"} owner : set ( \"April\" ) print ( peek ( manipulated )) --> {red = \"April's shoes\", blue = \"April's socks\"} Usage \u00b6 To create a new ForPairs object, call the constructor with an input table and a processor function. The first two arguments are use and scope , just like computed objects . The third and fourth arguments are one of the key-value pairs read from the input table. local itemColours = { shoes = \"red\" , socks = \"blue\" } local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) return colour , item end ) You can read the processed table using peek() : local itemColours = { shoes = \"red\" , socks = \"blue\" } local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) return colour , item end ) print ( peek ( swapped )) --> { red = \"shoes\", blue = \"socks\" } The input table can be a state object. When the input table changes, the output will update. local itemColours = scope : Value ({ shoes = \"red\" , socks = \"blue\" }) local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) return colour , item end ) print ( peek ( swapped )) --> { red = \"shoes\", blue = \"socks\" } itemColours : set ({ sandals = \"red\" , socks = \"green\" }) print ( peek ( swapped )) --> { red = \"sandals\", green = \"socks\" } You can also use() state objects in your calculations, just like a computed. local itemColours = { shoes = \"red\" , socks = \"blue\" } local shouldSwap = scope : Value ( false ) local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) if use ( shouldSwap ) then return colour , item else return item , colour end end ) print ( peek ( swapped )) --> { shoes = \"red\", socks = \"blue\" } shouldSwap : set ( true ) print ( peek ( swapped )) --> { red = \"shoes\", blue = \"socks\" } Anything added to the scope is cleaned up for you when either the processed key or the processed value is removed. local itemColours = scope : Value ({ shoes = \"red\" , socks = \"blue\" }) local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) table.insert ( scope , function () print ( \"No longer wearing \" .. colour .. \" \" .. item ) end ) return colour , item end ) itemColours : set ({ shoes = \"red\" , socks = \"green\" }) --> No longer wearing blue socks How ForPairs optimises your code Rather than creating a new output table from scratch every time the input table is changed, ForPairs will try and reuse as much as possible to improve performance. Since ForPairs has to depend on both keys and values, changing any value in the input table will cause a recalculation for that key-value pair. Inversely, ForPairs won't recalculate any key-value pairs that stay the same. Instead, these will be preserved in the output table. If you don't need the keys or the values, Fusion can offer better optimisations. For example, if you're working with an array of values where position doesn't matter, ForValues can move values between keys. Alternatively, if you're working with a set of objects stored in keys, and don't need the values in the table, ForKeys will ignore the values for optimal performance.","title":"ForPairs"},{"location":"tutorials/tables/forpairs/#usage","text":"To create a new ForPairs object, call the constructor with an input table and a processor function. The first two arguments are use and scope , just like computed objects . The third and fourth arguments are one of the key-value pairs read from the input table. local itemColours = { shoes = \"red\" , socks = \"blue\" } local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) return colour , item end ) You can read the processed table using peek() : local itemColours = { shoes = \"red\" , socks = \"blue\" } local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) return colour , item end ) print ( peek ( swapped )) --> { red = \"shoes\", blue = \"socks\" } The input table can be a state object. When the input table changes, the output will update. local itemColours = scope : Value ({ shoes = \"red\" , socks = \"blue\" }) local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) return colour , item end ) print ( peek ( swapped )) --> { red = \"shoes\", blue = \"socks\" } itemColours : set ({ sandals = \"red\" , socks = \"green\" }) print ( peek ( swapped )) --> { red = \"sandals\", green = \"socks\" } You can also use() state objects in your calculations, just like a computed. local itemColours = { shoes = \"red\" , socks = \"blue\" } local shouldSwap = scope : Value ( false ) local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) if use ( shouldSwap ) then return colour , item else return item , colour end end ) print ( peek ( swapped )) --> { shoes = \"red\", socks = \"blue\" } shouldSwap : set ( true ) print ( peek ( swapped )) --> { red = \"shoes\", blue = \"socks\" } Anything added to the scope is cleaned up for you when either the processed key or the processed value is removed. local itemColours = scope : Value ({ shoes = \"red\" , socks = \"blue\" }) local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) table.insert ( scope , function () print ( \"No longer wearing \" .. colour .. \" \" .. item ) end ) return colour , item end ) itemColours : set ({ shoes = \"red\" , socks = \"green\" }) --> No longer wearing blue socks How ForPairs optimises your code Rather than creating a new output table from scratch every time the input table is changed, ForPairs will try and reuse as much as possible to improve performance. Since ForPairs has to depend on both keys and values, changing any value in the input table will cause a recalculation for that key-value pair. Inversely, ForPairs won't recalculate any key-value pairs that stay the same. Instead, these will be preserved in the output table. If you don't need the keys or the values, Fusion can offer better optimisations. For example, if you're working with an array of values where position doesn't matter, ForValues can move values between keys. Alternatively, if you're working with a set of objects stored in keys, and don't need the values in the table, ForKeys will ignore the values for optimal performance.","title":"Usage"},{"location":"tutorials/tables/forvalues/","text":"ForValues is a state object that processes values from another table. It supports both constants and state objects. local numbers = { 1 , 2 , 3 , 4 , 5 } local multiplier = Value ( 2 ) local multiplied = ForValues ( numbers , function ( use , num ) return num * use ( multiplier ) end ) print ( peek ( multiplied )) --> {2, 4, 6, 8, 10} multiplier : set ( 10 ) print ( peek ( multiplied )) --> {10, 20, 30, 40, 50} Usage \u00b6 To create a new ForValues object, call the constructor with an input table and a processor function. The first two arguments are use and scope , just like computed objects . The third argument is one of the values read from the input table. local numbers = { 1 , 2 , 3 , 4 , 5 } local doubled = scope : ForValues ( numbers , function ( use , scope , num ) return num * 2 end ) You can read the table of processed values using peek() : local numbers = { 1 , 2 , 3 , 4 , 5 } local doubled = scope : ForValues ( numbers , function ( use , scope , num ) return num * 2 end ) print ( peek ( doubled )) --> {2, 4, 6, 8, 10} The input table can be a state object. When the input table changes, the output will update. local numbers = scope : Value ({}) local doubled = scope : ForValues ( numbers , function ( use , scope , num ) return num * 2 end ) numbers : set ({ 1 , 2 , 3 , 4 , 5 }) print ( peek ( doubled )) --> {2, 4, 6, 8, 10} numbers : set ({ 5 , 15 , 25 }) print ( peek ( doubled )) --> {10, 30, 50} You can also use() state objects in your calculations, just like a computed. local numbers = { 1 , 2 , 3 , 4 , 5 } local factor = scope : Value ( 2 ) local multiplied = scope : ForValues ( numbers , function ( use , scope , num ) return num * use ( factor ) end ) print ( peek ( multiplied )) --> {2, 4, 6, 8, 10} factor : set ( 10 ) print ( peek ( multiplied )) --> {10, 20, 30, 40, 50} Anything added to the scope is cleaned up for you when the processed value is removed. local names = scope : Value ({ \"Jodi\" , \"Amber\" , \"Umair\" }) local shoutingNames = scope : ForValues ( names , function ( use , scope , name ) table.insert ( scope , function () print ( \"Goodbye, \" .. name .. \"!\" ) end ) return string.upper ( name ) end ) names : set ({ \"Amber\" , \"Umair\" }) --> Goodbye, Jodi! How ForValues optimises your code Rather than creating a new output table from scratch every time the input table is changed, ForValues will try and reuse as much as possible to improve performance. Say you're measuring the lengths of an array of words: local words = scope : Value ({ \"Orange\" , \"Red\" , \"Magenta\" }) local lengths = scope : ForValues ( words , function ( use , scope , word ) return # word end ) print ( peek ( lengths )) --> {6, 3, 7} The word lengths don't depend on the position of the word in the array. This means that rearranging the words in the input array will just rearrange the lengths in the output array: ForValues takes advantage of this - when input values move around, the output values will move around too, instead of being recalculated. Note that values are only reused once. For example, if you added another occurence of 'Orange', your calculation would have to run again for the second 'Orange':","title":"ForValues"},{"location":"tutorials/tables/forvalues/#usage","text":"To create a new ForValues object, call the constructor with an input table and a processor function. The first two arguments are use and scope , just like computed objects . The third argument is one of the values read from the input table. local numbers = { 1 , 2 , 3 , 4 , 5 } local doubled = scope : ForValues ( numbers , function ( use , scope , num ) return num * 2 end ) You can read the table of processed values using peek() : local numbers = { 1 , 2 , 3 , 4 , 5 } local doubled = scope : ForValues ( numbers , function ( use , scope , num ) return num * 2 end ) print ( peek ( doubled )) --> {2, 4, 6, 8, 10} The input table can be a state object. When the input table changes, the output will update. local numbers = scope : Value ({}) local doubled = scope : ForValues ( numbers , function ( use , scope , num ) return num * 2 end ) numbers : set ({ 1 , 2 , 3 , 4 , 5 }) print ( peek ( doubled )) --> {2, 4, 6, 8, 10} numbers : set ({ 5 , 15 , 25 }) print ( peek ( doubled )) --> {10, 30, 50} You can also use() state objects in your calculations, just like a computed. local numbers = { 1 , 2 , 3 , 4 , 5 } local factor = scope : Value ( 2 ) local multiplied = scope : ForValues ( numbers , function ( use , scope , num ) return num * use ( factor ) end ) print ( peek ( multiplied )) --> {2, 4, 6, 8, 10} factor : set ( 10 ) print ( peek ( multiplied )) --> {10, 20, 30, 40, 50} Anything added to the scope is cleaned up for you when the processed value is removed. local names = scope : Value ({ \"Jodi\" , \"Amber\" , \"Umair\" }) local shoutingNames = scope : ForValues ( names , function ( use , scope , name ) table.insert ( scope , function () print ( \"Goodbye, \" .. name .. \"!\" ) end ) return string.upper ( name ) end ) names : set ({ \"Amber\" , \"Umair\" }) --> Goodbye, Jodi! How ForValues optimises your code Rather than creating a new output table from scratch every time the input table is changed, ForValues will try and reuse as much as possible to improve performance. Say you're measuring the lengths of an array of words: local words = scope : Value ({ \"Orange\" , \"Red\" , \"Magenta\" }) local lengths = scope : ForValues ( words , function ( use , scope , word ) return # word end ) print ( peek ( lengths )) --> {6, 3, 7} The word lengths don't depend on the position of the word in the array. This means that rearranging the words in the input array will just rearrange the lengths in the output array: ForValues takes advantage of this - when input values move around, the output values will move around too, instead of being recalculated. Note that values are only reused once. For example, if you added another occurence of 'Orange', your calculation would have to run again for the second 'Orange':","title":"Usage"}]} \ No newline at end of file +{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Rediscover the joy of coding. \u00b6 Code is more dynamic, complex and intertwined than ever before. Errors cascade out of control, things update in the wrong order, and it's all connected by difficult, unreadable spaghetti. No longer. Fusion introduces modern 'reactive' concepts for managing code, so you can spend more time getting your logic right, and less time implementing buggy boilerplate code connections. Starting from simple roots, concepts neatly combine and build up with very little learning curve. At every stage, you can robustly guarantee what your code will do, and when you come back in six months, your code is easy to pick back up. Getting started guide Download Scroll down for a quick look at 3 main features. Representing change \u00b6 Fusion introduces \u2018state objects\u2019. They aren\u2019t that complex, but allow you to write dynamic code that\u2019s highly readable, behaves predictably and splits into parts easily. State objects are used to represent changeable or dynamic values in your program. You can peek at their value at any time. -- For example, suppose this function returned a state object. local currentTimeObj = getCurrentTimeStateObject () -- State objects are objects... print ( typeof ( currentTimeObj )) --> table -- ...and you can peek at their value (or \u2018state\u2019) at any time. print ( currentTimeObj : get ()) --> 0.0 task . wait ( 5 ) print ( currentTimeObj : get ()) --> 5.0 You can write out your logic using Fusion's built-in state objects. Here's the two basic ones, Value and Computed: -- This creates a state object that you can set manually. -- You can change its value using myName:set(). local myName = Value ( \"Daniel\" ) -- This creates a state object from a calculation. -- It determines its own value automatically. local myGreeting = Computed ( function () return \"Hello! My name is \" .. myName : get () end ) To watch what a state object does, you can use an Observer. For example, you can run some code when an object changes value. -- This observer watches for when the greeting changes. local myObserver = Observer ( myGreeting ) -- Let\u2019s print out the greeting when there\u2019s a new one. local disconnect = myObserver : onChange ( function () print ( myGreeting : get ()) end ) -- This will run the code above! myName : set ( \"Danny\" ) Building instances \u00b6 Fusion offers comprehensive APIs to build or enrich instances from code, so you can easily integrate with your game scripts. Fusion provides dedicated functions to create and modify instances. They allow you to easily configure your instance in one place. -- This will create a red part in the workspace. local myPart = New \"Part\" { Parent = workspace , BrickColor = BrickColor . Red () } -- This adds on some extras after. Hydrate ( myPart ) { Material = \"Wood\" , Transparency = 0.5 } They offer powerful features to keep all your instance code close together. For example, you can listen for events or add children. -- This will create a rounded button. -- When you click it, it\u2019ll greet you. local myButton = New \"TextButton\" { Text = \"Click me\" , [ OnEvent \"Activated\" ] = function () print ( \"Hello! I\u2019m a button.\" ) end , [ Children ] = New \"UICorner\" { CornerRadius = UDim . new ( 1 , 0 ) } } You can also plug state objects in directly. The instance updates as the state object changes value. -- Creating a state object you can control... local message = Value ( \"Hello!\" ) -- Now you can plug that state object into the Text property. local myLabel = New \"TextLabel\" { Text = message } print ( myLabel . Text ) --> Hello! -- The Text property now responds to changes: message : set ( \"Goodbye!\" ) print ( myLabel . Text ) --> Goodbye! Animating anything \u00b6 Fusion gives you best-in-class tools to animate anything you can think of, completely out of the box. Fusion lets you use tweens or physically based springs to animate any value you want - not just instance properties. -- This could be anything you want, as long as it's a state object. local health = Value ( 100 ) -- Easily make it tween between values... local style = TweenInfo . new ( 0.5 , Enum . EasingStyle . Quad ) local tweenHealth = Tween ( health , style ) -- ...or use spring physics for extra responsiveness. local springHealth = Spring ( health , 30 , 0.9 ) Tween and Spring are state objects, just like anything else that changes in your program. That means it's easy to process them afterwards. -- You can round the animated health to whole numbers. local wholeHealth = Computed ( function () return math . round ( health : get ()) end ) -- You can format it as text and put it in some UI, too. local myText = New \"TextLabel\" { Text = Computed ( function () return \"Health: \" .. wholeHealth : get () end ) } You can even configure your animations using state objects, too. This makes it easy to swap out animations or disable them when needed. -- Define some tweening styles... local TWEEN_FAST = TweenInfo . new ( 0.5 , Enum . EasingStyle . Elastic ) local TWEEN_SLOW = TweenInfo . new ( 2 , Enum . EasingStyle . Sine ) -- Choose more dramatic styles at low health... local style = Computed ( function () return if health : get () < 20 then TWEEN_FAST else TWEEN_SLOW end ) -- Plug it right into your animation! local tweenHealth = Tween ( health , style ) Sparked your curiosity? \u00b6 Those are the core features of Fusion, and they're the foundation of everything - whether it\u2019s complex 3D UI systems, procedural animation, or just a hello world app. It all fits on one page, and that's the magic. You don't have to keep relearning ever-more-complex tools as you scale up from prototype to product. If you'd like to learn in depth, we have a comprehensive beginner's tutorial track , complete with diagrams, examples and code. We would love to welcome you into our warm, vibrant community. Hopefully, we'll see you there :)","title":"Home"},{"location":"#rediscover-the-joy-of-coding","text":"Code is more dynamic, complex and intertwined than ever before. Errors cascade out of control, things update in the wrong order, and it's all connected by difficult, unreadable spaghetti. No longer. Fusion introduces modern 'reactive' concepts for managing code, so you can spend more time getting your logic right, and less time implementing buggy boilerplate code connections. Starting from simple roots, concepts neatly combine and build up with very little learning curve. At every stage, you can robustly guarantee what your code will do, and when you come back in six months, your code is easy to pick back up. Getting started guide Download Scroll down for a quick look at 3 main features.","title":"Rediscover the joy of coding."},{"location":"#representing-change","text":"Fusion introduces \u2018state objects\u2019. They aren\u2019t that complex, but allow you to write dynamic code that\u2019s highly readable, behaves predictably and splits into parts easily. State objects are used to represent changeable or dynamic values in your program. You can peek at their value at any time. -- For example, suppose this function returned a state object. local currentTimeObj = getCurrentTimeStateObject () -- State objects are objects... print ( typeof ( currentTimeObj )) --> table -- ...and you can peek at their value (or \u2018state\u2019) at any time. print ( currentTimeObj : get ()) --> 0.0 task . wait ( 5 ) print ( currentTimeObj : get ()) --> 5.0 You can write out your logic using Fusion's built-in state objects. Here's the two basic ones, Value and Computed: -- This creates a state object that you can set manually. -- You can change its value using myName:set(). local myName = Value ( \"Daniel\" ) -- This creates a state object from a calculation. -- It determines its own value automatically. local myGreeting = Computed ( function () return \"Hello! My name is \" .. myName : get () end ) To watch what a state object does, you can use an Observer. For example, you can run some code when an object changes value. -- This observer watches for when the greeting changes. local myObserver = Observer ( myGreeting ) -- Let\u2019s print out the greeting when there\u2019s a new one. local disconnect = myObserver : onChange ( function () print ( myGreeting : get ()) end ) -- This will run the code above! myName : set ( \"Danny\" )","title":"Representing change"},{"location":"#building-instances","text":"Fusion offers comprehensive APIs to build or enrich instances from code, so you can easily integrate with your game scripts. Fusion provides dedicated functions to create and modify instances. They allow you to easily configure your instance in one place. -- This will create a red part in the workspace. local myPart = New \"Part\" { Parent = workspace , BrickColor = BrickColor . Red () } -- This adds on some extras after. Hydrate ( myPart ) { Material = \"Wood\" , Transparency = 0.5 } They offer powerful features to keep all your instance code close together. For example, you can listen for events or add children. -- This will create a rounded button. -- When you click it, it\u2019ll greet you. local myButton = New \"TextButton\" { Text = \"Click me\" , [ OnEvent \"Activated\" ] = function () print ( \"Hello! I\u2019m a button.\" ) end , [ Children ] = New \"UICorner\" { CornerRadius = UDim . new ( 1 , 0 ) } } You can also plug state objects in directly. The instance updates as the state object changes value. -- Creating a state object you can control... local message = Value ( \"Hello!\" ) -- Now you can plug that state object into the Text property. local myLabel = New \"TextLabel\" { Text = message } print ( myLabel . Text ) --> Hello! -- The Text property now responds to changes: message : set ( \"Goodbye!\" ) print ( myLabel . Text ) --> Goodbye!","title":"Building instances"},{"location":"#animating-anything","text":"Fusion gives you best-in-class tools to animate anything you can think of, completely out of the box. Fusion lets you use tweens or physically based springs to animate any value you want - not just instance properties. -- This could be anything you want, as long as it's a state object. local health = Value ( 100 ) -- Easily make it tween between values... local style = TweenInfo . new ( 0.5 , Enum . EasingStyle . Quad ) local tweenHealth = Tween ( health , style ) -- ...or use spring physics for extra responsiveness. local springHealth = Spring ( health , 30 , 0.9 ) Tween and Spring are state objects, just like anything else that changes in your program. That means it's easy to process them afterwards. -- You can round the animated health to whole numbers. local wholeHealth = Computed ( function () return math . round ( health : get ()) end ) -- You can format it as text and put it in some UI, too. local myText = New \"TextLabel\" { Text = Computed ( function () return \"Health: \" .. wholeHealth : get () end ) } You can even configure your animations using state objects, too. This makes it easy to swap out animations or disable them when needed. -- Define some tweening styles... local TWEEN_FAST = TweenInfo . new ( 0.5 , Enum . EasingStyle . Elastic ) local TWEEN_SLOW = TweenInfo . new ( 2 , Enum . EasingStyle . Sine ) -- Choose more dramatic styles at low health... local style = Computed ( function () return if health : get () < 20 then TWEEN_FAST else TWEEN_SLOW end ) -- Plug it right into your animation! local tweenHealth = Tween ( health , style )","title":"Animating anything"},{"location":"#sparked-your-curiosity","text":"Those are the core features of Fusion, and they're the foundation of everything - whether it\u2019s complex 3D UI systems, procedural animation, or just a hello world app. It all fits on one page, and that's the magic. You don't have to keep relearning ever-more-complex tools as you scale up from prototype to product. If you'd like to learn in depth, we have a comprehensive beginner's tutorial track , complete with diagrams, examples and code. We would love to welcome you into our warm, vibrant community. Hopefully, we'll see you there :)","title":"Sparked your curiosity?"},{"location":"api-reference/","text":"API Reference \u00b6 Welcome to the API Reference! This is where you can find more technical documentation about what the Fusion library provides. For a beginner-friendly experience, try the tutorials. Most Popular \u00b6 General \u00b6 Errors Contextual Memory \u00b6 Scope deriveScope doCleanup scoped State \u00b6 UsedAs Computed Observer peek Value Roblox \u00b6 Child Children Hydrate New Animation \u00b6 Animatable Spring Tween","title":"API Reference"},{"location":"api-reference/#api-reference","text":"Welcome to the API Reference! This is where you can find more technical documentation about what the Fusion library provides. For a beginner-friendly experience, try the tutorials.","title":"API Reference"},{"location":"api-reference/#most-popular","text":"","title":"Most Popular"},{"location":"api-reference/#general","text":"Errors Contextual","title":"General"},{"location":"api-reference/#memory","text":"Scope deriveScope doCleanup scoped","title":"Memory"},{"location":"api-reference/#state","text":"UsedAs Computed Observer peek Value","title":"State"},{"location":"api-reference/#roblox","text":"Child Children Hydrate New","title":"Roblox"},{"location":"api-reference/#animation","text":"Animatable Spring Tween","title":"Animation"},{"location":"api-reference/animation/members/spring/","text":"Animation Members Spring Spring -> Spring \u00b6 function Fusion . Spring < T > ( scope : Scope < unknown > , goal : UsedAs < T > , speed : UsedAs < number > ? , damping : UsedAs < number > ? ) -> Spring < T > Constructs and returns a new spring state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local spring = scope : Spring ( goal , speed , damping ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. goal : UsedAs \u00b6 The goal that this object should follow. For best results, the goal should be animatable . speed : UsedAs ? \u00b6 Multiplies how fast the motion should occur; doubling the speed exactly halves the time it takes for the motion to complete. damping : UsedAs ? \u00b6 The amount of resistance the motion encounters. 0 represents no resistance, 1 is just enough resistance to prevent overshoot (critical damping), and larger values damp out inertia effects and straighten the motion. Returns -> Spring \u00b6 A freshly constructed spring state object. Learn More \u00b6 Springs tutorial","title":"Spring"},{"location":"api-reference/animation/members/spring/#spring-springt","text":"function Fusion . Spring < T > ( scope : Scope < unknown > , goal : UsedAs < T > , speed : UsedAs < number > ? , damping : UsedAs < number > ? ) -> Spring < T > Constructs and returns a new spring state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local spring = scope : Spring ( goal , speed , damping )","title":"Spring -> Spring<T>"},{"location":"api-reference/animation/members/spring/#parameters","text":"","title":"Parameters"},{"location":"api-reference/animation/members/spring/#scope-scopes","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<S>"},{"location":"api-reference/animation/members/spring/#goal-usedast","text":"The goal that this object should follow. For best results, the goal should be animatable .","title":"goal : UsedAs<T>"},{"location":"api-reference/animation/members/spring/#speed-usedast","text":"Multiplies how fast the motion should occur; doubling the speed exactly halves the time it takes for the motion to complete.","title":"speed : UsedAs<T>?"},{"location":"api-reference/animation/members/spring/#damping-usedast","text":"The amount of resistance the motion encounters. 0 represents no resistance, 1 is just enough resistance to prevent overshoot (critical damping), and larger values damp out inertia effects and straighten the motion.","title":"damping : UsedAs<T>?"},{"location":"api-reference/animation/members/spring/#returns-springt","text":"A freshly constructed spring state object.","title":"Returns -> Spring<T>"},{"location":"api-reference/animation/members/spring/#learn-more","text":"Springs tutorial","title":"Learn More"},{"location":"api-reference/animation/members/tween/","text":"Animation Members Tween Tween -> Tween \u00b6 function Fusion . Tween < T > ( scope : Scope < unknown > , goal : StateObject < T > , tweenInfo : UsedAs < TweenInfo > ? ) -> Tween < T > Constructs and returns a new tween state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local tween = scope : Tween ( goal , info ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. goal : UsedAs \u00b6 The goal that this object should follow. For best results, the goal should be animatable . info : UsedAs ? \u00b6 Determines the easing curve that the motion will follow. Returns -> Tween \u00b6 A freshly constructed tween state object. Learn More \u00b6 Tweens tutorial","title":"Tween"},{"location":"api-reference/animation/members/tween/#tween-tweent","text":"function Fusion . Tween < T > ( scope : Scope < unknown > , goal : StateObject < T > , tweenInfo : UsedAs < TweenInfo > ? ) -> Tween < T > Constructs and returns a new tween state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local tween = scope : Tween ( goal , info )","title":"Tween -> Tween<T>"},{"location":"api-reference/animation/members/tween/#parameters","text":"","title":"Parameters"},{"location":"api-reference/animation/members/tween/#scope-scopes","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<S>"},{"location":"api-reference/animation/members/tween/#goal-usedast","text":"The goal that this object should follow. For best results, the goal should be animatable .","title":"goal : UsedAs<T>"},{"location":"api-reference/animation/members/tween/#info-usedastweeninfo","text":"Determines the easing curve that the motion will follow.","title":"info : UsedAs<TweenInfo>?"},{"location":"api-reference/animation/members/tween/#returns-tweent","text":"A freshly constructed tween state object.","title":"Returns -> Tween<T>"},{"location":"api-reference/animation/members/tween/#learn-more","text":"Tweens tutorial","title":"Learn More"},{"location":"api-reference/animation/types/animatable/","text":"Animation Types Animatable Animatable \u00b6 export type Animatable = number | CFrame | Color3 | ColorSequenceKeypoint | DateTime | NumberRange | NumberSequenceKeypoint | PhysicalProperties | Ray | Rect | Region3 | Region3int16 | UDim | UDim2 | Vector2 | Vector2int16 | Vector3 | Vector3int16 Any data type that Fusion can decompose into a tuple of animatable parameters. Passing other types to animation objects Other types can be passed to Tween and Spring objects, however those types will not animate. Instead, non- Animatable types will immediately arrive at their goal value. Learn More \u00b6 Tweens tutorial Springs tutorial","title":"Animatable"},{"location":"api-reference/animation/types/animatable/#animatable","text":"export type Animatable = number | CFrame | Color3 | ColorSequenceKeypoint | DateTime | NumberRange | NumberSequenceKeypoint | PhysicalProperties | Ray | Rect | Region3 | Region3int16 | UDim | UDim2 | Vector2 | Vector2int16 | Vector3 | Vector3int16 Any data type that Fusion can decompose into a tuple of animatable parameters. Passing other types to animation objects Other types can be passed to Tween and Spring objects, however those types will not animate. Instead, non- Animatable types will immediately arrive at their goal value.","title":"Animatable"},{"location":"api-reference/animation/types/animatable/#learn-more","text":"Tweens tutorial Springs tutorial","title":"Learn More"},{"location":"api-reference/animation/types/spring/","text":"Animation Types Spring Spring \u00b6 export type Spring < T > = StateObject < T > & Dependent & { kind : \"Spring\" , setPosition : ( self , newPosition : T ) -> (), setVelocity : ( self , newVelocity : T ) -> (), addVelocity : ( self , deltaVelocity : T ) -> () } A specialised state object for following a goal state smoothly over time, using physics to shape the motion. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from the goal state. The methods on this type allow for direct control over the position and velocity of the motion. Other than that, this type is of limited utility outside of Fusion itself. Members \u00b6 kind : \"Spring\" \u00b6 A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart. Methods \u00b6 setPosition -> () \u00b6 function Spring : setPosition ( newPosition : T ): () Immediately snaps the spring to the given position. The position must have the same typeof() as the goal state. setVelocity -> () \u00b6 function Spring : setVelocity ( newVelocity : T ): () Overwrites the spring's velocity without changing its position. The velocity must have the same typeof() as the goal state. addVelocity -> () \u00b6 function Spring : addVelocity ( deltaVelocity : T ): () Appends to the spring's velocity without changing its position. The velocity must have the same typeof() as the goal state. Learn More \u00b6 Springs tutorial","title":"Spring"},{"location":"api-reference/animation/types/spring/#spring","text":"export type Spring < T > = StateObject < T > & Dependent & { kind : \"Spring\" , setPosition : ( self , newPosition : T ) -> (), setVelocity : ( self , newVelocity : T ) -> (), addVelocity : ( self , deltaVelocity : T ) -> () } A specialised state object for following a goal state smoothly over time, using physics to shape the motion. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from the goal state. The methods on this type allow for direct control over the position and velocity of the motion. Other than that, this type is of limited utility outside of Fusion itself.","title":"Spring"},{"location":"api-reference/animation/types/spring/#members","text":"","title":"Members"},{"location":"api-reference/animation/types/spring/#kind-spring","text":"A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart.","title":"kind : \"Spring\""},{"location":"api-reference/animation/types/spring/#methods","text":"","title":"Methods"},{"location":"api-reference/animation/types/spring/#setposition-","text":"function Spring : setPosition ( newPosition : T ): () Immediately snaps the spring to the given position. The position must have the same typeof() as the goal state.","title":"setPosition -> ()"},{"location":"api-reference/animation/types/spring/#setvelocity-","text":"function Spring : setVelocity ( newVelocity : T ): () Overwrites the spring's velocity without changing its position. The velocity must have the same typeof() as the goal state.","title":"setVelocity -> ()"},{"location":"api-reference/animation/types/spring/#addvelocity-","text":"function Spring : addVelocity ( deltaVelocity : T ): () Appends to the spring's velocity without changing its position. The velocity must have the same typeof() as the goal state.","title":"addVelocity -> ()"},{"location":"api-reference/animation/types/spring/#learn-more","text":"Springs tutorial","title":"Learn More"},{"location":"api-reference/animation/types/tween/","text":"Animation Types Tween Tween \u00b6 export type Tween < T > = StateObject < T > & Dependent & { kind : \"Tween\" } A specialised state object for following a goal state smoothly over time, using a TweenInfo to shape the motion. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from the goal state. This type isn't generally useful outside of Fusion itself. Members \u00b6 kind : \"Tween\" \u00b6 A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart. Learn More \u00b6 Tweens tutorial","title":"Tween"},{"location":"api-reference/animation/types/tween/#tween","text":"export type Tween < T > = StateObject < T > & Dependent & { kind : \"Tween\" } A specialised state object for following a goal state smoothly over time, using a TweenInfo to shape the motion. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from the goal state. This type isn't generally useful outside of Fusion itself.","title":"Tween"},{"location":"api-reference/animation/types/tween/#members","text":"","title":"Members"},{"location":"api-reference/animation/types/tween/#kind-tween","text":"A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart.","title":"kind : \"Tween\""},{"location":"api-reference/animation/types/tween/#learn-more","text":"Tweens tutorial","title":"Learn More"},{"location":"api-reference/general/errors/","text":"General Errors Errors \u00b6 Whenever Fusion outputs any errors or messages to the console, it will have a short error ID at the end. This is used to uniquely identify what kind of error or message you're seeing. Use the search box below to paste in or type an error ID, and it will scroll to the details for you. callbackError \u00b6 Error in callback: attempt to perform arithmetic (add) on number and string Thrown by: Computed , ForKeys , ForValues , ForPairs , Contextual Fusion ran a function you specified, but the function threw an error that Fusion couldn't handle. The error includes a more specific message which can be used to diagnose the issue. cannotAssignProperty \u00b6 The class type 'Foo' has no assignable property 'Bar'. Thrown by: New , Hydrate You tried to set a property on an instance, but the property can't be assigned to for some reason. This could be because the property doesn't exist, or because it's locked by Roblox to prevent edits. Check your privileges Different scripts may have different privileges - for example, plugins will be allowed more privileges than in-game scripts. Make sure you have the necessary privileges to assign to your properties! cannotConnectChange \u00b6 The Frame class doesn't have a property called 'Foo'. Thrown by: OnChange You tried to connect to a property change event, but the property you specify doesn't exist on the instance. cannotConnectEvent \u00b6 The Frame class doesn't have an event called 'Foo'. Thrown by: OnEvent You tried to connect to an event on an instance, but the event you specify doesn't exist on the instance. cannotCreateClass \u00b6 Can't create a new instance of class 'EditableImage'. Thrown by: New You attempted to create a type of instance that Fusion can't create. Beta features Some instances are only creatable when you have certain Studio betas enabled. Check your Beta Features tab to ensure that beta features aren't causing the issue. cleanupWasRenamed \u00b6 `Fusion.cleanup` was renamed to `Fusion.doCleanup`. This will be an error in future versions of Fusion. Thrown by: doCleanup You attempted to use cleanup() in Fusion 0.3, which replaces it with the doCleanup() method. destroyedTwice \u00b6 Attempted to destroy Computed twice; ensure you're not manually calling `:destroy()` while using scopes. See discussion #292 on GitHub for advice. Thrown by: Value , Computed , Observer , ForKeys , ForValues , ForPairs , Spring , Tween Related discussions: #292 The :destroy() method of the object in question was called more than once. This usually means you called :destroy() manually, which is almost never required because Fusion's constructors always link objects to scopes . When that scope is passed to doCleanup() , the :destroy() method is called on every object inside. destructorRedundant \u00b6 Computed destructors no longer do anything. If you wish to run code on destroy, `table.insert` a function into the `scope` argument. See discussion #292 on GitHub for advice. Thrown by: Computed , ForKeys , ForValues , ForPairs Related discussions: #292 You passed an extra parameter to the constructor, which has historically been interpreted as a function that runs when a value is cleaned up. This mechanism has been replaced by scopes . forKeyCollision \u00b6 The key '6' was returned multiple times simultaneously, which is not allowed in `For` objects. Thrown by: ForKeys , ForPairs When called with different items from the table, the same key was returned for both of them. This is not allowed, because keys have to be unique in a table. invalidAttributeChangeHandler \u00b6 The change handler for the 'Active' attribute must be a function. Thrown by: AttributeChange AttributeChange expected you to provide a function for it to run when the attribute changes, but you provided something other than a function. For example, you might have accidentally provided nil . invalidAttributeOutType \u00b6 [AttributeOut] properties must be given Value objects. Thrown by: AttributeOut AttributeOut expected you to give it a value , but you gave it something else. invalidChangeHandler \u00b6 The change handler for the 'AbsoluteSize' property must be a function. Thrown by: OnChange OnChange expected you to provide a function for it to run when the property changes, but you provided something other than a function. For example, you might have accidentally provided nil . invalidEventHandler \u00b6 The handler for the 'MouseEnter' event must be a function. Thrown by: OnEvent OnEvent expected you to provide a function for it to run when the event is fired, but you provided something other than a function. For example, you might have accidentally provided nil . invalidOutProperty \u00b6 The Frame class doesn't have a property called 'MouseButton1Down'. Thrown by: Out The property that you tried to output doesn't exist on the instance that Out was used with. invalidOutType \u00b6 [Out] properties must be given Value objects. Thrown by: Out Out expected you to give it a value , but you gave it something else. invalidPropertyType \u00b6 'Frame.BackgroundColor3' expected a 'Color3' type, but got a 'Vector3' type. Thrown by: New , Hydrate You attempted to assign a value to a Roblox instance's property, but the assignment threw an error because that property doesn't accept values of that type. invalidRefType \u00b6 Instance refs must be Value objects. Thrown by: Ref Ref expected you to give it a value , but you gave it something else. invalidSpringDamping \u00b6 The damping ratio for a spring must be >= 0. (damping was -1.00) Thrown by: Spring You provided a damping ratio that the spring doesn't support, for example NaN , or a negative damping implying negative friction. invalidSpringSpeed \u00b6 The speed of a spring must be >= 0. (speed was NaN) Thrown by: Spring You provided a speed multiplier that the spring doesn't support, for example NaN or a negative speed implying the spring moves backwards through time. mergeConflict \u00b6 Multiple definitions for 'Observer' found while merging. Thrown by: scoped Fusion tried to merge together multiple tables, but a key was found in more than one of the tables, and it's unclear which one you intended to have in the final merged result. This can happen subtly with methods such as scoped() which automatically merge together all of their arguments. mistypedSpringDamping \u00b6 The damping ratio for a spring must be a number. (got a string) Thrown by: Spring You provided a damping ratio that the spring couldn't understand. Damping ratio has to be a number. mistypedSpringSpeed \u00b6 The speed of a spring must be a number. (got a string) Thrown by: Spring You provided a speed multiplier that the spring couldn't understand. Speed has to be a number. mistypedTweenInfo \u00b6 The tween info of a tween must be a TweenInfo. (got a table) Thrown by: Tween You provided an easing curve that the tween couldn't understand. The easing curve has to be specified using Roblox's TweenInfo data type. noTaskScheduler \u00b6 Fusion is not connected to an external task scheduler. Fusion depends on a task scheduler being present to perform certain time-related tasks such as deferral, delays, or updating animations. You'll need to define a set of standard task scheduler functions that Fusion can use for those purposes. Roblox users should never see this error, as Fusion automatically connects to Roblox's task scheduling APIs. possiblyOutlives \u00b6 The Value object could be destroyed before the Computed that is use()-ing it; review the order they're created in, and what scopes they belong to. See discussion #292 on GitHub for advice. Thrown by: Spring , Tween , New , Hydrate , Attribute , AttributeOut , Out , Ref , Computed , Observer Related discussions: #292 If you use an object after it's been destroyed, then your code can break. This mainly happens when one object 'outlives' another object that it's using. Because scopes clean up the newest objects first, this can happen when an old object depends on something much newer that itself. During cleanup, a situation could arise where the newer object is destroyed, then the older object runs code of some kind that needed the newer object to be there. Fusion can check for situations like this by analysing the scopes. This message is shown when Fusion can prove one of these situations will occur. There are two typical solutions: If the objects should always be created and destroyed at the exact same time, then ensure they're created in the correct order. Otherwise, move the objects into separate scopes, and ensure that both scopes can exist without the other scope. propertySetError \u00b6 Error setting property: UIAspectRatioConstraint.AspectRatio set to a non-positive value. Value must be a positive. Thrown by: New , Hydrate You attempted to set a property, but Roblox threw an error in response. The error includes a more specific message which can be used to diagnose the issue. scopeMissing \u00b6 To create Observers, provide a scope. (e.g. `myScope:Observer(watching)`). See discussion #292 on GitHub for advice. Thrown by: New , Hydrate , Value , Computed , Observer , ForKeys , ForValues , ForPairs , Spring , Tween Related discussions: #292 You attempted to create an object without providing a scope as the first parameter. Scopes are mandatory for all Fusion constructors so that Fusion knows when the object should be destroyed. springNanGoal \u00b6 A spring was given a NaN goal, so some simulation has been skipped. Ensure no springs have NaN goals. Thrown by: Spring The goal parameter given to the spring during construction contained one or more NaN values. This typically occurs when zero is accidentally divided by zero, or some other invalid mathematical operation has occurred. Check that your code is free of maths errors, and handles all edge cases. springNanMotion \u00b6 A spring encountered NaN during motion, so has snapped to the goal position. Ensure no springs have NaN positions or velocities. Thrown by: Spring While calculating updated position and velocity, one or both of those values ended up as NaN. This typically occurs when zero is accidentally divided by zero, or some other invalid mathematical operation has occurred. Check that your code is free of maths errors, and handles all edge cases. springTypeMismatch \u00b6 The type 'Vector3' doesn't match the spring's type 'Color3'. Thrown by: Spring The spring expected you to provide a type matching the data type that the spring is currently outputting. However, you provided a different data type. stateGetWasRemoved \u00b6 `StateObject:get()` has been replaced by `use()` and `peek()` - see discussion #217 on GitHub. Thrown by: Value , Computed , ForKeys , ForValues , ForPairs , Spring , Tween Related discussions: #217 Older versions of Fusion let you call :get() directly on state objects to read their current value and attempt to infer dependencies. This has been replaced by use functions in Fusion 0.3 for more predictable behaviour and better support for constant values. unknownMessage \u00b6 Unknown error: attempt to call a nil value Fusion ran into a problem, but couldn't associate it with a valid type of error. This is a fallback error type which shouldn't be seen by end users, because it indicates that Fusion code isn't reporting errors correctly. unrecognisedChildType \u00b6 'string' type children aren't accepted by `[Children]`. Thrown by: Children You provided a value inside of [Children] which didn't meet the definition of a child value. Check that you're only passing instances, arrays and state objects. unrecognisedPropertyKey \u00b6 'number' keys aren't accepted in property tables. Thrown by: New , Hydrate You provided something other than a property assignment ( Property = Value ) or special key in your property table. Most commonly, this means you tried to add child instances directly into the property table, rather than passing them into the [Children] special key. unrecognisedPropertyStage \u00b6 'children' isn't a valid stage for a special key to be applied at. Thrown by: New , Hydrate You attempted to use a special key which has a misconfigured stage , so Fusion didn't know when to apply it during instance construction. useAfterDestroy \u00b6 The Value object is no longer valid - it was destroyed before the Computed that is use()-ing. See discussion #292 on GitHub for advice. Thrown by: Spring , Tween , New , Hydrate , Attribute , AttributeOut , Out , Ref , Computed , Observer Related discussions: #292 Your code attempted to access an object after that object was destroyed, either because its :destroy() method was called manually, or because the object's scope was cleaned up. Make sure your objects are being added to the correct scopes according to when you expect them to be destroyed. Additionally, make sure your code can detect and deal with situations where other objects are no longer available.","title":"Errors"},{"location":"api-reference/general/errors/#errors","text":"Whenever Fusion outputs any errors or messages to the console, it will have a short error ID at the end. This is used to uniquely identify what kind of error or message you're seeing. Use the search box below to paste in or type an error ID, and it will scroll to the details for you.","title":"Errors"},{"location":"api-reference/general/errors/#callbackerror","text":"Error in callback: attempt to perform arithmetic (add) on number and string Thrown by: Computed , ForKeys , ForValues , ForPairs , Contextual Fusion ran a function you specified, but the function threw an error that Fusion couldn't handle. The error includes a more specific message which can be used to diagnose the issue.","title":"callbackError"},{"location":"api-reference/general/errors/#cannotassignproperty","text":"The class type 'Foo' has no assignable property 'Bar'. Thrown by: New , Hydrate You tried to set a property on an instance, but the property can't be assigned to for some reason. This could be because the property doesn't exist, or because it's locked by Roblox to prevent edits. Check your privileges Different scripts may have different privileges - for example, plugins will be allowed more privileges than in-game scripts. Make sure you have the necessary privileges to assign to your properties!","title":"cannotAssignProperty"},{"location":"api-reference/general/errors/#cannotconnectchange","text":"The Frame class doesn't have a property called 'Foo'. Thrown by: OnChange You tried to connect to a property change event, but the property you specify doesn't exist on the instance.","title":"cannotConnectChange"},{"location":"api-reference/general/errors/#cannotconnectevent","text":"The Frame class doesn't have an event called 'Foo'. Thrown by: OnEvent You tried to connect to an event on an instance, but the event you specify doesn't exist on the instance.","title":"cannotConnectEvent"},{"location":"api-reference/general/errors/#cannotcreateclass","text":"Can't create a new instance of class 'EditableImage'. Thrown by: New You attempted to create a type of instance that Fusion can't create. Beta features Some instances are only creatable when you have certain Studio betas enabled. Check your Beta Features tab to ensure that beta features aren't causing the issue.","title":"cannotCreateClass"},{"location":"api-reference/general/errors/#cleanupwasrenamed","text":"`Fusion.cleanup` was renamed to `Fusion.doCleanup`. This will be an error in future versions of Fusion. Thrown by: doCleanup You attempted to use cleanup() in Fusion 0.3, which replaces it with the doCleanup() method.","title":"cleanupWasRenamed"},{"location":"api-reference/general/errors/#destroyedtwice","text":"Attempted to destroy Computed twice; ensure you're not manually calling `:destroy()` while using scopes. See discussion #292 on GitHub for advice. Thrown by: Value , Computed , Observer , ForKeys , ForValues , ForPairs , Spring , Tween Related discussions: #292 The :destroy() method of the object in question was called more than once. This usually means you called :destroy() manually, which is almost never required because Fusion's constructors always link objects to scopes . When that scope is passed to doCleanup() , the :destroy() method is called on every object inside.","title":"destroyedTwice"},{"location":"api-reference/general/errors/#destructorredundant","text":"Computed destructors no longer do anything. If you wish to run code on destroy, `table.insert` a function into the `scope` argument. See discussion #292 on GitHub for advice. Thrown by: Computed , ForKeys , ForValues , ForPairs Related discussions: #292 You passed an extra parameter to the constructor, which has historically been interpreted as a function that runs when a value is cleaned up. This mechanism has been replaced by scopes .","title":"destructorRedundant"},{"location":"api-reference/general/errors/#forkeycollision","text":"The key '6' was returned multiple times simultaneously, which is not allowed in `For` objects. Thrown by: ForKeys , ForPairs When called with different items from the table, the same key was returned for both of them. This is not allowed, because keys have to be unique in a table.","title":"forKeyCollision"},{"location":"api-reference/general/errors/#invalidattributechangehandler","text":"The change handler for the 'Active' attribute must be a function. Thrown by: AttributeChange AttributeChange expected you to provide a function for it to run when the attribute changes, but you provided something other than a function. For example, you might have accidentally provided nil .","title":"invalidAttributeChangeHandler"},{"location":"api-reference/general/errors/#invalidattributeouttype","text":"[AttributeOut] properties must be given Value objects. Thrown by: AttributeOut AttributeOut expected you to give it a value , but you gave it something else.","title":"invalidAttributeOutType"},{"location":"api-reference/general/errors/#invalidchangehandler","text":"The change handler for the 'AbsoluteSize' property must be a function. Thrown by: OnChange OnChange expected you to provide a function for it to run when the property changes, but you provided something other than a function. For example, you might have accidentally provided nil .","title":"invalidChangeHandler"},{"location":"api-reference/general/errors/#invalideventhandler","text":"The handler for the 'MouseEnter' event must be a function. Thrown by: OnEvent OnEvent expected you to provide a function for it to run when the event is fired, but you provided something other than a function. For example, you might have accidentally provided nil .","title":"invalidEventHandler"},{"location":"api-reference/general/errors/#invalidoutproperty","text":"The Frame class doesn't have a property called 'MouseButton1Down'. Thrown by: Out The property that you tried to output doesn't exist on the instance that Out was used with.","title":"invalidOutProperty"},{"location":"api-reference/general/errors/#invalidouttype","text":"[Out] properties must be given Value objects. Thrown by: Out Out expected you to give it a value , but you gave it something else.","title":"invalidOutType"},{"location":"api-reference/general/errors/#invalidpropertytype","text":"'Frame.BackgroundColor3' expected a 'Color3' type, but got a 'Vector3' type. Thrown by: New , Hydrate You attempted to assign a value to a Roblox instance's property, but the assignment threw an error because that property doesn't accept values of that type.","title":"invalidPropertyType"},{"location":"api-reference/general/errors/#invalidreftype","text":"Instance refs must be Value objects. Thrown by: Ref Ref expected you to give it a value , but you gave it something else.","title":"invalidRefType"},{"location":"api-reference/general/errors/#invalidspringdamping","text":"The damping ratio for a spring must be >= 0. (damping was -1.00) Thrown by: Spring You provided a damping ratio that the spring doesn't support, for example NaN , or a negative damping implying negative friction.","title":"invalidSpringDamping"},{"location":"api-reference/general/errors/#invalidspringspeed","text":"The speed of a spring must be >= 0. (speed was NaN) Thrown by: Spring You provided a speed multiplier that the spring doesn't support, for example NaN or a negative speed implying the spring moves backwards through time.","title":"invalidSpringSpeed"},{"location":"api-reference/general/errors/#mergeconflict","text":"Multiple definitions for 'Observer' found while merging. Thrown by: scoped Fusion tried to merge together multiple tables, but a key was found in more than one of the tables, and it's unclear which one you intended to have in the final merged result. This can happen subtly with methods such as scoped() which automatically merge together all of their arguments.","title":"mergeConflict"},{"location":"api-reference/general/errors/#mistypedspringdamping","text":"The damping ratio for a spring must be a number. (got a string) Thrown by: Spring You provided a damping ratio that the spring couldn't understand. Damping ratio has to be a number.","title":"mistypedSpringDamping"},{"location":"api-reference/general/errors/#mistypedspringspeed","text":"The speed of a spring must be a number. (got a string) Thrown by: Spring You provided a speed multiplier that the spring couldn't understand. Speed has to be a number.","title":"mistypedSpringSpeed"},{"location":"api-reference/general/errors/#mistypedtweeninfo","text":"The tween info of a tween must be a TweenInfo. (got a table) Thrown by: Tween You provided an easing curve that the tween couldn't understand. The easing curve has to be specified using Roblox's TweenInfo data type.","title":"mistypedTweenInfo"},{"location":"api-reference/general/errors/#notaskscheduler","text":"Fusion is not connected to an external task scheduler. Fusion depends on a task scheduler being present to perform certain time-related tasks such as deferral, delays, or updating animations. You'll need to define a set of standard task scheduler functions that Fusion can use for those purposes. Roblox users should never see this error, as Fusion automatically connects to Roblox's task scheduling APIs.","title":"noTaskScheduler"},{"location":"api-reference/general/errors/#possiblyoutlives","text":"The Value object could be destroyed before the Computed that is use()-ing it; review the order they're created in, and what scopes they belong to. See discussion #292 on GitHub for advice. Thrown by: Spring , Tween , New , Hydrate , Attribute , AttributeOut , Out , Ref , Computed , Observer Related discussions: #292 If you use an object after it's been destroyed, then your code can break. This mainly happens when one object 'outlives' another object that it's using. Because scopes clean up the newest objects first, this can happen when an old object depends on something much newer that itself. During cleanup, a situation could arise where the newer object is destroyed, then the older object runs code of some kind that needed the newer object to be there. Fusion can check for situations like this by analysing the scopes. This message is shown when Fusion can prove one of these situations will occur. There are two typical solutions: If the objects should always be created and destroyed at the exact same time, then ensure they're created in the correct order. Otherwise, move the objects into separate scopes, and ensure that both scopes can exist without the other scope.","title":"possiblyOutlives"},{"location":"api-reference/general/errors/#propertyseterror","text":"Error setting property: UIAspectRatioConstraint.AspectRatio set to a non-positive value. Value must be a positive. Thrown by: New , Hydrate You attempted to set a property, but Roblox threw an error in response. The error includes a more specific message which can be used to diagnose the issue.","title":"propertySetError"},{"location":"api-reference/general/errors/#scopemissing","text":"To create Observers, provide a scope. (e.g. `myScope:Observer(watching)`). See discussion #292 on GitHub for advice. Thrown by: New , Hydrate , Value , Computed , Observer , ForKeys , ForValues , ForPairs , Spring , Tween Related discussions: #292 You attempted to create an object without providing a scope as the first parameter. Scopes are mandatory for all Fusion constructors so that Fusion knows when the object should be destroyed.","title":"scopeMissing"},{"location":"api-reference/general/errors/#springnangoal","text":"A spring was given a NaN goal, so some simulation has been skipped. Ensure no springs have NaN goals. Thrown by: Spring The goal parameter given to the spring during construction contained one or more NaN values. This typically occurs when zero is accidentally divided by zero, or some other invalid mathematical operation has occurred. Check that your code is free of maths errors, and handles all edge cases.","title":"springNanGoal"},{"location":"api-reference/general/errors/#springnanmotion","text":"A spring encountered NaN during motion, so has snapped to the goal position. Ensure no springs have NaN positions or velocities. Thrown by: Spring While calculating updated position and velocity, one or both of those values ended up as NaN. This typically occurs when zero is accidentally divided by zero, or some other invalid mathematical operation has occurred. Check that your code is free of maths errors, and handles all edge cases.","title":"springNanMotion"},{"location":"api-reference/general/errors/#springtypemismatch","text":"The type 'Vector3' doesn't match the spring's type 'Color3'. Thrown by: Spring The spring expected you to provide a type matching the data type that the spring is currently outputting. However, you provided a different data type.","title":"springTypeMismatch"},{"location":"api-reference/general/errors/#stategetwasremoved","text":"`StateObject:get()` has been replaced by `use()` and `peek()` - see discussion #217 on GitHub. Thrown by: Value , Computed , ForKeys , ForValues , ForPairs , Spring , Tween Related discussions: #217 Older versions of Fusion let you call :get() directly on state objects to read their current value and attempt to infer dependencies. This has been replaced by use functions in Fusion 0.3 for more predictable behaviour and better support for constant values.","title":"stateGetWasRemoved"},{"location":"api-reference/general/errors/#unknownmessage","text":"Unknown error: attempt to call a nil value Fusion ran into a problem, but couldn't associate it with a valid type of error. This is a fallback error type which shouldn't be seen by end users, because it indicates that Fusion code isn't reporting errors correctly.","title":"unknownMessage"},{"location":"api-reference/general/errors/#unrecognisedchildtype","text":"'string' type children aren't accepted by `[Children]`. Thrown by: Children You provided a value inside of [Children] which didn't meet the definition of a child value. Check that you're only passing instances, arrays and state objects.","title":"unrecognisedChildType"},{"location":"api-reference/general/errors/#unrecognisedpropertykey","text":"'number' keys aren't accepted in property tables. Thrown by: New , Hydrate You provided something other than a property assignment ( Property = Value ) or special key in your property table. Most commonly, this means you tried to add child instances directly into the property table, rather than passing them into the [Children] special key.","title":"unrecognisedPropertyKey"},{"location":"api-reference/general/errors/#unrecognisedpropertystage","text":"'children' isn't a valid stage for a special key to be applied at. Thrown by: New , Hydrate You attempted to use a special key which has a misconfigured stage , so Fusion didn't know when to apply it during instance construction.","title":"unrecognisedPropertyStage"},{"location":"api-reference/general/errors/#useafterdestroy","text":"The Value object is no longer valid - it was destroyed before the Computed that is use()-ing. See discussion #292 on GitHub for advice. Thrown by: Spring , Tween , New , Hydrate , Attribute , AttributeOut , Out , Ref , Computed , Observer Related discussions: #292 Your code attempted to access an object after that object was destroyed, either because its :destroy() method was called manually, or because the object's scope was cleaned up. Make sure your objects are being added to the correct scopes according to when you expect them to be destroyed. Additionally, make sure your code can detect and deal with situations where other objects are no longer available.","title":"useAfterDestroy"},{"location":"api-reference/general/members/contextual/","text":"General Members Contextual Contextual -> Contextual \u00b6 function Fusion . Contextual < T > ( defaultValue : T ): Contextual < T > Constructs and returns a new contextual . Parameters \u00b6 defaultValue : T \u00b6 The value which Contextual:now() should return if no value has been specified by Contextual:is():during() . Returns -> Contextual \u00b6 A freshly constructed contextual. Learn More \u00b6 Sharing Values tutorial","title":"Contextual"},{"location":"api-reference/general/members/contextual/#contextual-contextualt","text":"function Fusion . Contextual < T > ( defaultValue : T ): Contextual < T > Constructs and returns a new contextual .","title":"Contextual -> Contextual<T>"},{"location":"api-reference/general/members/contextual/#parameters","text":"","title":"Parameters"},{"location":"api-reference/general/members/contextual/#defaultvalue-t","text":"The value which Contextual:now() should return if no value has been specified by Contextual:is():during() .","title":"defaultValue : T"},{"location":"api-reference/general/members/contextual/#returns-contextualt","text":"A freshly constructed contextual.","title":"Returns -> Contextual<T>"},{"location":"api-reference/general/members/contextual/#learn-more","text":"Sharing Values tutorial","title":"Learn More"},{"location":"api-reference/general/members/version/","text":"General Members version version : Version \u00b6 Fusion . version : Version The version of the Fusion source code. isRelease is only true when using a version of Fusion downloaded from the Releases page .","title":"version"},{"location":"api-reference/general/members/version/#version-version","text":"Fusion . version : Version The version of the Fusion source code. isRelease is only true when using a version of Fusion downloaded from the Releases page .","title":"version : Version"},{"location":"api-reference/general/types/contextual/","text":"General Types Contextual Contextual \u00b6 export type Contextual < T > = { type : \"Contextual\" , now : ( self ) -> T , is : ( self , newValue : T ) -> { during : < R , A ... > ( self , callback : ( A ...) -> R , A ...) -> R } } An object representing a widely-accessible value, which can take on different values at different times in different coroutines. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially. Fields \u00b6 type : \"Contextual\" \u00b6 A type string which can be used for runtime type checking. Methods \u00b6 now -> T \u00b6 function Contextual : now (): T Returns the current value of this contextual. This varies based on when the function is called, and in what coroutine it was called. is/during -> R \u00b6 function Contextual : is ( newValue : T ): { during : < R , A ... > ( self , callback : ( A ...) -> R , A ... ) -> R } Runs the callback with the arguments A... and returns the value the callback returns ( R ). The Contextual will appear to be newValue in the callback, unless it's overridden by another :is():during() call. Learn More \u00b6 Sharing Values tutorial","title":"Contextual"},{"location":"api-reference/general/types/contextual/#contextual","text":"export type Contextual < T > = { type : \"Contextual\" , now : ( self ) -> T , is : ( self , newValue : T ) -> { during : < R , A ... > ( self , callback : ( A ...) -> R , A ...) -> R } } An object representing a widely-accessible value, which can take on different values at different times in different coroutines. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially.","title":"Contextual"},{"location":"api-reference/general/types/contextual/#fields","text":"","title":"Fields"},{"location":"api-reference/general/types/contextual/#type-contextual","text":"A type string which can be used for runtime type checking.","title":"type : \"Contextual\""},{"location":"api-reference/general/types/contextual/#methods","text":"","title":"Methods"},{"location":"api-reference/general/types/contextual/#now-t","text":"function Contextual : now (): T Returns the current value of this contextual. This varies based on when the function is called, and in what coroutine it was called.","title":"now -> T"},{"location":"api-reference/general/types/contextual/#isduring-r","text":"function Contextual : is ( newValue : T ): { during : < R , A ... > ( self , callback : ( A ...) -> R , A ... ) -> R } Runs the callback with the arguments A... and returns the value the callback returns ( R ). The Contextual will appear to be newValue in the callback, unless it's overridden by another :is():during() call.","title":"is/during -> R"},{"location":"api-reference/general/types/contextual/#learn-more","text":"Sharing Values tutorial","title":"Learn More"},{"location":"api-reference/general/types/version/","text":"General Types Version Version \u00b6 export type Version = { major : number , minor : number , isRelease : boolean } Describes a version of Fusion's source code. Members \u00b6 major : number \u00b6 The major version number. If this is greater than 0 , then two versions sharing the same major version number are not expected to be incompatible or have breaking changes. minor : number \u00b6 The minor version number. Describes version updates that are not enumerated by the major version number, such as versions prior to 1.0, or versions which are non-breaking. isRelease : boolean \u00b6 Describes whether the version was sourced from an official release package.","title":"Version"},{"location":"api-reference/general/types/version/#version","text":"export type Version = { major : number , minor : number , isRelease : boolean } Describes a version of Fusion's source code.","title":"Version"},{"location":"api-reference/general/types/version/#members","text":"","title":"Members"},{"location":"api-reference/general/types/version/#major-number","text":"The major version number. If this is greater than 0 , then two versions sharing the same major version number are not expected to be incompatible or have breaking changes.","title":"major : number"},{"location":"api-reference/general/types/version/#minor-number","text":"The minor version number. Describes version updates that are not enumerated by the major version number, such as versions prior to 1.0, or versions which are non-breaking.","title":"minor : number"},{"location":"api-reference/general/types/version/#isrelease-boolean","text":"Describes whether the version was sourced from an official release package.","title":"isRelease : boolean"},{"location":"api-reference/memory/members/derivescope/","text":"Memory Members deriveScope deriveScope -> Scope \u00b6 function Fusion . deriveScope < T > ( existing : Scope < T > ): Scope < T > Returns a blank scope with the same methods as an existing scope. Scopes are not unique Fusion can recycle old unused scopes. This helps make scopes more lightweight, but it also means they don't uniquely belong to any part of your program. As a result, you shouldn't hold on to scopes after they've been cleaned up, and you shouldn't use them as unique identifiers anywhere. Parameters \u00b6 existing : Scope \u00b6 An existing scope, whose methods should be re-used for the new scope. Returns -> Scope \u00b6 A blank scope with the same methods as the existing scope. Learn More \u00b6 Scopes tutorial","title":"deriveScope"},{"location":"api-reference/memory/members/derivescope/#derivescope-scopet","text":"function Fusion . deriveScope < T > ( existing : Scope < T > ): Scope < T > Returns a blank scope with the same methods as an existing scope. Scopes are not unique Fusion can recycle old unused scopes. This helps make scopes more lightweight, but it also means they don't uniquely belong to any part of your program. As a result, you shouldn't hold on to scopes after they've been cleaned up, and you shouldn't use them as unique identifiers anywhere.","title":"deriveScope -> Scope<T>"},{"location":"api-reference/memory/members/derivescope/#parameters","text":"","title":"Parameters"},{"location":"api-reference/memory/members/derivescope/#existing-scopet","text":"An existing scope, whose methods should be re-used for the new scope.","title":"existing : Scope<T>"},{"location":"api-reference/memory/members/derivescope/#returns-scopet","text":"A blank scope with the same methods as the existing scope.","title":"Returns -> Scope<T>"},{"location":"api-reference/memory/members/derivescope/#learn-more","text":"Scopes tutorial","title":"Learn More"},{"location":"api-reference/memory/members/docleanup/","text":"Memory Members doCleanup doCleanup -> () \u00b6 function Fusion . doCleanup ( ...: unknown ): () Attempts to destroy all arguments based on their runtime type. This is a black hole! Any values you pass into doCleanup should be treated as completely gone. Make sure you remove all references to those values, and ensure your code never uses them again. Parameters \u00b6 ... : unknown \u00b6 A value which should be disposed of; the value's runtime type will be inspected to determine what should happen. if function , it is called ...else if {destroy: (self) -> ()} , :destroy() is called ...else if {Destroy: (self) -> ()} , :Destroy() is called ...else if {any} , doCleanup is called on all members When Fusion is running inside of Roblox: if Instance , :Destroy() is called ...else if RBXScriptConnection , :Disconnect() is called If none of these conditions match, the value is ignored. Learn More \u00b6 Scopes tutorial","title":"doCleanup"},{"location":"api-reference/memory/members/docleanup/#docleanup-","text":"function Fusion . doCleanup ( ...: unknown ): () Attempts to destroy all arguments based on their runtime type. This is a black hole! Any values you pass into doCleanup should be treated as completely gone. Make sure you remove all references to those values, and ensure your code never uses them again.","title":"doCleanup -> ()"},{"location":"api-reference/memory/members/docleanup/#parameters","text":"","title":"Parameters"},{"location":"api-reference/memory/members/docleanup/#unknown","text":"A value which should be disposed of; the value's runtime type will be inspected to determine what should happen. if function , it is called ...else if {destroy: (self) -> ()} , :destroy() is called ...else if {Destroy: (self) -> ()} , :Destroy() is called ...else if {any} , doCleanup is called on all members When Fusion is running inside of Roblox: if Instance , :Destroy() is called ...else if RBXScriptConnection , :Disconnect() is called If none of these conditions match, the value is ignored.","title":"... : unknown"},{"location":"api-reference/memory/members/docleanup/#learn-more","text":"Scopes tutorial","title":"Learn More"},{"location":"api-reference/memory/members/scoped/","text":"Memory Members scoped scoped -> Scope \u00b6 function Fusion . scoped < T > ( constructors : T ): Scope < T > Returns a blank scope , with the __index metatable pointing at the given list of constructors for syntax convenience. Scopes are not unique Fusion can recycle old unused scopes. This helps make scopes more lightweight, but it also means they don't uniquely belong to any part of your program. As a result, you shouldn't hold on to scopes after they've been cleaned up, and you shouldn't use them as unique identifiers anywhere. Parameters \u00b6 constructors : T \u00b6 A table, ideally including functions which take a scope as their first parameter. Those functions will turn into methods. Returns -> Scope \u00b6 A blank scope with the specified methods. Learn More \u00b6 Scopes tutorial","title":"scoped"},{"location":"api-reference/memory/members/scoped/#scoped-scopet","text":"function Fusion . scoped < T > ( constructors : T ): Scope < T > Returns a blank scope , with the __index metatable pointing at the given list of constructors for syntax convenience. Scopes are not unique Fusion can recycle old unused scopes. This helps make scopes more lightweight, but it also means they don't uniquely belong to any part of your program. As a result, you shouldn't hold on to scopes after they've been cleaned up, and you shouldn't use them as unique identifiers anywhere.","title":"scoped -> Scope<T>"},{"location":"api-reference/memory/members/scoped/#parameters","text":"","title":"Parameters"},{"location":"api-reference/memory/members/scoped/#constructors-t","text":"A table, ideally including functions which take a scope as their first parameter. Those functions will turn into methods.","title":"constructors : T"},{"location":"api-reference/memory/members/scoped/#returns-scopet","text":"A blank scope with the specified methods.","title":"Returns -> Scope<T>"},{"location":"api-reference/memory/members/scoped/#learn-more","text":"Scopes tutorial","title":"Learn More"},{"location":"api-reference/memory/types/scope/","text":"Memory Types Scope Scope \u00b6 export type Scope < Constructors > = { unknown } & Constructors A table collecting all objects created as part of an independent unit of code, with optional Constructors as methods which can be called. Scopes are not unique Fusion can recycle old unused scopes. This helps make scopes more lightweight, but it also means they don't uniquely belong to any part of your program. As a result, you shouldn't hold on to scopes after they've been cleaned up, and you shouldn't use them as unique identifiers anywhere. Learn More \u00b6 Scopes tutorial","title":"Scope"},{"location":"api-reference/memory/types/scope/#scope","text":"export type Scope < Constructors > = { unknown } & Constructors A table collecting all objects created as part of an independent unit of code, with optional Constructors as methods which can be called. Scopes are not unique Fusion can recycle old unused scopes. This helps make scopes more lightweight, but it also means they don't uniquely belong to any part of your program. As a result, you shouldn't hold on to scopes after they've been cleaned up, and you shouldn't use them as unique identifiers anywhere.","title":"Scope"},{"location":"api-reference/memory/types/scope/#learn-more","text":"Scopes tutorial","title":"Learn More"},{"location":"api-reference/memory/types/scopedobject/","text":"Memory Types ScopedObject ScopedObject \u00b6 export type ScopedObject = { scope : Scope < unknown > ? , destroy : () -> () } An object designed for use with scopes . Objects satisfying this interface can be probed for information about their lifetime and how long they live relative to other objects satisfying this interface. These objects are also recognised by doCleanup . Members \u00b6 scope : Scope ? \u00b6 The scope which this object was constructed with, or nil if the object has been destroyed. Unchanged until destruction The scope is expected to be set once upon construction. It should not be assigned to again, except when the scope is destroyed - at which point it should be set to nil to indicate that it no longer exists inside of a scope. This is typically done inside the :destroy() method, if it exists. Methods \u00b6 destroy -> () \u00b6 function ScopedObject : destroy (): () Called by doCleanup to destroy this object. User code should generally not call this; instead, destroy the scope as a whole. Double-destruction prevention Fusion's objects throw destroyedTwice if they detect a nil scope during :destroy() . It's strongly recommended that you emulate this behaviour if you're implementing your own objects, as this protects against double-destruction and exposes potential scoping issues further ahead of time. Learn More \u00b6 Scopes tutorial","title":"ScopedObject"},{"location":"api-reference/memory/types/scopedobject/#scopedobject","text":"export type ScopedObject = { scope : Scope < unknown > ? , destroy : () -> () } An object designed for use with scopes . Objects satisfying this interface can be probed for information about their lifetime and how long they live relative to other objects satisfying this interface. These objects are also recognised by doCleanup .","title":"ScopedObject"},{"location":"api-reference/memory/types/scopedobject/#members","text":"","title":"Members"},{"location":"api-reference/memory/types/scopedobject/#scope-scopeunknown","text":"The scope which this object was constructed with, or nil if the object has been destroyed. Unchanged until destruction The scope is expected to be set once upon construction. It should not be assigned to again, except when the scope is destroyed - at which point it should be set to nil to indicate that it no longer exists inside of a scope. This is typically done inside the :destroy() method, if it exists.","title":"scope : Scope<unknown>?"},{"location":"api-reference/memory/types/scopedobject/#methods","text":"","title":"Methods"},{"location":"api-reference/memory/types/scopedobject/#destroy-","text":"function ScopedObject : destroy (): () Called by doCleanup to destroy this object. User code should generally not call this; instead, destroy the scope as a whole. Double-destruction prevention Fusion's objects throw destroyedTwice if they detect a nil scope during :destroy() . It's strongly recommended that you emulate this behaviour if you're implementing your own objects, as this protects against double-destruction and exposes potential scoping issues further ahead of time.","title":"destroy -> ()"},{"location":"api-reference/memory/types/scopedobject/#learn-more","text":"Scopes tutorial","title":"Learn More"},{"location":"api-reference/memory/types/task/","text":"Memory Types Task Task \u00b6 export type Task = Instance | RBXScriptConnection | () -> () | { destroy : ( self ) -> ()} | { Destroy : ( self ) -> ()} | { Task } Types which doCleanup has defined behaviour for. Not enforced Fusion does not use static types to enforce that doCleanup is given a type which it can process. This type is only exposed for your own use. Learn More \u00b6 Scopes tutorial","title":"Task"},{"location":"api-reference/memory/types/task/#task","text":"export type Task = Instance | RBXScriptConnection | () -> () | { destroy : ( self ) -> ()} | { Destroy : ( self ) -> ()} | { Task } Types which doCleanup has defined behaviour for. Not enforced Fusion does not use static types to enforce that doCleanup is given a type which it can process. This type is only exposed for your own use.","title":"Task"},{"location":"api-reference/memory/types/task/#learn-more","text":"Scopes tutorial","title":"Learn More"},{"location":"api-reference/roblox/members/attribute/","text":"Memory Members Attribute Attribute -> SpecialKey \u00b6 function Fusion . Attribute ( attributeName : string ): SpecialKey Given an attribute name, returns a special key which can modify attributes of that name. When paired with a value in a property table , the special key sets the attribute to that value. Parameters \u00b6 attributeName : string \u00b6 The name of the attribute that the special key should target. Returns -> SpecialKey \u00b6 A special key for modifying attributes of that name.","title":"Attribute"},{"location":"api-reference/roblox/members/attribute/#attribute-specialkey","text":"function Fusion . Attribute ( attributeName : string ): SpecialKey Given an attribute name, returns a special key which can modify attributes of that name. When paired with a value in a property table , the special key sets the attribute to that value.","title":"Attribute -> SpecialKey"},{"location":"api-reference/roblox/members/attribute/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/attribute/#attributename-string","text":"The name of the attribute that the special key should target.","title":"attributeName : string"},{"location":"api-reference/roblox/members/attribute/#returns-specialkey","text":"A special key for modifying attributes of that name.","title":"Returns -> SpecialKey"},{"location":"api-reference/roblox/members/attributechange/","text":"Memory Members AttributeChange AttributeChange -> SpecialKey \u00b6 function Fusion . AttributeChange ( attributeName : string ): SpecialKey Given an attribute name, returns a special key which can listen to changes for attributes of that name. When paired with a callback in a property table , the special key connects the callback to the attribute's change event. Parameters \u00b6 attributeName : string \u00b6 The name of the attribute that the special key should target. Returns -> SpecialKey \u00b6 A special key for listening to changes for attributes of that name.","title":"AttributeChange"},{"location":"api-reference/roblox/members/attributechange/#attributechange-specialkey","text":"function Fusion . AttributeChange ( attributeName : string ): SpecialKey Given an attribute name, returns a special key which can listen to changes for attributes of that name. When paired with a callback in a property table , the special key connects the callback to the attribute's change event.","title":"AttributeChange -> SpecialKey"},{"location":"api-reference/roblox/members/attributechange/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/attributechange/#attributename-string","text":"The name of the attribute that the special key should target.","title":"attributeName : string"},{"location":"api-reference/roblox/members/attributechange/#returns-specialkey","text":"A special key for listening to changes for attributes of that name.","title":"Returns -> SpecialKey"},{"location":"api-reference/roblox/members/attributeout/","text":"Memory Members AttributeOut AttributeOut -> SpecialKey \u00b6 function Fusion . AttributeOut ( attributeName : string ): SpecialKey Given an attribute name, returns a special key which can output values from attributes of that name. When paired with a value object in a property table , the special key sets the value when the attribute changes. Parameters \u00b6 attributeName : string \u00b6 The name of the attribute that the special key should target. Returns -> SpecialKey \u00b6 A special key for outputting values from attributes of that name.","title":"AttributeOut"},{"location":"api-reference/roblox/members/attributeout/#attributeout-specialkey","text":"function Fusion . AttributeOut ( attributeName : string ): SpecialKey Given an attribute name, returns a special key which can output values from attributes of that name. When paired with a value object in a property table , the special key sets the value when the attribute changes.","title":"AttributeOut -> SpecialKey"},{"location":"api-reference/roblox/members/attributeout/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/attributeout/#attributename-string","text":"The name of the attribute that the special key should target.","title":"attributeName : string"},{"location":"api-reference/roblox/members/attributeout/#returns-specialkey","text":"A special key for outputting values from attributes of that name.","title":"Returns -> SpecialKey"},{"location":"api-reference/roblox/members/children/","text":"Memory Members Children Children : SpecialKey \u00b6 Fusion . Children : SpecialKey A special key which parents other instances into this instance. When paired with a Child in a property table , the special key explores the Child to find every Instance nested inside. It then parents those instances under the instance which the special key was applied to. In particular, this special key will recursively explore arrays and bind to any state objects . Learn More \u00b6 Parenting tutorial","title":"Children"},{"location":"api-reference/roblox/members/children/#children-specialkey","text":"Fusion . Children : SpecialKey A special key which parents other instances into this instance. When paired with a Child in a property table , the special key explores the Child to find every Instance nested inside. It then parents those instances under the instance which the special key was applied to. In particular, this special key will recursively explore arrays and bind to any state objects .","title":"Children : SpecialKey"},{"location":"api-reference/roblox/members/children/#learn-more","text":"Parenting tutorial","title":"Learn More"},{"location":"api-reference/roblox/members/hydrate/","text":"Memory Members Hydrate Hydrate -> ( PropertyTable ) -> Instance \u00b6 function Fusion . Hydrate ( target : Instance ): ( props : PropertyTable ) -> Instance Given an instance, returns a component for binding extra functionality to that instance. In the property table, string keys are assigned as properties on the instance. If the value is a state object , it is re-assigned every time the value of the state object changes. Any special keys present in the property table are applied to the instance after string keys are processed, in the order specified by their stage . A special exception is made for assigning Parent , which is only assigned after the descendants stage. Do not overwrite properties If the instance was previously created with New or previously hydrated, do not assign to any properties that were previously specified in those prior calls. Duplicated assignments can interfere with each other in unpredictable ways. Parameters \u00b6 target : Instance \u00b6 The instance which should be modified. Returns -> ( PropertyTable ) -> Instance \u00b6 A component that hydrates that instance, accepting various properties to build up bindings and operations applied to the instance. Learn More \u00b6 Hydration tutorial","title":"Hydrate"},{"location":"api-reference/roblox/members/hydrate/#hydrate-propertytable-instance","text":"function Fusion . Hydrate ( target : Instance ): ( props : PropertyTable ) -> Instance Given an instance, returns a component for binding extra functionality to that instance. In the property table, string keys are assigned as properties on the instance. If the value is a state object , it is re-assigned every time the value of the state object changes. Any special keys present in the property table are applied to the instance after string keys are processed, in the order specified by their stage . A special exception is made for assigning Parent , which is only assigned after the descendants stage. Do not overwrite properties If the instance was previously created with New or previously hydrated, do not assign to any properties that were previously specified in those prior calls. Duplicated assignments can interfere with each other in unpredictable ways.","title":"Hydrate -> (PropertyTable) -> Instance"},{"location":"api-reference/roblox/members/hydrate/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/hydrate/#target-instance","text":"The instance which should be modified.","title":"target : Instance"},{"location":"api-reference/roblox/members/hydrate/#returns-propertytable-instance","text":"A component that hydrates that instance, accepting various properties to build up bindings and operations applied to the instance.","title":"Returns -> (PropertyTable) -> Instance"},{"location":"api-reference/roblox/members/hydrate/#learn-more","text":"Hydration tutorial","title":"Learn More"},{"location":"api-reference/roblox/members/new/","text":"Memory Members New New -> ( PropertyTable ) -> Instance \u00b6 function Fusion . New ( className : string ): ( props : PropertyTable ) -> Instance Given a class name, returns a component for constructing instances of that class. In the property table, string keys are assigned as properties on the instance. If the value is a state object , it is re-assigned every time the value of the state object changes. Any special keys present in the property table are applied to the instance after string keys are processed, in the order specified by their stage . A special exception is made for assigning Parent , which is only assigned after the descendants stage. Parameters \u00b6 className : string \u00b6 The kind of instance that should be constructed. Returns -> ( PropertyTable ) -> Instance \u00b6 A component that constructs instances of that type, accepting various properties to customise each instance uniquely. Learn More \u00b6 New Instances tutorial","title":"New"},{"location":"api-reference/roblox/members/new/#new-propertytable-instance","text":"function Fusion . New ( className : string ): ( props : PropertyTable ) -> Instance Given a class name, returns a component for constructing instances of that class. In the property table, string keys are assigned as properties on the instance. If the value is a state object , it is re-assigned every time the value of the state object changes. Any special keys present in the property table are applied to the instance after string keys are processed, in the order specified by their stage . A special exception is made for assigning Parent , which is only assigned after the descendants stage.","title":"New -> (PropertyTable) -> Instance"},{"location":"api-reference/roblox/members/new/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/new/#classname-string","text":"The kind of instance that should be constructed.","title":"className : string"},{"location":"api-reference/roblox/members/new/#returns-propertytable-instance","text":"A component that constructs instances of that type, accepting various properties to customise each instance uniquely.","title":"Returns -> (PropertyTable) -> Instance"},{"location":"api-reference/roblox/members/new/#learn-more","text":"New Instances tutorial","title":"Learn More"},{"location":"api-reference/roblox/members/onchange/","text":"Memory Members OnChange OnChange -> SpecialKey \u00b6 function Fusion . OnChange ( propertyName : string ): SpecialKey Given an property name, returns a special key which can listen to changes for properties of that name. When paired with a callback in a property table , the special key connects the callback to the property's change event. Parameters \u00b6 propertyName : string \u00b6 The name of the property that the special key should target. Returns -> SpecialKey \u00b6 A special key for listening to changes for properties of that name. Learn More \u00b6 Change Events tutorial","title":"OnChange"},{"location":"api-reference/roblox/members/onchange/#onchange-specialkey","text":"function Fusion . OnChange ( propertyName : string ): SpecialKey Given an property name, returns a special key which can listen to changes for properties of that name. When paired with a callback in a property table , the special key connects the callback to the property's change event.","title":"OnChange -> SpecialKey"},{"location":"api-reference/roblox/members/onchange/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/onchange/#propertyname-string","text":"The name of the property that the special key should target.","title":"propertyName : string"},{"location":"api-reference/roblox/members/onchange/#returns-specialkey","text":"A special key for listening to changes for properties of that name.","title":"Returns -> SpecialKey"},{"location":"api-reference/roblox/members/onchange/#learn-more","text":"Change Events tutorial","title":"Learn More"},{"location":"api-reference/roblox/members/onevent/","text":"Memory Members OnEvent OnEvent -> SpecialKey \u00b6 function Fusion . OnEvent ( eventName : string ): SpecialKey Given an event name, returns a special key which can listen for events of that name. When paired with a callback in a property table , the special key connects the callback to the event. Parameters \u00b6 eventName : string \u00b6 The name of the event that the special key should target. Returns -> SpecialKey \u00b6 A special key for listening to events of that name. Learn More \u00b6 Events tutorial","title":"OnEvent"},{"location":"api-reference/roblox/members/onevent/#onevent-specialkey","text":"function Fusion . OnEvent ( eventName : string ): SpecialKey Given an event name, returns a special key which can listen for events of that name. When paired with a callback in a property table , the special key connects the callback to the event.","title":"OnEvent -> SpecialKey"},{"location":"api-reference/roblox/members/onevent/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/onevent/#eventname-string","text":"The name of the event that the special key should target.","title":"eventName : string"},{"location":"api-reference/roblox/members/onevent/#returns-specialkey","text":"A special key for listening to events of that name.","title":"Returns -> SpecialKey"},{"location":"api-reference/roblox/members/onevent/#learn-more","text":"Events tutorial","title":"Learn More"},{"location":"api-reference/roblox/members/out/","text":"Memory Members Out Out -> SpecialKey \u00b6 function Fusion . Out ( propertyName : string ): SpecialKey Given an property name, returns a special key which can output values from properties of that name. When paired with a value object in a property table , the special key sets the value when the property changes. Parameters \u00b6 propertyName : string \u00b6 The name of the property that the special key should target. Returns -> SpecialKey \u00b6 A special key for outputting values from properties of that name. Learn More \u00b6 Outputs tutorial","title":"Out"},{"location":"api-reference/roblox/members/out/#out-specialkey","text":"function Fusion . Out ( propertyName : string ): SpecialKey Given an property name, returns a special key which can output values from properties of that name. When paired with a value object in a property table , the special key sets the value when the property changes.","title":"Out -> SpecialKey"},{"location":"api-reference/roblox/members/out/#parameters","text":"","title":"Parameters"},{"location":"api-reference/roblox/members/out/#propertyname-string","text":"The name of the property that the special key should target.","title":"propertyName : string"},{"location":"api-reference/roblox/members/out/#returns-specialkey","text":"A special key for outputting values from properties of that name.","title":"Returns -> SpecialKey"},{"location":"api-reference/roblox/members/out/#learn-more","text":"Outputs tutorial","title":"Learn More"},{"location":"api-reference/roblox/members/ref/","text":"Memory Members Ref Ref : SpecialKey \u00b6 Fusion . Ref : SpecialKey A special key which outputs the instance it's being applied to. When paired with a value object in a property table , the special key sets the value to the instance. Learn More \u00b6 References tutorial","title":"Ref"},{"location":"api-reference/roblox/members/ref/#ref-specialkey","text":"Fusion . Ref : SpecialKey A special key which outputs the instance it's being applied to. When paired with a value object in a property table , the special key sets the value to the instance.","title":"Ref : SpecialKey"},{"location":"api-reference/roblox/members/ref/#learn-more","text":"References tutorial","title":"Learn More"},{"location":"api-reference/roblox/types/child/","text":"Roblox Types Child Child \u00b6 export type Child = Instance | StateObject < Child > | {[ unknown ]: Child } All of the types understood by the [Children] special key. Learn More \u00b6 Parenting tutorial Instance Handling tutorial","title":"Child"},{"location":"api-reference/roblox/types/child/#child","text":"export type Child = Instance | StateObject < Child > | {[ unknown ]: Child } All of the types understood by the [Children] special key.","title":"Child"},{"location":"api-reference/roblox/types/child/#learn-more","text":"Parenting tutorial Instance Handling tutorial","title":"Learn More"},{"location":"api-reference/roblox/types/propertytable/","text":"Roblox Types PropertyTable PropertyTable \u00b6 export type PropertyTable = {[ string | SpecialKey ]: unknown } A table of named instance properties and special keys , which can be passed to New to create an instance. This type can be overly generic In most cases, you should know what properties your code is looking for. In those cases, you should prefer to list out the properties explicitly, to document what your code needs. You should only use this type if you don't know what properties your code will accept.","title":"PropertyTable"},{"location":"api-reference/roblox/types/propertytable/#propertytable","text":"export type PropertyTable = {[ string | SpecialKey ]: unknown } A table of named instance properties and special keys , which can be passed to New to create an instance. This type can be overly generic In most cases, you should know what properties your code is looking for. In those cases, you should prefer to list out the properties explicitly, to document what your code needs. You should only use this type if you don't know what properties your code will accept.","title":"PropertyTable"},{"location":"api-reference/roblox/types/specialkey/","text":"General Types SpecialKey SpecialKey \u00b6 export type SpecialKey = { type : \"SpecialKey\" , kind : string , stage : \"self\" | \"descendants\" | \"ancestor\" | \"observer\" , apply : ( self , scope : Scope < unknown > , value : unknown , applyTo : Instance ) -> () } When used as the key in a property table , defines a custom operation to apply to the created Roblox instance. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially. Members \u00b6 type : \"SpecialKey\" \u00b6 A type string which can be used for runtime type checking. kind : string \u00b6 A more specific type string which can be used for runtime type checking. This can be used to tell types of special key apart. stage : \"self\" | \"descendants\" | \"ancestor\" | \"observer\" \u00b6 Describes the type of operation, which subsequently determines when it's applied relative to other operations. self runs before parenting any instances descendants runs once descendants are parented, but before this instance is parented to its ancestor ancestor runs after all parenting operations are complete observer runs after all other operations, so the final state of the instance can be observed Methods \u00b6 apply -> () \u00b6 function SpecialKey : apply ( self , scope : Scope < unknown > , value : unknown , applyTo : Instance ): () Called to apply this operation to an instance. value is the value from the property table, and applyTo is the instance to apply the operation to. The given scope is cleaned up when the operation is being unapplied, including when the instance is destroyed. Operations should use the scope to clean up any connections or undo any changes they cause.","title":"SpecialKey"},{"location":"api-reference/roblox/types/specialkey/#specialkey","text":"export type SpecialKey = { type : \"SpecialKey\" , kind : string , stage : \"self\" | \"descendants\" | \"ancestor\" | \"observer\" , apply : ( self , scope : Scope < unknown > , value : unknown , applyTo : Instance ) -> () } When used as the key in a property table , defines a custom operation to apply to the created Roblox instance. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially.","title":"SpecialKey"},{"location":"api-reference/roblox/types/specialkey/#members","text":"","title":"Members"},{"location":"api-reference/roblox/types/specialkey/#type-specialkey","text":"A type string which can be used for runtime type checking.","title":"type : \"SpecialKey\""},{"location":"api-reference/roblox/types/specialkey/#kind-string","text":"A more specific type string which can be used for runtime type checking. This can be used to tell types of special key apart.","title":"kind : string"},{"location":"api-reference/roblox/types/specialkey/#stage-self-descendants-ancestor-observer","text":"Describes the type of operation, which subsequently determines when it's applied relative to other operations. self runs before parenting any instances descendants runs once descendants are parented, but before this instance is parented to its ancestor ancestor runs after all parenting operations are complete observer runs after all other operations, so the final state of the instance can be observed","title":"stage : \"self\" | \"descendants\" | \"ancestor\" | \"observer\""},{"location":"api-reference/roblox/types/specialkey/#methods","text":"","title":"Methods"},{"location":"api-reference/roblox/types/specialkey/#apply-","text":"function SpecialKey : apply ( self , scope : Scope < unknown > , value : unknown , applyTo : Instance ): () Called to apply this operation to an instance. value is the value from the property table, and applyTo is the instance to apply the operation to. The given scope is cleaned up when the operation is being unapplied, including when the instance is destroyed. Operations should use the scope to clean up any connections or undo any changes they cause.","title":"apply -> ()"},{"location":"api-reference/state/members/computed/","text":"State Members Computed Computed -> Computed \u00b6 function Fusion . Computed < T , S > ( scope : Scope < S > , processor : ( Use , Scope < S > ) -> T ) -> Computed < T > Constructs and returns a new computed state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local computed = scope : Computed ( processor ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. processor : ( Use , Scope ) -> T \u00b6 Computes the value that will be used by the computed. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the computed. Returns -> Computed \u00b6 A freshly constructed computed state object. Learn More \u00b6 Computeds tutorial","title":"Computed"},{"location":"api-reference/state/members/computed/#computed-computedt","text":"function Fusion . Computed < T , S > ( scope : Scope < S > , processor : ( Use , Scope < S > ) -> T ) -> Computed < T > Constructs and returns a new computed state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local computed = scope : Computed ( processor )","title":"Computed -> Computed<T>"},{"location":"api-reference/state/members/computed/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/members/computed/#scope-scopes","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<S>"},{"location":"api-reference/state/members/computed/#processor-use-scopes-t","text":"Computes the value that will be used by the computed. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the computed.","title":"processor : (Use, Scope<S>) -> T"},{"location":"api-reference/state/members/computed/#returns-computedt","text":"A freshly constructed computed state object.","title":"Returns -> Computed<T>"},{"location":"api-reference/state/members/computed/#learn-more","text":"Computeds tutorial","title":"Learn More"},{"location":"api-reference/state/members/forkeys/","text":"State Members ForKeys ForKeys -> For \u00b6 function Fusion . ForKeys < KI , KO , V , S > ( scope : Scope < S > , inputTable : UsedAs < {[ KI ]: V } > , processor : ( Use , Scope < S > , key : KI ) -> KO ) -> For < KO , V > Constructs and returns a new For state object which processes keys and preserves values. Use scoped() method syntax This function is intended to be accessed as a method on a scope: local forObj = scope : ForKeys ( inputTable , processor ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. inputTable : UsedAs <{[KI]: V}> \u00b6 The table which will provide the input keys and input values for this object. If it is a state object, this object will respond to changes in that state. processor : ( Use , Scope , key: KI) -> KO \u00b6 Accepts a KI key from the input table, and returns the KO key that should appear in the output table. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the whole object. Returns -> For \u00b6 A freshly constructed For state object. Learn More \u00b6 ForKeys tutorial","title":"ForKeys"},{"location":"api-reference/state/members/forkeys/#forkeys-forko-v","text":"function Fusion . ForKeys < KI , KO , V , S > ( scope : Scope < S > , inputTable : UsedAs < {[ KI ]: V } > , processor : ( Use , Scope < S > , key : KI ) -> KO ) -> For < KO , V > Constructs and returns a new For state object which processes keys and preserves values. Use scoped() method syntax This function is intended to be accessed as a method on a scope: local forObj = scope : ForKeys ( inputTable , processor )","title":"ForKeys -> For<KO, V>"},{"location":"api-reference/state/members/forkeys/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/members/forkeys/#scope-scopes","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<S>"},{"location":"api-reference/state/members/forkeys/#inputtable-usedaski-v","text":"The table which will provide the input keys and input values for this object. If it is a state object, this object will respond to changes in that state.","title":"inputTable : UsedAs<{[KI]: V}>"},{"location":"api-reference/state/members/forkeys/#processor-use-scopes-key-ki-ko","text":"Accepts a KI key from the input table, and returns the KO key that should appear in the output table. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the whole object.","title":"processor : (Use, Scope<S>, key: KI) -> KO"},{"location":"api-reference/state/members/forkeys/#returns-forko-v","text":"A freshly constructed For state object.","title":"Returns -> For<KO, V>"},{"location":"api-reference/state/members/forkeys/#learn-more","text":"ForKeys tutorial","title":"Learn More"},{"location":"api-reference/state/members/forpairs/","text":"State Members ForPairs ForPairs -> For \u00b6 function Fusion . ForPairs < KI , KO , VI , VO , S > ( scope : Scope < S > , inputTable : UsedAs < {[ KI ]: VI } > , processor : ( Use , Scope < S > , key : KI , value : VI ) -> ( KO , VO ) ) -> For < KO , VO > Constructs and returns a new For state object which processes keys and values in pairs. Use scoped() method syntax This function is intended to be accessed as a method on a scope: local forObj = scope : ForPairs ( inputTable , processor ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. inputTable : UsedAs <{[KI]: VI}> \u00b6 The table which will provide the input keys and input values for this object. If it is a state object, this object will respond to changes in that state. processor : ( Use , Scope , key: KI, value: VI) -> (KO, VO) \u00b6 Accepts a KI key and VI value pair from the input table, and returns the KO key and VO value pair that should appear in the output table. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the whole object. Returns -> For \u00b6 A freshly constructed For state object. Learn More \u00b6 ForPairs tutorial","title":"ForPairs"},{"location":"api-reference/state/members/forpairs/#forpairs-forko-vo","text":"function Fusion . ForPairs < KI , KO , VI , VO , S > ( scope : Scope < S > , inputTable : UsedAs < {[ KI ]: VI } > , processor : ( Use , Scope < S > , key : KI , value : VI ) -> ( KO , VO ) ) -> For < KO , VO > Constructs and returns a new For state object which processes keys and values in pairs. Use scoped() method syntax This function is intended to be accessed as a method on a scope: local forObj = scope : ForPairs ( inputTable , processor )","title":"ForPairs -> For<KO, VO>"},{"location":"api-reference/state/members/forpairs/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/members/forpairs/#scope-scopes","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<S>"},{"location":"api-reference/state/members/forpairs/#inputtable-usedaski-vi","text":"The table which will provide the input keys and input values for this object. If it is a state object, this object will respond to changes in that state.","title":"inputTable : UsedAs<{[KI]: VI}>"},{"location":"api-reference/state/members/forpairs/#processor-use-scopes-key-ki-value-vi-ko-vo","text":"Accepts a KI key and VI value pair from the input table, and returns the KO key and VO value pair that should appear in the output table. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the whole object.","title":"processor : (Use, Scope<S>, key: KI, value: VI) -> (KO, VO)"},{"location":"api-reference/state/members/forpairs/#returns-forko-vo","text":"A freshly constructed For state object.","title":"Returns -> For<KO, VO>"},{"location":"api-reference/state/members/forpairs/#learn-more","text":"ForPairs tutorial","title":"Learn More"},{"location":"api-reference/state/members/forvalues/","text":"State Members ForValues ForValues -> For \u00b6 function Fusion . ForValues < K , VI , VO , S > ( scope : Scope < S > , inputTable : UsedAs < {[ K ]: VI } > , processor : ( Use , Scope < S > , value : VI ) -> VO ) -> For < K , VO > Constructs and returns a new For state object which processes values and preserves keys. Use scoped() method syntax This function is intended to be accessed as a method on a scope: local forObj = scope : ForValues ( inputTable , processor ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. inputTable : UsedAs <{[K]: VI}> \u00b6 The table which will provide the input keys and input values for this object. If it is a state object, this object will respond to changes in that state. processor : ( Use , Scope , value: VI) -> VO \u00b6 Accepts a VI value from the input table, and returns the VO value that should appear in the output table. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the whole object. Returns -> For \u00b6 A freshly constructed For state object. Learn More \u00b6 ForValues tutorial","title":"ForValues"},{"location":"api-reference/state/members/forvalues/#forvalues-fork-vo","text":"function Fusion . ForValues < K , VI , VO , S > ( scope : Scope < S > , inputTable : UsedAs < {[ K ]: VI } > , processor : ( Use , Scope < S > , value : VI ) -> VO ) -> For < K , VO > Constructs and returns a new For state object which processes values and preserves keys. Use scoped() method syntax This function is intended to be accessed as a method on a scope: local forObj = scope : ForValues ( inputTable , processor )","title":"ForValues -> For<K, VO>"},{"location":"api-reference/state/members/forvalues/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/members/forvalues/#scope-scopes","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<S>"},{"location":"api-reference/state/members/forvalues/#inputtable-usedask-vi","text":"The table which will provide the input keys and input values for this object. If it is a state object, this object will respond to changes in that state.","title":"inputTable : UsedAs<{[K]: VI}>"},{"location":"api-reference/state/members/forvalues/#processor-use-scopes-value-vi-vo","text":"Accepts a VI value from the input table, and returns the VO value that should appear in the output table. The processor is given a use function for including other objects in the computation, and a scope for queueing destruction tasks to run on re-computation. The given scope has the same methods as the scope used to create the whole object.","title":"processor : (Use, Scope<S>, value: VI) -> VO"},{"location":"api-reference/state/members/forvalues/#returns-fork-vo","text":"A freshly constructed For state object.","title":"Returns -> For<K, VO>"},{"location":"api-reference/state/members/forvalues/#learn-more","text":"ForValues tutorial","title":"Learn More"},{"location":"api-reference/state/members/observer/","text":"State Members Observer Observer -> Observer \u00b6 function Fusion . Observer ( scope : Scope < unknown > , watching : unknown ) -> Observer Constructs and returns a new observer . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local observer = scope : Observer ( watching ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. watching : unknown \u00b6 The target that the observer should watch for changes. Works best with state objects While non- state object values are accepted for compatibility, they won't be able to trigger updates. Returns -> Observer \u00b6 A freshly constructed observer. Learn More \u00b6 Observers tutorial","title":"Observer"},{"location":"api-reference/state/members/observer/#observer-observer","text":"function Fusion . Observer ( scope : Scope < unknown > , watching : unknown ) -> Observer Constructs and returns a new observer . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local observer = scope : Observer ( watching )","title":"Observer -> Observer"},{"location":"api-reference/state/members/observer/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/members/observer/#scope-scopeunknown","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<unknown>"},{"location":"api-reference/state/members/observer/#watching-unknown","text":"The target that the observer should watch for changes. Works best with state objects While non- state object values are accepted for compatibility, they won't be able to trigger updates.","title":"watching : unknown"},{"location":"api-reference/state/members/observer/#returns-observer","text":"A freshly constructed observer.","title":"Returns -> Observer"},{"location":"api-reference/state/members/observer/#learn-more","text":"Observers tutorial","title":"Learn More"},{"location":"api-reference/state/members/peek/","text":"State Members peek peek : Use \u00b6 function Fusion . peek < T > ( target : UsedAs < T > ): T Extract a value of type T from its input. This is a general-purpose implementation of Use . It does not do any extra processing or book-keeping beyond what is required to determine the returned value. Specific implementations If you're given a specific implementation of Use by an API, it's highly likely that you are expected to use that implementation instead of peek() . This applies to reusable code too. It's often best to ask for a Use callback if your code needs to extract values, so an appropriate implementation can be passed in. Alternatively for reusable code, you can avoid extracting values entirely, and expect the user to do it prior to calling your code. This can work well if you unconditionally use all inputs, but beware that you may end up extracting more values than you need - this can have performance implications. Parameters \u00b6 target : UsedAs \u00b6 The abstract representation of T to extract a value from. Returns -> T \u00b6 The current value of T , derived from target . Learn More \u00b6 Values tutorial","title":"peek"},{"location":"api-reference/state/members/peek/#peek-use","text":"function Fusion . peek < T > ( target : UsedAs < T > ): T Extract a value of type T from its input. This is a general-purpose implementation of Use . It does not do any extra processing or book-keeping beyond what is required to determine the returned value. Specific implementations If you're given a specific implementation of Use by an API, it's highly likely that you are expected to use that implementation instead of peek() . This applies to reusable code too. It's often best to ask for a Use callback if your code needs to extract values, so an appropriate implementation can be passed in. Alternatively for reusable code, you can avoid extracting values entirely, and expect the user to do it prior to calling your code. This can work well if you unconditionally use all inputs, but beware that you may end up extracting more values than you need - this can have performance implications.","title":"peek : Use"},{"location":"api-reference/state/members/peek/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/members/peek/#target-usedast","text":"The abstract representation of T to extract a value from.","title":"target : UsedAs<T>"},{"location":"api-reference/state/members/peek/#returns-t","text":"The current value of T , derived from target .","title":"Returns -> T"},{"location":"api-reference/state/members/peek/#learn-more","text":"Values tutorial","title":"Learn More"},{"location":"api-reference/state/members/value/","text":"State Members Value Value -> Value \u00b6 function Fusion . Value < T > ( scope : Scope < unknown > , initialValue : T ) -> Value < T > Constructs and returns a new value state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local computed = scope : Computed ( processor ) Parameters \u00b6 scope : Scope \u00b6 The scope which should be used to store destruction tasks for this object. initialValue : T \u00b6 The initial value that will be stored until the next value is :set() . Returns -> Value \u00b6 A freshly constructed value state object. Learn More \u00b6 Values tutorial","title":"Value"},{"location":"api-reference/state/members/value/#value-valuet","text":"function Fusion . Value < T > ( scope : Scope < unknown > , initialValue : T ) -> Value < T > Constructs and returns a new value state object . Use scoped() method syntax This function is intended to be accessed as a method on a scope: local computed = scope : Computed ( processor )","title":"Value -> Value<T>"},{"location":"api-reference/state/members/value/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/members/value/#scope-scopes","text":"The scope which should be used to store destruction tasks for this object.","title":"scope : Scope<S>"},{"location":"api-reference/state/members/value/#initialvalue-t","text":"The initial value that will be stored until the next value is :set() .","title":"initialValue : T"},{"location":"api-reference/state/members/value/#returns-valuet","text":"A freshly constructed value state object.","title":"Returns -> Value<T>"},{"location":"api-reference/state/members/value/#learn-more","text":"Values tutorial","title":"Learn More"},{"location":"api-reference/state/types/computed/","text":"State Types Computed Computed \u00b6 export type Computed < T > = StateObject < T > & Dependent & { kind : \"Computed\" } A specialised state object for tracking single values computed from a user-defined computation. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from the objects used as part of the computation. This type isn't generally useful outside of Fusion itself. Members \u00b6 kind : \"Computed\" \u00b6 A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart. Learn More \u00b6 Computeds tutorial","title":"Computed"},{"location":"api-reference/state/types/computed/#computed","text":"export type Computed < T > = StateObject < T > & Dependent & { kind : \"Computed\" } A specialised state object for tracking single values computed from a user-defined computation. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from the objects used as part of the computation. This type isn't generally useful outside of Fusion itself.","title":"Computed"},{"location":"api-reference/state/types/computed/#members","text":"","title":"Members"},{"location":"api-reference/state/types/computed/#kind-computed","text":"A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart.","title":"kind : \"Computed\""},{"location":"api-reference/state/types/computed/#learn-more","text":"Computeds tutorial","title":"Learn More"},{"location":"api-reference/state/types/dependency/","text":"State Types Dependency Dependency \u00b6 export type Dependency = ScopedObject & { dependentSet : {[ Dependent ]: unknown } } A reactive graph object which can broadcast updates. Other graph objects can declare themselves as dependent upon this object to receive updates. This type includes ScopedObject , which allows the lifetime and destruction order of the reactive graph to be analysed. Members \u00b6 dependentSet : {[ Dependent ]: unknown} \u00b6 The reactive graph objects which declare themselves as dependent upon this object.","title":"Dependency"},{"location":"api-reference/state/types/dependency/#dependency","text":"export type Dependency = ScopedObject & { dependentSet : {[ Dependent ]: unknown } } A reactive graph object which can broadcast updates. Other graph objects can declare themselves as dependent upon this object to receive updates. This type includes ScopedObject , which allows the lifetime and destruction order of the reactive graph to be analysed.","title":"Dependency"},{"location":"api-reference/state/types/dependency/#members","text":"","title":"Members"},{"location":"api-reference/state/types/dependency/#dependentset-dependent-unknown","text":"The reactive graph objects which declare themselves as dependent upon this object.","title":"dependentSet : {[Dependent]: unknown}"},{"location":"api-reference/state/types/dependent/","text":"State Types Dependent Dependent \u00b6 export type Dependent = ScopedObject & { update : ( self ) -> boolean , dependencySet : {[ Dependency ]: unknown } } A reactive graph object which can add itself to dependencies and receive updates. This type includes ScopedObject , which allows the lifetime and destruction order of the reactive graph to be analysed. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially. Members \u00b6 dependencySet : {[ Dependency ]: unknown} \u00b6 Everything this reactive graph object currently declares itself as dependent upon. Methods \u00b6 update -> boolean \u00b6 function Dependent : update (): boolean Called from a dependency when a change occurs. Returns true if the update should propagate transitively through this object, or false if the update should not continue through this object specifically. Return value ignored for non-dependencies If this Dependent is not also a Dependency , the return value does nothing, as an object must be declarable as a dependency for other objects to receive updates from it.","title":"Dependent"},{"location":"api-reference/state/types/dependent/#dependent","text":"export type Dependent = ScopedObject & { update : ( self ) -> boolean , dependencySet : {[ Dependency ]: unknown } } A reactive graph object which can add itself to dependencies and receive updates. This type includes ScopedObject , which allows the lifetime and destruction order of the reactive graph to be analysed. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially.","title":"Dependent"},{"location":"api-reference/state/types/dependent/#members","text":"","title":"Members"},{"location":"api-reference/state/types/dependent/#dependencyset-dependency-unknown","text":"Everything this reactive graph object currently declares itself as dependent upon.","title":"dependencySet : {[Dependency]: unknown}"},{"location":"api-reference/state/types/dependent/#methods","text":"","title":"Methods"},{"location":"api-reference/state/types/dependent/#update-boolean","text":"function Dependent : update (): boolean Called from a dependency when a change occurs. Returns true if the update should propagate transitively through this object, or false if the update should not continue through this object specifically. Return value ignored for non-dependencies If this Dependent is not also a Dependency , the return value does nothing, as an object must be declarable as a dependency for other objects to receive updates from it.","title":"update -> boolean"},{"location":"api-reference/state/types/for/","text":"State Types For For \u00b6 export type For < KO , VO > = StateObject < {[ KO ]: VO } > & Dependent & { kind : \"For\" } A specialised state object for tracking multiple values computed from user-defined computations, which are merged into an output table. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from objects used as part of any of the computations. This type isn't generally useful outside of Fusion itself. Members \u00b6 kind : \"For\" \u00b6 A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart. Learn More \u00b6 ForValues tutorial ForKeys tutorial ForPairs tutorial","title":"For"},{"location":"api-reference/state/types/for/#for","text":"export type For < KO , VO > = StateObject < {[ KO ]: VO } > & Dependent & { kind : \"For\" } A specialised state object for tracking multiple values computed from user-defined computations, which are merged into an output table. In addition to the standard state object interfaces, this object is a dependent so it can receive updates from objects used as part of any of the computations. This type isn't generally useful outside of Fusion itself.","title":"For"},{"location":"api-reference/state/types/for/#members","text":"","title":"Members"},{"location":"api-reference/state/types/for/#kind-for","text":"A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart.","title":"kind : \"For\""},{"location":"api-reference/state/types/for/#learn-more","text":"ForValues tutorial ForKeys tutorial ForPairs tutorial","title":"Learn More"},{"location":"api-reference/state/types/observer/","text":"State Types Observer Observer \u00b6 export type Observer = Dependent & { type : \"Observer\" , onChange : ( self , callback : () -> ()) -> (() -> ()), onBind : ( self , callback : () -> ()) -> (() -> ()) } A user-constructed dependent that runs user code when its dependency is updated. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially. Members \u00b6 type : \"Observer\" \u00b6 A type string which can be used for runtime type checking. Methods \u00b6 onChange -> (() -> ()) \u00b6 function Observer : onChange ( callback : () -> () ): (() -> ()) Registers the callback to run when an update is received. The returned function will unregister the callback. onBind -> (() -> ()) \u00b6 function Observer : onBind ( callback : () -> () ): (() -> ()) Runs the callback immediately, and registers the callback to run when an update is received. The returned function will unregister the callback. Learn More \u00b6 Observers tutorial","title":"Observer"},{"location":"api-reference/state/types/observer/#observer","text":"export type Observer = Dependent & { type : \"Observer\" , onChange : ( self , callback : () -> ()) -> (() -> ()), onBind : ( self , callback : () -> ()) -> (() -> ()) } A user-constructed dependent that runs user code when its dependency is updated. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially.","title":"Observer"},{"location":"api-reference/state/types/observer/#members","text":"","title":"Members"},{"location":"api-reference/state/types/observer/#type-observer","text":"A type string which can be used for runtime type checking.","title":"type : \"Observer\""},{"location":"api-reference/state/types/observer/#methods","text":"","title":"Methods"},{"location":"api-reference/state/types/observer/#onchange-","text":"function Observer : onChange ( callback : () -> () ): (() -> ()) Registers the callback to run when an update is received. The returned function will unregister the callback.","title":"onChange -> (() -> ())"},{"location":"api-reference/state/types/observer/#onbind-","text":"function Observer : onBind ( callback : () -> () ): (() -> ()) Runs the callback immediately, and registers the callback to run when an update is received. The returned function will unregister the callback.","title":"onBind -> (() -> ())"},{"location":"api-reference/state/types/observer/#learn-more","text":"Observers tutorial","title":"Learn More"},{"location":"api-reference/state/types/stateobject/","text":"State Types StateObject StateObject \u00b6 export type StateObject < T > = Dependency & { type : \"State\" , kind : string } Stores a value of T which can change over time. As a dependency , it can broadcast updates when its value changes. This type isn't generally useful outside of Fusion itself; you should prefer to work with UsedAs in your own code. Members \u00b6 type : \"State\" \u00b6 A type string which can be used for runtime type checking. kind : string \u00b6 A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart.","title":"StateObject"},{"location":"api-reference/state/types/stateobject/#stateobject","text":"export type StateObject < T > = Dependency & { type : \"State\" , kind : string } Stores a value of T which can change over time. As a dependency , it can broadcast updates when its value changes. This type isn't generally useful outside of Fusion itself; you should prefer to work with UsedAs in your own code.","title":"StateObject"},{"location":"api-reference/state/types/stateobject/#members","text":"","title":"Members"},{"location":"api-reference/state/types/stateobject/#type-state","text":"A type string which can be used for runtime type checking.","title":"type : \"State\""},{"location":"api-reference/state/types/stateobject/#kind-string","text":"A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart.","title":"kind : string"},{"location":"api-reference/state/types/use/","text":"State Types Use Use \u00b6 export type Use = < T > ( target : UsedAs < T > ) -> T A function which extracts a value of T from something that can be used as T . The most generic implementation of this is the peek() function , which performs this extraction with no additional steps. However, certain APIs may provide their own implementation, so they can perform additional processing for certain representations. Most notably, computeds provide their own use() function which adds inputs to a watchlist, which allows them to re-calculate as inputs change. Parameters \u00b6 target : UsedAs \u00b6 The representation of T to extract a value from. Returns -> T \u00b6 The current value of T , derived from target . Learn More \u00b6 Values tutorial Computeds tutorial","title":"Use"},{"location":"api-reference/state/types/use/#use","text":"export type Use = < T > ( target : UsedAs < T > ) -> T A function which extracts a value of T from something that can be used as T . The most generic implementation of this is the peek() function , which performs this extraction with no additional steps. However, certain APIs may provide their own implementation, so they can perform additional processing for certain representations. Most notably, computeds provide their own use() function which adds inputs to a watchlist, which allows them to re-calculate as inputs change.","title":"Use"},{"location":"api-reference/state/types/use/#parameters","text":"","title":"Parameters"},{"location":"api-reference/state/types/use/#target-usedast","text":"The representation of T to extract a value from.","title":"target : UsedAs<T>"},{"location":"api-reference/state/types/use/#returns-t","text":"The current value of T , derived from target .","title":"Returns -> T"},{"location":"api-reference/state/types/use/#learn-more","text":"Values tutorial Computeds tutorial","title":"Learn More"},{"location":"api-reference/state/types/usedas/","text":"State Types UsedAs UsedAs \u00b6 export type UsedAs < T > = T | StateObject < T > Something which describes a value of type T . When it is used in a calculation, it becomes that value. Recommended Instead of using one of the more specific variants, your code should aim to use this type as often as possible. It allows your logic to deal with many representations of values at once, Variants \u00b6 T - represents unchanging constant values StateObject - represents dynamically updating values Learn More \u00b6 Components tutorial","title":"UsedAs"},{"location":"api-reference/state/types/usedas/#usedas","text":"export type UsedAs < T > = T | StateObject < T > Something which describes a value of type T . When it is used in a calculation, it becomes that value. Recommended Instead of using one of the more specific variants, your code should aim to use this type as often as possible. It allows your logic to deal with many representations of values at once,","title":"UsedAs"},{"location":"api-reference/state/types/usedas/#variants","text":"T - represents unchanging constant values StateObject - represents dynamically updating values","title":"Variants"},{"location":"api-reference/state/types/usedas/#learn-more","text":"Components tutorial","title":"Learn More"},{"location":"api-reference/state/types/value/","text":"State Types Value Value \u00b6 export type Value < T > = StateObject < T > & { kind : \"State\" , set : ( self , newValue : T ) -> () } A specialised state object which allows regular Luau code to control its value. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially. Members \u00b6 kind : \"Value\" \u00b6 A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart. Methods \u00b6 set -> () \u00b6 function Value : set ( newValue : T ): () Updates the value of this state object. Other objects using the value are notified immediately of the change. Learn More \u00b6 Values tutorial","title":"Value"},{"location":"api-reference/state/types/value/#value","text":"export type Value < T > = StateObject < T > & { kind : \"State\" , set : ( self , newValue : T ) -> () } A specialised state object which allows regular Luau code to control its value. Non-standard type syntax The above type definition uses self to denote methods. At time of writing, Luau does not interpret self specially.","title":"Value"},{"location":"api-reference/state/types/value/#members","text":"","title":"Members"},{"location":"api-reference/state/types/value/#kind-value","text":"A more specific type string which can be used for runtime type checking. This can be used to tell types of state object apart.","title":"kind : \"Value\""},{"location":"api-reference/state/types/value/#methods","text":"","title":"Methods"},{"location":"api-reference/state/types/value/#set-","text":"function Value : set ( newValue : T ): () Updates the value of this state object. Other objects using the value are notified immediately of the change.","title":"set -> ()"},{"location":"api-reference/state/types/value/#learn-more","text":"Values tutorial","title":"Learn More"},{"location":"examples/","text":"Examples \u00b6 Welcome to the Examples section! Here, you can find various open-source examples and projects, so you can see how Fusion works in a real setting. The Cookbook \u00b6 Oftentimes, you might be stuck on a small problem. You want to create something specific, but don't know how to do it with Fusion's tools. The cookbook can help with that! It's a collection of snippets which show you how to do various small tasks with Fusion, like processing arrays, applying animations and responding to different events. Visit the cookbook to see what's available. Open-Source Projects \u00b6 Fusion Wordle (for Fusion 0.2) \u00b6 See how Fusion can be used to build a mobile-first UI-centric game, with server validation, spring animations and sounds. Play and edit the game on Roblox. Fusion Obby (for Fusion 0.1) \u00b6 See how Fusion can be used to build a minimal interface for an obby, with an animated checkpoint counter and simulated confetti. Play and edit the game on Roblox.","title":"Home"},{"location":"examples/#examples","text":"Welcome to the Examples section! Here, you can find various open-source examples and projects, so you can see how Fusion works in a real setting.","title":"Examples"},{"location":"examples/#the-cookbook","text":"Oftentimes, you might be stuck on a small problem. You want to create something specific, but don't know how to do it with Fusion's tools. The cookbook can help with that! It's a collection of snippets which show you how to do various small tasks with Fusion, like processing arrays, applying animations and responding to different events. Visit the cookbook to see what's available.","title":"The Cookbook"},{"location":"examples/#open-source-projects","text":"","title":"Open-Source Projects"},{"location":"examples/#fusion-wordle-for-fusion-02","text":"See how Fusion can be used to build a mobile-first UI-centric game, with server validation, spring animations and sounds. Play and edit the game on Roblox.","title":"Fusion Wordle (for Fusion 0.2)"},{"location":"examples/#fusion-obby-for-fusion-01","text":"See how Fusion can be used to build a minimal interface for an obby, with an animated checkpoint counter and simulated confetti. Play and edit the game on Roblox.","title":"Fusion Obby (for Fusion 0.1)"},{"location":"examples/cookbook/","text":"Cookbook \u00b6 Oftentimes, you might be stuck on a small problem. You want to create something specific, but don't know how to do it with Fusion's tools. The cookbook can help with that! It's a collection of snippets which show you how to do various small tasks with Fusion, like processing arrays, applying animations and responding to different events. Navigation \u00b6 Using the sidebar to the left, you can browse all of the cookbook examples by name.","title":"Cookbook"},{"location":"examples/cookbook/#cookbook","text":"Oftentimes, you might be stuck on a small problem. You want to create something specific, but don't know how to do it with Fusion's tools. The cookbook can help with that! It's a collection of snippets which show you how to do various small tasks with Fusion, like processing arrays, applying animations and responding to different events.","title":"Cookbook"},{"location":"examples/cookbook/#navigation","text":"Using the sidebar to the left, you can browse all of the cookbook examples by name.","title":"Navigation"},{"location":"examples/cookbook/animated-computed/","text":"This example shows you how to animate a single value with an animation curve of your preference. For demonstration, the example uses Roblox API members. Overview \u00b6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 local Players = game : GetService ( \"Players\" ) local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children = Fusion . Children local TWEEN_INFO = TweenInfo . new ( 0.5 , Enum . EasingStyle . Sine , Enum . EasingDirection . InOut ) -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion ) -- You can set this at any time to indicate where The Thing should be. local showTheThing = scope : Value ( false ) local exampleUI = scope : New \"ScreenGui\" { Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ), Name = \"Example UI\" , [ Children ] = scope : New \"Frame\" { Name = \"The Thing\" , Position = scope : Tween ( scope : Computed ( function ( use ) local CENTRE = UDim2 . fromScale ( 0.5 , 0.5 ) local OFFSCREEN = UDim2 . fromScale ( - 0.5 , 0.5 ) return if use ( showTheThing ) then CENTRE else OFFSCREEN end ), TWEEN_INFO ), Size = UDim2 . fromOffset ( 200 , 200 ) } } -- Without toggling the value, you won't see it animate. task . defer ( function () while true do task . wait ( 1 ) showTheThing : set ( not peek ( showTheThing )) end end ) Explanation \u00b6 There's three key components to the above code snippet. Firstly, there's showTheThing . When this is true , The Thing should be in the centre of the screen. Otherwise, The Thing should be off-screen. 13 14 -- You can set this at any time to indicate where The Thing should be. local showTheThing = scope : Value ( false ) Next, there's the computed object on line 26. This takes that boolean value, and turns it into a UDim2 position for The Thing to use. You can imagine this as the 'non-animated' version of what you want The Thing to do, if it were to instantly teleport around. 26 27 28 29 30 scope : Computed ( function ( use ) local CENTRE = UDim2 . fromScale ( 0.5 , 0.5 ) local OFFSCREEN = UDim2 . fromScale ( - 0.5 , 0.5 ) return if use ( showTheThing ) then CENTRE else OFFSCREEN end ), Finally, there's the tween object that the computed is being passed into. The tween object will smoothly move towards the computed over time. If needed, you could separate the computed into a dedicated variable to access it independently. 25 26 27 28 29 30 31 32 Position = scope : Tween ( scope : Computed ( function ( use ) local CENTRE = UDim2 . fromScale ( 0.5 , 0.5 ) local OFFSCREEN = UDim2 . fromScale ( - 0.5 , 0.5 ) return if use ( showTheThing ) then CENTRE else OFFSCREEN end ), TWEEN_INFO ), The 'shape' of the animation is saved in a TWEEN_INFO constant defined earlier in the code. The Tween tutorial explains how each parameter shapes the motion. 7 8 9 10 11 local TWEEN_INFO = TweenInfo . new ( 0.5 , Enum . EasingStyle . Sine , Enum . EasingDirection . InOut ) Fluid animations with springs For extra smooth animation shapes that preserve velocity, consider trying spring objects . They're very similar in usage and can help improve the responsiveness of the motion.","title":"Animated Computed"},{"location":"examples/cookbook/animated-computed/#overview","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 local Players = game : GetService ( \"Players\" ) local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children = Fusion . Children local TWEEN_INFO = TweenInfo . new ( 0.5 , Enum . EasingStyle . Sine , Enum . EasingDirection . InOut ) -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion ) -- You can set this at any time to indicate where The Thing should be. local showTheThing = scope : Value ( false ) local exampleUI = scope : New \"ScreenGui\" { Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ), Name = \"Example UI\" , [ Children ] = scope : New \"Frame\" { Name = \"The Thing\" , Position = scope : Tween ( scope : Computed ( function ( use ) local CENTRE = UDim2 . fromScale ( 0.5 , 0.5 ) local OFFSCREEN = UDim2 . fromScale ( - 0.5 , 0.5 ) return if use ( showTheThing ) then CENTRE else OFFSCREEN end ), TWEEN_INFO ), Size = UDim2 . fromOffset ( 200 , 200 ) } } -- Without toggling the value, you won't see it animate. task . defer ( function () while true do task . wait ( 1 ) showTheThing : set ( not peek ( showTheThing )) end end )","title":"Overview"},{"location":"examples/cookbook/animated-computed/#explanation","text":"There's three key components to the above code snippet. Firstly, there's showTheThing . When this is true , The Thing should be in the centre of the screen. Otherwise, The Thing should be off-screen. 13 14 -- You can set this at any time to indicate where The Thing should be. local showTheThing = scope : Value ( false ) Next, there's the computed object on line 26. This takes that boolean value, and turns it into a UDim2 position for The Thing to use. You can imagine this as the 'non-animated' version of what you want The Thing to do, if it were to instantly teleport around. 26 27 28 29 30 scope : Computed ( function ( use ) local CENTRE = UDim2 . fromScale ( 0.5 , 0.5 ) local OFFSCREEN = UDim2 . fromScale ( - 0.5 , 0.5 ) return if use ( showTheThing ) then CENTRE else OFFSCREEN end ), Finally, there's the tween object that the computed is being passed into. The tween object will smoothly move towards the computed over time. If needed, you could separate the computed into a dedicated variable to access it independently. 25 26 27 28 29 30 31 32 Position = scope : Tween ( scope : Computed ( function ( use ) local CENTRE = UDim2 . fromScale ( 0.5 , 0.5 ) local OFFSCREEN = UDim2 . fromScale ( - 0.5 , 0.5 ) return if use ( showTheThing ) then CENTRE else OFFSCREEN end ), TWEEN_INFO ), The 'shape' of the animation is saved in a TWEEN_INFO constant defined earlier in the code. The Tween tutorial explains how each parameter shapes the motion. 7 8 9 10 11 local TWEEN_INFO = TweenInfo . new ( 0.5 , Enum . EasingStyle . Sine , Enum . EasingDirection . InOut ) Fluid animations with springs For extra smooth animation shapes that preserve velocity, consider trying spring objects . They're very similar in usage and can help improve the responsiveness of the motion.","title":"Explanation"},{"location":"examples/cookbook/button-component/","text":"This example is a relatively complete button component implemented using Fusion's Roblox API. It handles many common interactions such as hovering and clicking. This should be a generally useful template for assembling components of your own. For further ideas and best practices for building components, see the Components tutorial . Overview \u00b6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children , OnEvent = Fusion . Children , Fusion . OnEvent type UsedAs < T > = Fusion . UsedAs < T > local COLOUR_BLACK = Color3 . new ( 0 , 0 , 0 ) local COLOUR_WHITE = Color3 . new ( 1 , 1 , 1 ) local COLOUR_TEXT = COLOUR_WHITE local COLOUR_BG_REST = Color3 . fromHex ( \"0085FF\" ) local COLOUR_BG_HOVER = COLOUR_BG_REST : Lerp ( COLOUR_WHITE , 0.25 ) local COLOUR_BG_HELD = COLOUR_BG_REST : Lerp ( COLOUR_BLACK , 0.25 ) local COLOUR_BG_DISABLED = Color3 . fromHex ( \"CCCCCC\" ) local BG_FADE_SPEED = 20 -- spring speed units local ROUNDED_CORNERS = UDim . new ( 0 , 4 ) local PADDING = UDim2 . fromOffset ( 6 , 4 ) local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Name : UsedAs < string > ? , Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , AutomaticSize : UsedAs < Enum . AutomaticSize > ? }, Text : UsedAs < string > ? , Disabled : UsedAs < boolean > ? , OnClick : (() -> ()) ? } ): Fusion . Child local isHovering = scope : Value ( false ) local isHeldDown = scope : Value ( false ) return scope : New \"TextButton\" { Name = props . Name , LayoutOrder = props . Layout . LayoutOrder , Position = props . Layout . Position , AnchorPoint = props . Layout . AnchorPoint , ZIndex = props . Layout . ZIndex , Size = props . Layout . Size , AutomaticSize = props . Layout . AutomaticSize , Text = props . Text , TextColor3 = COLOUR_TEXT , BackgroundColor3 = scope : Spring ( scope : Computed ( function ( use ) -- The order of conditions matter here; it defines which states -- visually override other states, with earlier states being -- more important. return if use ( props . Disabled ) then COLOUR_BG_DISABLED elseif use ( isHeldDown ) then COLOUR_BG_HELD elseif use ( isHovering ) then COLOUR_BG_HOVER else return COLOUR_BG_REST end end ), BG_FADE_SPEED ), [ OnEvent \"Activated\" ] = function () if props . OnClick ~= nil and not peek ( props . Disabled ) then -- Explicitly called with no arguments to match the typedef. -- If passed straight to `OnEvent`, the function might receive -- arguments from the event. If the function secretly *does* -- take arguments (despite the type) this would cause problems. props . OnClick () end end , [ OnEvent \"MouseButton1Down\" ] = function () isHeldDown : set ( true ) end , [ OnEvent \"MouseButton1Up\" ] = function () isHeldDown : set ( false ) end , [ OnEvent \"MouseEnter\" ] = function () -- Roblox calls this event even if the button is being covered by -- other UI. For simplicity, this does not account for that. isHovering : set ( true ) end , [ OnEvent \"MouseLeave\" ] = function () -- If the button is being held down, but the cursor moves off the -- button, then we won't receive the mouse up event. To make sure -- the button doesn't get stuck held down, we'll release it if the -- cursor leaves the button. isHeldDown : set ( false ) isHovering : set ( false ) end , [ Children ] = { New \"UICorner\" { CornerRadius = ROUNDED_CORNERS }, New \"UIPadding\" { PaddingTop = PADDING . Y , PaddingBottom = PADDING . Y , PaddingLeft = PADDING . X , PaddingRight = PADDING . X } } } end return Button Explanation \u00b6 The main part of note is the function signature. It's highly recommended that you statically type the function signature for components, because it not only improves autocomplete and error checking, but also acts as up-to-date, machine readable documentation. 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Name : UsedAs < string > ? , Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , AutomaticSize : UsedAs < Enum . AutomaticSize > ? }, Text : UsedAs < string > ? , Disabled : UsedAs < boolean > ? , OnClick : (() -> ()) ? } ): Fusion . Child The scope parameter specifies that the component depends on Fusion's methods. If you're not sure how to write type definitions for scopes, the 'Scopes' section of the Components tutorial goes into further detail. The property table is laid out with each property on a new line, so it's easy to scan the list and see what properties are available. Most are typed with UsedAs , which allows the user to use state objects if they desire. They're also ? (optional), which can reduce boilerplate when using the component. Not all properties have to be that way, but usually it's better to have the flexibility. Property grouping You can group properties together in nested tables, like the Layout table above, to avoid long mixed lists of properties. In addition to being more readable, this can sometimes help with passing around lots of properties at once, because you can pass the whole nested table as one value if you'd like to. The return type of the function is Fusion.Child , which tells the user that the component is compatible with Fusion's [Children] API, without exposing what children it's returning specifically. This helps ensure the user doesn't accidentally depend on the internal structure of the component.","title":"Button Component"},{"location":"examples/cookbook/button-component/#overview","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children , OnEvent = Fusion . Children , Fusion . OnEvent type UsedAs < T > = Fusion . UsedAs < T > local COLOUR_BLACK = Color3 . new ( 0 , 0 , 0 ) local COLOUR_WHITE = Color3 . new ( 1 , 1 , 1 ) local COLOUR_TEXT = COLOUR_WHITE local COLOUR_BG_REST = Color3 . fromHex ( \"0085FF\" ) local COLOUR_BG_HOVER = COLOUR_BG_REST : Lerp ( COLOUR_WHITE , 0.25 ) local COLOUR_BG_HELD = COLOUR_BG_REST : Lerp ( COLOUR_BLACK , 0.25 ) local COLOUR_BG_DISABLED = Color3 . fromHex ( \"CCCCCC\" ) local BG_FADE_SPEED = 20 -- spring speed units local ROUNDED_CORNERS = UDim . new ( 0 , 4 ) local PADDING = UDim2 . fromOffset ( 6 , 4 ) local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Name : UsedAs < string > ? , Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , AutomaticSize : UsedAs < Enum . AutomaticSize > ? }, Text : UsedAs < string > ? , Disabled : UsedAs < boolean > ? , OnClick : (() -> ()) ? } ): Fusion . Child local isHovering = scope : Value ( false ) local isHeldDown = scope : Value ( false ) return scope : New \"TextButton\" { Name = props . Name , LayoutOrder = props . Layout . LayoutOrder , Position = props . Layout . Position , AnchorPoint = props . Layout . AnchorPoint , ZIndex = props . Layout . ZIndex , Size = props . Layout . Size , AutomaticSize = props . Layout . AutomaticSize , Text = props . Text , TextColor3 = COLOUR_TEXT , BackgroundColor3 = scope : Spring ( scope : Computed ( function ( use ) -- The order of conditions matter here; it defines which states -- visually override other states, with earlier states being -- more important. return if use ( props . Disabled ) then COLOUR_BG_DISABLED elseif use ( isHeldDown ) then COLOUR_BG_HELD elseif use ( isHovering ) then COLOUR_BG_HOVER else return COLOUR_BG_REST end end ), BG_FADE_SPEED ), [ OnEvent \"Activated\" ] = function () if props . OnClick ~= nil and not peek ( props . Disabled ) then -- Explicitly called with no arguments to match the typedef. -- If passed straight to `OnEvent`, the function might receive -- arguments from the event. If the function secretly *does* -- take arguments (despite the type) this would cause problems. props . OnClick () end end , [ OnEvent \"MouseButton1Down\" ] = function () isHeldDown : set ( true ) end , [ OnEvent \"MouseButton1Up\" ] = function () isHeldDown : set ( false ) end , [ OnEvent \"MouseEnter\" ] = function () -- Roblox calls this event even if the button is being covered by -- other UI. For simplicity, this does not account for that. isHovering : set ( true ) end , [ OnEvent \"MouseLeave\" ] = function () -- If the button is being held down, but the cursor moves off the -- button, then we won't receive the mouse up event. To make sure -- the button doesn't get stuck held down, we'll release it if the -- cursor leaves the button. isHeldDown : set ( false ) isHovering : set ( false ) end , [ Children ] = { New \"UICorner\" { CornerRadius = ROUNDED_CORNERS }, New \"UIPadding\" { PaddingTop = PADDING . Y , PaddingBottom = PADDING . Y , PaddingLeft = PADDING . X , PaddingRight = PADDING . X } } } end return Button","title":"Overview"},{"location":"examples/cookbook/button-component/#explanation","text":"The main part of note is the function signature. It's highly recommended that you statically type the function signature for components, because it not only improves autocomplete and error checking, but also acts as up-to-date, machine readable documentation. 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Name : UsedAs < string > ? , Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , AutomaticSize : UsedAs < Enum . AutomaticSize > ? }, Text : UsedAs < string > ? , Disabled : UsedAs < boolean > ? , OnClick : (() -> ()) ? } ): Fusion . Child The scope parameter specifies that the component depends on Fusion's methods. If you're not sure how to write type definitions for scopes, the 'Scopes' section of the Components tutorial goes into further detail. The property table is laid out with each property on a new line, so it's easy to scan the list and see what properties are available. Most are typed with UsedAs , which allows the user to use state objects if they desire. They're also ? (optional), which can reduce boilerplate when using the component. Not all properties have to be that way, but usually it's better to have the flexibility. Property grouping You can group properties together in nested tables, like the Layout table above, to avoid long mixed lists of properties. In addition to being more readable, this can sometimes help with passing around lots of properties at once, because you can pass the whole nested table as one value if you'd like to. The return type of the function is Fusion.Child , which tells the user that the component is compatible with Fusion's [Children] API, without exposing what children it's returning specifically. This helps ensure the user doesn't accidentally depend on the internal structure of the component.","title":"Explanation"},{"location":"examples/cookbook/drag-and-drop/","text":"This example shows a full drag-and-drop implementation for mouse input only, using Fusion's Roblox API. To ensure best accessibility, any interactions you implement shouldn't force you to hold the mouse button down. Either allow drag-and-drop with single clicks, or provide a non-dragging alternative. This ensures people with reduced motor ability aren't locked out of UI functions. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 local Players = game : GetService ( \"Players\" ) local UserInputService = game : GetService ( \"UserInputService\" ) local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children , OnEvent = Fusion . Children , Fusion . OnEvent type UsedAs < T > = Fusion . UsedAs < T > type DragInfo = { id : string , mouseOffset : Vector2 -- relative to the dragged item } local function Draggable ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { ID : string , Name : UsedAs < string > ? , Parent : Fusion . StateObject < Instance ? > , -- StateObject so it's observable Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , OutAbsolutePosition : Fusion . Value < Vector2 > ? , }, Dragging : { MousePosition : UsedAs < Vector2 > , SelfDragInfo : UsedAs < DragInfo ? > , OverlayFrame : UsedAs < Instance ? > } [ typeof ( Children )]: Fusion . Child } ): Fusion . Child -- When `nil`, the parent can't be measured for some reason. local parentSize = scope : Value ( nil ) do local function measureParentNow () local parent = peek ( props . Parent ) parentSize : set ( if parent ~= nil and parent : IsA ( \"GuiObject\" ) then parent . AbsoluteSize else nil ) end local resizeConn = nil local function stopMeasuring () if resizeConn ~= nil then resizeConn : Disconnect () resizeConn = nil end end scope : Observer ( props . Parent ): onBind ( function () stopMeasuring () measureParentNow () if peek ( parentSize ) ~= nil then resizeConn = parent : GetPropertyChangedSignal ( \"AbsoluteSize\" ) : Connect ( measureParentNow ) end end ) table.insert ( scope , stopMeasuring ) end return New \"Frame\" { Name = props . Name or \"Draggable\" , Parent = scope : Computed ( function ( use ) return if use ( props . Dragging . SelfDragInfo ) ~= nil then use ( props . Dragging . OverlayFrame ) else use ( props . Parent ) end ), LayoutOrder = props . Layout . LayoutOrder , AnchorPoint = props . Layout . AnchorPoint , ZIndex = props . Layout . ZIndex , AutomaticSize = props . Layout . AutomaticSize , BackgroundTransparency = 1 , Position = scope : Computed ( function ( use ) local dragInfo = use ( props . Dragging . SelfDragInfo ) if dragInfo == nil then return use ( props . Layout . Position ) or UDim2 . fromOffset ( 0 , 0 ) else local mousePos = use ( props . Dragging . MousePosition ) local topLeftCorner = mousePos - dragInfo . mouseOffset return UDim2 . fromOffset ( topLeftCorner . X , topLeftCorner . Y ) end end ), -- Calculated manually so the Scale can be set relative to -- `props.Parent` at all times, rather than the `Parent` of this Frame. Size = scope : Computed ( function ( use ) local udim2 = use ( props . Layout . Size ) or UDim2 . fromOffset ( 0 , 0 ) local parentSize = use ( parentSize ) or Vector2 . zero return UDim2 . fromOffset ( udim2 . X . Scale * parentSize . X + udim2 . X . Offset , udim2 . Y . Scale * parentSize . Y + udim2 . Y . Offset ) end ), [ Out \"AbsolutePosition\" ] = props . OutAbsolutePosition , [ Children ] = props [ Children ] } end local COLOUR_COMPLETED = Color3 . new ( 0 , 1 , 0 ) local COLOUR_NOT_COMPLETED = Color3 . new ( 1 , 1 , 1 ) local TODO_ITEM_SIZE = UDim2 . new ( 1 , 0 , 0 , 50 ) local function newUniqueID () -- You can replace this with a better method for generating unique IDs. return game : GetService ( \"HttpService\" ): GenerateGUID () end type TodoItem = { id : string , text : string , completed : Fusion . Value < boolean > } local todoItems : Fusion . Value < TodoItem > = { { id = newUniqueID (), text = \"Wake up today\" , completed = Value ( true ) }, { id = newUniqueID (), text = \"Read the Fusion docs\" , completed = Value ( true ) }, { id = newUniqueID (), text = \"Take over the universe\" , completed = Value ( false ) } } local function getTodoItemForID ( id : string ): TodoItem ? for _ , item in todoItems do if item . id == id then return item end end return nil end local function TodoEntry ( outerScope : Fusion . Scope < {} > , props : { Item : TodoItem , Parent : Fusion . StateObject < Instance ? > , Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , OutAbsolutePosition : Fusion . Value < Vector2 > ? , }, Dragging : { MousePosition : UsedAs < Vector2 > , SelfDragInfo : UsedAs < CurrentlyDragging ? > , OverlayFrame : UsedAs < Instance > ? }, OnMouseDown : () -> () ? } ): Fusion . Child local scope = scoped ( Fusion , { Draggable = Draggable }) table.insert ( outerScope , scope ) local itemPosition = scope : Value ( nil ) local itemIsDragging = scope : Computed ( function ( use ) local dragInfo = use ( props . CurrentlyDragging ) return dragInfo ~= nil and dragInfo . id == props . Item . id end ) return scope : Draggable { ID = props . Item . id , Name = props . Item . text , Parent = props . Parent , Layout = props . Layout , Dragging = props . Dragging , [ Children ] = scope : New \"TextButton\" { Name = \"TodoEntry\" , Size = UDim2 . fromScale ( 1 , 1 ), BackgroundColor3 = scope : Computed ( function ( use ) return if use ( props . Item . completed ) then COLOUR_COMPLETED else COLOUR_NOT_COMPLETED end end ), Text = props . Item . text , TextSize = 28 , [ OnEvent \"MouseButton1Down\" ] = props . OnMouseDown -- Don't detect mouse up here, because in some rare cases, the event -- could be missed due to lag between the item's position and the -- cursor position. } } end -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion ) local mousePos = scope : Value ( UserInputService : GetMouseLocation ()) table.insert ( scope , UserInputService . InputChanged : Connect ( function ( inputObject ) if inputObject . UserInputType == Enum . UserInputType . MouseMovement then -- If this code did not read coordinates from the same method, it -- might inconsistently handle UI insets. So, keep it simple! mousePos : set ( UserInputService : GetMouseLocation ()) end end ) ) local dropAction = scope : Value ( nil ) local taskLists = scope : ForPairs ( { incomplete = \"mark-as-incomplete\" , completed = \"mark-as-completed\" }, function ( use , scope , listName , listDropAction ) return listName , scope : New \"ScrollingFrame\" { Name = ` TaskList ({ listName }) ` , Position = UDim2 . fromScale ( 0.1 , 0.1 ), Size = UDim2 . fromScale ( 0.35 , 0.9 ), BackgroundTransparency = 0.75 , BackgroundColor3 = Color3 . new ( 1 , 0 , 0 ), [ OnEvent \"MouseEnter\" ] = function () dropAction : set ( listDropAction ) end , [ OnEvent \"MouseLeave\" ] = function () -- A different item might have overwritten this already. if peek ( dropAction ) == listDropAction then dropAction : set ( nil ) end end , [ Children ] = { New \"UIListLayout\" { SortOrder = \"Name\" , Padding = UDim . new ( 0 , 5 ) } } } end ) local overlayFrame = scope : New \"Frame\" { Size = UDim2 . fromScale ( 1 , 1 ), ZIndex = 10 , BackgroundTransparency = 1 } local currentlyDragging : Fusion . Value < DragInfo ? > = scope : Value ( nil ) local allEntries = scope : ForValues ( todoItems , function ( use , scope , item ) local itemPosition = scope : Value ( nil ) return scope : TodoEntry { Item = item , Parent = scope : Computed ( function ( use ) return if use ( item . completed ) then use ( taskLists ). completed else use ( taskLists ). incomplete end ), Layout = { Size = TODO_ITEM_SIZE , OutAbsolutePosition = itemPosition }, Dragging = { MousePosition = mousePos , SelfDragInfo = scope : Computed ( function ( use ) local dragInfo = use ( currentlyDragging ) return if dragInfo == nil or dragInfo . id ~= item . id then nil else dragInfo end ) OverlayFrame = overlayFrame }, OnMouseDown = function () if peek ( currentlyDragging ) == nil then local itemPos = peek ( itemPosition ) or Vector2 . zero local mouseOffset = peek ( mousePos ) - itemPos currentlyDragging : set ({ id = item . id , mouseOffset = mouseOffset }) end end } end ) table.insert ( scope , UserInputService . InputEnded : Connect ( function ( inputObject ) if inputObject . UserInputType ~= Enum . UserInputType . MouseButton1 then return end local dragInfo = peek ( currentlyDragging ) if dragInfo == nil then return end local item = getTodoItemForID ( dragInfo . id ) local action = peek ( dropAction ) if item ~= nil then if action == \"mark-as-incomplete\" then item . completed : set ( false ) elseif action == \"mark-as-completed\" then item . completed : set ( true ) end end currentlyDragging : set ( nil ) end ) ) local ui = scope : New \"ScreenGui\" { Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ) [ Children ] = { overlayFrame , taskLists , -- Don't pass `allEntries` in here - they manage their own parent! } } Explanation \u00b6 The basic idea is to create a container which stores the UI you want to drag. This container then reparents itself as it gets dragged around between different containers. The Draggable component implements everything necessary to make a seamlessly re-parentable container. 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 type DragInfo = { id : string , mouseOffset : Vector2 -- relative to the dragged item } local function Draggable ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { ID : string , Name : UsedAs < string > ? , Parent : Fusion . StateObject < Instance ? > , -- StateObject so it's observable Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , OutAbsolutePosition : Fusion . Value < Vector2 > ? , }, Dragging : { MousePosition : UsedAs < Vector2 > , SelfDragInfo : UsedAs < DragInfo ? > , OverlayFrame : UsedAs < Instance ? > } [ typeof ( Children )]: Fusion . Child } ): Fusion . Child By default, Draggable behaves like a regular Frame, parenting itself to the Parent property and applying its Layout properties. It only behaves specially when Dragging.SelfDragInfo is provided. Firstly, it reparents itself to Dragging.OverlayFrame , so it can be seen in front of other UI. 67 68 69 70 71 72 Parent = scope : Computed ( function ( use ) return if use ( props . Dragging . SelfDragInfo ) ~= nil then use ( props . Dragging . OverlayFrame ) else use ( props . Parent ) end ), Because of this reparenting, Draggable has to do some extra work to keep the size consistent; it manually calculates the size based on the size of Parent , so it doesn't change size when moved to Dragging.OverlayFrame . 90 91 92 93 94 95 96 97 98 99 -- Calculated manually so the Scale can be set relative to -- `props.Parent` at all times, rather than the `Parent` of this Frame. Size = scope : Computed ( function ( use ) local udim2 = use ( props . Layout . Size ) or UDim2 . fromOffset ( 0 , 0 ) local parentSize = use ( parentSize ) or Vector2 . zero return UDim2 . fromOffset ( udim2 . X . Scale * parentSize . X + udim2 . X . Offset , udim2 . Y . Scale * parentSize . Y + udim2 . Y . Offset ) end ), The Draggable also needs to snap to the mouse cursor, so it can be moved by the user. Ideally, the mouse would stay fixed in position relative to the Draggable , so there are no abrupt changes in the position of any elements. As part of Dragging.SelfDragInfo , a mouseOffset is provided, which describes how far the mouse should stay from the top-left corner. So, when setting the position of the Draggable , that offset can be applied to keep the UI fixed in position relative to the mouse. 81 82 83 84 85 86 87 88 89 90 Position = scope : Computed ( function ( use ) local dragInfo = use ( props . Dragging . SelfDragInfo ) if dragInfo == nil then return use ( props . Layout . Position ) or UDim2 . fromOffset ( 0 , 0 ) else local mousePos = use ( props . Dragging . MousePosition ) local topLeftCorner = mousePos - dragInfo . mouseOffset return UDim2 . fromOffset ( topLeftCorner . X , topLeftCorner . Y ) end end ), This is all that's needed to make a generic container that can seamlessly move between distinct parts of the UI. The rest of the example demonstrates how this can be integrated into real world UI. The example creates a list of TodoItem objects, each with a unique ID, text message, and completion status. Because we don't expect the ID or text to change, they're just constant values. However, the completion status is expected to change, so that's specified to be a Value object. 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 type TodoItem = { id : string , text : string , completed : Fusion . Value < boolean > } local todoItems : Fusion . Value < TodoItem > = { { id = newUniqueID (), text = \"Wake up today\" , completed = Value ( true ) }, { id = newUniqueID (), text = \"Read the Fusion docs\" , completed = Value ( true ) }, { id = newUniqueID (), text = \"Take over the universe\" , completed = Value ( false ) } } The TodoEntry component is meant to represent one individual TodoItem . 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 local function TodoEntry ( outerScope : Fusion . Scope < {} > , props : { Item : TodoItem , Parent : Fusion . StateObject < Instance ? > , Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , OutAbsolutePosition : Fusion . Value < Vector2 > ? , }, Dragging : { MousePosition : UsedAs < Vector2 > , SelfDragInfo : UsedAs < CurrentlyDragging ? > , OverlayFrame : UsedAs < Instance > ? }, OnMouseDown : () -> () ? } ): Fusion . Child Notice that it shares many of the same property groups as Draggable - these can be passed directly through. 182 183 184 185 186 187 return scope : Draggable { ID = props . Item . id , Name = props . Item . text , Parent = props . Parent , Layout = props . Layout , Dragging = props . Dragging , It also provides an OnMouseDown callback, which can be used to pick up the entry if the mouse is pressed down above the entry. Note the comment about why it is not desirable to detect mouse-up here; the UI should unconditionally respond to mouse-up, even if the mouse happens to briefly leave this element. 202 203 204 205 206 [ OnEvent \"MouseButton1Down\" ] = props . OnMouseDown -- Don't detect mouse up here, because in some rare cases, the event -- could be missed due to lag between the item's position and the -- cursor position. Now, the destinations for these entries can be created. To help decide where to drop items later, the dropAction tracks which destination the mouse is hovered over. 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 local dropAction = scope : Value ( nil ) local taskLists = scope : ForPairs ( { incomplete = \"mark-as-incomplete\" , completed = \"mark-as-completed\" }, function ( use , scope , listName , listDropAction ) return listName , scope : New \"ScrollingFrame\" { Name = ` TaskList ({ listName }) ` , Position = UDim2 . fromScale ( 0.1 , 0.1 ), Size = UDim2 . fromScale ( 0.35 , 0.9 ), BackgroundTransparency = 0.75 , BackgroundColor3 = Color3 . new ( 1 , 0 , 0 ), [ OnEvent \"MouseEnter\" ] = function () dropAction : set ( listDropAction ) end , [ OnEvent \"MouseLeave\" ] = function () -- A different item might have overwritten this already. if peek ( dropAction ) == listDropAction then dropAction : set ( nil ) end end , [ Children ] = { New \"UIListLayout\" { SortOrder = \"Name\" , Padding = UDim . new ( 0 , 5 ) } } } end ) This is also where the 'overlay frame' is created, which gives currently-dragged UI a dedicated layer above all other UI to freely move around. 265 266 267 268 269 local overlayFrame = scope : New \"Frame\" { Size = UDim2 . fromScale ( 1 , 1 ), ZIndex = 10 , BackgroundTransparency = 1 } Finally, each TodoItem is created as a TodoEntry . Some state is also created to track which entry is being dragged at the moment. 271 272 273 274 275 276 277 278 local currentlyDragging : Fusion . Value < DragInfo ? > = scope : Value ( nil ) local allEntries = scope : ForValues ( todoItems , function ( use , scope , item ) local itemPosition = scope : Value ( nil ) return scope : TodoEntry { Item = item , Each entry dynamically picks one of the two destinations based on its completion status. 279 280 281 282 283 284 Parent = scope : Computed ( function ( use ) return if use ( item . completed ) then use ( taskLists ). completed else use ( taskLists ). incomplete end ), It also provides the information needed by the Draggable . Note that the current drag information is filtered from the currentlyDragging state so the Draggable won't see information about other entries being dragged. 289 290 291 292 293 294 295 296 297 298 299 Dragging = { MousePosition = mousePos , SelfDragInfo = scope : Computed ( function ( use ) local dragInfo = use ( currentlyDragging ) return if dragInfo == nil or dragInfo . id ~= item . id then nil else dragInfo end ) OverlayFrame = overlayFrame }, Now it's time to handle starting and stopping the drag. To begin the drag, this code makes use of the OnMouseDown callback. If nothing else is being dragged right now, the position of the mouse relative to the item is captured. Then, that mouseOffset and the id of the item are passed into the currentlyDragging state to indicate this entry is being dragged. 300 301 302 303 304 305 306 307 308 309 OnMouseDown = function () if peek ( currentlyDragging ) == nil then local itemPos = peek ( itemPosition ) or Vector2 . zero local mouseOffset = peek ( mousePos ) - itemPos currentlyDragging : set ({ id = item . id , mouseOffset = mouseOffset }) end end To end the drag, a global InputEnded listener is created, which should reliably fire no matter where or when the event occurs. If there's a dropAction to take, for example mark-as-completed , then that action is executed here. In all cases, currentlyDragging is cleared, so the entry is no longer dragged. 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 table.insert ( scope , UserInputService . InputEnded : Connect ( function ( inputObject ) if inputObject . UserInputType ~= Enum . UserInputType . MouseButton1 then return end local dragInfo = peek ( currentlyDragging ) if dragInfo == nil then return end local item = getTodoItemForID ( dragInfo . id ) local action = peek ( dropAction ) if item ~= nil then if action == \"mark-as-incomplete\" then item . completed : set ( false ) elseif action == \"mark-as-completed\" then item . completed : set ( true ) end end currentlyDragging : set ( nil ) end ) ) All that remains is to parent the task lists and overlay frames to a UI, so they can be seen. Because the TodoEntry component manages their own parent, this code shouldn't pass in allEntries as a child here. 336 337 338 339 340 341 342 343 344 local ui = scope : New \"ScreenGui\" { Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ) [ Children ] = { overlayFrame , taskLists , -- Don't pass `allEntries` in here - they manage their own parent! } }","title":"Drag & Drop"},{"location":"examples/cookbook/drag-and-drop/#explanation","text":"The basic idea is to create a container which stores the UI you want to drag. This container then reparents itself as it gets dragged around between different containers. The Draggable component implements everything necessary to make a seamlessly re-parentable container. 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 type DragInfo = { id : string , mouseOffset : Vector2 -- relative to the dragged item } local function Draggable ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { ID : string , Name : UsedAs < string > ? , Parent : Fusion . StateObject < Instance ? > , -- StateObject so it's observable Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , OutAbsolutePosition : Fusion . Value < Vector2 > ? , }, Dragging : { MousePosition : UsedAs < Vector2 > , SelfDragInfo : UsedAs < DragInfo ? > , OverlayFrame : UsedAs < Instance ? > } [ typeof ( Children )]: Fusion . Child } ): Fusion . Child By default, Draggable behaves like a regular Frame, parenting itself to the Parent property and applying its Layout properties. It only behaves specially when Dragging.SelfDragInfo is provided. Firstly, it reparents itself to Dragging.OverlayFrame , so it can be seen in front of other UI. 67 68 69 70 71 72 Parent = scope : Computed ( function ( use ) return if use ( props . Dragging . SelfDragInfo ) ~= nil then use ( props . Dragging . OverlayFrame ) else use ( props . Parent ) end ), Because of this reparenting, Draggable has to do some extra work to keep the size consistent; it manually calculates the size based on the size of Parent , so it doesn't change size when moved to Dragging.OverlayFrame . 90 91 92 93 94 95 96 97 98 99 -- Calculated manually so the Scale can be set relative to -- `props.Parent` at all times, rather than the `Parent` of this Frame. Size = scope : Computed ( function ( use ) local udim2 = use ( props . Layout . Size ) or UDim2 . fromOffset ( 0 , 0 ) local parentSize = use ( parentSize ) or Vector2 . zero return UDim2 . fromOffset ( udim2 . X . Scale * parentSize . X + udim2 . X . Offset , udim2 . Y . Scale * parentSize . Y + udim2 . Y . Offset ) end ), The Draggable also needs to snap to the mouse cursor, so it can be moved by the user. Ideally, the mouse would stay fixed in position relative to the Draggable , so there are no abrupt changes in the position of any elements. As part of Dragging.SelfDragInfo , a mouseOffset is provided, which describes how far the mouse should stay from the top-left corner. So, when setting the position of the Draggable , that offset can be applied to keep the UI fixed in position relative to the mouse. 81 82 83 84 85 86 87 88 89 90 Position = scope : Computed ( function ( use ) local dragInfo = use ( props . Dragging . SelfDragInfo ) if dragInfo == nil then return use ( props . Layout . Position ) or UDim2 . fromOffset ( 0 , 0 ) else local mousePos = use ( props . Dragging . MousePosition ) local topLeftCorner = mousePos - dragInfo . mouseOffset return UDim2 . fromOffset ( topLeftCorner . X , topLeftCorner . Y ) end end ), This is all that's needed to make a generic container that can seamlessly move between distinct parts of the UI. The rest of the example demonstrates how this can be integrated into real world UI. The example creates a list of TodoItem objects, each with a unique ID, text message, and completion status. Because we don't expect the ID or text to change, they're just constant values. However, the completion status is expected to change, so that's specified to be a Value object. 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 type TodoItem = { id : string , text : string , completed : Fusion . Value < boolean > } local todoItems : Fusion . Value < TodoItem > = { { id = newUniqueID (), text = \"Wake up today\" , completed = Value ( true ) }, { id = newUniqueID (), text = \"Read the Fusion docs\" , completed = Value ( true ) }, { id = newUniqueID (), text = \"Take over the universe\" , completed = Value ( false ) } } The TodoEntry component is meant to represent one individual TodoItem . 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 local function TodoEntry ( outerScope : Fusion . Scope < {} > , props : { Item : TodoItem , Parent : Fusion . StateObject < Instance ? > , Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? , Size : UsedAs < UDim2 > ? , OutAbsolutePosition : Fusion . Value < Vector2 > ? , }, Dragging : { MousePosition : UsedAs < Vector2 > , SelfDragInfo : UsedAs < CurrentlyDragging ? > , OverlayFrame : UsedAs < Instance > ? }, OnMouseDown : () -> () ? } ): Fusion . Child Notice that it shares many of the same property groups as Draggable - these can be passed directly through. 182 183 184 185 186 187 return scope : Draggable { ID = props . Item . id , Name = props . Item . text , Parent = props . Parent , Layout = props . Layout , Dragging = props . Dragging , It also provides an OnMouseDown callback, which can be used to pick up the entry if the mouse is pressed down above the entry. Note the comment about why it is not desirable to detect mouse-up here; the UI should unconditionally respond to mouse-up, even if the mouse happens to briefly leave this element. 202 203 204 205 206 [ OnEvent \"MouseButton1Down\" ] = props . OnMouseDown -- Don't detect mouse up here, because in some rare cases, the event -- could be missed due to lag between the item's position and the -- cursor position. Now, the destinations for these entries can be created. To help decide where to drop items later, the dropAction tracks which destination the mouse is hovered over. 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 local dropAction = scope : Value ( nil ) local taskLists = scope : ForPairs ( { incomplete = \"mark-as-incomplete\" , completed = \"mark-as-completed\" }, function ( use , scope , listName , listDropAction ) return listName , scope : New \"ScrollingFrame\" { Name = ` TaskList ({ listName }) ` , Position = UDim2 . fromScale ( 0.1 , 0.1 ), Size = UDim2 . fromScale ( 0.35 , 0.9 ), BackgroundTransparency = 0.75 , BackgroundColor3 = Color3 . new ( 1 , 0 , 0 ), [ OnEvent \"MouseEnter\" ] = function () dropAction : set ( listDropAction ) end , [ OnEvent \"MouseLeave\" ] = function () -- A different item might have overwritten this already. if peek ( dropAction ) == listDropAction then dropAction : set ( nil ) end end , [ Children ] = { New \"UIListLayout\" { SortOrder = \"Name\" , Padding = UDim . new ( 0 , 5 ) } } } end ) This is also where the 'overlay frame' is created, which gives currently-dragged UI a dedicated layer above all other UI to freely move around. 265 266 267 268 269 local overlayFrame = scope : New \"Frame\" { Size = UDim2 . fromScale ( 1 , 1 ), ZIndex = 10 , BackgroundTransparency = 1 } Finally, each TodoItem is created as a TodoEntry . Some state is also created to track which entry is being dragged at the moment. 271 272 273 274 275 276 277 278 local currentlyDragging : Fusion . Value < DragInfo ? > = scope : Value ( nil ) local allEntries = scope : ForValues ( todoItems , function ( use , scope , item ) local itemPosition = scope : Value ( nil ) return scope : TodoEntry { Item = item , Each entry dynamically picks one of the two destinations based on its completion status. 279 280 281 282 283 284 Parent = scope : Computed ( function ( use ) return if use ( item . completed ) then use ( taskLists ). completed else use ( taskLists ). incomplete end ), It also provides the information needed by the Draggable . Note that the current drag information is filtered from the currentlyDragging state so the Draggable won't see information about other entries being dragged. 289 290 291 292 293 294 295 296 297 298 299 Dragging = { MousePosition = mousePos , SelfDragInfo = scope : Computed ( function ( use ) local dragInfo = use ( currentlyDragging ) return if dragInfo == nil or dragInfo . id ~= item . id then nil else dragInfo end ) OverlayFrame = overlayFrame }, Now it's time to handle starting and stopping the drag. To begin the drag, this code makes use of the OnMouseDown callback. If nothing else is being dragged right now, the position of the mouse relative to the item is captured. Then, that mouseOffset and the id of the item are passed into the currentlyDragging state to indicate this entry is being dragged. 300 301 302 303 304 305 306 307 308 309 OnMouseDown = function () if peek ( currentlyDragging ) == nil then local itemPos = peek ( itemPosition ) or Vector2 . zero local mouseOffset = peek ( mousePos ) - itemPos currentlyDragging : set ({ id = item . id , mouseOffset = mouseOffset }) end end To end the drag, a global InputEnded listener is created, which should reliably fire no matter where or when the event occurs. If there's a dropAction to take, for example mark-as-completed , then that action is executed here. In all cases, currentlyDragging is cleared, so the entry is no longer dragged. 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 table.insert ( scope , UserInputService . InputEnded : Connect ( function ( inputObject ) if inputObject . UserInputType ~= Enum . UserInputType . MouseButton1 then return end local dragInfo = peek ( currentlyDragging ) if dragInfo == nil then return end local item = getTodoItemForID ( dragInfo . id ) local action = peek ( dropAction ) if item ~= nil then if action == \"mark-as-incomplete\" then item . completed : set ( false ) elseif action == \"mark-as-completed\" then item . completed : set ( true ) end end currentlyDragging : set ( nil ) end ) ) All that remains is to parent the task lists and overlay frames to a UI, so they can be seen. Because the TodoEntry component manages their own parent, this code shouldn't pass in allEntries as a child here. 336 337 338 339 340 341 342 343 344 local ui = scope : New \"ScreenGui\" { Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ) [ Children ] = { overlayFrame , taskLists , -- Don't pass `allEntries` in here - they manage their own parent! } }","title":"Explanation"},{"location":"examples/cookbook/fetch-data-from-server/","text":"This code shows how to deal with yielding/blocking code, such as fetching data from a server. Because these tasks don't complete immediately, they can't be directly run inside of a Computed , so this example provides a robust framework for handling this in a way that doesn't corrupt your code. This example assumes the presence of a Roblox-like task scheduler. Overview \u00b6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local function fetchUserBio ( userID : number ): string -- pretend this calls out to a server somewhere, causing this code to yield task . wait ( 1 ) return \"This is the bio for user \" .. userID .. \"!\" end -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion ) -- This doesn't have to be a `Value` - any kind of state object works too. local currentUserID = scope : Value ( 1670764 ) -- While the bio is loading, this is `nil` instead of a string. local currentUserBio : Fusion . Value < string ? > = scope : Value ( nil ) do local fetchInProgress = nil local function performFetch () local userID = peek ( currentUserID ) currentUserBio : set ( nil ) if fetchInProgress ~= nil then task . cancel ( fetchInProgress ) end fetchInProgress = task . spawn ( function () currentUserBio : set ( fetchUserBio ()) fetchInProgress = nil end ) end scope : Observer ( currentUserID ): onBind ( performFetch ) end scope : Observer ( currentUserBio ): onBind ( function () local bio = peek ( currentUserBio ) if bio == nil then print ( \"User bio is loading...\" ) else print ( \"Loaded user bio:\" , bio ) end end ) Explanation \u00b6 If you yield or wait inside of a Computed , you can easily corrupt your entire program. However, this example has a function, fetchUserBio , that yields. 5 6 7 8 9 10 11 local function fetchUserBio ( userID : number ): string -- pretend this calls out to a server somewhere, causing this code to yield task . wait ( 1 ) return \"This is the bio for user \" .. userID .. \"!\" end It also has some arbitrary state object, currentUserID , that it needs to convert into a bio somehow. 15 16 -- This doesn't have to be a `Value` - any kind of state object works too. local currentUserID = scope : Value ( 1670764 ) Because Computed can't yield, this code has to manually manage a currentUserBio object, which will store the output of the code in a way that can be used by other Fusion objects later. Notice that the 'loading' state is explicitly documented. It's a good idea to be clear and honest when you have no data to show, because it allows other code to respond to that case flexibly. 18 19 -- While the bio is loading, this is `nil` instead of a string. local currentUserBio : Fusion . Value < string ? > = scope : Value ( nil ) To perform the actual fetch, a simple function can be written which calls fetchUserBio in a separate task. Once it returns a bio, the currentUserBio can be updated. To avoid two fetches overwriting each other, any existing fetch task is canceled before the new task is created. 22 23 24 25 26 27 28 29 30 31 32 33 local fetchInProgress = nil local function performFetch () local userID = peek ( currentUserID ) currentUserBio : set ( nil ) if fetchInProgress ~= nil then task . cancel ( fetchInProgress ) end fetchInProgress = task . spawn ( function () currentUserBio : set ( fetchUserBio ()) fetchInProgress = nil end ) end Finally, to run this function when the currentUserID changes, performFetch can be added to an Observer . The onBind method also runs performFetch once at the start of the program, so the request is sent out automatically. 34 scope : Observer ( currentUserID ): onBind ( performFetch ) That's all you need - now, any other Fusion code can read and depend upon currentUserBio as if it were any other kind of state object. Just remember to handle the 'loading' state as well as the successful state. 37 38 39 40 41 42 43 44 scope : Observer ( currentUserBio ): onBind ( function () local bio = peek ( currentUserBio ) if bio == nil then print ( \"User bio is loading...\" ) else print ( \"Loaded user bio:\" , bio ) end end ) You may wish to expand this code with error handling if fetchUserBio() can throw errors.","title":"Fetch Data From Server"},{"location":"examples/cookbook/fetch-data-from-server/#overview","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local function fetchUserBio ( userID : number ): string -- pretend this calls out to a server somewhere, causing this code to yield task . wait ( 1 ) return \"This is the bio for user \" .. userID .. \"!\" end -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion ) -- This doesn't have to be a `Value` - any kind of state object works too. local currentUserID = scope : Value ( 1670764 ) -- While the bio is loading, this is `nil` instead of a string. local currentUserBio : Fusion . Value < string ? > = scope : Value ( nil ) do local fetchInProgress = nil local function performFetch () local userID = peek ( currentUserID ) currentUserBio : set ( nil ) if fetchInProgress ~= nil then task . cancel ( fetchInProgress ) end fetchInProgress = task . spawn ( function () currentUserBio : set ( fetchUserBio ()) fetchInProgress = nil end ) end scope : Observer ( currentUserID ): onBind ( performFetch ) end scope : Observer ( currentUserBio ): onBind ( function () local bio = peek ( currentUserBio ) if bio == nil then print ( \"User bio is loading...\" ) else print ( \"Loaded user bio:\" , bio ) end end )","title":"Overview"},{"location":"examples/cookbook/fetch-data-from-server/#explanation","text":"If you yield or wait inside of a Computed , you can easily corrupt your entire program. However, this example has a function, fetchUserBio , that yields. 5 6 7 8 9 10 11 local function fetchUserBio ( userID : number ): string -- pretend this calls out to a server somewhere, causing this code to yield task . wait ( 1 ) return \"This is the bio for user \" .. userID .. \"!\" end It also has some arbitrary state object, currentUserID , that it needs to convert into a bio somehow. 15 16 -- This doesn't have to be a `Value` - any kind of state object works too. local currentUserID = scope : Value ( 1670764 ) Because Computed can't yield, this code has to manually manage a currentUserBio object, which will store the output of the code in a way that can be used by other Fusion objects later. Notice that the 'loading' state is explicitly documented. It's a good idea to be clear and honest when you have no data to show, because it allows other code to respond to that case flexibly. 18 19 -- While the bio is loading, this is `nil` instead of a string. local currentUserBio : Fusion . Value < string ? > = scope : Value ( nil ) To perform the actual fetch, a simple function can be written which calls fetchUserBio in a separate task. Once it returns a bio, the currentUserBio can be updated. To avoid two fetches overwriting each other, any existing fetch task is canceled before the new task is created. 22 23 24 25 26 27 28 29 30 31 32 33 local fetchInProgress = nil local function performFetch () local userID = peek ( currentUserID ) currentUserBio : set ( nil ) if fetchInProgress ~= nil then task . cancel ( fetchInProgress ) end fetchInProgress = task . spawn ( function () currentUserBio : set ( fetchUserBio ()) fetchInProgress = nil end ) end Finally, to run this function when the currentUserID changes, performFetch can be added to an Observer . The onBind method also runs performFetch once at the start of the program, so the request is sent out automatically. 34 scope : Observer ( currentUserID ): onBind ( performFetch ) That's all you need - now, any other Fusion code can read and depend upon currentUserBio as if it were any other kind of state object. Just remember to handle the 'loading' state as well as the successful state. 37 38 39 40 41 42 43 44 scope : Observer ( currentUserBio ): onBind ( function () local bio = peek ( currentUserBio ) if bio == nil then print ( \"User bio is loading...\" ) else print ( \"Loaded user bio:\" , bio ) end end ) You may wish to expand this code with error handling if fetchUserBio() can throw errors.","title":"Explanation"},{"location":"examples/cookbook/light-and-dark-theme/","text":"This example demonstrates how to create dynamic theme colours using Fusion's state objects. Overview \u00b6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 local Fusion = --initialise Fusion here however you please! local scoped = Fusion . scoped local Theme = {} Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"222222\" ), dark = Color3 . fromHex ( \"FFFFFF\" ) } } -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion ) Theme . current = scope : Value ( \"light\" ) Theme . dynamic = {} for colour , variants in Theme . colours do Theme . dynamic [ colour ] = scope : Computed ( function ( use ) return variants [ use ( Theme . current )] end ) end Theme . current : set ( \"light\" ) print ( peek ( Theme . dynamic . background )) --> 255, 255, 255 Theme . current : set ( \"dark\" ) print ( peek ( Theme . dynamic . background )) --> 34, 34, 34 Explanation \u00b6 To begin, this example defines a set of colours with light and dark variants. 6 7 8 9 10 11 12 13 14 15 Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"222222\" ), dark = Color3 . fromHex ( \"FFFFFF\" ) } } A Value object stores which variant is in use right now. 20 Theme . current = scope : Value ( \"light\" ) Finally, each colour is turned into a Computed , which dynamically pulls the desired variant from the list. 21 22 23 24 25 26 Theme . dynamic = {} for colour , variants in Theme . colours do Theme . dynamic [ colour ] = scope : Computed ( function ( use ) return variants [ use ( Theme . current )] end ) end This allows other code to easily access theme colours from Theme.dynamic . 28 29 30 31 32 Theme . current : set ( \"light\" ) print ( peek ( Theme . dynamic . background )) --> 255, 255, 255 Theme . current : set ( \"dark\" ) print ( peek ( Theme . dynamic . background )) --> 34, 34, 34","title":"Light & Dark Theme"},{"location":"examples/cookbook/light-and-dark-theme/#overview","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 local Fusion = --initialise Fusion here however you please! local scoped = Fusion . scoped local Theme = {} Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"222222\" ), dark = Color3 . fromHex ( \"FFFFFF\" ) } } -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion ) Theme . current = scope : Value ( \"light\" ) Theme . dynamic = {} for colour , variants in Theme . colours do Theme . dynamic [ colour ] = scope : Computed ( function ( use ) return variants [ use ( Theme . current )] end ) end Theme . current : set ( \"light\" ) print ( peek ( Theme . dynamic . background )) --> 255, 255, 255 Theme . current : set ( \"dark\" ) print ( peek ( Theme . dynamic . background )) --> 34, 34, 34","title":"Overview"},{"location":"examples/cookbook/light-and-dark-theme/#explanation","text":"To begin, this example defines a set of colours with light and dark variants. 6 7 8 9 10 11 12 13 14 15 Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"222222\" ), dark = Color3 . fromHex ( \"FFFFFF\" ) } } A Value object stores which variant is in use right now. 20 Theme . current = scope : Value ( \"light\" ) Finally, each colour is turned into a Computed , which dynamically pulls the desired variant from the list. 21 22 23 24 25 26 Theme . dynamic = {} for colour , variants in Theme . colours do Theme . dynamic [ colour ] = scope : Computed ( function ( use ) return variants [ use ( Theme . current )] end ) end This allows other code to easily access theme colours from Theme.dynamic . 28 29 30 31 32 Theme . current : set ( \"light\" ) print ( peek ( Theme . dynamic . background )) --> 255, 255, 255 Theme . current : set ( \"dark\" ) print ( peek ( Theme . dynamic . background )) --> 34, 34, 34","title":"Explanation"},{"location":"examples/cookbook/loading-spinner/","text":"This example implements a procedural spinning animation using Fusion's Roblox APIs. Overview \u00b6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 local RunService = game : GetService ( \"RunService\" ) local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children = Fusion . Children type UsedAs < T > = Fusion . UsedAs < T > local SPIN_DEGREES_PER_SECOND = 180 local SPIN_SIZE = 50 local function Spinner ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? }, CurrentTime : UsedAs < number > , } ): Fusion . Child return scope : New \"ImageLabel\" { Name = \"Spinner\" , LayoutOrder = props . Layout . LayoutOrder , Position = props . Layout . Position , AnchorPoint = props . Layout . AnchorPoint , ZIndex = props . Layout . ZIndex , Size = UDim2 . fromOffset ( SPIN_SIZE , SPIN_SIZE ), BackgroundTransparency = 1 , Image = \"rbxassetid://your-loading-spinner-image\" , -- replace this! Rotation = scope : Computed ( function ( use ) return ( use ( props . CurrentTime ) * SPIN_DEGREES_PER_SECOND ) % 360 end ) } end -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion , { Spinner = Spinner }) local currentTime = scope : Value ( os.clock ()) table.insert ( scope , RunService . RenderStepped : Connect ( function () currentTime : set ( os.clock ()) end ) ) local spinner = scope : Spinner { Layout = { Position = UDim2 . fromScale ( 0.5 , 0.5 ), AnchorPoint = Vector2 . new ( 0.5 , 0.5 ), Size = UDim2 . fromOffset ( 50 , 50 ) }, CurrentTime = currentTime } Explanation \u00b6 The Spinner components implements the animation for the loading spinner. It's largely a standard Fusion component definition. The main thing to note is that it asks for a CurrentTime property. 11 12 13 14 15 16 17 18 19 20 21 22 local function Spinner ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? }, CurrentTime : UsedAs < number > , } ): Fusion . Child The CurrentTime is used to drive the rotation of the loading spinner. 36 37 38 Rotation = scope : Computed ( function ( use ) return ( use ( props . CurrentTime ) * SPIN_DEGREES_PER_SECOND ) % 360 end ) That's all that's required for the Spinner component. Later on, the example creates a Value object that will store the current time, and starts a process to keep it up to date. 47 48 49 50 51 52 local currentTime = scope : Value ( os.clock ()) table.insert ( scope , RunService . RenderStepped : Connect ( function () currentTime : set ( os.clock ()) end ) ) This can then be passed in as CurrentTime when the Spinner is created. 54 55 56 57 58 59 60 61 local spinner = scope : Spinner { Layout = { Position = UDim2 . fromScale ( 0.5 , 0.5 ), AnchorPoint = Vector2 . new ( 0.5 , 0.5 ), Size = UDim2 . fromOffset ( 50 , 50 ) }, CurrentTime = currentTime }","title":"Loading Spinner"},{"location":"examples/cookbook/loading-spinner/#overview","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 local RunService = game : GetService ( \"RunService\" ) local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children = Fusion . Children type UsedAs < T > = Fusion . UsedAs < T > local SPIN_DEGREES_PER_SECOND = 180 local SPIN_SIZE = 50 local function Spinner ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? }, CurrentTime : UsedAs < number > , } ): Fusion . Child return scope : New \"ImageLabel\" { Name = \"Spinner\" , LayoutOrder = props . Layout . LayoutOrder , Position = props . Layout . Position , AnchorPoint = props . Layout . AnchorPoint , ZIndex = props . Layout . ZIndex , Size = UDim2 . fromOffset ( SPIN_SIZE , SPIN_SIZE ), BackgroundTransparency = 1 , Image = \"rbxassetid://your-loading-spinner-image\" , -- replace this! Rotation = scope : Computed ( function ( use ) return ( use ( props . CurrentTime ) * SPIN_DEGREES_PER_SECOND ) % 360 end ) } end -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion , { Spinner = Spinner }) local currentTime = scope : Value ( os.clock ()) table.insert ( scope , RunService . RenderStepped : Connect ( function () currentTime : set ( os.clock ()) end ) ) local spinner = scope : Spinner { Layout = { Position = UDim2 . fromScale ( 0.5 , 0.5 ), AnchorPoint = Vector2 . new ( 0.5 , 0.5 ), Size = UDim2 . fromOffset ( 50 , 50 ) }, CurrentTime = currentTime }","title":"Overview"},{"location":"examples/cookbook/loading-spinner/#explanation","text":"The Spinner components implements the animation for the loading spinner. It's largely a standard Fusion component definition. The main thing to note is that it asks for a CurrentTime property. 11 12 13 14 15 16 17 18 19 20 21 22 local function Spinner ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Layout : { LayoutOrder : UsedAs < number > ? , Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , ZIndex : UsedAs < number > ? }, CurrentTime : UsedAs < number > , } ): Fusion . Child The CurrentTime is used to drive the rotation of the loading spinner. 36 37 38 Rotation = scope : Computed ( function ( use ) return ( use ( props . CurrentTime ) * SPIN_DEGREES_PER_SECOND ) % 360 end ) That's all that's required for the Spinner component. Later on, the example creates a Value object that will store the current time, and starts a process to keep it up to date. 47 48 49 50 51 52 local currentTime = scope : Value ( os.clock ()) table.insert ( scope , RunService . RenderStepped : Connect ( function () currentTime : set ( os.clock ()) end ) ) This can then be passed in as CurrentTime when the Spinner is created. 54 55 56 57 58 59 60 61 local spinner = scope : Spinner { Layout = { Position = UDim2 . fromScale ( 0.5 , 0.5 ), AnchorPoint = Vector2 . new ( 0.5 , 0.5 ), Size = UDim2 . fromOffset ( 50 , 50 ) }, CurrentTime = currentTime }","title":"Explanation"},{"location":"examples/cookbook/player-list/","text":"This shows how to use Fusion's Roblox API to create a simple, dynamically updating player list. Overview \u00b6 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 local Players = game : GetService ( \"Players\" ) local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children = Fusion . Children type UsedAs < T > = Fusion . UsedAs < T > local function PlayerList ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Players : UsedAs < { Player } > } ): Fusion . Child return scope : New \"Frame\" { Name = \"PlayerList\" , Position = UDim2 . fromScale ( 1 , 0 ), AnchorPoint = Vector2 . new ( 1 , 0 ), Size = UDim2 . fromOffset ( 300 , 0 ), AutomaticSize = \"Y\" , BackgroundTransparency = 0.5 , BackgroundColor3 = Color3 . new ( 0 , 0 , 0 ), [ Children ] = { scope : New \"UICorner\" { CornerRadius = UDim . new ( 0 , 8 ) }, scope : New \"UIListLayout\" { SortOrder = \"Name\" , FillDirection = \"Vertical\" }, scope : ForValues ( props . Players , function ( use , scope , player ) return scope : New \"TextLabel\" { Name = \"PlayerListRow: \" .. player . DisplayName , Size = UDim2 . new ( 1 , 0 , 0 , 25 ), BackgroundTransparency = 1 , Text = player . DisplayName , TextColor3 = Color3 . new ( 1 , 1 , 1 ), Font = Enum . Font . GothamMedium , TextSize = 16 , TextXAlignment = \"Right\" , TextTruncate = \"AtEnd\" , [ Children ] = scope : New \"UIPadding\" { PaddingLeft = UDim . new ( 0 , 10 ), PaddingRight = UDim . new ( 0 , 10 ) } } end ) } } end -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion , { PlayerList = PlayerList }) local players = scope : Value ( Players : GetPlayers ()) local function updatePlayers () players : set ( Players : GetPlayers ()) end table.insert ( scope , { Players . PlayerAdded : Connect ( updatePlayers ), Players . PlayerRemoving : Connect ( updatePlayers ) }) local gui = scope : New \"ScreenGui\" { Name = \"PlayerListGui\" , Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ), [ Children ] = scope : PlayerList { Players = players } } Explanation \u00b6 The PlayerList component is designed to be simple and self-contained. The only thing it needs is a Players list - it handles everything else, including its position, size, appearance and behaviour. 8 9 10 11 12 13 local function PlayerList ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Players : UsedAs < { Player } > } ): Fusion . Child After creating a vertically expanding Frame with some style and layout added, it turns the Players into a series of text labels using ForValues , which will automatically create and remove them as the Players list changes. 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 scope : ForValues ( props . Players , function ( use , scope , player ) return scope : New \"TextLabel\" { Name = \"PlayerListRow: \" .. player . DisplayName , Size = UDim2 . new ( 1 , 0 , 0 , 25 ), BackgroundTransparency = 1 , Text = player . DisplayName , TextColor3 = Color3 . new ( 1 , 1 , 1 ), Font = Enum . Font . GothamMedium , TextSize = 16 , TextXAlignment = \"Right\" , TextTruncate = \"AtEnd\" , [ Children ] = scope : New \"UIPadding\" { PaddingLeft = UDim . new ( 0 , 10 ), PaddingRight = UDim . new ( 0 , 10 ) } } end ) That's all that the PlayerList component has to do. Later on, the code creates a Value object to store a list of players, and update it every time a player joins or leaves the game. 63 64 65 66 67 68 69 70 local players = scope : Value ( Players : GetPlayers ()) local function updatePlayers () players : set ( Players : GetPlayers ()) end table.insert ( scope , { Players . PlayerAdded : Connect ( updatePlayers ), Players . PlayerRemoving : Connect ( updatePlayers ) }) That object can then be passed in as Players when creating the PlayerList . 72 73 74 75 76 77 78 79 local gui = scope : New \"ScreenGui\" { Name = \"PlayerListGui\" , Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ), [ Children ] = scope : PlayerList { Players = players } }","title":"Player List"},{"location":"examples/cookbook/player-list/#overview","text":"1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 local Players = game : GetService ( \"Players\" ) local Fusion = -- initialise Fusion here however you please! local scoped = Fusion . scoped local Children = Fusion . Children type UsedAs < T > = Fusion . UsedAs < T > local function PlayerList ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Players : UsedAs < { Player } > } ): Fusion . Child return scope : New \"Frame\" { Name = \"PlayerList\" , Position = UDim2 . fromScale ( 1 , 0 ), AnchorPoint = Vector2 . new ( 1 , 0 ), Size = UDim2 . fromOffset ( 300 , 0 ), AutomaticSize = \"Y\" , BackgroundTransparency = 0.5 , BackgroundColor3 = Color3 . new ( 0 , 0 , 0 ), [ Children ] = { scope : New \"UICorner\" { CornerRadius = UDim . new ( 0 , 8 ) }, scope : New \"UIListLayout\" { SortOrder = \"Name\" , FillDirection = \"Vertical\" }, scope : ForValues ( props . Players , function ( use , scope , player ) return scope : New \"TextLabel\" { Name = \"PlayerListRow: \" .. player . DisplayName , Size = UDim2 . new ( 1 , 0 , 0 , 25 ), BackgroundTransparency = 1 , Text = player . DisplayName , TextColor3 = Color3 . new ( 1 , 1 , 1 ), Font = Enum . Font . GothamMedium , TextSize = 16 , TextXAlignment = \"Right\" , TextTruncate = \"AtEnd\" , [ Children ] = scope : New \"UIPadding\" { PaddingLeft = UDim . new ( 0 , 10 ), PaddingRight = UDim . new ( 0 , 10 ) } } end ) } } end -- Don't forget to pass this to `doCleanup` if you disable the script. local scope = scoped ( Fusion , { PlayerList = PlayerList }) local players = scope : Value ( Players : GetPlayers ()) local function updatePlayers () players : set ( Players : GetPlayers ()) end table.insert ( scope , { Players . PlayerAdded : Connect ( updatePlayers ), Players . PlayerRemoving : Connect ( updatePlayers ) }) local gui = scope : New \"ScreenGui\" { Name = \"PlayerListGui\" , Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ), [ Children ] = scope : PlayerList { Players = players } }","title":"Overview"},{"location":"examples/cookbook/player-list/#explanation","text":"The PlayerList component is designed to be simple and self-contained. The only thing it needs is a Players list - it handles everything else, including its position, size, appearance and behaviour. 8 9 10 11 12 13 local function PlayerList ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Players : UsedAs < { Player } > } ): Fusion . Child After creating a vertically expanding Frame with some style and layout added, it turns the Players into a series of text labels using ForValues , which will automatically create and remove them as the Players list changes. 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 scope : ForValues ( props . Players , function ( use , scope , player ) return scope : New \"TextLabel\" { Name = \"PlayerListRow: \" .. player . DisplayName , Size = UDim2 . new ( 1 , 0 , 0 , 25 ), BackgroundTransparency = 1 , Text = player . DisplayName , TextColor3 = Color3 . new ( 1 , 1 , 1 ), Font = Enum . Font . GothamMedium , TextSize = 16 , TextXAlignment = \"Right\" , TextTruncate = \"AtEnd\" , [ Children ] = scope : New \"UIPadding\" { PaddingLeft = UDim . new ( 0 , 10 ), PaddingRight = UDim . new ( 0 , 10 ) } } end ) That's all that the PlayerList component has to do. Later on, the code creates a Value object to store a list of players, and update it every time a player joins or leaves the game. 63 64 65 66 67 68 69 70 local players = scope : Value ( Players : GetPlayers ()) local function updatePlayers () players : set ( Players : GetPlayers ()) end table.insert ( scope , { Players . PlayerAdded : Connect ( updatePlayers ), Players . PlayerRemoving : Connect ( updatePlayers ) }) That object can then be passed in as Players when creating the PlayerList . 72 73 74 75 76 77 78 79 local gui = scope : New \"ScreenGui\" { Name = \"PlayerListGui\" , Parent = Players . LocalPlayer : FindFirstChildOfClass ( \"PlayerGui\" ), [ Children ] = scope : PlayerList { Players = players } }","title":"Explanation"},{"location":"extras/","text":"Extras \u00b6 Welcome to the Extras section! Here, you can find guidelines and assets for Fusion branding, download backgrounds and wallpapers for your devices, and more! Commonly Used \u00b6 Backgrounds Brand Guidelines","title":"Home"},{"location":"extras/#extras","text":"Welcome to the Extras section! Here, you can find guidelines and assets for Fusion branding, download backgrounds and wallpapers for your devices, and more!","title":"Extras"},{"location":"extras/#commonly-used","text":"Backgrounds Brand Guidelines","title":"Commonly Used"},{"location":"extras/backgrounds/","text":"All backgrounds are PNG format, and have been optimised for these resolutions: Ultrawide (7680 x 1440) Widescreen (2560 x 1440) 3:2 (2256 x 1504) Mobile (1125 x 2436) These backgrounds are intended for personal use only! These backgrounds are, and remain, the copyright of Elttob. You may not use these, commercially or otherwise, without explicit written consent. Isosceles \u00b6 A pattern of isosceles triangles distributed along the bottom, with the Fusion gradient. Background is hex colour #1D1D1F, and so might not be ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile Extrusion \u00b6 A Fusion logo, with extruded fill lines coming out of the logo. Background is hex colour #0D0D0F, and so might not be ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile Construction \u00b6 The Fusion logo, with construction lines shown and other geometric patterns. Background is 100% black, ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile Glow \u00b6 A centred Fusion logo emitting light on a dark background. Background is 100% black, ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile Glow (Alternate) \u00b6 A centred Fusion logo emitting light on a dark background. Uses an alternate design of the logo, which is now used for livestreams. Background is 100% black, ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile","title":"Backgrounds"},{"location":"extras/backgrounds/#isosceles","text":"A pattern of isosceles triangles distributed along the bottom, with the Fusion gradient. Background is hex colour #1D1D1F, and so might not be ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile","title":"Isosceles"},{"location":"extras/backgrounds/#extrusion","text":"A Fusion logo, with extruded fill lines coming out of the logo. Background is hex colour #0D0D0F, and so might not be ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile","title":"Extrusion"},{"location":"extras/backgrounds/#construction","text":"The Fusion logo, with construction lines shown and other geometric patterns. Background is 100% black, ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile","title":"Construction"},{"location":"extras/backgrounds/#glow","text":"A centred Fusion logo emitting light on a dark background. Background is 100% black, ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile","title":"Glow"},{"location":"extras/backgrounds/#glow-alternate","text":"A centred Fusion logo emitting light on a dark background. Uses an alternate design of the logo, which is now used for livestreams. Background is 100% black, ideal for OLED screens. Ultrawide Widescreen 3:2 Mobile","title":"Glow (Alternate)"},{"location":"extras/brand-guidelines/","text":"The Fusion branding is designed to be simplistic, modern, easy to recognise and distinctive. Colours \u00b6 Primaries \u00b6 These colours are used in the Fusion logo and most illustrations. They might not be suitable for text or all backgrounds. FusionDoc greys \u00b6 These colours are used by the FusionDoc theme on this website for all grey tones used on pages. FusionDoc accents \u00b6 These colours are used by the FusionDoc theme on this website for accent colours on links and interactive elements. Best Practices \u00b6 We would love you to use the Fusion branding in your own work, but please be mindful that you use it appropriately. If you're not sure, feel free to reach out over Discord or Twitter - it's always better to ask first to be secure! These aren't hard and fast rules, and we can't and don't want to police how people use our stuff. Instead, these are provided as best practices to follow. We'll add any common examples or questions to this list over time. Brand Confusion \u00b6 Fusion's logo and name are designed to represent Fusion's official projects. Please don't use them to represent things that are not Fusion; for example, if you build your own UI library, it's better to design a new logo. In general, prefer to keep some distance between your project branding and Fusion's branding, enough distance that people don't get confused about who makes what, or whether a project is officially supported or not. It's a good litmus test to imagine a first-time user who knows nothing about Fusion, and how they will perceive your project. Don't do this In this example, someone made their own UI library and named it Fusion 2. Note that this is an extreme example for demonstration purposes - most violations of this principle are probably more subtle. This is bad because people might mistakenly think the official Fusion project approves or provides support for this unrelated project, which would cause many headaches and is dishonest about the relationship between the two libraries. Plus, in this example, we reserve the right to update Fusion to version 2 at any time, which would immediately cause a naming conflict. Instead, do this This logo makes it more clear that the project is not a port of, update to or bindings for the Fusion library. Instead, it's a completely distinct project which only takes inspiration from Fusion, but is otherwise unrelated. It's okay for the logo to remind people of Fusion's design. Remember - you don't have to be completely original, just distinct enough that it isn't confusing for people. Acceptable, but be careful Here, this plugin is using the Fusion logo to represent a 'Convert to Fusion' action. This is fine, because users will understand the Fusion logo represents the thing being converted to. However, be careful, as free-standing uses of the Fusion icon like this can easily become confusing. Make sure people understand the logo represents Fusion, and not the plugin, so confusion between the two is minimised.","title":"Brand Guidelines"},{"location":"extras/brand-guidelines/#colours","text":"","title":"Colours"},{"location":"extras/brand-guidelines/#primaries","text":"These colours are used in the Fusion logo and most illustrations. They might not be suitable for text or all backgrounds.","title":"Primaries"},{"location":"extras/brand-guidelines/#fusiondoc-greys","text":"These colours are used by the FusionDoc theme on this website for all grey tones used on pages.","title":"FusionDoc greys"},{"location":"extras/brand-guidelines/#fusiondoc-accents","text":"These colours are used by the FusionDoc theme on this website for accent colours on links and interactive elements.","title":"FusionDoc accents"},{"location":"extras/brand-guidelines/#best-practices","text":"We would love you to use the Fusion branding in your own work, but please be mindful that you use it appropriately. If you're not sure, feel free to reach out over Discord or Twitter - it's always better to ask first to be secure! These aren't hard and fast rules, and we can't and don't want to police how people use our stuff. Instead, these are provided as best practices to follow. We'll add any common examples or questions to this list over time.","title":"Best Practices"},{"location":"extras/brand-guidelines/#brand-confusion","text":"Fusion's logo and name are designed to represent Fusion's official projects. Please don't use them to represent things that are not Fusion; for example, if you build your own UI library, it's better to design a new logo. In general, prefer to keep some distance between your project branding and Fusion's branding, enough distance that people don't get confused about who makes what, or whether a project is officially supported or not. It's a good litmus test to imagine a first-time user who knows nothing about Fusion, and how they will perceive your project. Don't do this In this example, someone made their own UI library and named it Fusion 2. Note that this is an extreme example for demonstration purposes - most violations of this principle are probably more subtle. This is bad because people might mistakenly think the official Fusion project approves or provides support for this unrelated project, which would cause many headaches and is dishonest about the relationship between the two libraries. Plus, in this example, we reserve the right to update Fusion to version 2 at any time, which would immediately cause a naming conflict. Instead, do this This logo makes it more clear that the project is not a port of, update to or bindings for the Fusion library. Instead, it's a completely distinct project which only takes inspiration from Fusion, but is otherwise unrelated. It's okay for the logo to remind people of Fusion's design. Remember - you don't have to be completely original, just distinct enough that it isn't confusing for people. Acceptable, but be careful Here, this plugin is using the Fusion logo to represent a 'Convert to Fusion' action. This is fine, because users will understand the Fusion logo represents the thing being converted to. However, be careful, as free-standing uses of the Fusion icon like this can easily become confusing. Make sure people understand the logo represents Fusion, and not the plugin, so confusion between the two is minimised.","title":"Brand Confusion"},{"location":"tutorials/","text":"Welcome to the Fusion tutorial section! Here, you'll learn how to build great things with Fusion, even if you're a complete newcomer to the library. You'll not only learn how Fusion's features work, but you'll also be presented with wisdom from those who've worked with some of the largest Fusion codebases today. But first, some advice from the maintainers... Fusion is pre-1.0 software. We (the maintainers and contributors) work hard to keep releases bug-free and relatively complete, so it should be safe to use in production. Many people already do, and report fantastic results! However, we mark Fusion as pre-1.0 because we are working on the design of the library itself. We strive for the best library design we can deliver, which means breaking changes are common and sweeping. With Fusion, you should expect: upgrades to be frictionful, requiring code to be rethought features to be superseded or removed across versions advice or best practices to change over time You should also expect: careful consideration around breakage, even though we reserve the right to do it clear communication ahead of any major changes helpful advice to answer your questions and ease your porting process We hope you enjoy using Fusion! What You Need To Know \u00b6 These tutorials assume: That you're comfortable with the Luau scripting language. These tutorials aren't an introduction to Luau! If you'd like to learn, check out the Roblox documentation . That - if you're using Roblox features - you're familiar with how Roblox works. You don't have to be an expert! Knowing about basic instances, events and data types will be good enough. Based on your existing knowledge, you may find some tutorials easier or harder. Don't be discouraged - Fusion's built to be easy to learn, but it may still take a bit of time to absorb some concepts. Learn at a pace which is right for you. Installing Fusion \u00b6 There are two ways of installing Fusion, dependent on your use case. If you are creating Luau experiences in Roblox Studio, then you can import a Roblox model file containing Fusion. Steps (click to expand) Head over to Fusion's 'Releases' page . Click the 'Assets' dropdown to view the downloadable files: Now, click on the Fusion.rbxm file to download it. This model contains Fusion. Head into Roblox Studio to import the model; if you're just following the tutorials, an empty baseplate will do. Right-click on ReplicatedStorage , and select 'Insert from File': Select the Fusion.rbxm file you just downloaded. You should see a 'Fusion' module script appear in ReplicatedStorage ! Now, you can create a script for testing: Create a LocalScript in StarterGui or StarterPlayerScripts . Remove the default code, and paste the following code in: 1 2 local ReplicatedStorage = game : GetService ( \"ReplicatedStorage\" ) local Fusion = require ( ReplicatedStorage . Fusion ) Press 'Play' - if there are no errors, everything was set up correctly!. If you're using pure Luau, or if you're synchronising external files into Roblox Studio, then you can use Fusion's source code directly. Steps (click to expand) Head over to Fusion's 'Releases' page . Under 'Assets', download Source code (zip) . Inside is a copy of the Fusion GitHub repository. Inside the zip, copy the src folder - it may be inside another folder. Paste the src folder into your local project, wherever you keep your libraries (e.g. inside a lib or shared folder) Rename the pasted folder from src to Fusion . Once everything is set up, you should be able to require() Fusion in one of the following ways: -- Rojo local Fusion = require ( ReplicatedStorage . Fusion ) -- darklua local Fusion = require ( \"../shared/Fusion\" ) -- vanilla Luau local Fusion = require ( \"../shared/Fusion/init.lua\" ) Getting Help \u00b6 Fusion is built to be easy to use, and this website strives to be as useful and comprehensive as possible. However, you might need targeted help on a specific issue, or you might want to grow your understanding of Fusion in other ways. The best place to get help is the #fusion channel over on the Roblox OSS Discord server . Maintainers and contributors drop in frequently, alongside many eager Fusion users. For bugs and feature requests, open an issue on GitHub.","title":"Get Started"},{"location":"tutorials/#what-you-need-to-know","text":"These tutorials assume: That you're comfortable with the Luau scripting language. These tutorials aren't an introduction to Luau! If you'd like to learn, check out the Roblox documentation . That - if you're using Roblox features - you're familiar with how Roblox works. You don't have to be an expert! Knowing about basic instances, events and data types will be good enough. Based on your existing knowledge, you may find some tutorials easier or harder. Don't be discouraged - Fusion's built to be easy to learn, but it may still take a bit of time to absorb some concepts. Learn at a pace which is right for you.","title":"What You Need To Know"},{"location":"tutorials/#installing-fusion","text":"There are two ways of installing Fusion, dependent on your use case. If you are creating Luau experiences in Roblox Studio, then you can import a Roblox model file containing Fusion. Steps (click to expand) Head over to Fusion's 'Releases' page . Click the 'Assets' dropdown to view the downloadable files: Now, click on the Fusion.rbxm file to download it. This model contains Fusion. Head into Roblox Studio to import the model; if you're just following the tutorials, an empty baseplate will do. Right-click on ReplicatedStorage , and select 'Insert from File': Select the Fusion.rbxm file you just downloaded. You should see a 'Fusion' module script appear in ReplicatedStorage ! Now, you can create a script for testing: Create a LocalScript in StarterGui or StarterPlayerScripts . Remove the default code, and paste the following code in: 1 2 local ReplicatedStorage = game : GetService ( \"ReplicatedStorage\" ) local Fusion = require ( ReplicatedStorage . Fusion ) Press 'Play' - if there are no errors, everything was set up correctly!. If you're using pure Luau, or if you're synchronising external files into Roblox Studio, then you can use Fusion's source code directly. Steps (click to expand) Head over to Fusion's 'Releases' page . Under 'Assets', download Source code (zip) . Inside is a copy of the Fusion GitHub repository. Inside the zip, copy the src folder - it may be inside another folder. Paste the src folder into your local project, wherever you keep your libraries (e.g. inside a lib or shared folder) Rename the pasted folder from src to Fusion . Once everything is set up, you should be able to require() Fusion in one of the following ways: -- Rojo local Fusion = require ( ReplicatedStorage . Fusion ) -- darklua local Fusion = require ( \"../shared/Fusion\" ) -- vanilla Luau local Fusion = require ( \"../shared/Fusion/init.lua\" )","title":"Installing Fusion"},{"location":"tutorials/#getting-help","text":"Fusion is built to be easy to use, and this website strives to be as useful and comprehensive as possible. However, you might need targeted help on a specific issue, or you might want to grow your understanding of Fusion in other ways. The best place to get help is the #fusion channel over on the Roblox OSS Discord server . Maintainers and contributors drop in frequently, alongside many eager Fusion users. For bugs and feature requests, open an issue on GitHub.","title":"Getting Help"},{"location":"tutorials/animation/springs/","text":"Springs follow the value of other state objects using a physical spring simulation. This can be used for 'springy' effects, or for smoothing out movement naturally without abrupt changes in direction. Usage \u00b6 To create a new spring object, call scope:Spring() and pass it a state object to move towards: local goal = scope : Value ( 0 ) local animated = scope : Spring ( goal ) The spring will smoothly follow the 'goal' state object over time. As with other state objects, you can peek() at its value at any time: print ( peek ( animated )) --> 0.26425... To configure how the spring moves, you can provide a speed and damping ratio to use. Both are optional, and both can be state objects if desired: local goal = scope : Value ( 0 ) local speed = 25 local damping = scope : Value ( 0.5 ) local animated = scope : Spring ( goal , speed , damping ) You can also set the position and velocity of the spring at any time. animated : setPosition ( 5 ) -- teleport the spring to 5 animated : setVelocity ( 2 ) -- from here, move 2 units/second You can use many different kinds of values with springs, not just numbers. Vectors, CFrames, Color3s, UDim2s and other number-based types are supported; each number inside the type is animated individually. local goalPosition = scope : Value ( UDim2 . new ( 0.5 , 0 , 0 , 0 )) local animated = scope : Spring ( goalPosition , 25 , 0.5 ) Damping Ratio \u00b6 The damping ratio (a.k.a damping) of the spring changes the friction in the physics simulation. Lower values allow the spring to move freely and oscillate up and down, while higher values restrict movement. Zero damping \u00b6 Zero damping means no friction is applied, so the spring will oscillate forever without losing energy. This is generally not useful. Underdamping \u00b6 A damping between 0 and 1 means some friction is applied. The spring will still oscillate, but it will lose energy and eventually settle at the goal. Critical damping \u00b6 A damping of exactly 1 means just enough friction is applied to stop the spring from oscillating. It reaches its goal as quickly as possible without going past. This is also commonly known as critical damping. Overdamping \u00b6 A damping above 1 applies excessive friction to the spring. The spring behaves like it's moving through honey, glue or some other viscous fluid. Overdamping reduces the effect of velocity changes, and makes movement more rigid. Speed \u00b6 The speed of the spring scales how much time it takes for the spring to move. Doubling the speed makes it move twice as fast; halving the speed makes it move twice as slow. Interruption \u00b6 Springs do not share the same interruption problems as tweens. When the goal changes, springs are guaranteed to preserve both position and velocity, reducing jank: This also means springs are suitable for following rapidly changing values:","title":"Springs"},{"location":"tutorials/animation/springs/#usage","text":"To create a new spring object, call scope:Spring() and pass it a state object to move towards: local goal = scope : Value ( 0 ) local animated = scope : Spring ( goal ) The spring will smoothly follow the 'goal' state object over time. As with other state objects, you can peek() at its value at any time: print ( peek ( animated )) --> 0.26425... To configure how the spring moves, you can provide a speed and damping ratio to use. Both are optional, and both can be state objects if desired: local goal = scope : Value ( 0 ) local speed = 25 local damping = scope : Value ( 0.5 ) local animated = scope : Spring ( goal , speed , damping ) You can also set the position and velocity of the spring at any time. animated : setPosition ( 5 ) -- teleport the spring to 5 animated : setVelocity ( 2 ) -- from here, move 2 units/second You can use many different kinds of values with springs, not just numbers. Vectors, CFrames, Color3s, UDim2s and other number-based types are supported; each number inside the type is animated individually. local goalPosition = scope : Value ( UDim2 . new ( 0.5 , 0 , 0 , 0 )) local animated = scope : Spring ( goalPosition , 25 , 0.5 )","title":"Usage"},{"location":"tutorials/animation/springs/#damping-ratio","text":"The damping ratio (a.k.a damping) of the spring changes the friction in the physics simulation. Lower values allow the spring to move freely and oscillate up and down, while higher values restrict movement.","title":"Damping Ratio"},{"location":"tutorials/animation/springs/#zero-damping","text":"Zero damping means no friction is applied, so the spring will oscillate forever without losing energy. This is generally not useful.","title":"Zero damping"},{"location":"tutorials/animation/springs/#underdamping","text":"A damping between 0 and 1 means some friction is applied. The spring will still oscillate, but it will lose energy and eventually settle at the goal.","title":"Underdamping"},{"location":"tutorials/animation/springs/#critical-damping","text":"A damping of exactly 1 means just enough friction is applied to stop the spring from oscillating. It reaches its goal as quickly as possible without going past. This is also commonly known as critical damping.","title":"Critical damping"},{"location":"tutorials/animation/springs/#overdamping","text":"A damping above 1 applies excessive friction to the spring. The spring behaves like it's moving through honey, glue or some other viscous fluid. Overdamping reduces the effect of velocity changes, and makes movement more rigid.","title":"Overdamping"},{"location":"tutorials/animation/springs/#speed","text":"The speed of the spring scales how much time it takes for the spring to move. Doubling the speed makes it move twice as fast; halving the speed makes it move twice as slow.","title":"Speed"},{"location":"tutorials/animation/springs/#interruption","text":"Springs do not share the same interruption problems as tweens. When the goal changes, springs are guaranteed to preserve both position and velocity, reducing jank: This also means springs are suitable for following rapidly changing values:","title":"Interruption"},{"location":"tutorials/animation/tweens/","text":"Tweens follow the value of other state objects using a pre-made animation curve. This can be used for basic, predictable animations. Usage \u00b6 To create a new tween object, call scope:Tween() and pass it a state object to move towards: local goal = scope : Value ( 0 ) local animated = scope : Tween ( goal ) The tween will smoothly follow the 'goal' state object over time. As with other state objects, you can peek() at its value at any time: print ( peek ( animated )) --> 0.26425... To configure how the tween moves, you can provide a TweenInfo to change the shape of the animation curve. It's optional, and it can be a state object if desired: local goal = scope : Value ( 0 ) local style = TweenInfo . new ( 0.5 , Enum . EasingStyle . Quad ) local animated = scope : Tween ( goal , style ) You can use many different kinds of values with tweens, not just numbers. Vectors, CFrames, Color3s, UDim2s and other number-based types are supported; each number inside the type is animated individually. local goalPosition = scope : Value ( UDim2 . new ( 0.5 , 0 , 0 , 0 )) local animated = scope : Tween ( goalPosition , TweenInfo . new ( 0.5 , Enum . EasingStyle . Quad )) Time \u00b6 The first parameter of TweenInfo is time. This specifies how long it should take for the value to animate to the goal, in seconds. Easing Style \u00b6 The second parameter of TweenInfo is easing style. By setting this to various Enum.EasingStyle values, you can select different pre-made animation curves. Easing Direction \u00b6 The third parameter of TweenInfo is easing direction. This can be set to one of three values to control how the tween starts and stops: Enum.EasingDirection.Out makes the tween animate out smoothly. Enum.EasingDirection.In makes the tween animate in smoothly. Enum.EasingDirection.InOut makes the tween animate in and out smoothly. Repeats \u00b6 The fourth parameter of TweenInfo is repeat count. This can be used to loop the animation a number of times. Setting the repeat count to a negative number causes it to loop infinitely. This is not generally useful for transition animations. Reversing \u00b6 The fifth parameter of TweenInfo is a reversing option. When enabled, the animation will return to the starting point. This is not typically useful because the animation doesn't end at the goal value, and might not end at the start value either if the animation is interrupted. Delay \u00b6 The sixth and final parameter of TweenInfo is delay. Increasing this delay adds empty space before the beginning of the animation curve. It's important to note this is not the same as a true delay. This option does not delay the input signal - it only makes the tween animation longer. Interruption \u00b6 Because tweens are built from pre-made, fixed animation curves, you should avoid interrupting those animation curves before they're finished. Interrupting a tween halfway through leads to abrupt changes in velocity, which can cause your animation to feel janky: Tweens also can't track constantly changing targets very well. That's because the tween is always getting interrupted as it gets started, so it never has time to play out much of its animation. These issues arise because tweens don't 'remember' their previous velocity when they start animating towards a new goal. If you need velocity to be remembered, it's a much better idea to use springs, which can preserve their momentum.","title":"Tweens"},{"location":"tutorials/animation/tweens/#usage","text":"To create a new tween object, call scope:Tween() and pass it a state object to move towards: local goal = scope : Value ( 0 ) local animated = scope : Tween ( goal ) The tween will smoothly follow the 'goal' state object over time. As with other state objects, you can peek() at its value at any time: print ( peek ( animated )) --> 0.26425... To configure how the tween moves, you can provide a TweenInfo to change the shape of the animation curve. It's optional, and it can be a state object if desired: local goal = scope : Value ( 0 ) local style = TweenInfo . new ( 0.5 , Enum . EasingStyle . Quad ) local animated = scope : Tween ( goal , style ) You can use many different kinds of values with tweens, not just numbers. Vectors, CFrames, Color3s, UDim2s and other number-based types are supported; each number inside the type is animated individually. local goalPosition = scope : Value ( UDim2 . new ( 0.5 , 0 , 0 , 0 )) local animated = scope : Tween ( goalPosition , TweenInfo . new ( 0.5 , Enum . EasingStyle . Quad ))","title":"Usage"},{"location":"tutorials/animation/tweens/#time","text":"The first parameter of TweenInfo is time. This specifies how long it should take for the value to animate to the goal, in seconds.","title":"Time"},{"location":"tutorials/animation/tweens/#easing-style","text":"The second parameter of TweenInfo is easing style. By setting this to various Enum.EasingStyle values, you can select different pre-made animation curves.","title":"Easing Style"},{"location":"tutorials/animation/tweens/#easing-direction","text":"The third parameter of TweenInfo is easing direction. This can be set to one of three values to control how the tween starts and stops: Enum.EasingDirection.Out makes the tween animate out smoothly. Enum.EasingDirection.In makes the tween animate in smoothly. Enum.EasingDirection.InOut makes the tween animate in and out smoothly.","title":"Easing Direction"},{"location":"tutorials/animation/tweens/#repeats","text":"The fourth parameter of TweenInfo is repeat count. This can be used to loop the animation a number of times. Setting the repeat count to a negative number causes it to loop infinitely. This is not generally useful for transition animations.","title":"Repeats"},{"location":"tutorials/animation/tweens/#reversing","text":"The fifth parameter of TweenInfo is a reversing option. When enabled, the animation will return to the starting point. This is not typically useful because the animation doesn't end at the goal value, and might not end at the start value either if the animation is interrupted.","title":"Reversing"},{"location":"tutorials/animation/tweens/#delay","text":"The sixth and final parameter of TweenInfo is delay. Increasing this delay adds empty space before the beginning of the animation curve. It's important to note this is not the same as a true delay. This option does not delay the input signal - it only makes the tween animation longer.","title":"Delay"},{"location":"tutorials/animation/tweens/#interruption","text":"Because tweens are built from pre-made, fixed animation curves, you should avoid interrupting those animation curves before they're finished. Interrupting a tween halfway through leads to abrupt changes in velocity, which can cause your animation to feel janky: Tweens also can't track constantly changing targets very well. That's because the tween is always getting interrupted as it gets started, so it never has time to play out much of its animation. These issues arise because tweens don't 'remember' their previous velocity when they start animating towards a new goal. If you need velocity to be remembered, it's a much better idea to use springs, which can preserve their momentum.","title":"Interruption"},{"location":"tutorials/best-practices/callbacks/","text":"Normally, components are controlled by the code creating them. This is called top-down control, and is the primary flow of control in Fusion. However, sometimes components need to talk back to their controlling code, for example to report when button clicks occur. In Luau \u00b6 Callbacks are functions which you pass into other functions. They're useful because they allow the function to 'call back' into your code, so your code can do something in response: local function printMessage () print ( \"Hello, world!\" ) end -- Here, we're passing `printMessage` as a callback -- `task.delay` will call it after 5 seconds task . delay ( 5 , printMessage ) If your function accepts a callback, then you can call it like any other function. Luau will execute the function, then return to your code. In this example, the fiveTimes function calls a callback five times: Luau code Output local function fiveTimes ( callback : ( number ) -> () ) for x = 1 , 5 do callback ( x ) end end fiveTimes ( function ( num ) print ( \"The number is\" , num ) end ) The number is 1 The number is 2 The number is 3 The number is 4 The number is 5 In Fusion \u00b6 Components can use callbacks the same way. Consider this button component; when the button is clicked, the button needs to run some external code: local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > props : { Position : UsedAs < UDim2 > ? , Size : UsedAs < UDim2 > ? , Text : UsedAs < string > ? } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), Position = props . Position , Size = props . Size , Text = props . Text , TextColor3 = Color3 . new ( 1 , 1 , 1 ), [ OnEvent \"Activated\" ] = -- ??? } end It can ask the controlling code to provide an OnClick callback in props . local button = scope : Button { Text = \"Hello, world!\" , OnClick = function () print ( \"The button was clicked\" ) end } Assuming that callback is passed in, the callback can be passed directly into [OnEvent] , because [OnEvent] accepts functions. It can even be optional - Luau won't add the key to the table if the value is nil . local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Position : UsedAs < UDim2 > ? , Size : UsedAs < UDim2 > ? , Text : UsedAs < string > ? , OnClick : (() -> ()) ? } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), Position = props . Position , Size = props . Size , Text = props . Text , TextColor3 = Color3 . new ( 1 , 1 , 1 ), [ OnEvent \"Activated\" ] = props . OnClick } end Alternatively, we can call props.OnClick manually, which is useful if you want to do your own processing first: local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Position : UsedAs < UDim2 > ? , Size : UsedAs < UDim2 > ? , Text : UsedAs < string > ? , Disabled : UsedAs < boolean > ? , OnClick : (() -> ()) ? } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), Position = props . Position , Size = props . Size , Text = props . Text , TextColor3 = Color3 . new ( 1 , 1 , 1 ), [ OnEvent \"Activated\" ] = function () if props . OnClick ~= nil and not peek ( props . Disabled ) then props . OnClick () end end } end This is the primary way components talk to their controlling code in Fusion.","title":"Callbacks"},{"location":"tutorials/best-practices/callbacks/#in-luau","text":"Callbacks are functions which you pass into other functions. They're useful because they allow the function to 'call back' into your code, so your code can do something in response: local function printMessage () print ( \"Hello, world!\" ) end -- Here, we're passing `printMessage` as a callback -- `task.delay` will call it after 5 seconds task . delay ( 5 , printMessage ) If your function accepts a callback, then you can call it like any other function. Luau will execute the function, then return to your code. In this example, the fiveTimes function calls a callback five times: Luau code Output local function fiveTimes ( callback : ( number ) -> () ) for x = 1 , 5 do callback ( x ) end end fiveTimes ( function ( num ) print ( \"The number is\" , num ) end ) The number is 1 The number is 2 The number is 3 The number is 4 The number is 5","title":"In Luau"},{"location":"tutorials/best-practices/callbacks/#in-fusion","text":"Components can use callbacks the same way. Consider this button component; when the button is clicked, the button needs to run some external code: local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > props : { Position : UsedAs < UDim2 > ? , Size : UsedAs < UDim2 > ? , Text : UsedAs < string > ? } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), Position = props . Position , Size = props . Size , Text = props . Text , TextColor3 = Color3 . new ( 1 , 1 , 1 ), [ OnEvent \"Activated\" ] = -- ??? } end It can ask the controlling code to provide an OnClick callback in props . local button = scope : Button { Text = \"Hello, world!\" , OnClick = function () print ( \"The button was clicked\" ) end } Assuming that callback is passed in, the callback can be passed directly into [OnEvent] , because [OnEvent] accepts functions. It can even be optional - Luau won't add the key to the table if the value is nil . local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Position : UsedAs < UDim2 > ? , Size : UsedAs < UDim2 > ? , Text : UsedAs < string > ? , OnClick : (() -> ()) ? } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), Position = props . Position , Size = props . Size , Text = props . Text , TextColor3 = Color3 . new ( 1 , 1 , 1 ), [ OnEvent \"Activated\" ] = props . OnClick } end Alternatively, we can call props.OnClick manually, which is useful if you want to do your own processing first: local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Position : UsedAs < UDim2 > ? , Size : UsedAs < UDim2 > ? , Text : UsedAs < string > ? , Disabled : UsedAs < boolean > ? , OnClick : (() -> ()) ? } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), Position = props . Position , Size = props . Size , Text = props . Text , TextColor3 = Color3 . new ( 1 , 1 , 1 ), [ OnEvent \"Activated\" ] = function () if props . OnClick ~= nil and not peek ( props . Disabled ) then props . OnClick () end end } end This is the primary way components talk to their controlling code in Fusion.","title":"In Fusion"},{"location":"tutorials/best-practices/components/","text":"You can use functions to create self-contained, reusable blocks of code. In the world of UI, you may think of them as components - though they can be used for much more than just UI. For example, consider this function, which generates a button based on some props the user passes in: type UsedAs < T > = Fusion . UsedAs < T > local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Position : UsedAs < UDim2 > ? , AnchorPoint : UsedAs < Vector2 > ? , Size : UsedAs < UDim2 > ? , LayoutOrder : UsedAs < number > ? , ButtonText : UsedAs < string > } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0 , 0.25 , 1 ), Position = props . Position , AnchorPoint = props . AnchorPoint , Size = props . Size , LayoutOrder = props . LayoutOrder , Text = props . ButtonText , TextSize = 28 , TextColor3 = Color3 . new ( 1 , 1 , 1 ), [ Children ] = UICorner { CornerRadius = UDim2 . new ( 0 , 8 ) } } end You can call this function later to generate as many buttons as you need. local helloBtn = Button ( scope , { ButtonText = \"Hello\" , Size = UDim2 . fromOffset ( 200 , 50 ) }) helloBtn . Parent = Players . LocalPlayer . PlayerGui . ScreenGui Since the scope is the first parameter, it can even be used with scoped() syntax. local scope = scoped ( Fusion , { Button = Button }) local helloBtn = scope : Button { ButtonText = \"Hello\" , Size = UDim2 . fromOffset ( 200 , 50 ) } helloBtn . Parent = Players . LocalPlayer . PlayerGui . ScreenGui This is the primary way of writing components in Fusion. You create functions that accept scope and props , then return some content from them. Properties \u00b6 If you don't say what props should contain, it might be hard to figure out how to use it. You can specify your list of properties by adding a type to props , which gives you useful autocomplete and type checking. local function Cake ( -- ... some stuff here ... props : { Size : Vector3 , Colour : Color3 , IsTasty : boolean } ) -- ... some other stuff here ... end Note that the above code only accepts constant values, not state objects. If you want to accept either a constant or a state object, you can use the UsedAs type. type UsedAs < T > = Fusion . UsedAs < T > local function Cake ( -- ... some stuff here ... props : { Size : UsedAs < Vector3 > , Colour : UsedAs < Color3 > , IsTasty : UsedAs < boolean > } ) -- ... some other stuff here ... end This is usually what you want, because it means the user can easily switch a property to dynamically change over time, while still writing properties normally when they don't change over time. You can mostly treat UsedAs properties like they're state objects, because functions like peek() and use() automatically choose the right behaviour for you. You can use the rest of Luau's type checking features to do more complex things, like making certain properties optional, or restricting that values are valid for a given property. Go wild! Be mindful of the angle brackets Remember that, when working with UsedAs , you should be mindful of whether you're putting things inside the angled brackets, or outside of them. Putting some things inside of the angle brackets can change their meaning, compared to putting them outside of the angle brackets. Consider these two type definitions carefully: -- A Vector3, or a state object storing Vector3, or nil. UsedAs < Vector3 > ? -- A Vector3?, or a state object storing Vector3? UsedAs < Vector3 ? > The first type is best for optional properties , where you provide a default value if it isn't specified by the user. If the user does specify it, they're forced to always give a valid value for it. The second type is best if the property understands nil as a valid value. This means the user can set it to nil at any time. Scopes \u00b6 In addition to props , you should also ask for a scope . The scope parameter should come first, so that your users can use scoped() syntax to create it. -- barebones syntax local thing = Component ( scope , { -- ... some properties here ... }) -- scoped() syntax local thing = scope : Component { -- ... some properties here ... } It's a good idea to provide a type for scope . This lets you specify what methods you need the scope to have. scope : Fusion . Scope < YourMethodsHere > If you don't know what methods to ask for, consider these two strategies. If you only use common methods (like Fusion's constructors) then it's a safe assumption that the user will also have those methods. You can ask for a scope with those methods pre-defined. local function Component ( scope : Fusion . Scope < typeof ( Fusion ) > , props : {} ) return scope : New \"Thing\" { -- ... rest of code here ... } end If you need more specific or niche things that the user likely won't have (for example, components you use internally), then you should not ask for those. Instead, create a new inner scope with the methods you need. local function Component ( outerScope : Fusion . Scope < {} > , props : {} ) local scope = scoped ( Fusion , { SpecialThing1 = require ( script . SpecialThing1 ), SpecialThing2 = require ( script . SpecialThing2 ), }) table.insert ( outerScope , scope ) return scope : SpecialThing1 { -- ... rest of code here ... } end If you're not sure which strategy to pick, the second is always a safe fallback, because it assumes less about your users and helps hide implementation details. Modules \u00b6 It's common to save different components inside of different files. There's a number of advantages to this: it's easier to find the source code for a specific component it keep each file shorter and simpler it makes sure components are properly independent, and can't interfere it encourages reusing components everywhere, not just in one file Here's an example of how you could split up some components into modules: Main file PopUp Message Button 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) local scoped , doCleanup = Fusion . scoped , Fusion . doCleanup local scope = scoped ( Fusion , { PopUp = require ( script . Parent . PopUp ) }) local ui = scope : New \"ScreenGui\" { -- ...some properties... [ Children ] = scope : PopUp { Message = \"Hello, world!\" , DismissText = \"Close\" } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) type UsedAs < T > = Fusion . UsedAs < T > local function PopUp ( outerScope : Fusion . Scope < {} > , props : { Message : UsedAs < string > , DismissText : UsedAs < string > } ) local scope = scoped ( Fusion , { Message = require ( script . Parent . Message ), Button = require ( script . Parent . Button ) }) table.insert ( outerScope , scope ) return scope : New \"Frame\" { -- ...some properties... [ Children ] = { scope : Message { Scope = scope , Text = props . Message } scope : Button { Scope = scope , Text = props . DismissText } } } end return PopUp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) type UsedAs < T > = Fusion . UsedAs < T > local function Message ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Text : UsedAs < string > } ) return scope : New \"TextLabel\" { AutomaticSize = \"XY\" , BackgroundTransparency = 1 , -- ...some properties... Text = props . Text } end return Message 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) type UsedAs < T > = Fusion . UsedAs < T > local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Text : UsedAs < string > } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), AutoButtonColor = true , -- ...some properties... Text = props . Text } end return Button","title":"Components"},{"location":"tutorials/best-practices/components/#properties","text":"If you don't say what props should contain, it might be hard to figure out how to use it. You can specify your list of properties by adding a type to props , which gives you useful autocomplete and type checking. local function Cake ( -- ... some stuff here ... props : { Size : Vector3 , Colour : Color3 , IsTasty : boolean } ) -- ... some other stuff here ... end Note that the above code only accepts constant values, not state objects. If you want to accept either a constant or a state object, you can use the UsedAs type. type UsedAs < T > = Fusion . UsedAs < T > local function Cake ( -- ... some stuff here ... props : { Size : UsedAs < Vector3 > , Colour : UsedAs < Color3 > , IsTasty : UsedAs < boolean > } ) -- ... some other stuff here ... end This is usually what you want, because it means the user can easily switch a property to dynamically change over time, while still writing properties normally when they don't change over time. You can mostly treat UsedAs properties like they're state objects, because functions like peek() and use() automatically choose the right behaviour for you. You can use the rest of Luau's type checking features to do more complex things, like making certain properties optional, or restricting that values are valid for a given property. Go wild! Be mindful of the angle brackets Remember that, when working with UsedAs , you should be mindful of whether you're putting things inside the angled brackets, or outside of them. Putting some things inside of the angle brackets can change their meaning, compared to putting them outside of the angle brackets. Consider these two type definitions carefully: -- A Vector3, or a state object storing Vector3, or nil. UsedAs < Vector3 > ? -- A Vector3?, or a state object storing Vector3? UsedAs < Vector3 ? > The first type is best for optional properties , where you provide a default value if it isn't specified by the user. If the user does specify it, they're forced to always give a valid value for it. The second type is best if the property understands nil as a valid value. This means the user can set it to nil at any time.","title":"Properties"},{"location":"tutorials/best-practices/components/#scopes","text":"In addition to props , you should also ask for a scope . The scope parameter should come first, so that your users can use scoped() syntax to create it. -- barebones syntax local thing = Component ( scope , { -- ... some properties here ... }) -- scoped() syntax local thing = scope : Component { -- ... some properties here ... } It's a good idea to provide a type for scope . This lets you specify what methods you need the scope to have. scope : Fusion . Scope < YourMethodsHere > If you don't know what methods to ask for, consider these two strategies. If you only use common methods (like Fusion's constructors) then it's a safe assumption that the user will also have those methods. You can ask for a scope with those methods pre-defined. local function Component ( scope : Fusion . Scope < typeof ( Fusion ) > , props : {} ) return scope : New \"Thing\" { -- ... rest of code here ... } end If you need more specific or niche things that the user likely won't have (for example, components you use internally), then you should not ask for those. Instead, create a new inner scope with the methods you need. local function Component ( outerScope : Fusion . Scope < {} > , props : {} ) local scope = scoped ( Fusion , { SpecialThing1 = require ( script . SpecialThing1 ), SpecialThing2 = require ( script . SpecialThing2 ), }) table.insert ( outerScope , scope ) return scope : SpecialThing1 { -- ... rest of code here ... } end If you're not sure which strategy to pick, the second is always a safe fallback, because it assumes less about your users and helps hide implementation details.","title":"Scopes"},{"location":"tutorials/best-practices/components/#modules","text":"It's common to save different components inside of different files. There's a number of advantages to this: it's easier to find the source code for a specific component it keep each file shorter and simpler it makes sure components are properly independent, and can't interfere it encourages reusing components everywhere, not just in one file Here's an example of how you could split up some components into modules: Main file PopUp Message Button 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) local scoped , doCleanup = Fusion . scoped , Fusion . doCleanup local scope = scoped ( Fusion , { PopUp = require ( script . Parent . PopUp ) }) local ui = scope : New \"ScreenGui\" { -- ...some properties... [ Children ] = scope : PopUp { Message = \"Hello, world!\" , DismissText = \"Close\" } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) type UsedAs < T > = Fusion . UsedAs < T > local function PopUp ( outerScope : Fusion . Scope < {} > , props : { Message : UsedAs < string > , DismissText : UsedAs < string > } ) local scope = scoped ( Fusion , { Message = require ( script . Parent . Message ), Button = require ( script . Parent . Button ) }) table.insert ( outerScope , scope ) return scope : New \"Frame\" { -- ...some properties... [ Children ] = { scope : Message { Scope = scope , Text = props . Message } scope : Button { Scope = scope , Text = props . DismissText } } } end return PopUp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) type UsedAs < T > = Fusion . UsedAs < T > local function Message ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Text : UsedAs < string > } ) return scope : New \"TextLabel\" { AutomaticSize = \"XY\" , BackgroundTransparency = 1 , -- ...some properties... Text = props . Text } end return Message 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 local Fusion = require ( game : GetService ( \"ReplicatedStorage\" ). Fusion ) type UsedAs < T > = Fusion . UsedAs < T > local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { Text : UsedAs < string > } ) return scope : New \"TextButton\" { BackgroundColor3 = Color3 . new ( 0.25 , 0.5 , 1 ), AutoButtonColor = true , -- ...some properties... Text = props . Text } end return Button","title":"Modules"},{"location":"tutorials/best-practices/instance-handling/","text":"Components are a good fit for Roblox instances. You can assemble complex groups of instances by combining simpler, self-contained parts. To ensure maximum compatibility, there are a few best practices to consider. Returns \u00b6 Anything you return from a component should be supported by [Children] . -- returns an instance return scope : New \"Frame\" {} -- returns an array of instances return { scope : New \"Frame\" {}, scope : New \"Frame\" {}, scope : New \"Frame\" {} } -- returns a state object containing instances return scope : ForValues ({ 1 , 2 , 3 }, function ( use , scope , number ) return scope : New \"Frame\" {} end ) -- mix of arrays, instances and state objects return { scope : New \"Frame\" {}, { scope : New \"Frame\" {}, scope : ForValues ( ... ) } scope : ForValues ( ... ) } Returning multiple values is fragile Don't return multiple values directly from your function. When a function returns multiple values directly, the extra returned values can easily get lost. local function BadThing ( scope , props ) -- returns *multiple* instances (not surrounded by curly braces!) return scope : New \"Frame\" {}, scope : New \"Frame\" {}, scope : New \"Frame\" {} end local things = { -- Luau doesn't let you add multiple returns to a list like this. -- Only the first Frame will be added. scope : BadThing {}, scope : New \"TextButton\" {} } print ( things ) --> { Frame, TextButton } Instead, you should return them inside of an array. Because the array is a single return value, it won't get lost. If your returns are compatible with [Children] like above, you can insert a component anywhere you'd normally insert an instance. You can pass in one component on its own... local ui = scope : New \"ScreenGui\" { [ Children ] = scope : Button { Text = \"Hello, world!\" } } ...you can include components as part of an array.. local ui = scope : New \"ScreenGui\" { [ Children ] = { scope : New \"UIListLayout\" {}, scope : Button { Text = \"Hello, world!\" }, scope : Button { Text = \"Hello, again!\" } } } ...and you can return them from state objects, too. local stuff = { \"Hello\" , \"world\" , \"from\" , \"Fusion\" } local ui = scope : New \"ScreenGui\" { [ Children ] = { scope : New \"UIListLayout\" {}, scope : ForValues ( stuff , function ( use , scope , text ) return scope : Button { Text = text } end ) } } Containers \u00b6 Some components, for example pop-ups, might contain lots of different content: Ideally, you would be able to reuse the pop-up 'container', while placing your own content inside. The simplest way to do this is to pass instances through to [Children] . For example, if you accept a table of props , you can add a [Children] key: local function PopUp ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { [ typeof ( Children )]: Fusion . Children } ) return scope : New \"Frame\" { [ Children ] = props [ Children ] } end Accepting multiple instances If you have multiple 'slots' where you want to pass through instances, you can make other properties and give them the Fusion.Children type. Later on, when a pop-up is created, instances can now be parented into that pop-up: scope : PopUp { [ Children ] = { scope : Label { Text = \"New item collected\" }, scope : ItemPreview { Item = Items . BRICK }, scope : Button { Text = \"Add to inventory\" } } } If you need to add other instances, you can still use arrays and state objects as normal. You can include instances you're given, in exactly the same way you would include any other instances. scope : New \"Frame\" { -- ... some other properties ... [ Children ] = { -- the component provides some children here scope : New \"UICorner\" { CornerRadius = UDim . new ( 0 , 8 ) }, -- include children from outside the component here props [ Children ] } }","title":"Instance Handling"},{"location":"tutorials/best-practices/instance-handling/#returns","text":"Anything you return from a component should be supported by [Children] . -- returns an instance return scope : New \"Frame\" {} -- returns an array of instances return { scope : New \"Frame\" {}, scope : New \"Frame\" {}, scope : New \"Frame\" {} } -- returns a state object containing instances return scope : ForValues ({ 1 , 2 , 3 }, function ( use , scope , number ) return scope : New \"Frame\" {} end ) -- mix of arrays, instances and state objects return { scope : New \"Frame\" {}, { scope : New \"Frame\" {}, scope : ForValues ( ... ) } scope : ForValues ( ... ) } Returning multiple values is fragile Don't return multiple values directly from your function. When a function returns multiple values directly, the extra returned values can easily get lost. local function BadThing ( scope , props ) -- returns *multiple* instances (not surrounded by curly braces!) return scope : New \"Frame\" {}, scope : New \"Frame\" {}, scope : New \"Frame\" {} end local things = { -- Luau doesn't let you add multiple returns to a list like this. -- Only the first Frame will be added. scope : BadThing {}, scope : New \"TextButton\" {} } print ( things ) --> { Frame, TextButton } Instead, you should return them inside of an array. Because the array is a single return value, it won't get lost. If your returns are compatible with [Children] like above, you can insert a component anywhere you'd normally insert an instance. You can pass in one component on its own... local ui = scope : New \"ScreenGui\" { [ Children ] = scope : Button { Text = \"Hello, world!\" } } ...you can include components as part of an array.. local ui = scope : New \"ScreenGui\" { [ Children ] = { scope : New \"UIListLayout\" {}, scope : Button { Text = \"Hello, world!\" }, scope : Button { Text = \"Hello, again!\" } } } ...and you can return them from state objects, too. local stuff = { \"Hello\" , \"world\" , \"from\" , \"Fusion\" } local ui = scope : New \"ScreenGui\" { [ Children ] = { scope : New \"UIListLayout\" {}, scope : ForValues ( stuff , function ( use , scope , text ) return scope : Button { Text = text } end ) } }","title":"Returns"},{"location":"tutorials/best-practices/instance-handling/#containers","text":"Some components, for example pop-ups, might contain lots of different content: Ideally, you would be able to reuse the pop-up 'container', while placing your own content inside. The simplest way to do this is to pass instances through to [Children] . For example, if you accept a table of props , you can add a [Children] key: local function PopUp ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { [ typeof ( Children )]: Fusion . Children } ) return scope : New \"Frame\" { [ Children ] = props [ Children ] } end Accepting multiple instances If you have multiple 'slots' where you want to pass through instances, you can make other properties and give them the Fusion.Children type. Later on, when a pop-up is created, instances can now be parented into that pop-up: scope : PopUp { [ Children ] = { scope : Label { Text = \"New item collected\" }, scope : ItemPreview { Item = Items . BRICK }, scope : Button { Text = \"Add to inventory\" } } } If you need to add other instances, you can still use arrays and state objects as normal. You can include instances you're given, in exactly the same way you would include any other instances. scope : New \"Frame\" { -- ... some other properties ... [ Children ] = { -- the component provides some children here scope : New \"UICorner\" { CornerRadius = UDim . new ( 0 , 8 ) }, -- include children from outside the component here props [ Children ] } }","title":"Containers"},{"location":"tutorials/best-practices/optimisation/","text":"Fusion tries to handle your code in the smartest way it can. To help achieve the best performance, you can give Fusion more information about what you're trying to do, or avoid a few problematic scenarios that slow Fusion down. Update Skipping \u00b6 Fusion tries to skip updates when they result in 'meaningless changes'. TL;DR When your computations return values that aren't meaningfully different, Fusion doesn't bother to perform further updates. However, Fusion can't automatically do this for tables. So, you should freeze every table you create, unless you need to change what's inside the table later (for example, if it's a list that changes over time). This allows Fusion to apply more aggressive optimisations for free. Example \u00b6 Imagine you have a number, and you're using a computed to calculate whether it's even or odd. An observer is used to see how often this results in other code being run. Luau code Output local number = scope : Value ( 1 ) local isEven = scope : Computed ( function ( use ) return use ( number ) % 2 == 0 end ) scope : Observer ( isEven ): onChange ( function () print ( \"-> isEven has changed to \" .. peek ( isEven )) end ) print ( \"Number becomes 2\" ) number : set ( 2 ) print ( \"Number becomes 3\" ) number : set ( 3 ) print ( \"Number becomes 13\" ) number : set ( 13 ) print ( \"Number becomes 14\" ) number : set ( 14 ) print ( \"Number becomes 24\" ) number : set ( 24 ) Number becomes 2 -> isEven has changed to true Number becomes 3 -> isEven has changed to false Number becomes 13 Number becomes 14 -> isEven has changed to true Number becomes 24 Notice that the observer only runs when isEven returns a meaningfully different value: When the number changed from 2 to 3, isEven returned false . This is meaningfully different from the previous value of isEven , which was true . As a result, the observer is run and the printed message is seen. When the number changed from 3 to 13, isEven returned false . This is not meaningfully different from the previous value of isEven , which was false . As a result, the observer does not run, and no printed message is seen. Similarity \u00b6 When trying to determine if a change is 'meaningless', Fusion compares the old and new values, using what's called the similarity test . The similarity test is a fast, approximate test that Fusion uses to guess which updates it can safely discard. If two values pass the similarity test, then you should be able to use them interchangeably without affecting most Luau code. In Fusion's case, if the values before and after a change are similar, then Fusion won't continue updating other code beyond that change, because those updates aren't likely to have an effect on the outcome of computations. Here's what the similarity test looks for: Different types: Two values of different types are never similar to each other. Tables: Frozen tables are similar to other values when they're == to each other. Tables with a metatable are similar to other values when when they're == to each other. Other kinds of table are never similar to anything. Userdatas: Userdatas are similar to other values when they're == to each other. NaN: If each value does not == itself, then the two values are similar to each other. This doesn't apply to tables or userdatas. Any other values: Two values are similar to each other when they're == to each other. Roblox data types Roblox data types are not considered to be userdatas. Instead, the similarity test follows typeof() rules when determining type. Optimising For Similarity \u00b6 With this knowledge about the similarity test, you can experiment with how Fusion optimises different changes, and what breaks that optimisation. Tables \u00b6 Imagine you're setting a value object to a table of theme colours. You attach an observer object to see when Fusion thinks the theme meaningfully changed. Luau code Output local LIGHT_THEME = { name = \"light\" , -- imagine theme colours in here } local DARK_THEME = { name = \"dark\" , -- imagine theme colours in here } local currentTheme = scope : Value ( LIGHT_THEME ) scope : Observer ( currentTheme ): onChange ( function () print ( \"-> currentTheme changed to \" .. peek ( currentTheme ). name ) end ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) Set to DARK_THEME -> currentTheme changed to dark Set to DARK_THEME -> currentTheme changed to dark Set to LIGHT_THEME -> currentTheme changed to light Set to LIGHT_THEME -> currentTheme changed to light Because the LIGHT_THEME and DARK_THEME tables aren't frozen, and they don't have any metatables, Fusion will never skip over updates that change to or from those values. Why won't Fusion skip those updates? In Fusion, it's common to update arrays without creating a new array. This is known as mutating the array. local drinks = scope : Value ({ \"beer\" , \"pepsi\" }) do -- add tea local array = peek ( drinks ) table.insert ( array , \"tea\" ) -- mutation occurs here drinks : set ( array ) -- still the same array, so it's == end If Fusion skipped updates when the old and new values were == , then these mutating changes wouldn't cause an update. For that reason, Fusion doesn't skip updates for tables unless you do one of two things: You disable the ability to mutate the table (via table.freeze ). You indicate to Fusion that this isn't plain data by adding a metatable. Metatables are almost always used for OOP, where == is a sensible way of determining if two objects are similar. You can also use metatables to define how equality should work, which Fusion will respect - though Fusion expects it to be symmetric. According to the similarity test (and the question section above), one way to skip these updates is by freezing the original tables. Luau code Output local LIGHT_THEME = table . freeze { name = \"light\" , -- imagine theme colours in here } local DARK_THEME = table . freeze { name = \"dark\" , -- imagine theme colours in here } local currentTheme = scope : Value ( LIGHT_THEME ) scope : Observer ( currentTheme ): onChange ( function () print ( \"-> currentTheme changed to \" .. peek ( currentTheme ). name ) end ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) Set to DARK_THEME -> currentTheme changed to dark Set to DARK_THEME Set to LIGHT_THEME -> currentTheme changed to light Set to LIGHT_THEME Now, Fusion is confident enough to skip over the updates. In general, you should freeze all of your tables when working with Fusion, unless you have a reason for modifying them later on. There's almost zero cost to freezing a table, making this modification essentially free. Plus, this lets Fusion optimise your updates more aggressively, which means you spend less time running computations on average.","title":"Optimisation"},{"location":"tutorials/best-practices/optimisation/#update-skipping","text":"Fusion tries to skip updates when they result in 'meaningless changes'. TL;DR When your computations return values that aren't meaningfully different, Fusion doesn't bother to perform further updates. However, Fusion can't automatically do this for tables. So, you should freeze every table you create, unless you need to change what's inside the table later (for example, if it's a list that changes over time). This allows Fusion to apply more aggressive optimisations for free.","title":"Update Skipping"},{"location":"tutorials/best-practices/optimisation/#example","text":"Imagine you have a number, and you're using a computed to calculate whether it's even or odd. An observer is used to see how often this results in other code being run. Luau code Output local number = scope : Value ( 1 ) local isEven = scope : Computed ( function ( use ) return use ( number ) % 2 == 0 end ) scope : Observer ( isEven ): onChange ( function () print ( \"-> isEven has changed to \" .. peek ( isEven )) end ) print ( \"Number becomes 2\" ) number : set ( 2 ) print ( \"Number becomes 3\" ) number : set ( 3 ) print ( \"Number becomes 13\" ) number : set ( 13 ) print ( \"Number becomes 14\" ) number : set ( 14 ) print ( \"Number becomes 24\" ) number : set ( 24 ) Number becomes 2 -> isEven has changed to true Number becomes 3 -> isEven has changed to false Number becomes 13 Number becomes 14 -> isEven has changed to true Number becomes 24 Notice that the observer only runs when isEven returns a meaningfully different value: When the number changed from 2 to 3, isEven returned false . This is meaningfully different from the previous value of isEven , which was true . As a result, the observer is run and the printed message is seen. When the number changed from 3 to 13, isEven returned false . This is not meaningfully different from the previous value of isEven , which was false . As a result, the observer does not run, and no printed message is seen.","title":"Example"},{"location":"tutorials/best-practices/optimisation/#similarity","text":"When trying to determine if a change is 'meaningless', Fusion compares the old and new values, using what's called the similarity test . The similarity test is a fast, approximate test that Fusion uses to guess which updates it can safely discard. If two values pass the similarity test, then you should be able to use them interchangeably without affecting most Luau code. In Fusion's case, if the values before and after a change are similar, then Fusion won't continue updating other code beyond that change, because those updates aren't likely to have an effect on the outcome of computations. Here's what the similarity test looks for: Different types: Two values of different types are never similar to each other. Tables: Frozen tables are similar to other values when they're == to each other. Tables with a metatable are similar to other values when when they're == to each other. Other kinds of table are never similar to anything. Userdatas: Userdatas are similar to other values when they're == to each other. NaN: If each value does not == itself, then the two values are similar to each other. This doesn't apply to tables or userdatas. Any other values: Two values are similar to each other when they're == to each other. Roblox data types Roblox data types are not considered to be userdatas. Instead, the similarity test follows typeof() rules when determining type.","title":"Similarity"},{"location":"tutorials/best-practices/optimisation/#optimising-for-similarity","text":"With this knowledge about the similarity test, you can experiment with how Fusion optimises different changes, and what breaks that optimisation.","title":"Optimising For Similarity"},{"location":"tutorials/best-practices/optimisation/#tables","text":"Imagine you're setting a value object to a table of theme colours. You attach an observer object to see when Fusion thinks the theme meaningfully changed. Luau code Output local LIGHT_THEME = { name = \"light\" , -- imagine theme colours in here } local DARK_THEME = { name = \"dark\" , -- imagine theme colours in here } local currentTheme = scope : Value ( LIGHT_THEME ) scope : Observer ( currentTheme ): onChange ( function () print ( \"-> currentTheme changed to \" .. peek ( currentTheme ). name ) end ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) Set to DARK_THEME -> currentTheme changed to dark Set to DARK_THEME -> currentTheme changed to dark Set to LIGHT_THEME -> currentTheme changed to light Set to LIGHT_THEME -> currentTheme changed to light Because the LIGHT_THEME and DARK_THEME tables aren't frozen, and they don't have any metatables, Fusion will never skip over updates that change to or from those values. Why won't Fusion skip those updates? In Fusion, it's common to update arrays without creating a new array. This is known as mutating the array. local drinks = scope : Value ({ \"beer\" , \"pepsi\" }) do -- add tea local array = peek ( drinks ) table.insert ( array , \"tea\" ) -- mutation occurs here drinks : set ( array ) -- still the same array, so it's == end If Fusion skipped updates when the old and new values were == , then these mutating changes wouldn't cause an update. For that reason, Fusion doesn't skip updates for tables unless you do one of two things: You disable the ability to mutate the table (via table.freeze ). You indicate to Fusion that this isn't plain data by adding a metatable. Metatables are almost always used for OOP, where == is a sensible way of determining if two objects are similar. You can also use metatables to define how equality should work, which Fusion will respect - though Fusion expects it to be symmetric. According to the similarity test (and the question section above), one way to skip these updates is by freezing the original tables. Luau code Output local LIGHT_THEME = table . freeze { name = \"light\" , -- imagine theme colours in here } local DARK_THEME = table . freeze { name = \"dark\" , -- imagine theme colours in here } local currentTheme = scope : Value ( LIGHT_THEME ) scope : Observer ( currentTheme ): onChange ( function () print ( \"-> currentTheme changed to \" .. peek ( currentTheme ). name ) end ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to DARK_THEME\" ) currentTheme : set ( DARK_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) print ( \"Set to LIGHT_THEME\" ) currentTheme : set ( LIGHT_THEME ) Set to DARK_THEME -> currentTheme changed to dark Set to DARK_THEME Set to LIGHT_THEME -> currentTheme changed to light Set to LIGHT_THEME Now, Fusion is confident enough to skip over the updates. In general, you should freeze all of your tables when working with Fusion, unless you have a reason for modifying them later on. There's almost zero cost to freezing a table, making this modification essentially free. Plus, this lets Fusion optimise your updates more aggressively, which means you spend less time running computations on average.","title":"Tables"},{"location":"tutorials/best-practices/sharing-values/","text":"Sometimes values are used in far-away parts of the codebase. For example, many UI elements might share theme colours for light and dark theme. Globals \u00b6 Typically, values are shared by placing them in modules. These modules can be required from anywhere in the codebase, and their values can be imported into any code. Values shared in this way are known as globals . Theme.luau Somewhere else 1 2 3 4 5 6 7 8 9 local Theme = {} Theme . colours = { background = Color3 . fromHex ( \"FFFFFF\" ), text = Color3 . fromHex ( \"222222\" ), -- etc. } return Theme 1 2 3 4 local Theme = require ( \"path/to/Theme.luau\" ) local textColour = Theme . colours . text print ( textColour ) --> 34, 34, 34 In particular, you can share state objects this way, and every part of the codebase will be able to see and interact with those state objects. Theme.luau Somewhere else 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 local Fusion = require ( \"path/to/Fusion.luau\" ) local Theme = {} Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, -- etc. } function Theme . init ( scope : Fusion . Scope < typeof ( Fusion ) > ) Theme . currentTheme = scope : Value ( \"light\" ) end return Theme 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 local Fusion = require ( \"path/to/Fusion.luau\" ) local scoped , peek = Fusion . scoped , Fusion . peek local Theme = require ( \"path/to/Theme.luau\" ) local function printTheme () local theme = Theme . currentTheme print ( peek ( theme ), if typeof ( theme ) == \"string\" then \"constant\" else \"state object\" ) end local scope = scoped ( Fusion ) Theme . init ( scope ) printTheme () --> light state object Theme . currentTheme : set ( \"dark\" ) printTheme () --> dark state object Globals are very straightforward to implement and can be useful, but they can quickly cause problems if not used carefully. Hidden dependencies \u00b6 When you use a global inside a block of reusable code such as a component, you are making your code dependent on another code file without declaring it to the outside world. To some extent, this is entirely why using globals is desirable. While it's more 'correct' to accept the Theme via the parameters of your function, it often means the Theme has to be passed down through multiple layers of functions. This is known as prop drilling and is widely considered bad practice, because it clutters up unrelated code with parameters that are only passed through functions. To avoid prop drilling, globals are often used, which 'hides' the dependency on that external code file. You no longer have to pass it down through parameters. However, relying too heavily on these hidden dependencies can cause your code to behave in surprising, unintuitive ways, or it can obscure what functionality is available to developers using your code. Hard-to-locate writes \u00b6 If you write into globals from deep inside your code base, it becomes very hard to figure out where the global is being changed from, which significantly hurts debugging. Generally, it's best to treat globals as read-only . If you're writing to a global, it should be coming from a single well-signposted, easy-to-find place. You should also keep the principles of top-down control in mind; think of globals as 'flowing down' from the root of the program. Globals are best managed from high up in the program, because they have widespread effects, so consider using callbacks to pass control up the chain, rather than managing globals directly from every part of the code base. Memory management \u00b6 In addition, globals can complicate memory management. Because every part of your code base can access them, you can't destroy globals until the very end of your program. In the above example, this is solved with an init() method which passes the main scope to Theme . Because init() is called before anything else that uses Theme , the objects that Theme creates will be added to the scope first. When the main scope is cleaned up, doCleanup() will destroy things in reverse order. This means the Theme objects will be cleaned up last, after everything else in the program has been cleaned up. This only works if you know that the main script is the only entry point in your program. If you have two scripts running concurrently which try to init() the Theme module, they will overwrite each other. Non-replaceable for testing \u00b6 When your code uses a global, you're hard-coding a connection between your code and that specific global. This is problematic for testing; unless you're using an advanced testing framework with code injection, it's pretty much impossible to separate your code from that global code, which makes it impossible to replace global values for testing purposes. For example, if you wanted to write automated tests that verify light theme and dark theme are correctly applied throughout your UI, you can't replace any values stored in Theme . You might be able to write to the Theme by going through the normal process, but this fundamentally limits how you can test. For example, you couldn't run a test for light theme and dark theme at the same time. Contextuals \u00b6 The main drawback of globals is that they hold one value for all code. To solve this, Fusion introduces contextual values , which can be temporarily changed for the duration of a code block. To create a contextual, call the Contextual function from Fusion. It asks for a default value. local myContextual = Contextual ( \"foo\" ) At any time, you can query its current value using the :now() method. local myContextual = Contextual ( \"foo\" ) print ( myContextual : now ()) --> foo You can override the value for a limited span of time using :is():during() . Pass the temporary value to :is() , and pass a callback to :during() . While the callback is running, the contextual will adopt the temporary value. local myContextual = Contextual ( \"foo\" ) print ( myContextual : now ()) --> foo myContextual : is ( \"bar\" ): during ( function () print ( myContextual : now ()) --> bar end ) print ( myContextual : now ()) --> foo By storing widely-used values inside contextuals, you can isolate different code paths from each other, while retaining the easy, hidden referencing that globals offer. This makes testing and memory management significantly easier, and helps you locate which code is modifying any shared values. To demonstrate, the Theme example can be rewritten to use contextuals. Theme.luau Somewhere else 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 local Fusion = require ( \"path/to/Fusion.luau\" ) local Contextual = Fusion . Contextual local Theme = {} Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, -- etc. } Theme . currentTheme = Contextual ( \"light\" ) return Theme 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 local Fusion = require ( \"path/to/Fusion.luau\" ) local scoped , peek = Fusion . scoped , Fusion . peek local Theme = require ( \"path/to/Theme.luau\" ) local function printTheme () local theme = Theme . currentTheme : now () print ( peek ( theme ), if typeof ( theme ) == \"string\" then \"constant\" else \"state object\" ) end printTheme () --> light constant local scope = scoped ( Fusion ) local override = scope : Value ( \"light\" ) Theme . currentTheme : is ( override ): during ( function () printTheme () --> light state object override : set ( \"dark\" ) printTheme () --> dark state object end ) printTheme () --> light constant In this rewritten example, Theme no longer requires an init() function, because - instead of defining a state object globally - Theme only defines \"light\" as the default value. You're expected to replace the default value with a state object when you want to make the theme dynamic. This has a number of benefits: Because the override is time-limited to one span of your code, you can have multiple scripts running at the same time with completely different overrides. It also explicitly places your code in charge of memory management, because you're creating the object yourself. It's easy to locate where changes are coming from, because you can look for the nearest :is():during() call. Optionally, you could share a limited, read-only version of the value, while retaining private access to write to the value wherever you're overriding the contextual from. Testing becomes much simpler; you can override the contextual for different parts of your testing, without ever having to inject code, and without altering how you read and override the contextual in your production code. It's still possible to run into issues with contextuals, though. You're still hiding a dependency of your code, which can still lead to confusion and obscuring available features, just the same as globals. Unlike globals, contextuals are time-limited. If you connect to an event or start a delayed task, you won't be able to access the value anymore. Instead, capture the value at the start of the code block, so you can use it in delayed tasks.","title":"Sharing Values"},{"location":"tutorials/best-practices/sharing-values/#globals","text":"Typically, values are shared by placing them in modules. These modules can be required from anywhere in the codebase, and their values can be imported into any code. Values shared in this way are known as globals . Theme.luau Somewhere else 1 2 3 4 5 6 7 8 9 local Theme = {} Theme . colours = { background = Color3 . fromHex ( \"FFFFFF\" ), text = Color3 . fromHex ( \"222222\" ), -- etc. } return Theme 1 2 3 4 local Theme = require ( \"path/to/Theme.luau\" ) local textColour = Theme . colours . text print ( textColour ) --> 34, 34, 34 In particular, you can share state objects this way, and every part of the codebase will be able to see and interact with those state objects. Theme.luau Somewhere else 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 local Fusion = require ( \"path/to/Fusion.luau\" ) local Theme = {} Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, -- etc. } function Theme . init ( scope : Fusion . Scope < typeof ( Fusion ) > ) Theme . currentTheme = scope : Value ( \"light\" ) end return Theme 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 local Fusion = require ( \"path/to/Fusion.luau\" ) local scoped , peek = Fusion . scoped , Fusion . peek local Theme = require ( \"path/to/Theme.luau\" ) local function printTheme () local theme = Theme . currentTheme print ( peek ( theme ), if typeof ( theme ) == \"string\" then \"constant\" else \"state object\" ) end local scope = scoped ( Fusion ) Theme . init ( scope ) printTheme () --> light state object Theme . currentTheme : set ( \"dark\" ) printTheme () --> dark state object Globals are very straightforward to implement and can be useful, but they can quickly cause problems if not used carefully.","title":"Globals"},{"location":"tutorials/best-practices/sharing-values/#hidden-dependencies","text":"When you use a global inside a block of reusable code such as a component, you are making your code dependent on another code file without declaring it to the outside world. To some extent, this is entirely why using globals is desirable. While it's more 'correct' to accept the Theme via the parameters of your function, it often means the Theme has to be passed down through multiple layers of functions. This is known as prop drilling and is widely considered bad practice, because it clutters up unrelated code with parameters that are only passed through functions. To avoid prop drilling, globals are often used, which 'hides' the dependency on that external code file. You no longer have to pass it down through parameters. However, relying too heavily on these hidden dependencies can cause your code to behave in surprising, unintuitive ways, or it can obscure what functionality is available to developers using your code.","title":"Hidden dependencies"},{"location":"tutorials/best-practices/sharing-values/#hard-to-locate-writes","text":"If you write into globals from deep inside your code base, it becomes very hard to figure out where the global is being changed from, which significantly hurts debugging. Generally, it's best to treat globals as read-only . If you're writing to a global, it should be coming from a single well-signposted, easy-to-find place. You should also keep the principles of top-down control in mind; think of globals as 'flowing down' from the root of the program. Globals are best managed from high up in the program, because they have widespread effects, so consider using callbacks to pass control up the chain, rather than managing globals directly from every part of the code base.","title":"Hard-to-locate writes"},{"location":"tutorials/best-practices/sharing-values/#memory-management","text":"In addition, globals can complicate memory management. Because every part of your code base can access them, you can't destroy globals until the very end of your program. In the above example, this is solved with an init() method which passes the main scope to Theme . Because init() is called before anything else that uses Theme , the objects that Theme creates will be added to the scope first. When the main scope is cleaned up, doCleanup() will destroy things in reverse order. This means the Theme objects will be cleaned up last, after everything else in the program has been cleaned up. This only works if you know that the main script is the only entry point in your program. If you have two scripts running concurrently which try to init() the Theme module, they will overwrite each other.","title":"Memory management"},{"location":"tutorials/best-practices/sharing-values/#non-replaceable-for-testing","text":"When your code uses a global, you're hard-coding a connection between your code and that specific global. This is problematic for testing; unless you're using an advanced testing framework with code injection, it's pretty much impossible to separate your code from that global code, which makes it impossible to replace global values for testing purposes. For example, if you wanted to write automated tests that verify light theme and dark theme are correctly applied throughout your UI, you can't replace any values stored in Theme . You might be able to write to the Theme by going through the normal process, but this fundamentally limits how you can test. For example, you couldn't run a test for light theme and dark theme at the same time.","title":"Non-replaceable for testing"},{"location":"tutorials/best-practices/sharing-values/#contextuals","text":"The main drawback of globals is that they hold one value for all code. To solve this, Fusion introduces contextual values , which can be temporarily changed for the duration of a code block. To create a contextual, call the Contextual function from Fusion. It asks for a default value. local myContextual = Contextual ( \"foo\" ) At any time, you can query its current value using the :now() method. local myContextual = Contextual ( \"foo\" ) print ( myContextual : now ()) --> foo You can override the value for a limited span of time using :is():during() . Pass the temporary value to :is() , and pass a callback to :during() . While the callback is running, the contextual will adopt the temporary value. local myContextual = Contextual ( \"foo\" ) print ( myContextual : now ()) --> foo myContextual : is ( \"bar\" ): during ( function () print ( myContextual : now ()) --> bar end ) print ( myContextual : now ()) --> foo By storing widely-used values inside contextuals, you can isolate different code paths from each other, while retaining the easy, hidden referencing that globals offer. This makes testing and memory management significantly easier, and helps you locate which code is modifying any shared values. To demonstrate, the Theme example can be rewritten to use contextuals. Theme.luau Somewhere else 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 local Fusion = require ( \"path/to/Fusion.luau\" ) local Contextual = Fusion . Contextual local Theme = {} Theme . colours = { background = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, text = { light = Color3 . fromHex ( \"FFFFFF\" ), dark = Color3 . fromHex ( \"222222\" ) }, -- etc. } Theme . currentTheme = Contextual ( \"light\" ) return Theme 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 local Fusion = require ( \"path/to/Fusion.luau\" ) local scoped , peek = Fusion . scoped , Fusion . peek local Theme = require ( \"path/to/Theme.luau\" ) local function printTheme () local theme = Theme . currentTheme : now () print ( peek ( theme ), if typeof ( theme ) == \"string\" then \"constant\" else \"state object\" ) end printTheme () --> light constant local scope = scoped ( Fusion ) local override = scope : Value ( \"light\" ) Theme . currentTheme : is ( override ): during ( function () printTheme () --> light state object override : set ( \"dark\" ) printTheme () --> dark state object end ) printTheme () --> light constant In this rewritten example, Theme no longer requires an init() function, because - instead of defining a state object globally - Theme only defines \"light\" as the default value. You're expected to replace the default value with a state object when you want to make the theme dynamic. This has a number of benefits: Because the override is time-limited to one span of your code, you can have multiple scripts running at the same time with completely different overrides. It also explicitly places your code in charge of memory management, because you're creating the object yourself. It's easy to locate where changes are coming from, because you can look for the nearest :is():during() call. Optionally, you could share a limited, read-only version of the value, while retaining private access to write to the value wherever you're overriding the contextual from. Testing becomes much simpler; you can override the contextual for different parts of your testing, without ever having to inject code, and without altering how you read and override the contextual in your production code. It's still possible to run into issues with contextuals, though. You're still hiding a dependency of your code, which can still lead to confusion and obscuring available features, just the same as globals. Unlike globals, contextuals are time-limited. If you connect to an event or start a delayed task, you won't be able to access the value anymore. Instead, capture the value at the start of the code block, so you can use it in delayed tasks.","title":"Contextuals"},{"location":"tutorials/best-practices/state/","text":"Components can hold their own data privately using state objects. This can be useful, but you should be careful when adding state. Creation \u00b6 You can create state objects inside components as you would anywhere else. local HOVER_COLOUR = Color3 . new ( 0.5 , 0.75 , 1 ) local REST_COLOUR = Color3 . new ( 0.25 , 0.5 , 1 ) local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { -- ... some properties ... } ) local isHovering = scope : Value ( false ) return scope : New \"TextButton\" { BackgroundColor3 = scope : Computed ( function ( use ) return if use ( isHovering ) then HOVER_COLOUR else REST_COLOUR end ), -- ... ... some more code ... } end Because these state objects are made with the same scope as the rest of the component, they're destroyed alongside the rest of the component. Top-Down Control \u00b6 Remember that Fusion mainly works with a top-down flow of control. It's a good idea to keep that in mind when adding state to components. When you're making reusable components, it's more flexible if your component can be controlled externally. Components that control themselves entirely are hard to use and customise. Consider the example of a check box. Each check box often reflects a state object under the hood: It might seem logical to store the state object inside the check box, but this causes a few problems: because the state is hidden, it's awkward to read and write from outside often, the user already has a state object representing the same setting, so now there's two state objects where one would have sufficed local function CheckBox ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { -- ... some properties ... } ) local isChecked = scope : Value ( false ) -- problematic return scope : New \"ImageButton\" { [ OnEvent \"Activated\" ] = function () isChecked : set ( not peek ( isChecked )) end , -- ... some more code ... } end A slightly better solution is to pass the state object in. This ensures the controlling code has easy access to the state if it needs it. However, this is not a complete solution: the user is forced to store the state in a Value object, but they might be computing the value dynamically with other state objects instead the behaviour of clicking the check box is hardcoded; the user cannot intercept the click or toggle a different state local function CheckBox ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { IsChecked : Fusion . Value < boolean > -- slightly better } ) return scope : New \"ImageButton\" { [ OnEvent \"Activated\" ] = function () props . IsChecked : set ( not peek ( props . IsChecked )) end , -- ... some more code ... } end That's why the best solution is to use UsedAs to create read-only properties, and add callbacks for signalling actions and events. because UsedAs is read-only, it lets the user plug in any data source, including dynamic computations because the callback is provided by the user, the behaviour of clicking the check box is completely customisable local function CheckBox ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { IsChecked : UsedAs < boolean > , -- best OnClick : () -> () } ) return scope : New \"ImageButton\" { [ OnEvent \"Activated\" ] = function () props . OnClick () end , -- ... some more code ... } end The control is always top-down here; the check box's appearance is fully controlled by the creator. The creator of the check box decides to switch the setting when the check box is clicked. In Practice \u00b6 Setting up your components in this way makes extending their behaviour incredibly straightforward. Consider a scenario where you wish to group multiple options under a 'main' check box, so you can turn them all on/off at once. The appearance of that check box would not be controlled by a single state, but instead reflects the combination of multiple states. Because the code uses UsedAs , you can represent this with a Computed object. local playMusic = scope : Value ( true ) local playSFX = scope : Value ( false ) local playNarration = scope : Value ( true ) local checkBox = scope : CheckBox { Text = \"Play sounds\" , IsChecked = scope : Computed ( function ( use ) local anyChecked = use ( playMusic ) or use ( playSFX ) or use ( playNarration ) local allChecked = use ( playMusic ) and use ( playSFX ) and use ( playNarration ) if not anyChecked then return \"unchecked\" elseif not allChecked then return \"partially-checked\" else return \"checked\" end end ) } You can then implement the 'check all'/'uncheck all' behaviour inside OnClick : local playMusic = scope : Value ( true ) local playSFX = scope : Value ( false ) local playNarration = scope : Value ( true ) local checkBox = scope : CheckBox { -- ... same properties as before ... OnClick = function () local allChecked = peek ( playMusic ) and peek ( playSFX ) and peek ( playNarration ) playMusic : set ( not allChecked ) playSFX : set ( not allChecked ) playNarration : set ( not allChecked ) end } Because the check box was written to be flexible, it can handle complex usage easily. Best Practices \u00b6 Those examples lead us to the golden rule of reusable components: Golden Rule Reusable components should reflect program state. They should not control program state. At the bottom of the chain of control, components shouldn't be massively responsible. At these levels, reflective components are easier to work with. As you go up the chain of control, components get broader in scope and less reusable; those places are often suitable for controlling components. A well-balanced codebase places controlling components at key, strategic locations. They allow higher-up components to operate without special knowledge about what goes on below. At first, this might be difficult to do well, but with experience you'll have a better intuition for it. Remember that you can always rewrite your code if it becomes a problem!","title":"State"},{"location":"tutorials/best-practices/state/#creation","text":"You can create state objects inside components as you would anywhere else. local HOVER_COLOUR = Color3 . new ( 0.5 , 0.75 , 1 ) local REST_COLOUR = Color3 . new ( 0.25 , 0.5 , 1 ) local function Button ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { -- ... some properties ... } ) local isHovering = scope : Value ( false ) return scope : New \"TextButton\" { BackgroundColor3 = scope : Computed ( function ( use ) return if use ( isHovering ) then HOVER_COLOUR else REST_COLOUR end ), -- ... ... some more code ... } end Because these state objects are made with the same scope as the rest of the component, they're destroyed alongside the rest of the component.","title":"Creation"},{"location":"tutorials/best-practices/state/#top-down-control","text":"Remember that Fusion mainly works with a top-down flow of control. It's a good idea to keep that in mind when adding state to components. When you're making reusable components, it's more flexible if your component can be controlled externally. Components that control themselves entirely are hard to use and customise. Consider the example of a check box. Each check box often reflects a state object under the hood: It might seem logical to store the state object inside the check box, but this causes a few problems: because the state is hidden, it's awkward to read and write from outside often, the user already has a state object representing the same setting, so now there's two state objects where one would have sufficed local function CheckBox ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { -- ... some properties ... } ) local isChecked = scope : Value ( false ) -- problematic return scope : New \"ImageButton\" { [ OnEvent \"Activated\" ] = function () isChecked : set ( not peek ( isChecked )) end , -- ... some more code ... } end A slightly better solution is to pass the state object in. This ensures the controlling code has easy access to the state if it needs it. However, this is not a complete solution: the user is forced to store the state in a Value object, but they might be computing the value dynamically with other state objects instead the behaviour of clicking the check box is hardcoded; the user cannot intercept the click or toggle a different state local function CheckBox ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { IsChecked : Fusion . Value < boolean > -- slightly better } ) return scope : New \"ImageButton\" { [ OnEvent \"Activated\" ] = function () props . IsChecked : set ( not peek ( props . IsChecked )) end , -- ... some more code ... } end That's why the best solution is to use UsedAs to create read-only properties, and add callbacks for signalling actions and events. because UsedAs is read-only, it lets the user plug in any data source, including dynamic computations because the callback is provided by the user, the behaviour of clicking the check box is completely customisable local function CheckBox ( scope : Fusion . Scope < typeof ( Fusion ) > , props : { IsChecked : UsedAs < boolean > , -- best OnClick : () -> () } ) return scope : New \"ImageButton\" { [ OnEvent \"Activated\" ] = function () props . OnClick () end , -- ... some more code ... } end The control is always top-down here; the check box's appearance is fully controlled by the creator. The creator of the check box decides to switch the setting when the check box is clicked.","title":"Top-Down Control"},{"location":"tutorials/best-practices/state/#in-practice","text":"Setting up your components in this way makes extending their behaviour incredibly straightforward. Consider a scenario where you wish to group multiple options under a 'main' check box, so you can turn them all on/off at once. The appearance of that check box would not be controlled by a single state, but instead reflects the combination of multiple states. Because the code uses UsedAs , you can represent this with a Computed object. local playMusic = scope : Value ( true ) local playSFX = scope : Value ( false ) local playNarration = scope : Value ( true ) local checkBox = scope : CheckBox { Text = \"Play sounds\" , IsChecked = scope : Computed ( function ( use ) local anyChecked = use ( playMusic ) or use ( playSFX ) or use ( playNarration ) local allChecked = use ( playMusic ) and use ( playSFX ) and use ( playNarration ) if not anyChecked then return \"unchecked\" elseif not allChecked then return \"partially-checked\" else return \"checked\" end end ) } You can then implement the 'check all'/'uncheck all' behaviour inside OnClick : local playMusic = scope : Value ( true ) local playSFX = scope : Value ( false ) local playNarration = scope : Value ( true ) local checkBox = scope : CheckBox { -- ... same properties as before ... OnClick = function () local allChecked = peek ( playMusic ) and peek ( playSFX ) and peek ( playNarration ) playMusic : set ( not allChecked ) playSFX : set ( not allChecked ) playNarration : set ( not allChecked ) end } Because the check box was written to be flexible, it can handle complex usage easily.","title":"In Practice"},{"location":"tutorials/best-practices/state/#best-practices","text":"Those examples lead us to the golden rule of reusable components: Golden Rule Reusable components should reflect program state. They should not control program state. At the bottom of the chain of control, components shouldn't be massively responsible. At these levels, reflective components are easier to work with. As you go up the chain of control, components get broader in scope and less reusable; those places are often suitable for controlling components. A well-balanced codebase places controlling components at key, strategic locations. They allow higher-up components to operate without special knowledge about what goes on below. At first, this might be difficult to do well, but with experience you'll have a better intuition for it. Remember that you can always rewrite your code if it becomes a problem!","title":"Best Practices"},{"location":"tutorials/fundamentals/computeds/","text":"Computeds are state objects that immediately process values from other state objects. You pass in a callback to define a calculation. Then, you can use peek() to read the result of the calculation at any time. local numCoins = scope : Value ( 50 ) local itemPrice = scope : Value ( 10 ) local finalCoins = scope : Computed ( function ( use , scope ) return use ( numCoins ) - use ( itemPrice ) end ) print ( peek ( finalCoins )) --> 40 numCoins : set ( 25 ) itemPrice : set ( 15 ) print ( peek ( finalCoins )) --> 10 Usage \u00b6 To create a new computed object, call scope:Computed() and give it a function that performs your calculation. It takes two parameters which will be explained later; for the first part of this tutorial, they'll be left unnamed. 6 7 8 9 local scope = scoped ( Fusion ) local hardMaths = scope : Computed ( function ( _ , _ ) return 1 + 1 end ) The value the callback returns will be stored as the computed's value. You can get the computed's current value using peek() : 6 7 8 9 10 11 local scope = scoped ( Fusion ) local hardMaths = scope : Computed ( function ( _ , _ ) return 1 + 1 end ) print ( peek ( hardMaths )) --> 2 The calculation should be immediate - that is, it should never delay. That means you should not use computed objects when you need to wait for something to occur (e.g. waiting for a server to respond to a request). Using State Objects \u00b6 The calculation is only run once by default. If you try to peek() at state objects inside the calculation, your code breaks quickly: 6 7 8 9 10 11 12 13 14 15 16 local scope = scoped ( Fusion ) local number = scope : Value ( 2 ) local double = scope : Computed ( function ( _ , _ ) return peek ( number ) * 2 end ) print ( peek ( number ), peek ( double )) --> 2 4 -- The calculation won't re-run! Oh no! number : set ( 10 ) print ( peek ( number ), peek ( double )) --> 10 4 Instead, the computed object provides a use function as the first argument. As your logic runs, you can call this function with different state objects. If any of them changes, then the computed throws everything away and recalculates. 6 7 8 9 10 11 12 13 14 15 16 17 local scope = scoped ( Fusion ) local number = scope : Value ( 2 ) local double = scope : Computed ( function ( use , _ ) use ( number ) -- the calculation will re-run when `number` changes value return peek ( number ) * 2 end ) print ( peek ( number ), peek ( double )) --> 2 4 -- Now it re-runs! number : set ( 10 ) print ( peek ( number ), peek ( double )) --> 10 20 For convenience, use() will also read the value, just like peek() , so you can easily replace peek() calls with use() calls. This keeps your logic concise, readable and easily copyable. 6 7 8 9 10 11 12 13 14 15 local scope = scoped ( Fusion ) local number = scope : Value ( 2 ) local double = scope : Computed ( function ( use , _ ) return use ( number ) * 2 end ) print ( peek ( number ), peek ( double )) --> 2 4 number : set ( 10 ) print ( peek ( number ), peek ( double )) --> 10 20 It's recommended you always give the first parameter the name use , even if it already exists. This helps prevent you from using the wrong parameter if you have multiple computed objects at the same time. scope : Computed ( function ( use , _ ) -- ... scope : Computed ( function ( use , _ ) -- ... scope : Computed ( function ( use , _ ) return use ( number ) * 2 end ) -- ... end ) -- ... end ) Help! Using the same name gives me a warning. Depending on your setup, Luau might be configured to warn when you use the same variable name multiple times. In many cases, using the same variable name can be a mistake, but in this case we actually find it useful. So, to turn off the warning, try adding --!nolint LocalShadow to the top of your file. Keep in mind that Fusion sometimes applies optimisations; recalculations might be postponed or cancelled if the value of the computed isn't being used. This is why you should not use computed objects for things like playing sound effects. You will learn more about how Fusion does this later. Inner Scopes \u00b6 Sometimes, you'll need to create things inside computed objects temporarily. In these cases, you want the temporary things to be destroyed when you're done. You might try and reuse the scope you already have, to construct objects and add cleanup tasks. Luau code Output 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 local scope = scoped ( Fusion ) local number = scope : Value ( 5 ) local double = scope : Computed ( function ( use , _ ) local current = use ( number ) print ( \"Creating\" , current ) -- suppose we want to run some cleanup code for stuff in here table.insert ( scope , function () print ( \"Destroying\" , current ) end ) return current * 2 end ) print ( \"...setting to 25...\" ) number : set ( 25 ) print ( \"...setting to 2...\" ) number : set ( 2 ) print ( \"...cleaning up...\" ) doCleanup ( scope ) Creating 5 ...setting to 25... Creating 25 ...setting to 2... Creating 2 ...cleaning up... Destroying 2 Destroying 25 Destroying 5 However, this doesn't work the way you'd want it to. All of the tasks pile up at the end of the program, instead of being thrown away with the rest of the calculation. That's why the second argument is a different scope for you to use while inside the computed object. This scope argument is automatically cleaned up for you when the computed object recalculates. Luau code Output 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 local scope = scoped ( Fusion ) local number = scope : Value ( 5 ) local double = scope : Computed ( function ( use , myBrandNewScope ) local current = use ( number ) print ( \"Creating\" , current ) table.insert ( myBrandNewScope , function () print ( \"Destroying\" , current ) end ) return current * 2 end ) print ( \"...setting to 25...\" ) number : set ( 25 ) print ( \"...setting to 2...\" ) number : set ( 2 ) print ( \"...cleaning up...\" ) doCleanup ( scope ) Creating 5 ...setting to 25... Creating 25 Destroying 5 ...setting to 2... Creating 2 Destroying 25 ...cleaning up... Destroying 2 When using this new 'inner' scope, the tasks no longer pile up at the end of the program. Instead, they're cleaned up as soon as possible, when the computed object throws away the old calculation. It can help to give this parameter the same name as the original scope. This stops you from accidentally using the original scope inside the computed, and makes your code more easily copyable and movable. local scope = scoped ( Fusion ) scope : Computed ( function ( use , scope ) -- ... scope : Computed ( function ( use , scope ) -- ... scope : Computed ( function ( use , scope ) local innerValue = scope : Value ( 5 ) end ) -- ... end ) -- ... end ) Help! Using the same name gives me a warning. Depending on your setup, Luau might be configured to warn when you use the same variable name multiple times. In many cases, using the same variable name can be a mistake, but in this case we actually find it useful. So, to turn off the warning, try adding --!nolint LocalShadow to the top of your file. Once you understand computeds, as well as the previously discussed scopes, values and observers, you're well positioned to explore the rest of Fusion.","title":"Computeds"},{"location":"tutorials/fundamentals/computeds/#usage","text":"To create a new computed object, call scope:Computed() and give it a function that performs your calculation. It takes two parameters which will be explained later; for the first part of this tutorial, they'll be left unnamed. 6 7 8 9 local scope = scoped ( Fusion ) local hardMaths = scope : Computed ( function ( _ , _ ) return 1 + 1 end ) The value the callback returns will be stored as the computed's value. You can get the computed's current value using peek() : 6 7 8 9 10 11 local scope = scoped ( Fusion ) local hardMaths = scope : Computed ( function ( _ , _ ) return 1 + 1 end ) print ( peek ( hardMaths )) --> 2 The calculation should be immediate - that is, it should never delay. That means you should not use computed objects when you need to wait for something to occur (e.g. waiting for a server to respond to a request).","title":"Usage"},{"location":"tutorials/fundamentals/computeds/#using-state-objects","text":"The calculation is only run once by default. If you try to peek() at state objects inside the calculation, your code breaks quickly: 6 7 8 9 10 11 12 13 14 15 16 local scope = scoped ( Fusion ) local number = scope : Value ( 2 ) local double = scope : Computed ( function ( _ , _ ) return peek ( number ) * 2 end ) print ( peek ( number ), peek ( double )) --> 2 4 -- The calculation won't re-run! Oh no! number : set ( 10 ) print ( peek ( number ), peek ( double )) --> 10 4 Instead, the computed object provides a use function as the first argument. As your logic runs, you can call this function with different state objects. If any of them changes, then the computed throws everything away and recalculates. 6 7 8 9 10 11 12 13 14 15 16 17 local scope = scoped ( Fusion ) local number = scope : Value ( 2 ) local double = scope : Computed ( function ( use , _ ) use ( number ) -- the calculation will re-run when `number` changes value return peek ( number ) * 2 end ) print ( peek ( number ), peek ( double )) --> 2 4 -- Now it re-runs! number : set ( 10 ) print ( peek ( number ), peek ( double )) --> 10 20 For convenience, use() will also read the value, just like peek() , so you can easily replace peek() calls with use() calls. This keeps your logic concise, readable and easily copyable. 6 7 8 9 10 11 12 13 14 15 local scope = scoped ( Fusion ) local number = scope : Value ( 2 ) local double = scope : Computed ( function ( use , _ ) return use ( number ) * 2 end ) print ( peek ( number ), peek ( double )) --> 2 4 number : set ( 10 ) print ( peek ( number ), peek ( double )) --> 10 20 It's recommended you always give the first parameter the name use , even if it already exists. This helps prevent you from using the wrong parameter if you have multiple computed objects at the same time. scope : Computed ( function ( use , _ ) -- ... scope : Computed ( function ( use , _ ) -- ... scope : Computed ( function ( use , _ ) return use ( number ) * 2 end ) -- ... end ) -- ... end ) Help! Using the same name gives me a warning. Depending on your setup, Luau might be configured to warn when you use the same variable name multiple times. In many cases, using the same variable name can be a mistake, but in this case we actually find it useful. So, to turn off the warning, try adding --!nolint LocalShadow to the top of your file. Keep in mind that Fusion sometimes applies optimisations; recalculations might be postponed or cancelled if the value of the computed isn't being used. This is why you should not use computed objects for things like playing sound effects. You will learn more about how Fusion does this later.","title":"Using State Objects"},{"location":"tutorials/fundamentals/computeds/#inner-scopes","text":"Sometimes, you'll need to create things inside computed objects temporarily. In these cases, you want the temporary things to be destroyed when you're done. You might try and reuse the scope you already have, to construct objects and add cleanup tasks. Luau code Output 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 local scope = scoped ( Fusion ) local number = scope : Value ( 5 ) local double = scope : Computed ( function ( use , _ ) local current = use ( number ) print ( \"Creating\" , current ) -- suppose we want to run some cleanup code for stuff in here table.insert ( scope , function () print ( \"Destroying\" , current ) end ) return current * 2 end ) print ( \"...setting to 25...\" ) number : set ( 25 ) print ( \"...setting to 2...\" ) number : set ( 2 ) print ( \"...cleaning up...\" ) doCleanup ( scope ) Creating 5 ...setting to 25... Creating 25 ...setting to 2... Creating 2 ...cleaning up... Destroying 2 Destroying 25 Destroying 5 However, this doesn't work the way you'd want it to. All of the tasks pile up at the end of the program, instead of being thrown away with the rest of the calculation. That's why the second argument is a different scope for you to use while inside the computed object. This scope argument is automatically cleaned up for you when the computed object recalculates. Luau code Output 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 local scope = scoped ( Fusion ) local number = scope : Value ( 5 ) local double = scope : Computed ( function ( use , myBrandNewScope ) local current = use ( number ) print ( \"Creating\" , current ) table.insert ( myBrandNewScope , function () print ( \"Destroying\" , current ) end ) return current * 2 end ) print ( \"...setting to 25...\" ) number : set ( 25 ) print ( \"...setting to 2...\" ) number : set ( 2 ) print ( \"...cleaning up...\" ) doCleanup ( scope ) Creating 5 ...setting to 25... Creating 25 Destroying 5 ...setting to 2... Creating 2 Destroying 25 ...cleaning up... Destroying 2 When using this new 'inner' scope, the tasks no longer pile up at the end of the program. Instead, they're cleaned up as soon as possible, when the computed object throws away the old calculation. It can help to give this parameter the same name as the original scope. This stops you from accidentally using the original scope inside the computed, and makes your code more easily copyable and movable. local scope = scoped ( Fusion ) scope : Computed ( function ( use , scope ) -- ... scope : Computed ( function ( use , scope ) -- ... scope : Computed ( function ( use , scope ) local innerValue = scope : Value ( 5 ) end ) -- ... end ) -- ... end ) Help! Using the same name gives me a warning. Depending on your setup, Luau might be configured to warn when you use the same variable name multiple times. In many cases, using the same variable name can be a mistake, but in this case we actually find it useful. So, to turn off the warning, try adding --!nolint LocalShadow to the top of your file. Once you understand computeds, as well as the previously discussed scopes, values and observers, you're well positioned to explore the rest of Fusion.","title":"Inner Scopes"},{"location":"tutorials/fundamentals/observers/","text":"When you're working with state objects, it can be useful to detect various changes that happen to them. Observers allow you to detect those changes. Create one with a state object to 'watch', then connect code to run using :onChange() or :onBind() . local observer = scope : Observer ( health ) local disconnect = observer : onChange ( function () print ( \"The new value is: \" , peek ( health )) end ) task . wait ( 5 ) disconnect () Usage \u00b6 To create a new observer object, call scope:Observer() and give it a state object you want to detect changes on. 6 7 8 local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) local observer = scope : Observer ( health ) The observer will watch the state object for changes until it's destroyed. You can take advantage of this by connecting your own code using the observer's different methods. The first method is :onChange() , which runs your code when the state object changes value. Luau code Output 8 9 10 11 12 13 14 15 16 local observer = scope : Observer ( health ) print ( \"...connecting...\" ) observer : onChange ( function () print ( \"Observed a change to: \" , peek ( health )) end ) print ( \"...setting health to 25...\" ) health : set ( 25 ) ...connecting... ...setting health to 25... Observed a change to: 25 By default, the :onChange() connection is disconnected when the observer object is destroyed. However, if you want to disconnect it earlier, the :onChange() method returns an optional disconnect function. Calling it will disconnect that specific :onChange() handler early. 8 9 10 11 12 13 14 local disconnect = observer : onChange ( function () print ( \"The new value is: \" , peek ( health )) end ) -- disconnect the above handler after 5 seconds task . wait ( 5 ) disconnect () The second method is :onBind() . It works identically to :onChange() , but it also runs your code right away, which can often be useful. Luau code Output 8 9 10 11 12 13 14 15 16 local observer = scope : Observer ( health ) print ( \"...connecting...\" ) observer : onBind ( function () print ( \"Observed a change to: \" , peek ( health )) end ) print ( \"...setting health to 25...\" ) health : set ( 25 ) ...connecting... Observed a change to: 5 ...setting health to 25... Observed a change to: 25 What Counts As A Change? \u00b6 If you set the health to the same value multiple times in a row, you might notice your observer only runs the first time. Luau code Output 8 9 10 11 12 13 14 15 16 17 local observer = scope : Observer ( health ) observer : onChange ( function () print ( \"Observed a change to: \" , peek ( health )) end ) print ( \"...setting health to 25 three times...\" ) health : set ( 25 ) health : set ( 25 ) health : set ( 25 ) ...setting health to 25 three times... Observed a change to: 25 This is because the health object sees that it isn't actually changing value, so it doesn't broadcast any updates. Therefore, our observer doesn't run. This leads to improved performance because your code runs less often. Fusion applies these kinds of optimisations generously throughout your program.","title":"Observers"},{"location":"tutorials/fundamentals/observers/#usage","text":"To create a new observer object, call scope:Observer() and give it a state object you want to detect changes on. 6 7 8 local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) local observer = scope : Observer ( health ) The observer will watch the state object for changes until it's destroyed. You can take advantage of this by connecting your own code using the observer's different methods. The first method is :onChange() , which runs your code when the state object changes value. Luau code Output 8 9 10 11 12 13 14 15 16 local observer = scope : Observer ( health ) print ( \"...connecting...\" ) observer : onChange ( function () print ( \"Observed a change to: \" , peek ( health )) end ) print ( \"...setting health to 25...\" ) health : set ( 25 ) ...connecting... ...setting health to 25... Observed a change to: 25 By default, the :onChange() connection is disconnected when the observer object is destroyed. However, if you want to disconnect it earlier, the :onChange() method returns an optional disconnect function. Calling it will disconnect that specific :onChange() handler early. 8 9 10 11 12 13 14 local disconnect = observer : onChange ( function () print ( \"The new value is: \" , peek ( health )) end ) -- disconnect the above handler after 5 seconds task . wait ( 5 ) disconnect () The second method is :onBind() . It works identically to :onChange() , but it also runs your code right away, which can often be useful. Luau code Output 8 9 10 11 12 13 14 15 16 local observer = scope : Observer ( health ) print ( \"...connecting...\" ) observer : onBind ( function () print ( \"Observed a change to: \" , peek ( health )) end ) print ( \"...setting health to 25...\" ) health : set ( 25 ) ...connecting... Observed a change to: 5 ...setting health to 25... Observed a change to: 25","title":"Usage"},{"location":"tutorials/fundamentals/observers/#what-counts-as-a-change","text":"If you set the health to the same value multiple times in a row, you might notice your observer only runs the first time. Luau code Output 8 9 10 11 12 13 14 15 16 17 local observer = scope : Observer ( health ) observer : onChange ( function () print ( \"Observed a change to: \" , peek ( health )) end ) print ( \"...setting health to 25 three times...\" ) health : set ( 25 ) health : set ( 25 ) health : set ( 25 ) ...setting health to 25 three times... Observed a change to: 25 This is because the health object sees that it isn't actually changing value, so it doesn't broadcast any updates. Therefore, our observer doesn't run. This leads to improved performance because your code runs less often. Fusion applies these kinds of optimisations generously throughout your program.","title":"What Counts As A Change?"},{"location":"tutorials/fundamentals/scopes/","text":"In Fusion, you create a lot of objects. These objects need to be destroyed when you're done with them. Fusion has some coding conventions to make large quantities of objects easier to manage. Scopes \u00b6 When you create many objects at once, you often want to :destroy() them together later. To make this easier, some people add their objects to an array. Arrays that group together objects like this are given a special name: scopes . To create a new scope, create an empty array. 2 3 4 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} Later, when you create objects, they will ask for a scope as the first argument. 2 3 4 5 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} local thing = Fusion . Value ( scope , \"i am a thing\" ) That object will add itself to the scope. 2 3 4 5 6 7 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} local thing = Fusion . Value ( scope , \"i am a thing\" ) print ( scope [ 1 ] == thing ) --> true Repeat as many times as you like. Objects appear in order of creation. 2 3 4 5 6 7 8 9 10 11 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) print ( scope [ 1 ] == thing1 ) --> true print ( scope [ 2 ] == thing2 ) --> true print ( scope [ 3 ] == thing3 ) --> true Later, destroy the scope by using the doCleanup() function. The contents are destroyed in reverse order. 2 3 4 5 6 7 8 9 10 11 12 13 14 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup = Fusion . doCleanup local scope = {} local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) doCleanup ( scope ) -- Using `doCleanup` is the same as: -- thing3:destroy() -- thing2:destroy() -- thing1:destroy() Scopes passed to doCleanup can contain: Objects with :destroy() or :Destroy() methods to be called Functions to be run Roblox instances to destroy Roblox event connections to disconnect Other nested scopes to be cleaned up You can add these manually using table.insert if you need custom behaviour, or if you are working with objects that don't add themselves to scopes. That's all there is to scopes. They are arrays of objects which later get passed to a cleanup function. Improved Scopes \u00b6 This syntax is recommended From now on, you'll see this syntax used throughout the tutorials. Fusion can help manage your scopes for you. This unlocks convenient syntax, and allows Fusion to optimise your code. You can call scoped() to obtain a new scope. 2 3 4 5 6 7 8 9 10 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped () local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) doCleanup ( scope ) Unlike {} (which always creates a new array), scoped can re-use old arrays. This helps keep your program running smoothly. Beyond making your code more efficient, you can also use scoped for convenient syntax. You can pass a table of constructor functions into scoped : 2 3 4 5 6 7 8 9 10 11 12 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ({ Value = Fusion . Value }) local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) doCleanup ( scope ) You can now use those constructors as methods on scope . 2 3 4 5 6 7 8 9 10 11 12 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ({ Value = Fusion . Value }) local thing1 = scope : Value ( \"i am thing 1\" ) local thing2 = scope : Value ( \"i am thing 2\" ) local thing3 = scope : Value ( \"i am thing 3\" ) doCleanup ( scope ) This makes it harder to mess up writing scopes. Your code reads more naturally, too. Adding Methods In Bulk \u00b6 Try passing Fusion to scoped() - it's a table with functions. 2 3 4 5 6 7 8 9 10 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ( Fusion ) local thing1 = scope : Value ( \"i am thing 1\" ) local thing2 = scope : Value ( \"i am thing 2\" ) local thing3 = scope : Value ( \"i am thing 3\" ) doCleanup ( scope ) This gives you access to all of Fusion's constructors without having to import each one manually. If you need to mix in other things, you can pass in another table. local scope = scoped ( Fusion , { Foo = ..., Bar = ... }) You can do this for as many tables as you need. Conflicting names If you pass in two tables that contain things with the same name, scoped() will error. Reusing Methods From Other Scopes \u00b6 Sometimes, you'll want to make a new scope with the same methods as an existing scope. local foo = scoped ({ Foo = Foo , Bar = Bar , Baz = Baz }) -- it'd be nice to define this once only... local bar = scoped ({ Foo = Foo , Bar = Bar , Baz = Baz }) To do this, Fusion provides a deriveScope function. It behaves like scoped but lets you skip defining the methods. Instead, you give it an example of what the scope should look like. 2 3 4 5 6 7 8 9 10 11 12 13 14 15 local Fusion = require ( ReplicatedStorage . Fusion ) local scoped , deriveScope = Fusion . scoped , Fusion . deriveScope local doCleanup = Fusion . doCleanup local foo = scoped ({ Foo = Foo , Bar = Bar , Baz = Baz }) local bar = deriveScope ( foo ) doCleanup ( bar ) doCleanup ( foo ) Deriving scopes like this is highly efficient because Fusion can re-use the same information for both scopes. It also helps keep your definitions all in one place. When You'll Use This \u00b6 Scopes might sound like a lot of upfront work. However, you'll find in practice that Fusion manages a lot of this for you. You'll need to create and destroy your own scopes manually sometimes. For example, you'll need to create a scope in your main code file to start using Fusion, and you might want to make a few more in other parts of your code. However, Fusion manages most of your scopes for you, so for large parts of your codebase, you won't have to consider scopes and destruction at all.","title":"Scopes"},{"location":"tutorials/fundamentals/scopes/#scopes","text":"When you create many objects at once, you often want to :destroy() them together later. To make this easier, some people add their objects to an array. Arrays that group together objects like this are given a special name: scopes . To create a new scope, create an empty array. 2 3 4 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} Later, when you create objects, they will ask for a scope as the first argument. 2 3 4 5 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} local thing = Fusion . Value ( scope , \"i am a thing\" ) That object will add itself to the scope. 2 3 4 5 6 7 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} local thing = Fusion . Value ( scope , \"i am a thing\" ) print ( scope [ 1 ] == thing ) --> true Repeat as many times as you like. Objects appear in order of creation. 2 3 4 5 6 7 8 9 10 11 local Fusion = require ( ReplicatedStorage . Fusion ) local scope = {} local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) print ( scope [ 1 ] == thing1 ) --> true print ( scope [ 2 ] == thing2 ) --> true print ( scope [ 3 ] == thing3 ) --> true Later, destroy the scope by using the doCleanup() function. The contents are destroyed in reverse order. 2 3 4 5 6 7 8 9 10 11 12 13 14 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup = Fusion . doCleanup local scope = {} local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) doCleanup ( scope ) -- Using `doCleanup` is the same as: -- thing3:destroy() -- thing2:destroy() -- thing1:destroy() Scopes passed to doCleanup can contain: Objects with :destroy() or :Destroy() methods to be called Functions to be run Roblox instances to destroy Roblox event connections to disconnect Other nested scopes to be cleaned up You can add these manually using table.insert if you need custom behaviour, or if you are working with objects that don't add themselves to scopes. That's all there is to scopes. They are arrays of objects which later get passed to a cleanup function.","title":"Scopes"},{"location":"tutorials/fundamentals/scopes/#improved-scopes","text":"This syntax is recommended From now on, you'll see this syntax used throughout the tutorials. Fusion can help manage your scopes for you. This unlocks convenient syntax, and allows Fusion to optimise your code. You can call scoped() to obtain a new scope. 2 3 4 5 6 7 8 9 10 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped () local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) doCleanup ( scope ) Unlike {} (which always creates a new array), scoped can re-use old arrays. This helps keep your program running smoothly. Beyond making your code more efficient, you can also use scoped for convenient syntax. You can pass a table of constructor functions into scoped : 2 3 4 5 6 7 8 9 10 11 12 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ({ Value = Fusion . Value }) local thing1 = Fusion . Value ( scope , \"i am thing 1\" ) local thing2 = Fusion . Value ( scope , \"i am thing 2\" ) local thing3 = Fusion . Value ( scope , \"i am thing 3\" ) doCleanup ( scope ) You can now use those constructors as methods on scope . 2 3 4 5 6 7 8 9 10 11 12 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ({ Value = Fusion . Value }) local thing1 = scope : Value ( \"i am thing 1\" ) local thing2 = scope : Value ( \"i am thing 2\" ) local thing3 = scope : Value ( \"i am thing 3\" ) doCleanup ( scope ) This makes it harder to mess up writing scopes. Your code reads more naturally, too.","title":"Improved Scopes"},{"location":"tutorials/fundamentals/scopes/#adding-methods-in-bulk","text":"Try passing Fusion to scoped() - it's a table with functions. 2 3 4 5 6 7 8 9 10 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ( Fusion ) local thing1 = scope : Value ( \"i am thing 1\" ) local thing2 = scope : Value ( \"i am thing 2\" ) local thing3 = scope : Value ( \"i am thing 3\" ) doCleanup ( scope ) This gives you access to all of Fusion's constructors without having to import each one manually. If you need to mix in other things, you can pass in another table. local scope = scoped ( Fusion , { Foo = ..., Bar = ... }) You can do this for as many tables as you need. Conflicting names If you pass in two tables that contain things with the same name, scoped() will error.","title":"Adding Methods In Bulk"},{"location":"tutorials/fundamentals/scopes/#reusing-methods-from-other-scopes","text":"Sometimes, you'll want to make a new scope with the same methods as an existing scope. local foo = scoped ({ Foo = Foo , Bar = Bar , Baz = Baz }) -- it'd be nice to define this once only... local bar = scoped ({ Foo = Foo , Bar = Bar , Baz = Baz }) To do this, Fusion provides a deriveScope function. It behaves like scoped but lets you skip defining the methods. Instead, you give it an example of what the scope should look like. 2 3 4 5 6 7 8 9 10 11 12 13 14 15 local Fusion = require ( ReplicatedStorage . Fusion ) local scoped , deriveScope = Fusion . scoped , Fusion . deriveScope local doCleanup = Fusion . doCleanup local foo = scoped ({ Foo = Foo , Bar = Bar , Baz = Baz }) local bar = deriveScope ( foo ) doCleanup ( bar ) doCleanup ( foo ) Deriving scopes like this is highly efficient because Fusion can re-use the same information for both scopes. It also helps keep your definitions all in one place.","title":"Reusing Methods From Other Scopes"},{"location":"tutorials/fundamentals/scopes/#when-youll-use-this","text":"Scopes might sound like a lot of upfront work. However, you'll find in practice that Fusion manages a lot of this for you. You'll need to create and destroy your own scopes manually sometimes. For example, you'll need to create a scope in your main code file to start using Fusion, and you might want to make a few more in other parts of your code. However, Fusion manages most of your scopes for you, so for large parts of your codebase, you won't have to consider scopes and destruction at all.","title":"When You'll Use This"},{"location":"tutorials/fundamentals/values/","text":"Now that you understand how Fusion works with objects, you can create Fusion's simplest object. Values are objects which store single values. You can write to them with their :set() method, and read from them with the peek() function. local health = scope : Value ( 100 ) print ( peek ( health )) --> 100 health : set ( 25 ) print ( peek ( health )) --> 25 Usage \u00b6 To create a new value object, call scope:Value() and give it a value you want to store. 2 3 4 5 6 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) Fusion provides a global peek() function. It will read the value of whatever you give it. You'll use peek() to read the value of lots of things; for now, it's useful for printing health back out. 2 3 4 5 6 7 8 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local peek = Fusion . peek local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) print ( peek ( health )) --> 5 You can change the value using the :set() method. Unlike peek() , this is specific to value objects, so it's done on the object itself. 6 7 8 9 10 11 local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) print ( peek ( health )) --> 5 health : set ( 25 ) print ( peek ( health )) --> 25 Value objects are Fusion's simplest 'state object'. State objects contain a single value - their state , you might say - and that single value can be read out at any time using peek() . Later on, you'll discover more advanced state objects that can calculate their value in more interesting ways.","title":"Values"},{"location":"tutorials/fundamentals/values/#usage","text":"To create a new value object, call scope:Value() and give it a value you want to store. 2 3 4 5 6 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) Fusion provides a global peek() function. It will read the value of whatever you give it. You'll use peek() to read the value of lots of things; for now, it's useful for printing health back out. 2 3 4 5 6 7 8 local Fusion = require ( ReplicatedStorage . Fusion ) local doCleanup , scoped = Fusion . doCleanup , Fusion . scoped local peek = Fusion . peek local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) print ( peek ( health )) --> 5 You can change the value using the :set() method. Unlike peek() , this is specific to value objects, so it's done on the object itself. 6 7 8 9 10 11 local scope = scoped ( Fusion ) local health = scope : Value ( 5 ) print ( peek ( health )) --> 5 health : set ( 25 ) print ( peek ( health )) --> 25 Value objects are Fusion's simplest 'state object'. State objects contain a single value - their state , you might say - and that single value can be read out at any time using peek() . Later on, you'll discover more advanced state objects that can calculate their value in more interesting ways.","title":"Usage"},{"location":"tutorials/roblox/change-events/","text":"OnChange is a function that returns keys to use when hydrating or creating an instance. Those keys let you connect functions to property changed events on the instance. local input = scope : New \"TextBox\" { [ OnChange \"Text\" ] = function ( newText ) print ( \"You typed:\" , newText ) end } Usage \u00b6 OnChange doesn't need a scope - import it into your code from Fusion directly. local OnChange = Fusion . OnChange When you call OnChange with a property name, it will return a special key: local key = OnChange ( \"Text\" ) When used in a property table, you can pass in a handler and it will be run when that property changes. Arguments are different to Roblox API Normally in the Roblox API, when using :GetPropertyChangedSignal() on an instance, the callback will not receive any arguments. To make working with change events easier, OnChange will pass the new value of the property to the callback. local input = scope : New \"TextBox\" { [ OnChange ( \"Text\" )] = function ( newText ) print ( \"You typed:\" , newText ) end } If you're using quotes '' \"\" for the event name, the extra parentheses () are optional: local input = scope : New \"TextBox\" { [ OnChange \"Text\" ] = function ( newText ) print ( \"You typed:\" , newText ) end }","title":"Change Events"},{"location":"tutorials/roblox/change-events/#usage","text":"OnChange doesn't need a scope - import it into your code from Fusion directly. local OnChange = Fusion . OnChange When you call OnChange with a property name, it will return a special key: local key = OnChange ( \"Text\" ) When used in a property table, you can pass in a handler and it will be run when that property changes. Arguments are different to Roblox API Normally in the Roblox API, when using :GetPropertyChangedSignal() on an instance, the callback will not receive any arguments. To make working with change events easier, OnChange will pass the new value of the property to the callback. local input = scope : New \"TextBox\" { [ OnChange ( \"Text\" )] = function ( newText ) print ( \"You typed:\" , newText ) end } If you're using quotes '' \"\" for the event name, the extra parentheses () are optional: local input = scope : New \"TextBox\" { [ OnChange \"Text\" ] = function ( newText ) print ( \"You typed:\" , newText ) end }","title":"Usage"},{"location":"tutorials/roblox/events/","text":"OnEvent is a function that returns keys to use when hydrating or creating an instance. Those keys let you connect functions to events on the instance. local button = scope : New \"TextButton\" { [ OnEvent \"Activated\" ] = function ( _ , numClicks ) print ( \"The button was pressed\" , numClicks , \"time(s)!\" ) end } Usage \u00b6 OnEvent doesn't need a scope - import it into your code from Fusion directly. local OnEvent = Fusion . OnEvent When you call OnEvent with an event name, it will return a special key: local key = OnEvent ( \"Activated\" ) When that key is used in a property table, you can pass in a handler and it will be connected to the event for you: local button = scope : New \"TextButton\" { [ OnEvent ( \"Activated\" )] = function ( _ , numClicks ) print ( \"The button was pressed\" , numClicks , \"time(s)!\" ) end } If you're using quotes '' \"\" for the event name, the extra parentheses () are optional: local button = scope : New \"TextButton\" { [ OnEvent \"Activated\" ] = function ( _ , numClicks ) print ( \"The button was pressed\" , numClicks , \"time(s)!\" ) end }","title":"Events"},{"location":"tutorials/roblox/events/#usage","text":"OnEvent doesn't need a scope - import it into your code from Fusion directly. local OnEvent = Fusion . OnEvent When you call OnEvent with an event name, it will return a special key: local key = OnEvent ( \"Activated\" ) When that key is used in a property table, you can pass in a handler and it will be connected to the event for you: local button = scope : New \"TextButton\" { [ OnEvent ( \"Activated\" )] = function ( _ , numClicks ) print ( \"The button was pressed\" , numClicks , \"time(s)!\" ) end } If you're using quotes '' \"\" for the event name, the extra parentheses () are optional: local button = scope : New \"TextButton\" { [ OnEvent \"Activated\" ] = function ( _ , numClicks ) print ( \"The button was pressed\" , numClicks , \"time(s)!\" ) end }","title":"Usage"},{"location":"tutorials/roblox/hydration/","text":"Intent to replace While the contents of this page still apply (and are useful for explaining other features), Hydrate itself will be replaced by other primitives in the near future. See this issue on GitHub for further details. The process of connecting your scripts to a pre-made UI template is known as hydration . This is where logic in your scripts translate into UI effects, for example setting a message inside a TextLabel, moving menus around, or showing and hiding buttons. Screenshot: GameUIDatabase (Halo Infinite) Fusion provides a Hydrate function for hydrating an instance using a table of properties. If you pass in Fusion objects, changes will be applied immediately: local showUI = scope : Value ( false ) local ui = scope : Hydrate ( StarterGui . Template : Clone ()) { Name = \"MainGui\" , Enabled = showUI } print ( ui . Name ) --> MainGui print ( ui . Enabled ) --> false showUI : set ( true ) task . wait () -- important: changes are applied on the next frame! print ( ui . Enabled ) --> true Usage \u00b6 The Hydrate function is called in two parts. First, call the function with the instance you want to hydrate, then pass in the property table: local instance = workspace . Part scope : Hydrate ( instance )({ Color = Color3 . new ( 1 , 0 , 0 ) }) If you're using curly braces {} to pass your properties in, the extra parentheses () are optional: local instance = workspace . Part -- This only works when you're using curly braces {}! scope : Hydrate ( instance ) { Color = Color3 . new ( 1 , 0 , 0 ) } Hydrate returns the instance you give it, so you can use it in declarations: local instance = scope : Hydrate ( workspace . Part ) { Color = Color3 . new ( 1 , 0 , 0 ) } If you pass in constant values for properties, they'll be applied to the instance directly. However, if you pass in a Fusion object (like Value ), then changes will be applied immediately: local message = scope : Value ( \"Loading...\" ) scope : Hydrate ( PlayerGui . LoadingText ) { Text = message } print ( PlayerGui . Message . Text ) --> Loading... message : set ( \"All done!\" ) task . wait () -- important: changes are applied on the next frame! print ( PlayerGui . Message . Text ) --> All done!","title":"Hydration"},{"location":"tutorials/roblox/hydration/#usage","text":"The Hydrate function is called in two parts. First, call the function with the instance you want to hydrate, then pass in the property table: local instance = workspace . Part scope : Hydrate ( instance )({ Color = Color3 . new ( 1 , 0 , 0 ) }) If you're using curly braces {} to pass your properties in, the extra parentheses () are optional: local instance = workspace . Part -- This only works when you're using curly braces {}! scope : Hydrate ( instance ) { Color = Color3 . new ( 1 , 0 , 0 ) } Hydrate returns the instance you give it, so you can use it in declarations: local instance = scope : Hydrate ( workspace . Part ) { Color = Color3 . new ( 1 , 0 , 0 ) } If you pass in constant values for properties, they'll be applied to the instance directly. However, if you pass in a Fusion object (like Value ), then changes will be applied immediately: local message = scope : Value ( \"Loading...\" ) scope : Hydrate ( PlayerGui . LoadingText ) { Text = message } print ( PlayerGui . Message . Text ) --> Loading... message : set ( \"All done!\" ) task . wait () -- important: changes are applied on the next frame! print ( PlayerGui . Message . Text ) --> All done!","title":"Usage"},{"location":"tutorials/roblox/new-instances/","text":"Fusion provides a New function when you're hydrating newly-made instances. It creates a new instance, applies some default properties, then hydrates it with a property table. local message = scope : Value ( \"Hello there!\" ) local ui = scope : New \"TextLabel\" { Name = \"Greeting\" , Parent = PlayerGui . ScreenGui , Text = message } print ( ui . Name ) --> Greeting print ( ui . Text ) --> Hello there! message : set ( \"Goodbye friend!\" ) task . wait () -- important: changes are applied on the next frame! print ( ui . Text ) --> Goodbye friend! Usage \u00b6 The New function is called in two parts. First, call the function with the type of instance, then pass in the property table: local instance = scope : New ( \"Part\" )({ Parent = workspace , Color = Color3 . new ( 1 , 0 , 0 ) }) If you're using curly braces {} for your properties, and quotes '' \"\" for your class type, the extra parentheses () are optional: -- This only works when you're using curly braces {} and quotes '' \"\"! local instance = scope : New \"Part\" { Parent = workspace , Color = Color3 . new ( 1 , 0 , 0 ) } By design, New works just like Hydrate - it will apply properties the same way. See the Hydrate tutorial to learn more. Default Properties \u00b6 When you create an instance using Instance.new() , Roblox will give it some default properties. However, these tend to be outdated and aren't useful for most people, leading to repetitive boilerplate needed to disable features that nobody wants to use. The New function will apply some of it's own default properties to fix this. For example, by default borders on UI are disabled, automatic colouring is turned off and default content is removed. For a complete list, take a look at Fusion's default properties file.","title":"New Instances"},{"location":"tutorials/roblox/new-instances/#usage","text":"The New function is called in two parts. First, call the function with the type of instance, then pass in the property table: local instance = scope : New ( \"Part\" )({ Parent = workspace , Color = Color3 . new ( 1 , 0 , 0 ) }) If you're using curly braces {} for your properties, and quotes '' \"\" for your class type, the extra parentheses () are optional: -- This only works when you're using curly braces {} and quotes '' \"\"! local instance = scope : New \"Part\" { Parent = workspace , Color = Color3 . new ( 1 , 0 , 0 ) } By design, New works just like Hydrate - it will apply properties the same way. See the Hydrate tutorial to learn more.","title":"Usage"},{"location":"tutorials/roblox/new-instances/#default-properties","text":"When you create an instance using Instance.new() , Roblox will give it some default properties. However, these tend to be outdated and aren't useful for most people, leading to repetitive boilerplate needed to disable features that nobody wants to use. The New function will apply some of it's own default properties to fix this. For example, by default borders on UI are disabled, automatic colouring is turned off and default content is removed. For a complete list, take a look at Fusion's default properties file.","title":"Default Properties"},{"location":"tutorials/roblox/outputs/","text":"Out is a function that returns keys to use when hydrating or creating an instance. Those keys let you output a property's value to a Value object. local name = scope : Value () local thing = scope : New \"Part\" { [ Out \"Name\" ] = name } print ( peek ( name )) --> Part thing . Name = \"Jimmy\" print ( peek ( name )) --> Jimmy Usage \u00b6 Out doesn't need a scope - import it into your code from Fusion directly. local Out = Fusion . Out When you call Out with a property name, it will return a special key: local key = Out ( \"Activated\" ) When used in a property table, you can pass in a Value object. It will be set to the value of the property, and when the property changes, it will be set to the new value: local name = scope : Value () local thing = scope : New \"Part\" { [ Out ( \"Name\" )] = name } print ( peek ( name )) --> Part thing . Name = \"Jimmy\" print ( peek ( name )) --> Jimmy If you're using quotes '' \"\" for the event name, the extra parentheses () are optional: local thing = scope : New \"Part\" { [ Out \"Name\" ] = name } Two-Way Binding \u00b6 By default, Out only outputs changes to the property. If you set the value to something else, the property remains the same: local name = scope : Value () local thing = scope : New \"Part\" { [ Out \"Name\" ] = name -- When `thing.Name` changes, set `name` } print ( thing . Name , peek ( name )) --> Part Part name : set ( \"NewName\" ) task . wait () print ( thing . Name , peek ( name )) --> Part NewName If you want the value to both change and be changed by the property, you need to explicitly say so: local name = scope : Value () local thing = scope : New \"Part\" { Name = name -- When `name` changes, set `thing.Name` [ Out \"Name\" ] = name -- When `thing.Name` changes, set `name` } print ( thing . Name , peek ( name )) --> Part Part name : set ( \"NewName\" ) task . wait () print ( thing . Name , peek ( name )) --> NewName NewName This is known as two-way binding. Most of the time you won't need it, but it can come in handy when working with some kinds of UI - for example, a text box that users can write into, but which can also be modified by your scripts.","title":"Outputs"},{"location":"tutorials/roblox/outputs/#usage","text":"Out doesn't need a scope - import it into your code from Fusion directly. local Out = Fusion . Out When you call Out with a property name, it will return a special key: local key = Out ( \"Activated\" ) When used in a property table, you can pass in a Value object. It will be set to the value of the property, and when the property changes, it will be set to the new value: local name = scope : Value () local thing = scope : New \"Part\" { [ Out ( \"Name\" )] = name } print ( peek ( name )) --> Part thing . Name = \"Jimmy\" print ( peek ( name )) --> Jimmy If you're using quotes '' \"\" for the event name, the extra parentheses () are optional: local thing = scope : New \"Part\" { [ Out \"Name\" ] = name }","title":"Usage"},{"location":"tutorials/roblox/outputs/#two-way-binding","text":"By default, Out only outputs changes to the property. If you set the value to something else, the property remains the same: local name = scope : Value () local thing = scope : New \"Part\" { [ Out \"Name\" ] = name -- When `thing.Name` changes, set `name` } print ( thing . Name , peek ( name )) --> Part Part name : set ( \"NewName\" ) task . wait () print ( thing . Name , peek ( name )) --> Part NewName If you want the value to both change and be changed by the property, you need to explicitly say so: local name = scope : Value () local thing = scope : New \"Part\" { Name = name -- When `name` changes, set `thing.Name` [ Out \"Name\" ] = name -- When `thing.Name` changes, set `name` } print ( thing . Name , peek ( name )) --> Part Part name : set ( \"NewName\" ) task . wait () print ( thing . Name , peek ( name )) --> NewName NewName This is known as two-way binding. Most of the time you won't need it, but it can come in handy when working with some kinds of UI - for example, a text box that users can write into, but which can also be modified by your scripts.","title":"Two-Way Binding"},{"location":"tutorials/roblox/parenting/","text":"The [Children] key allows you to add children when hydrating or creating an instance. It accepts instances, arrays of children, and state objects containing children or nil . local folder = scope : New \"Folder\" { [ Children ] = { New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) }, New \"Part\" { Name = \"Sammy\" , Material = \"Glass\" } } } Usage \u00b6 Children doesn't need a scope - import it into your code from Fusion directly. local Children = Fusion . Children When using New or Hydrate , you can use [Children] as a key in the property table. Any instance you pass in will be parented: local folder = scope : New \"Folder\" { -- The part will be moved inside of the folder [ Children ] = workspace . Part } Since New and Hydrate both return their instances, you can nest them: -- Makes a Folder, containing a part called Gregory local folder = scope : New \"Folder\" { [ Children ] = scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) } } If you need to parent multiple children, arrays of children are accepted: -- Makes a Folder, containing parts called Gregory and Sammy local folder = scope : New \"Folder\" { [ Children ] = { scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) }, scope : New \"Part\" { Name = \"Sammy\" , Material = \"Glass\" } } } Arrays can be nested to any depth; all children will still be parented: local folder = scope : New \"Folder\" { [ Children ] = { { { { scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) } } } } } } State objects containing children or nil are also allowed: local value = scope : Value () local folder = scope : New \"Folder\" { [ Children ] = value } value : set ( scope : New \"Part\" { Name = \"Clyde\" , Transparency = 0.5 } ) You may use any combination of these to parent whichever children you need: local modelChildren = workspace . Model : GetChildren () local includeModel = scope : Value ( true ) local folder = scope : New \"Folder\" { -- array of children [ Children ] = { -- single instance scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) }, -- state object containing children (or nil) scope : Computed ( function ( use ) return if use ( includeModel ) then modelChildren : GetChildren () -- array of children else nil end ) } }","title":"Parenting"},{"location":"tutorials/roblox/parenting/#usage","text":"Children doesn't need a scope - import it into your code from Fusion directly. local Children = Fusion . Children When using New or Hydrate , you can use [Children] as a key in the property table. Any instance you pass in will be parented: local folder = scope : New \"Folder\" { -- The part will be moved inside of the folder [ Children ] = workspace . Part } Since New and Hydrate both return their instances, you can nest them: -- Makes a Folder, containing a part called Gregory local folder = scope : New \"Folder\" { [ Children ] = scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) } } If you need to parent multiple children, arrays of children are accepted: -- Makes a Folder, containing parts called Gregory and Sammy local folder = scope : New \"Folder\" { [ Children ] = { scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) }, scope : New \"Part\" { Name = \"Sammy\" , Material = \"Glass\" } } } Arrays can be nested to any depth; all children will still be parented: local folder = scope : New \"Folder\" { [ Children ] = { { { { scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) } } } } } } State objects containing children or nil are also allowed: local value = scope : Value () local folder = scope : New \"Folder\" { [ Children ] = value } value : set ( scope : New \"Part\" { Name = \"Clyde\" , Transparency = 0.5 } ) You may use any combination of these to parent whichever children you need: local modelChildren = workspace . Model : GetChildren () local includeModel = scope : Value ( true ) local folder = scope : New \"Folder\" { -- array of children [ Children ] = { -- single instance scope : New \"Part\" { Name = \"Gregory\" , Color = Color3 . new ( 1 , 0 , 0 ) }, -- state object containing children (or nil) scope : Computed ( function ( use ) return if use ( includeModel ) then modelChildren : GetChildren () -- array of children else nil end ) } }","title":"Usage"},{"location":"tutorials/roblox/references/","text":"The [Ref] key allows you to save a reference to an instance you're hydrating or creating. local myRef = scope : Value () local thing = scope : New \"Part\" { [ Ref ] = myRef } print ( peek ( myRef )) --> Part print ( peek ( myRef ) == thing ) --> true Usage \u00b6 Ref doesn't need a scope - import it into your code from Fusion directly. 1 2 local Fusion = require ( ReplicatedStorage . Fusion ) local Ref = Fusion . Ref When creating an instance with New , [Ref] will save that instance to a value object. local myRef = scope : Value () scope : New \"Part\" { [ Ref ] = myRef } print ( peek ( myRef )) --> Part Among other things, this allows you to refer to instances from other instances. local myPart = scope : Value () New \"SelectionBox\" { -- the selection box should adorn to the part Adornee = myPart } New \"Part\" { -- sets `myPart` to this part, which sets the adornee to this part [ Ref ] = myPart } You can also get references to instances from deep inside function calls. -- this will refer to the part, once we create it local myPart = scope : Value () scope : New \"Folder\" { [ Children ] = scope : New \"Folder\" { [ Children ] = scope : New \"Part\" { -- save a reference into the value object [ Ref ] = myPart } } } Nil hazard Before the part is created, the myPart value object will be nil . Be careful not to use it before it's created. If you need to know about the instance ahead of time, you should create the instance early, and parent it in later, when you create the rest of the instances. -- build the part elsewhere, so it can be saved to a variable local myPart = scope : New \"Part\" {} local folders = scope : New \"Folder\" { [ Children ] = scope : New \"Folder\" { -- parent the part into the folder here [ Children ] = myPart } }","title":"References"},{"location":"tutorials/roblox/references/#usage","text":"Ref doesn't need a scope - import it into your code from Fusion directly. 1 2 local Fusion = require ( ReplicatedStorage . Fusion ) local Ref = Fusion . Ref When creating an instance with New , [Ref] will save that instance to a value object. local myRef = scope : Value () scope : New \"Part\" { [ Ref ] = myRef } print ( peek ( myRef )) --> Part Among other things, this allows you to refer to instances from other instances. local myPart = scope : Value () New \"SelectionBox\" { -- the selection box should adorn to the part Adornee = myPart } New \"Part\" { -- sets `myPart` to this part, which sets the adornee to this part [ Ref ] = myPart } You can also get references to instances from deep inside function calls. -- this will refer to the part, once we create it local myPart = scope : Value () scope : New \"Folder\" { [ Children ] = scope : New \"Folder\" { [ Children ] = scope : New \"Part\" { -- save a reference into the value object [ Ref ] = myPart } } } Nil hazard Before the part is created, the myPart value object will be nil . Be careful not to use it before it's created. If you need to know about the instance ahead of time, you should create the instance early, and parent it in later, when you create the rest of the instances. -- build the part elsewhere, so it can be saved to a variable local myPart = scope : New \"Part\" {} local folders = scope : New \"Folder\" { [ Children ] = scope : New \"Folder\" { -- parent the part into the folder here [ Children ] = myPart } }","title":"Usage"},{"location":"tutorials/tables/forkeys/","text":"ForKeys is a state object that processes keys from another table. It supports both constants and state objects. local data = { Red = \"foo\" , Blue = \"bar\" } local prefix = scope : Value ( \"Key_\" ) local renamed = scope : ForKeys ( data , function ( use , key ) return use ( prefix ) .. key end ) print ( peek ( renamed )) --> {Key_Red = \"foo\", Key_Blue = \"bar\"} prefix : set ( \"colour\" ) print ( peek ( renamed )) --> {colourRed = \"foo\", colourBlue = \"bar\"} Usage \u00b6 To create a new ForKeys object, call the constructor with an input table and a processor function. The first two arguments are use and scope , just like computed objects . The third argument is one of the keys read from the input table. local data = { red = \"foo\" , blue = \"bar\" } local renamed = scope : ForKeys ( data , function ( use , scope , key ) return string.upper ( key ) end ) You can read the table of processed keys using peek() : local data = { red = \"foo\" , blue = \"bar\" } local renamed = scope : ForKeys ( data , function ( use , scope , key ) return string.upper ( key ) end ) print ( peek ( renamed )) --> {RED = \"foo\", BLUE = \"bar\"} The input table can be a state object. When the input table changes, the output will update. local foodSet = scope : Value ({}) local prefixes = { pie = \"tasty\" , chocolate = \"yummy\" , broccoli = \"gross\" } local renamedFoodSet = scope : ForKeys ( foodSet , function ( use , scope , food ) return prefixes [ food ] .. food end ) foodSet : set ({ pie = true }) print ( peek ( renamedFoodSet )) --> { tasty_pie = true } foodSet : set ({ broccoli = true , chocolate = true }) print ( peek ( renamedFoodSet )) --> { gross_broccoli = true, yummy_chocolate = true } You can also use() state objects in your calculations, just like a computed. local foodSet = scope : Value ({ broccoli = true , chocolate = true }) local prefixes = { chocolate = \"yummy\" , broccoli = scope : Value ( \"gross\" ) } local renamedFoodSet = scope : ForKeys ( foodSet , function ( use , scope , food ) return use ( prefixes [ food ]) .. food end ) print ( peek ( renamedFoodSet )) --> { gross_broccoli = true, yummy_chocolate = true } prefixes . broccoli : set ( \"scrumptious\" ) print ( peek ( renamedFoodSet )) --> { scrumptious_broccoli = true, yummy_chocolate = true } Anything added to the scope is cleaned up for you when the processed key is removed. local foodSet = scope : Value ({ broccoli = true , chocolate = true }) local shoutingFoodSet = scope : ForKeys ( names , function ( use , scope , food ) table.insert ( scope , function () print ( \"I ate the \" .. food .. \"!\" ) end ) return string.upper ( food ) end ) names : set ({ chocolate = true }) --> I ate the broccoli! How ForKeys optimises your code Rather than creating a new output table from scratch every time the input table is changed, ForKeys will try and reuse as much as possible to improve performance. Say you're converting an array to a dictionary: local array = scope : Value ({ \"Fusion\" , \"Knit\" , \"Matter\" }) local dict = scope : ForKeys ( array , function ( use , scope , index ) return \"Value\" .. index end ) print ( peek ( dict )) --> {Value1 = \"Fusion\", Value2 = \"Knit\", Value3 = \"Matter\"} Because ForKeys only operates on the keys, changing the values in the array doesn't affect the keys. Keys are only added or removed as needed: local array = scope : Value ({ \"Fusion\" , \"Knit\" , \"Matter\" }) local dict = scope : ForKeys ( array , function ( use , scope , index ) return \"Value\" .. index end ) print ( peek ( dict )) --> {Value1 = \"Fusion\", Value2 = \"Knit\", Value3 = \"Matter\"} array : set ({ \"Roact\" , \"Rodux\" , \"Promise\" }) print ( peek ( dict )) --> {Value1 = \"Roact\", Value2 = \"Rodux\", Value3 = \"Promise\"} ForKeys takes advantage of this - when a value changes, it's copied into the output table without recalculating the key. Keys are only calculated when a value is assigned to a new key.","title":"ForKeys"},{"location":"tutorials/tables/forkeys/#usage","text":"To create a new ForKeys object, call the constructor with an input table and a processor function. The first two arguments are use and scope , just like computed objects . The third argument is one of the keys read from the input table. local data = { red = \"foo\" , blue = \"bar\" } local renamed = scope : ForKeys ( data , function ( use , scope , key ) return string.upper ( key ) end ) You can read the table of processed keys using peek() : local data = { red = \"foo\" , blue = \"bar\" } local renamed = scope : ForKeys ( data , function ( use , scope , key ) return string.upper ( key ) end ) print ( peek ( renamed )) --> {RED = \"foo\", BLUE = \"bar\"} The input table can be a state object. When the input table changes, the output will update. local foodSet = scope : Value ({}) local prefixes = { pie = \"tasty\" , chocolate = \"yummy\" , broccoli = \"gross\" } local renamedFoodSet = scope : ForKeys ( foodSet , function ( use , scope , food ) return prefixes [ food ] .. food end ) foodSet : set ({ pie = true }) print ( peek ( renamedFoodSet )) --> { tasty_pie = true } foodSet : set ({ broccoli = true , chocolate = true }) print ( peek ( renamedFoodSet )) --> { gross_broccoli = true, yummy_chocolate = true } You can also use() state objects in your calculations, just like a computed. local foodSet = scope : Value ({ broccoli = true , chocolate = true }) local prefixes = { chocolate = \"yummy\" , broccoli = scope : Value ( \"gross\" ) } local renamedFoodSet = scope : ForKeys ( foodSet , function ( use , scope , food ) return use ( prefixes [ food ]) .. food end ) print ( peek ( renamedFoodSet )) --> { gross_broccoli = true, yummy_chocolate = true } prefixes . broccoli : set ( \"scrumptious\" ) print ( peek ( renamedFoodSet )) --> { scrumptious_broccoli = true, yummy_chocolate = true } Anything added to the scope is cleaned up for you when the processed key is removed. local foodSet = scope : Value ({ broccoli = true , chocolate = true }) local shoutingFoodSet = scope : ForKeys ( names , function ( use , scope , food ) table.insert ( scope , function () print ( \"I ate the \" .. food .. \"!\" ) end ) return string.upper ( food ) end ) names : set ({ chocolate = true }) --> I ate the broccoli! How ForKeys optimises your code Rather than creating a new output table from scratch every time the input table is changed, ForKeys will try and reuse as much as possible to improve performance. Say you're converting an array to a dictionary: local array = scope : Value ({ \"Fusion\" , \"Knit\" , \"Matter\" }) local dict = scope : ForKeys ( array , function ( use , scope , index ) return \"Value\" .. index end ) print ( peek ( dict )) --> {Value1 = \"Fusion\", Value2 = \"Knit\", Value3 = \"Matter\"} Because ForKeys only operates on the keys, changing the values in the array doesn't affect the keys. Keys are only added or removed as needed: local array = scope : Value ({ \"Fusion\" , \"Knit\" , \"Matter\" }) local dict = scope : ForKeys ( array , function ( use , scope , index ) return \"Value\" .. index end ) print ( peek ( dict )) --> {Value1 = \"Fusion\", Value2 = \"Knit\", Value3 = \"Matter\"} array : set ({ \"Roact\" , \"Rodux\" , \"Promise\" }) print ( peek ( dict )) --> {Value1 = \"Roact\", Value2 = \"Rodux\", Value3 = \"Promise\"} ForKeys takes advantage of this - when a value changes, it's copied into the output table without recalculating the key. Keys are only calculated when a value is assigned to a new key.","title":"Usage"},{"location":"tutorials/tables/forpairs/","text":"ForPairs is like ForValues and ForKeys in one object. It can process pairs of keys and values at the same time. It supports both constants and state objects. local itemColours = { shoes = \"red\" , socks = \"blue\" } local owner = scope : Value ( \"Janet\" ) local manipulated = scope : ForPairs ( itemColours , function ( use , scope , thing , colour ) local newKey = colour local newValue = use ( owner ) .. \"'s \" .. thing return newKey , newValue end ) print ( peek ( manipulated )) --> {red = \"Janet's shoes\", blue = \"Janet's socks\"} owner : set ( \"April\" ) print ( peek ( manipulated )) --> {red = \"April's shoes\", blue = \"April's socks\"} Usage \u00b6 To create a new ForPairs object, call the constructor with an input table and a processor function. The first two arguments are use and scope , just like computed objects . The third and fourth arguments are one of the key-value pairs read from the input table. local itemColours = { shoes = \"red\" , socks = \"blue\" } local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) return colour , item end ) You can read the processed table using peek() : local itemColours = { shoes = \"red\" , socks = \"blue\" } local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) return colour , item end ) print ( peek ( swapped )) --> { red = \"shoes\", blue = \"socks\" } The input table can be a state object. When the input table changes, the output will update. local itemColours = scope : Value ({ shoes = \"red\" , socks = \"blue\" }) local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) return colour , item end ) print ( peek ( swapped )) --> { red = \"shoes\", blue = \"socks\" } itemColours : set ({ sandals = \"red\" , socks = \"green\" }) print ( peek ( swapped )) --> { red = \"sandals\", green = \"socks\" } You can also use() state objects in your calculations, just like a computed. local itemColours = { shoes = \"red\" , socks = \"blue\" } local shouldSwap = scope : Value ( false ) local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) if use ( shouldSwap ) then return colour , item else return item , colour end end ) print ( peek ( swapped )) --> { shoes = \"red\", socks = \"blue\" } shouldSwap : set ( true ) print ( peek ( swapped )) --> { red = \"shoes\", blue = \"socks\" } Anything added to the scope is cleaned up for you when either the processed key or the processed value is removed. local itemColours = scope : Value ({ shoes = \"red\" , socks = \"blue\" }) local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) table.insert ( scope , function () print ( \"No longer wearing \" .. colour .. \" \" .. item ) end ) return colour , item end ) itemColours : set ({ shoes = \"red\" , socks = \"green\" }) --> No longer wearing blue socks How ForPairs optimises your code Rather than creating a new output table from scratch every time the input table is changed, ForPairs will try and reuse as much as possible to improve performance. Since ForPairs has to depend on both keys and values, changing any value in the input table will cause a recalculation for that key-value pair. Inversely, ForPairs won't recalculate any key-value pairs that stay the same. Instead, these will be preserved in the output table. If you don't need the keys or the values, Fusion can offer better optimisations. For example, if you're working with an array of values where position doesn't matter, ForValues can move values between keys. Alternatively, if you're working with a set of objects stored in keys, and don't need the values in the table, ForKeys will ignore the values for optimal performance.","title":"ForPairs"},{"location":"tutorials/tables/forpairs/#usage","text":"To create a new ForPairs object, call the constructor with an input table and a processor function. The first two arguments are use and scope , just like computed objects . The third and fourth arguments are one of the key-value pairs read from the input table. local itemColours = { shoes = \"red\" , socks = \"blue\" } local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) return colour , item end ) You can read the processed table using peek() : local itemColours = { shoes = \"red\" , socks = \"blue\" } local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) return colour , item end ) print ( peek ( swapped )) --> { red = \"shoes\", blue = \"socks\" } The input table can be a state object. When the input table changes, the output will update. local itemColours = scope : Value ({ shoes = \"red\" , socks = \"blue\" }) local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) return colour , item end ) print ( peek ( swapped )) --> { red = \"shoes\", blue = \"socks\" } itemColours : set ({ sandals = \"red\" , socks = \"green\" }) print ( peek ( swapped )) --> { red = \"sandals\", green = \"socks\" } You can also use() state objects in your calculations, just like a computed. local itemColours = { shoes = \"red\" , socks = \"blue\" } local shouldSwap = scope : Value ( false ) local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) if use ( shouldSwap ) then return colour , item else return item , colour end end ) print ( peek ( swapped )) --> { shoes = \"red\", socks = \"blue\" } shouldSwap : set ( true ) print ( peek ( swapped )) --> { red = \"shoes\", blue = \"socks\" } Anything added to the scope is cleaned up for you when either the processed key or the processed value is removed. local itemColours = scope : Value ({ shoes = \"red\" , socks = \"blue\" }) local swapped = scope : ForPairs ( data , function ( use , scope , item , colour ) table.insert ( scope , function () print ( \"No longer wearing \" .. colour .. \" \" .. item ) end ) return colour , item end ) itemColours : set ({ shoes = \"red\" , socks = \"green\" }) --> No longer wearing blue socks How ForPairs optimises your code Rather than creating a new output table from scratch every time the input table is changed, ForPairs will try and reuse as much as possible to improve performance. Since ForPairs has to depend on both keys and values, changing any value in the input table will cause a recalculation for that key-value pair. Inversely, ForPairs won't recalculate any key-value pairs that stay the same. Instead, these will be preserved in the output table. If you don't need the keys or the values, Fusion can offer better optimisations. For example, if you're working with an array of values where position doesn't matter, ForValues can move values between keys. Alternatively, if you're working with a set of objects stored in keys, and don't need the values in the table, ForKeys will ignore the values for optimal performance.","title":"Usage"},{"location":"tutorials/tables/forvalues/","text":"ForValues is a state object that processes values from another table. It supports both constants and state objects. local numbers = { 1 , 2 , 3 , 4 , 5 } local multiplier = Value ( 2 ) local multiplied = ForValues ( numbers , function ( use , num ) return num * use ( multiplier ) end ) print ( peek ( multiplied )) --> {2, 4, 6, 8, 10} multiplier : set ( 10 ) print ( peek ( multiplied )) --> {10, 20, 30, 40, 50} Usage \u00b6 To create a new ForValues object, call the constructor with an input table and a processor function. The first two arguments are use and scope , just like computed objects . The third argument is one of the values read from the input table. local numbers = { 1 , 2 , 3 , 4 , 5 } local doubled = scope : ForValues ( numbers , function ( use , scope , num ) return num * 2 end ) You can read the table of processed values using peek() : local numbers = { 1 , 2 , 3 , 4 , 5 } local doubled = scope : ForValues ( numbers , function ( use , scope , num ) return num * 2 end ) print ( peek ( doubled )) --> {2, 4, 6, 8, 10} The input table can be a state object. When the input table changes, the output will update. local numbers = scope : Value ({}) local doubled = scope : ForValues ( numbers , function ( use , scope , num ) return num * 2 end ) numbers : set ({ 1 , 2 , 3 , 4 , 5 }) print ( peek ( doubled )) --> {2, 4, 6, 8, 10} numbers : set ({ 5 , 15 , 25 }) print ( peek ( doubled )) --> {10, 30, 50} You can also use() state objects in your calculations, just like a computed. local numbers = { 1 , 2 , 3 , 4 , 5 } local factor = scope : Value ( 2 ) local multiplied = scope : ForValues ( numbers , function ( use , scope , num ) return num * use ( factor ) end ) print ( peek ( multiplied )) --> {2, 4, 6, 8, 10} factor : set ( 10 ) print ( peek ( multiplied )) --> {10, 20, 30, 40, 50} Anything added to the scope is cleaned up for you when the processed value is removed. local names = scope : Value ({ \"Jodi\" , \"Amber\" , \"Umair\" }) local shoutingNames = scope : ForValues ( names , function ( use , scope , name ) table.insert ( scope , function () print ( \"Goodbye, \" .. name .. \"!\" ) end ) return string.upper ( name ) end ) names : set ({ \"Amber\" , \"Umair\" }) --> Goodbye, Jodi! How ForValues optimises your code Rather than creating a new output table from scratch every time the input table is changed, ForValues will try and reuse as much as possible to improve performance. Say you're measuring the lengths of an array of words: local words = scope : Value ({ \"Orange\" , \"Red\" , \"Magenta\" }) local lengths = scope : ForValues ( words , function ( use , scope , word ) return # word end ) print ( peek ( lengths )) --> {6, 3, 7} The word lengths don't depend on the position of the word in the array. This means that rearranging the words in the input array will just rearrange the lengths in the output array: ForValues takes advantage of this - when input values move around, the output values will move around too, instead of being recalculated. Note that values are only reused once. For example, if you added another occurence of 'Orange', your calculation would have to run again for the second 'Orange':","title":"ForValues"},{"location":"tutorials/tables/forvalues/#usage","text":"To create a new ForValues object, call the constructor with an input table and a processor function. The first two arguments are use and scope , just like computed objects . The third argument is one of the values read from the input table. local numbers = { 1 , 2 , 3 , 4 , 5 } local doubled = scope : ForValues ( numbers , function ( use , scope , num ) return num * 2 end ) You can read the table of processed values using peek() : local numbers = { 1 , 2 , 3 , 4 , 5 } local doubled = scope : ForValues ( numbers , function ( use , scope , num ) return num * 2 end ) print ( peek ( doubled )) --> {2, 4, 6, 8, 10} The input table can be a state object. When the input table changes, the output will update. local numbers = scope : Value ({}) local doubled = scope : ForValues ( numbers , function ( use , scope , num ) return num * 2 end ) numbers : set ({ 1 , 2 , 3 , 4 , 5 }) print ( peek ( doubled )) --> {2, 4, 6, 8, 10} numbers : set ({ 5 , 15 , 25 }) print ( peek ( doubled )) --> {10, 30, 50} You can also use() state objects in your calculations, just like a computed. local numbers = { 1 , 2 , 3 , 4 , 5 } local factor = scope : Value ( 2 ) local multiplied = scope : ForValues ( numbers , function ( use , scope , num ) return num * use ( factor ) end ) print ( peek ( multiplied )) --> {2, 4, 6, 8, 10} factor : set ( 10 ) print ( peek ( multiplied )) --> {10, 20, 30, 40, 50} Anything added to the scope is cleaned up for you when the processed value is removed. local names = scope : Value ({ \"Jodi\" , \"Amber\" , \"Umair\" }) local shoutingNames = scope : ForValues ( names , function ( use , scope , name ) table.insert ( scope , function () print ( \"Goodbye, \" .. name .. \"!\" ) end ) return string.upper ( name ) end ) names : set ({ \"Amber\" , \"Umair\" }) --> Goodbye, Jodi! How ForValues optimises your code Rather than creating a new output table from scratch every time the input table is changed, ForValues will try and reuse as much as possible to improve performance. Say you're measuring the lengths of an array of words: local words = scope : Value ({ \"Orange\" , \"Red\" , \"Magenta\" }) local lengths = scope : ForValues ( words , function ( use , scope , word ) return # word end ) print ( peek ( lengths )) --> {6, 3, 7} The word lengths don't depend on the position of the word in the array. This means that rearranging the words in the input array will just rearrange the lengths in the output array: ForValues takes advantage of this - when input values move around, the output values will move around too, instead of being recalculated. Note that values are only reused once. For example, if you added another occurence of 'Orange', your calculation would have to run again for the second 'Orange':","title":"Usage"}]} \ No newline at end of file diff --git a/0.3/sitemap.xml.gz b/0.3/sitemap.xml.gz index dec6daf8a..5dbad126a 100644 Binary files a/0.3/sitemap.xml.gz and b/0.3/sitemap.xml.gz differ diff --git a/0.3/tutorials/best-practices/optimisation/index.html b/0.3/tutorials/best-practices/optimisation/index.html index a9f7d55ac..faff76c99 100644 --- a/0.3/tutorials/best-practices/optimisation/index.html +++ b/0.3/tutorials/best-practices/optimisation/index.html @@ -3031,7 +3031,7 @@

Tables