Conversation
Adds new methods to FusionUtil for binding and observing instance properties and attributes, including two-way binding support. Introduces mapValuesToStates for mapping dictionary values to state objects, with mutator support. Improves array and table utility functions with better error handling and documentation. Updates type annotations and internal structure for clarity and maintainability. Minor refactoring and docstring updates in init.luau for version management.
Updated FusionUtil to reference 'plainsour/lemon@0.4.0-dev.1.4' as Fusion_v0_3_0 in wally.toml and incremented the package version to 1.15.0. Adjusted documentation and removed outdated class order for FusionUtil in moonwave.toml. Minor docstring and formatting improvements in FusionUtil_v0_3_0 and init.luau.
Renamed various script files to the scripts/ directory for better organization and project structure.
Introduces debug part grouping in DrawUtil, allowing creation, clearing, and management of debug parts by group and id. Refactors DrawUtil API to support optional naming for vectors, points, and lines, and adds error handling for property assignment. InstanceUtil.emitParticles now supports delayed emission and cancellation, and minor doc and assertion fixes are made in MathUtil and TblUtil. Bumps wally.toml version to 1.16.0.
Bump package version to 1.16.1 and switch Fusion_v0_3_0 dependency from plainsour/lemon to elttob/fusion. Commented out the previous lemon dependency.
There was a problem hiding this comment.
Pull request overview
This PR bumps the version from 1.14.0 to 1.16.1 and includes significant enhancements to utility modules, particularly FusionUtil with new state binding functions, DebugUtil with breakpoint capabilities, DrawUtil with improved part management, and InstanceUtil with a refactored particle emission system. The PR also introduces development tooling scripts and updates dependencies.
Key Changes:
- Added comprehensive state observation and binding utilities to FusionUtil (outAttribute, outProperty, bindAttributeToState, bindPropertyToState, mapValuesToStates)
- Introduced breakpoint debugging functionality in DebugUtil with step/pause/resume controls
- Enhanced DrawUtil with group-based part management and a new
part()function - Refactored InstanceUtil's
emitParticlesto support delayed emissions with cancellation
Reviewed changes
Copilot reviewed 12 out of 17 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| src/RailUtil/wally.toml | Version bump to 1.16.1 and commented out older Fusion dependencies |
| src/RailUtil/TblUtil/init.luau | Fixed typo in documentation (@wihin → @Within) and improved BinarySearch documentation |
| src/RailUtil/MathUtil.luau | Removed type assertions from isBetween for performance |
| src/RailUtil/InstanceUtil/init.luau | Fixed indentation and refactored emitParticles to use QueryDescendants and support cancellation |
| src/RailUtil/FusionUtil/init.luau | Cleaned up code formatting and improved type annotations |
| src/RailUtil/FusionUtil/FusionUtil_v0_3_0.luau | Added extensive new utilities including state observers, property binders, mapValuesToStates, and table manipulation functions |
| src/RailUtil/FusionUtil/FusionUtil_v0_2_5.luau | Marked as deprecated and ignored |
| src/RailUtil/FusionUtil/FusionUtil_v0_2_0.luau | Marked as deprecated and ignored |
| src/RailUtil/DrawUtil.luau | Added part() function with group management, clearGroup, and clearPart utilities |
| src/RailUtil/DebugUtil.luau | Implemented breakpoint system with TestService attributes for pause/step/resume control |
| sourcemap.json | Updated with new package structure and file mappings |
| scripts/setup.sh | New shell script for environment setup and package management |
| scripts/runTests.story.luau | New test runner story file |
| scripts/runTests.sh | New shell script for initializing test environment |
| scripts/runTests.server.luau | New server-side test runner |
| scripts/publish.sh | New shell script for publishing to Wally |
| moonwave.toml | Removed FusionUtil class ordering section |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| local innerScope = innerScope(scope) | ||
|
|
||
| -- Create a Value state to hold this specific value | ||
| -- Cast to Value<any> to work around type system limitations | ||
| local valueState = Fusion.Value(innerScope, refValue) :: Value<any> | ||
|
|
||
| -- Store the scope for cleanup and value state for future updates | ||
| internalMap[key] = { | ||
| Scope = innerScope, | ||
| Value = valueState | ||
| } | ||
|
|
||
| -- Create the output state for this key | ||
| -- If a mutator is provided, wrap in a Computed to apply transformation | ||
| -- Otherwise, just use the raw value state | ||
| outputMapRaw[key] = Fusion.Computed(innerScope, function(use, cScope) |
There was a problem hiding this comment.
Variable shadowing issue: The local variable innerScope on line 1186 shadows the imported innerScope function from line 37. This creates ambiguity and makes the code harder to understand. Consider renaming the local variable to something like keyScope or itemScope to avoid confusion.
| local innerScope = innerScope(scope) | |
| -- Create a Value state to hold this specific value | |
| -- Cast to Value<any> to work around type system limitations | |
| local valueState = Fusion.Value(innerScope, refValue) :: Value<any> | |
| -- Store the scope for cleanup and value state for future updates | |
| internalMap[key] = { | |
| Scope = innerScope, | |
| Value = valueState | |
| } | |
| -- Create the output state for this key | |
| -- If a mutator is provided, wrap in a Computed to apply transformation | |
| -- Otherwise, just use the raw value state | |
| outputMapRaw[key] = Fusion.Computed(innerScope, function(use, cScope) | |
| local keyScope = innerScope(scope) | |
| -- Create a Value state to hold this specific value | |
| -- Cast to Value<any> to work around type system limitations | |
| local valueState = Fusion.Value(keyScope, refValue) :: Value<any> | |
| -- Store the scope for cleanup and value state for future updates | |
| internalMap[key] = { | |
| Scope = keyScope, | |
| Value = valueState | |
| } | |
| -- Create the output state for this key | |
| -- If a mutator is provided, wrap in a Computed to apply transformation | |
| -- Otherwise, just use the raw value state | |
| outputMapRaw[key] = Fusion.Computed(keyScope, function(use, cScope) |
|
|
||
| Calls the provided callback immediately with the initial state and then again anytime the state updates. | ||
| Just a simpler version of `scope:Observer(state):onBind(fn)`. | ||
| Kept for backwards compatability. |
There was a problem hiding this comment.
Spelling error in documentation: "compatability" should be "compatibility".
| Kept for backwards compatability. | |
| Kept for backwards compatibility. |
| local emitCount: number? = emitCount or emitter:GetAttribute("EmitCount") :: number? or emitter.Rate | ||
| if emitDelay and typeof(emitDelay) == "number" then | ||
| local delayedThread = task.delay(emitDelay, function() | ||
| emitter:Emit(emitCount) | ||
| end) | ||
| table.insert(delayedThreads, delayedThread) | ||
| else | ||
| emitter:Emit(emitCount) |
There was a problem hiding this comment.
Variable shadowing issue: The parameter emitCount is shadowed by a local variable with the same name on line 664. This makes the function parameter inaccessible within the Emit function. Consider renaming either the parameter or the local variable to avoid confusion and ensure proper scoping.
| local emitCount: number? = emitCount or emitter:GetAttribute("EmitCount") :: number? or emitter.Rate | |
| if emitDelay and typeof(emitDelay) == "number" then | |
| local delayedThread = task.delay(emitDelay, function() | |
| emitter:Emit(emitCount) | |
| end) | |
| table.insert(delayedThreads, delayedThread) | |
| else | |
| emitter:Emit(emitCount) | |
| local resolvedEmitCount: number? = emitCount or emitter:GetAttribute("EmitCount") :: number? or emitter.Rate | |
| if emitDelay and typeof(emitDelay) == "number" then | |
| local delayedThread = task.delay(emitDelay, function() | |
| emitter:Emit(resolvedEmitCount) | |
| end) | |
| table.insert(delayedThreads, delayedThread) | |
| else | |
| emitter:Emit(resolvedEmitCount) |
| currentValue:set(newValue) | ||
| return currentValue |
There was a problem hiding this comment.
Incorrect return value type in updateInsteadOfReplace mode: When updateInsteadOfReplace is true and the value is successfully updated, the function returns currentValue (line 1433 and 1477) which is the Value state object itself, not the previous value of type V. This is inconsistent with the documented return type V? and the behavior when updateInsteadOfReplace is false (which returns the actual previous value). The function should return the previous value before the update, not the Value state object.
| currentValue:set(newValue) | |
| return currentValue | |
| local previous = peek(currentValue) | |
| currentValue:set(newValue) | |
| return previous |
| currentValue:set(newValue) | ||
| return currentValue |
There was a problem hiding this comment.
Incorrect return value type in updateInsteadOfReplace mode: When updateInsteadOfReplace is true and the value is successfully updated, the function returns currentValue (the Value state object itself) instead of the previous value of type V. This is inconsistent with the documented return type V? and the behavior when updateInsteadOfReplace is false. The function should return the previous value before the update, not the Value state object.
| currentValue:set(newValue) | |
| return currentValue | |
| local previousValue = peek(currentValue) | |
| currentValue:set(newValue) | |
| return previousValue |
| # Fusion_v0_2_0 = "elttob/fusion@0.2.0" | ||
| # Fusion_v0_2_5 = "raild3x/fusion@0.2.5" | ||
| Fusion_v0_3_0 = "elttob/fusion@0.3.0" | ||
| # Fusion_v0_3_0 = "plainsour/lemon@0.4.0-dev.1.4" |
There was a problem hiding this comment.
Duplicate dependency key: Line 16 defines a commented-out dependency with the key Fusion_v0_3_0, but this key is already used on line 15 for an active dependency. While commented out, this creates confusion and could cause issues if uncommented accidentally. The comment should either use a different key name or be removed entirely.
| # Fusion_v0_3_0 = "plainsour/lemon@0.4.0-dev.1.4" | |
| # Lemon_v0_4_0_dev_1_4 = "plainsour/lemon@0.4.0-dev.1.4" |
|
|
||
| Observes a raw property of an instance and returns a state that is updated with the property's value. | ||
|
|
||
| :::caution Does not memoize the state |
There was a problem hiding this comment.
Documentation incomplete: The caution tag states "Does not memoize the state" but doesn't complete the thought. It should explain the implications or consequences of not memoizing, such as "Does not memoize the state, so calling this function multiple times will create separate Value states for the same property" or similar clarification.
| function InstanceUtil.getAttachmentsAlignedCFrame(partAttachment: Attachment, targetAttachment: Attachment): CFrame | ||
| assert(partAttachment:IsA("Attachment") or not targetAttachment:IsA("Attachment"), "Both inputs must be attachments") | ||
| return targetAttachment.WorldCFrame * partAttachment.CFrame:Inverse() | ||
| assert(partAttachment:IsA("Attachment") or not targetAttachment:IsA("Attachment"), "Both inputs must be attachments") |
There was a problem hiding this comment.
Logic error in assertion: The condition uses or when it should use and. Currently, it checks if partAttachment is an Attachment OR targetAttachment is NOT an Attachment, which is incorrect. The assertion should verify that BOTH inputs are Attachments. Change or to and to correctly validate both parameters.
| assert(partAttachment:IsA("Attachment") or not targetAttachment:IsA("Attachment"), "Both inputs must be attachments") | |
| assert(partAttachment:IsA("Attachment") and targetAttachment:IsA("Attachment"), "Both inputs must be attachments") |
| local id = props.Id or HttpService:GenerateGUID(false) | ||
| props.Id = nil | ||
|
|
||
| local group = upsertGroup(props.GroupName or "Default") |
There was a problem hiding this comment.
Property mismatch: The function parameter is documented as 'Group' (line 78), but the code checks for 'GroupName' (line 88). This inconsistency means users passing 'Group' won't have their property read correctly. Either update the documentation to match the implementation or change the code to use 'Group' instead of 'GroupName'.
| end | ||
|
|
||
| --[=[ | ||
| Clears all debug parts a group. |
There was a problem hiding this comment.
Incomplete documentation: The documentation mentions "Clears all debug parts a group" but is missing the word "from" or "in". It should read "Clears all debug parts from a group" or "Clears all debug parts in a group".
| Clears all debug parts a group. | |
| Clears all debug parts from a group. |
No description provided.