From e603de10d1e291c9f61b99d8df59c04eba100b24 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Sun, 28 Jan 2024 09:11:25 +0000 Subject: [PATCH 1/8] Compile Fusion library from corescripts/Libraries directory --- Libraries/Fusion/Animation/Spring.luau | 222 ++ .../Fusion/Animation/SpringScheduler.luau | 98 + Libraries/Fusion/Animation/Tween.luau | 126 + .../Fusion/Animation/TweenScheduler.luau | 71 + Libraries/Fusion/Animation/getTweenRatio.luau | 46 + Libraries/Fusion/Animation/lerpType.luau | 134 + Libraries/Fusion/Animation/packType.luau | 86 + .../Fusion/Animation/springCoefficients.luau | 86 + Libraries/Fusion/Animation/unpackType.luau | 92 + Libraries/Fusion/Colour/Oklab.luau | 56 + Libraries/Fusion/External.luau | 97 + Libraries/Fusion/Instances/Children.luau | 157 + Libraries/Fusion/Instances/Cleanup.luau | 23 + Libraries/Fusion/Instances/Hydrate.luau | 18 + Libraries/Fusion/Instances/New.luau | 34 + Libraries/Fusion/Instances/OnChange.luau | 53 + Libraries/Fusion/Instances/OnEvent.luau | 40 + Libraries/Fusion/Instances/Out.luau | 52 + Libraries/Fusion/Instances/Ref.luau | 32 + .../Fusion/Instances/applyInstanceProps.luau | 160 + Libraries/Fusion/Instances/defaultProps.luau | 117 + Libraries/Fusion/Logging/logError.luau | 43 + .../Fusion/Logging/logErrorNonFatal.luau | 43 + Libraries/Fusion/Logging/logWarn.luau | 22 + Libraries/Fusion/Logging/messages.luau | 55 + Libraries/Fusion/Logging/parseError.luau | 24 + Libraries/Fusion/MercuryExternal.luau | 75 + Libraries/Fusion/Polyfill/TweenInfo.luau | 112 + Libraries/Fusion/Polyfill/easing.luau | 347 ++ Libraries/Fusion/Polyfill/typeof.luau | 181 + Libraries/Fusion/PubTypes.luau | 162 + Libraries/Fusion/State/Computed.luau | 125 + Libraries/Fusion/State/ForKeys.luau | 274 ++ Libraries/Fusion/State/ForPairs.luau | 340 ++ Libraries/Fusion/State/ForValues.luau | 272 ++ Libraries/Fusion/State/Observer.luau | 90 + Libraries/Fusion/State/Value.luau | 60 + Libraries/Fusion/State/isState.luau | 11 + Libraries/Fusion/State/makeUseCallback.luau | 25 + Libraries/Fusion/State/peek.luau | 19 + Libraries/Fusion/State/updateAll.luau | 67 + Libraries/Fusion/Types.luau | 159 + Libraries/Fusion/Utility/Contextual.luau | 74 + Libraries/Fusion/Utility/None.luau | 12 + Libraries/Fusion/Utility/cleanup.luau | 54 + Libraries/Fusion/Utility/doNothing.luau | 9 + Libraries/Fusion/Utility/isSimilar.luau | 17 + .../Fusion/Utility/needsDestruction.luau | 14 + Libraries/Fusion/Utility/restrictRead.luau | 27 + Libraries/Fusion/Utility/xtypeof.luau | 21 + Libraries/Fusion/init.luau | 115 + README.md | 2 +- compile.sh | 3 + luau/10000001.luau | 3417 ----------------- luau/46295863.luau | 36 +- luau/host.luau | 141 +- luau/join.luau | 4 +- 57 files changed, 4678 insertions(+), 3574 deletions(-) create mode 100644 Libraries/Fusion/Animation/Spring.luau create mode 100644 Libraries/Fusion/Animation/SpringScheduler.luau create mode 100644 Libraries/Fusion/Animation/Tween.luau create mode 100644 Libraries/Fusion/Animation/TweenScheduler.luau create mode 100644 Libraries/Fusion/Animation/getTweenRatio.luau create mode 100644 Libraries/Fusion/Animation/lerpType.luau create mode 100644 Libraries/Fusion/Animation/packType.luau create mode 100644 Libraries/Fusion/Animation/springCoefficients.luau create mode 100644 Libraries/Fusion/Animation/unpackType.luau create mode 100644 Libraries/Fusion/Colour/Oklab.luau create mode 100644 Libraries/Fusion/External.luau create mode 100644 Libraries/Fusion/Instances/Children.luau create mode 100644 Libraries/Fusion/Instances/Cleanup.luau create mode 100644 Libraries/Fusion/Instances/Hydrate.luau create mode 100644 Libraries/Fusion/Instances/New.luau create mode 100644 Libraries/Fusion/Instances/OnChange.luau create mode 100644 Libraries/Fusion/Instances/OnEvent.luau create mode 100644 Libraries/Fusion/Instances/Out.luau create mode 100644 Libraries/Fusion/Instances/Ref.luau create mode 100644 Libraries/Fusion/Instances/applyInstanceProps.luau create mode 100644 Libraries/Fusion/Instances/defaultProps.luau create mode 100644 Libraries/Fusion/Logging/logError.luau create mode 100644 Libraries/Fusion/Logging/logErrorNonFatal.luau create mode 100644 Libraries/Fusion/Logging/logWarn.luau create mode 100644 Libraries/Fusion/Logging/messages.luau create mode 100644 Libraries/Fusion/Logging/parseError.luau create mode 100644 Libraries/Fusion/MercuryExternal.luau create mode 100644 Libraries/Fusion/Polyfill/TweenInfo.luau create mode 100644 Libraries/Fusion/Polyfill/easing.luau create mode 100644 Libraries/Fusion/Polyfill/typeof.luau create mode 100644 Libraries/Fusion/PubTypes.luau create mode 100644 Libraries/Fusion/State/Computed.luau create mode 100644 Libraries/Fusion/State/ForKeys.luau create mode 100644 Libraries/Fusion/State/ForPairs.luau create mode 100644 Libraries/Fusion/State/ForValues.luau create mode 100644 Libraries/Fusion/State/Observer.luau create mode 100644 Libraries/Fusion/State/Value.luau create mode 100644 Libraries/Fusion/State/isState.luau create mode 100644 Libraries/Fusion/State/makeUseCallback.luau create mode 100644 Libraries/Fusion/State/peek.luau create mode 100644 Libraries/Fusion/State/updateAll.luau create mode 100644 Libraries/Fusion/Types.luau create mode 100644 Libraries/Fusion/Utility/Contextual.luau create mode 100644 Libraries/Fusion/Utility/None.luau create mode 100644 Libraries/Fusion/Utility/cleanup.luau create mode 100644 Libraries/Fusion/Utility/doNothing.luau create mode 100644 Libraries/Fusion/Utility/isSimilar.luau create mode 100644 Libraries/Fusion/Utility/needsDestruction.luau create mode 100644 Libraries/Fusion/Utility/restrictRead.luau create mode 100644 Libraries/Fusion/Utility/xtypeof.luau create mode 100644 Libraries/Fusion/init.luau delete mode 100644 luau/10000001.luau diff --git a/Libraries/Fusion/Animation/Spring.luau b/Libraries/Fusion/Animation/Spring.luau new file mode 100644 index 0000000..5b0744f --- /dev/null +++ b/Libraries/Fusion/Animation/Spring.luau @@ -0,0 +1,222 @@ +--!nonstrict + +--[[ + Constructs a new computed state object, which follows the value of another + state object using a spring simulation. +]] + +local PubTypes = require "../PubTypes" +local Types = require "../Types" +local logError = require "../Logging/logError" +local logErrorNonFatal = require "../Logging/logErrorNonFatal" +local unpackType = require "../Animation/unpackType" +local SpringScheduler = require "../Animation/SpringScheduler" +local updateAll = require "../State/updateAll" +local xtypeof = require "../Utility/xtypeof" +local peek = require "../State/peek" +local typeof = require "../Polyfill/typeof" + +local class = {} + +local CLASS_METATABLE = { __index = class } +local WEAK_KEYS_METATABLE = { __mode = "k" } + +--[[ + Sets the position of the internal springs, meaning the value of this + Spring will jump to the given value. This doesn't affect velocity. + + If the type doesn't match the current type of the spring, an error will be + thrown. +]] +function class:setPosition(newValue: PubTypes.Animatable) + local newType = typeof(newValue) + if newType ~= self._currentType then + logError("springTypeMismatch", nil, newType, self._currentType) + end + + self._springPositions = unpackType(newValue, newType) + self._currentValue = newValue + SpringScheduler.add(self) + updateAll(self) +end + +--[[ + Sets the velocity of the internal springs, overwriting the existing velocity + of this Spring. This doesn't affect position. + + If the type doesn't match the current type of the spring, an error will be + thrown. +]] +function class:setVelocity(newValue: PubTypes.Animatable) + local newType = typeof(newValue) + if newType ~= self._currentType then + logError("springTypeMismatch", nil, newType, self._currentType) + end + + self._springVelocities = unpackType(newValue, newType) + SpringScheduler.add(self) +end + +--[[ + Adds to the velocity of the internal springs, on top of the existing + velocity of this Spring. This doesn't affect position. + + If the type doesn't match the current type of the spring, an error will be + thrown. +]] +function class:addVelocity(deltaValue: PubTypes.Animatable) + local deltaType = typeof(deltaValue) + if deltaType ~= self._currentType then + logError("springTypeMismatch", nil, deltaType, self._currentType) + end + + local springDeltas = unpackType(deltaValue, deltaType) + for index, delta in ipairs(springDeltas) do + self._springVelocities[index] += delta + end + SpringScheduler.add(self) +end + +--[[ + Called when the goal state changes value, or when the speed or damping has + changed. +]] +function class:update(): boolean + local goalValue = peek(self._goalState) + + -- figure out if this was a goal change or a speed/damping change + if goalValue == self._goalValue then + -- speed/damping change + local damping = peek(self._damping) + if typeof(damping) ~= "number" then + logErrorNonFatal("mistypedSpringDamping", nil, typeof(damping)) + elseif damping < 0 then + logErrorNonFatal("invalidSpringDamping", nil, damping) + else + self._currentDamping = damping + end + + local speed = peek(self._speed) + if typeof(speed) ~= "number" then + logErrorNonFatal("mistypedSpringSpeed", nil, typeof(speed)) + elseif speed < 0 then + logErrorNonFatal("invalidSpringSpeed", nil, speed) + else + self._currentSpeed = speed + end + + return false + else + -- goal change - reconfigure spring to target new goal + self._goalValue = goalValue + + local oldType = self._currentType + local newType = typeof(goalValue) + self._currentType = newType + + local springGoals = unpackType(goalValue, newType) + local numSprings = #springGoals + self._springGoals = springGoals + + if newType ~= oldType then + -- if the type changed, snap to the new value and rebuild the + -- position and velocity tables + self._currentValue = self._goalValue + + -- local springPositions = (numSprings, 0) + local springPositions = {} + for i = 1, numSprings do + springPositions[i] = 0 + end + -- local springVelocities = (numSprings, 0) + local springVelocities = {} + for i = 1, numSprings do + springVelocities[i] = 0 + end + + for index, springGoal in ipairs(springGoals) do + springPositions[index] = springGoal + end + self._springPositions = springPositions + self._springVelocities = springVelocities + + -- the spring may have been animating before, so stop that + SpringScheduler.remove(self) + return true + + -- otherwise, the type hasn't changed, just the goal... + elseif numSprings == 0 then + -- if the type isn't animatable, snap to the new value + self._currentValue = self._goalValue + return true + else + -- if it's animatable, let it animate to the goal + SpringScheduler.add(self) + return false + end + end +end + +--[[ + Returns the interior value of this state object. +]] +function class:_peek(): any + return self._currentValue +end + +function class:get() + logError "stateGetWasRemoved" +end + +local function Spring( + goalState: PubTypes.Value, + speed: PubTypes.CanBeState?, + damping: PubTypes.CanBeState? +): Types.Spring + -- apply defaults for speed and damping + if speed == nil then + speed = 10 + end + if damping == nil then + damping = 1 + end + + local dependencySet = { [goalState] = true } + if xtypeof(speed) == "State" then + dependencySet[speed] = true + end + if xtypeof(damping) == "State" then + dependencySet[damping] = true + end + + local self = setmetatable({ + type = "State", + kind = "Spring", + dependencySet = dependencySet, + -- if we held strong references to the dependents, then they wouldn't be + -- able to get garbage collected when they fall out of scope + dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), + _speed = speed, + _damping = damping, + + _goalState = goalState, + _goalValue = nil, + + _currentType = nil, + _currentValue = nil, + _currentSpeed = peek(speed), + _currentDamping = peek(damping), + + _springPositions = nil, + _springGoals = nil, + _springVelocities = nil, + }, CLASS_METATABLE) + + -- add this object to the goal state's dependent set + goalState.dependentSet[self] = true + self:update() + + return self +end + +return Spring diff --git a/Libraries/Fusion/Animation/SpringScheduler.luau b/Libraries/Fusion/Animation/SpringScheduler.luau new file mode 100644 index 0000000..86996ae --- /dev/null +++ b/Libraries/Fusion/Animation/SpringScheduler.luau @@ -0,0 +1,98 @@ +--!strict + +--[[ + Manages batch updating of spring objects. +]] + +local Types = require "../Types" +local External = require "../External" +local packType = require "../Animation/packType" +local springCoefficients = require "../Animation/springCoefficients" +local updateAll = require "../State/updateAll" + +type Set = { [T]: any } +type Spring = Types.Spring + +local SpringScheduler = {} + +local EPSILON = 0.0001 +local activeSprings: Set = {} +local lastUpdateTime = External.lastUpdateStep() + +function SpringScheduler.add(spring: Spring) + -- we don't necessarily want to use the most accurate time - here we snap to + -- the last update time so that springs started within the same frame have + -- identical time steps + spring._lastSchedule = lastUpdateTime + spring._startDisplacements = {} + spring._startVelocities = {} + for index, goal in ipairs(spring._springGoals) do + spring._startDisplacements[index] = spring._springPositions[index] + - goal + spring._startVelocities[index] = spring._springVelocities[index] + end + + activeSprings[spring] = true +end + +function SpringScheduler.remove(spring: Spring) + activeSprings[spring] = nil +end + +local function updateAllSprings(now: number) + local springsToSleep: Set = {} + lastUpdateTime = now + + for spring in pairs(activeSprings) do + local posPos, posVel, velPos, velVel = springCoefficients( + lastUpdateTime - spring._lastSchedule, + spring._currentDamping, + spring._currentSpeed + ) + + local positions = spring._springPositions + local velocities = spring._springVelocities + local startDisplacements = spring._startDisplacements + local startVelocities = spring._startVelocities + local isMoving = false + + for index, goal in ipairs(spring._springGoals) do + local oldDisplacement = startDisplacements[index] + local oldVelocity = startVelocities[index] + local newDisplacement = oldDisplacement * posPos + + oldVelocity * posVel + local newVelocity = oldDisplacement * velPos + oldVelocity * velVel + + if + math.abs(newDisplacement) > EPSILON + or math.abs(newVelocity) > EPSILON + then + isMoving = true + end + + positions[index] = newDisplacement + goal + velocities[index] = newVelocity + end + + if not isMoving then + springsToSleep[spring] = true + end + end + + for spring in pairs(activeSprings) do + spring._currentValue = + packType(spring._springPositions, spring._currentType) + updateAll(spring) + end + + for spring in pairs(springsToSleep) do + activeSprings[spring] = nil + -- Guarantee that springs reach exact goals, since mathematically they only approach it infinitely + spring._currentValue = + packType(spring._springGoals, spring._currentType) + end +end + +External.bindToUpdateStep(updateAllSprings) + +return SpringScheduler diff --git a/Libraries/Fusion/Animation/Tween.luau b/Libraries/Fusion/Animation/Tween.luau new file mode 100644 index 0000000..5bd62a8 --- /dev/null +++ b/Libraries/Fusion/Animation/Tween.luau @@ -0,0 +1,126 @@ +--!nonstrict + +--[[ + Constructs a new computed state object, which follows the value of another + state object using a tween. +]] + +local PubTypes = require "../PubTypes" +local External = require "../External" +local Types = require "../Types" +local TweenScheduler = require "../Animation/TweenScheduler" +local logError = require "../Logging/logError" +local logErrorNonFatal = require "../Logging/logErrorNonFatal" +local xtypeof = require "../Utility/xtypeof" +local peek = require "../State/peek" +local typeof = require "../Polyfill/typeof" + +local class = {} + +local CLASS_METATABLE = { __index = class } +local WEAK_KEYS_METATABLE = { __mode = "k" } + +--[[ + Called when the goal state changes value; this will initiate a new tween. + Returns false as the current value doesn't change right away. +]] +function class:update(): boolean + local goalValue = peek(self._goalState) + + -- if the goal hasn't changed, then this is a TweenInfo change. + -- in that case, if we're not currently animating, we can skip everything + if goalValue == self._nextValue and not self._currentlyAnimating then + return false + end + + local tweenInfo = peek(self._tweenInfo) + + -- if we receive a bad TweenInfo, then error and stop the update + if typeof(tweenInfo) ~= "TweenInfo" then + logErrorNonFatal("mistypedTweenInfo", nil, typeof(tweenInfo)) + return false + end + + self._prevValue = self._currentValue + self._nextValue = goalValue + + self._currentTweenStartTime = External.lastUpdateStep() + self._currentTweenInfo = tweenInfo + + local tweenDuration = tweenInfo.DelayTime + tweenInfo.Time + if tweenInfo.Reverses then + tweenDuration += tweenInfo.Time + end + tweenDuration *= tweenInfo.RepeatCount + 1 + self._currentTweenDuration = tweenDuration + + -- start animating this tween + TweenScheduler.add(self) + + return false +end + +--[[ + Returns the interior value of this state object. +]] +function class:_peek(): any + return self._currentValue +end + +function class:get() + logError "stateGetWasRemoved" +end + +local function Tween( + goalState: PubTypes.StateObject, + tweenInfo: PubTypes.CanBeState? +): Types.Tween + local currentValue = peek(goalState) + + -- apply defaults for tween info + if tweenInfo == nil then + tweenInfo = TweenInfo.new() + end + + local dependencySet = { [goalState] = true } + local tweenInfoIsState = xtypeof(tweenInfo) == "State" + if tweenInfoIsState then + dependencySet[tweenInfo] = true + end + + local startingTweenInfo = peek(tweenInfo) + -- If we start with a bad TweenInfo, then we don't want to construct a Tween + if typeof(startingTweenInfo) ~= "TweenInfo" then + logError("mistypedTweenInfo", nil, typeof(startingTweenInfo)) + end + + local self = setmetatable({ + type = "State", + kind = "Tween", + dependencySet = dependencySet, + -- if we held strong references to the dependents, then they wouldn't be + -- able to get garbage collected when they fall out of scope + dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), + _goalState = goalState, + _tweenInfo = tweenInfo, + _tweenInfoIsState = tweenInfoIsState, + + _prevValue = currentValue, + _nextValue = currentValue, + _currentValue = currentValue, + + -- store current tween into separately from 'real' tween into, so it + -- isn't affected by :setTweenInfo() until next change + _currentTweenInfo = tweenInfo, + _currentTweenDuration = 0, + _currentTweenStartTime = 0, + _currentlyAnimating = false, + }, CLASS_METATABLE) + + -- add this object to the goal state's dependent set + goalState.dependentSet[self] = true + + return self +end + +return Tween diff --git a/Libraries/Fusion/Animation/TweenScheduler.luau b/Libraries/Fusion/Animation/TweenScheduler.luau new file mode 100644 index 0000000..c822178 --- /dev/null +++ b/Libraries/Fusion/Animation/TweenScheduler.luau @@ -0,0 +1,71 @@ +--!strict + +--[[ + Manages batch updating of tween objects. +]] + +local Types = require "../Types" +local External = require "../External" +local lerpType = require "../Animation/lerpType" +local getTweenRatio = require "../Animation/getTweenRatio" +local updateAll = require "../State/updateAll" + +local TweenScheduler = {} + +type Set = { [T]: any } +type Tween = Types.Tween + +local WEAK_KEYS_METATABLE = { __mode = "k" } + +-- all the tweens currently being updated +local allTweens: Set = {} +setmetatable(allTweens, WEAK_KEYS_METATABLE) + +--[[ + Adds a Tween to be updated every render step. +]] +function TweenScheduler.add(tween: Tween) + allTweens[tween] = true +end + +--[[ + Removes a Tween from the scheduler. +]] +function TweenScheduler.remove(tween: Tween) + allTweens[tween] = nil +end + +--[[ + Updates all Tween objects. +]] +local function updateAllTweens(now: number) + -- FIXME: Typed Luau doesn't understand this loop yet + for tween: Tween in pairs(allTweens :: any) do + local currentTime = now - tween._currentTweenStartTime + + if + currentTime > tween._currentTweenDuration + and tween._currentTweenInfo.RepeatCount > -1 + then + if tween._currentTweenInfo.Reverses then + tween._currentValue = tween._prevValue + else + tween._currentValue = tween._nextValue + end + tween._currentlyAnimating = false + updateAll(tween) + TweenScheduler.remove(tween) + else + local ratio = getTweenRatio(tween._currentTweenInfo, currentTime) + local currentValue = + lerpType(tween._prevValue, tween._nextValue, ratio) + tween._currentValue = currentValue + tween._currentlyAnimating = true + updateAll(tween) + end + end +end + +External.bindToUpdateStep(updateAllTweens) + +return TweenScheduler diff --git a/Libraries/Fusion/Animation/getTweenRatio.luau b/Libraries/Fusion/Animation/getTweenRatio.luau new file mode 100644 index 0000000..8ec5aac --- /dev/null +++ b/Libraries/Fusion/Animation/getTweenRatio.luau @@ -0,0 +1,46 @@ +--!strict + +--[[ + Given a `tweenInfo` and `currentTime`, returns a ratio which can be used to + tween between two values over time. +]] + +-- local TweenService = game:GetService "TweenService" +local easing = require "../Polyfill/easing" + +local function getTweenRatio(tweenInfo: TweenInfo, currentTime: number): number + local delay = tweenInfo.DelayTime + local duration = tweenInfo.Time + local reverses = tweenInfo.Reverses + local numCycles = 1 + tweenInfo.RepeatCount + local easeStyle = tweenInfo.EasingStyle + local easeDirection = tweenInfo.EasingDirection + + local cycleDuration = delay + duration + if reverses then + cycleDuration += duration + end + + if + currentTime >= cycleDuration * numCycles + and tweenInfo.RepeatCount > -1 + then + return 1 + end + + local cycleTime = currentTime % cycleDuration + + if cycleTime <= delay then + return 0 + end + + local tweenProgress = (cycleTime - delay) / duration + if tweenProgress > 1 then + tweenProgress = 2 - tweenProgress + end + + -- return TweenService:GetValue(tweenProgress, easeStyle, easeDirection) + return easing[easeStyle][easeDirection](tweenProgress, 0, 1) +end + +return getTweenRatio diff --git a/Libraries/Fusion/Animation/lerpType.luau b/Libraries/Fusion/Animation/lerpType.luau new file mode 100644 index 0000000..5067588 --- /dev/null +++ b/Libraries/Fusion/Animation/lerpType.luau @@ -0,0 +1,134 @@ +--!strict + +--[[ + Linearly interpolates the given animatable types by a ratio. + If the types are different or not animatable, then the first value will be + returned for ratios below 0.5, and the second value for 0.5 and above. + + FIXME: This function uses a lot of redefinitions to suppress false positives + from the Luau typechecker - ideally these wouldn't be required +]] + +local Oklab = require "../Colour/Oklab" +local typeof = require "../Polyfill/typeof" + +local function lerpType(from: any, to: any, ratio: number): any + local typeString = typeof(from) + + if typeof(to) == typeString then + -- both types must match for interpolation to make sense + if typeString == "number" then + return (to - from) * ratio + from + elseif typeString == "CFrame" then + return from:Lerp(to, ratio) + elseif typeString == "Color3" then + local fromLab = Oklab.to(from) + local toLab = Oklab.to(to) + return Oklab.from(fromLab:Lerp(toLab, ratio), false) + -- elseif typeString == "ColorSequenceKeypoint" then + -- local to, from = + -- to :: ColorSequenceKeypoint, from :: ColorSequenceKeypoint + -- local fromLab = Oklab.to(from.Value) + -- local toLab = Oklab.to(to.Value) + -- return ColorSequenceKeypoint.new( + -- (to.Time - from.Time) * ratio + from.Time, + -- Oklab.from(fromLab:Lerp(toLab, ratio), false) + -- ) + -- elseif typeString == "DateTime" then + -- local to, from = to :: DateTime, from :: DateTime + -- return DateTime.fromUnixTimestampMillis( + -- (to.UnixTimestampMillis - from.UnixTimestampMillis) * ratio + -- + from.UnixTimestampMillis + -- ) + -- elseif typeString == "NumberRange" then + -- local to, from = to :: NumberRange, from :: NumberRange + -- return NumberRange.new( + -- (to.Min - from.Min) * ratio + from.Min, + -- (to.Max - from.Max) * ratio + from.Max + -- ) + -- elseif typeString == "NumberSequenceKeypoint" then + -- local to, from = + -- to :: NumberSequenceKeypoint, from :: NumberSequenceKeypoint + -- return NumberSequenceKeypoint.new( + -- (to.Time - from.Time) * ratio + from.Time, + -- (to.Value - from.Value) * ratio + from.Value, + -- (to.Envelope - from.Envelope) * ratio + from.Envelope + -- ) + -- elseif typeString == "PhysicalProperties" then + -- local to, from = + -- to :: PhysicalProperties, from :: PhysicalProperties + -- return PhysicalProperties.new( + -- (to.Density - from.Density) * ratio + from.Density, + -- (to.Friction - from.Friction) * ratio + from.Friction, + -- (to.Elasticity - from.Elasticity) * ratio + from.Elasticity, + -- (to.FrictionWeight - from.FrictionWeight) * ratio + -- + from.FrictionWeight, + -- (to.ElasticityWeight - from.ElasticityWeight) * ratio + -- + from.ElasticityWeight + -- ) + elseif typeString == "Ray" then + return Ray.new( + from.Origin:Lerp(to.Origin, ratio), + from.Direction:Lerp(to.Direction, ratio) + ) + -- elseif typeString == "Rect" then + -- local to, from = to :: Rect, from :: Rect + -- return Rect.new( + -- from.Min:Lerp(to.Min, ratio), + -- from.Max:Lerp(to.Max, ratio) + -- ) + elseif typeString == "Region3" then + -- FUTURE: support rotated Region3s if/when they become constructable + local position = + from.CFrame.Position:Lerp(to.CFrame.Position, ratio) + local halfSize = from.Size:Lerp(to.Size, ratio) / 2 + return Region3.new(position - halfSize, position + halfSize) + elseif typeString == "Region3int16" then + return Region3int16.new( + Vector3int16.new( + (to.Min.X - from.Min.X) * ratio + from.Min.X, + (to.Min.Y - from.Min.Y) * ratio + from.Min.Y, + (to.Min.Z - from.Min.Z) * ratio + from.Min.Z + ), + Vector3int16.new( + (to.Max.X - from.Max.X) * ratio + from.Max.X, + (to.Max.Y - from.Max.Y) * ratio + from.Max.Y, + (to.Max.Z - from.Max.Z) * ratio + from.Max.Z + ) + ) + elseif typeString == "UDim" then + return UDim.new( + (to.Scale - from.Scale) * ratio + from.Scale, + (to.Offset - from.Offset) * ratio + from.Offset + ) + elseif typeString == "UDim2" then + return UDim2.new( + (to.X.Scale - from.X.Scale) * ratio + from.X.Scale, + (to.X.Offset - from.X.Offset) * ratio + from.X.Offset, + (to.Y.Scale - from.Y.Scale) * ratio + from.Y.Scale, + (to.Y.Offset - from.Y.Offset) * ratio + from.Y.Offset + ) + elseif typeString == "Vector2" or typeString == "Vector3" then + return from:Lerp(to, ratio) + elseif typeString == "Vector2int16" then + return Vector2int16.new( + (to.X - from.X) * ratio + from.X, + (to.Y - from.Y) * ratio + from.Y + ) + elseif typeString == "Vector3int16" then + return Vector3int16.new( + (to.X - from.X) * ratio + from.X, + (to.Y - from.Y) * ratio + from.Y, + (to.Z - from.Z) * ratio + from.Z + ) + end + end + + -- fallback case: the types are different or not animatable + if ratio < 0.5 then + return from + end + return to +end + +return lerpType diff --git a/Libraries/Fusion/Animation/packType.luau b/Libraries/Fusion/Animation/packType.luau new file mode 100644 index 0000000..a48e2c3 --- /dev/null +++ b/Libraries/Fusion/Animation/packType.luau @@ -0,0 +1,86 @@ +--!strict + +--[[ + Packs an array of numbers into a given animatable data type. + If the type is not animatable, nil will be returned. + + FUTURE: When Luau supports singleton types, those could be used in + conjunction with intersection types to make this function fully statically + type checkable. +]] + +local PubTypes = require "../PubTypes" +local Oklab = require "../Colour/Oklab" + +local function packType( + numbers: { number }, + typeString: string +): PubTypes.Animatable? + if typeString == "number" then + return numbers[1] + elseif typeString == "CFrame" then + return CFrame.new(numbers[1], numbers[2], numbers[3]) + * CFrame.fromAxisAngle( + Vector3.new(numbers[4], numbers[5], numbers[6]).Unit, + numbers[7] + ) + elseif typeString == "Color3" then + return Oklab.from( + Vector3.new(numbers[1], numbers[2], numbers[3]), + false + ) + elseif typeString == "ColorSequenceKeypoint" then + return ColorSequenceKeypoint.new( + numbers[4], + Oklab.from(Vector3.new(numbers[1], numbers[2], numbers[3]), false) + ) + elseif typeString == "DateTime" then + return DateTime.fromUnixTimestampMillis(numbers[1]) + elseif typeString == "NumberRange" then + return NumberRange.new(numbers[1], numbers[2]) + elseif typeString == "NumberSequenceKeypoint" then + return NumberSequenceKeypoint.new(numbers[2], numbers[1], numbers[3]) + elseif typeString == "PhysicalProperties" then + return PhysicalProperties.new( + numbers[1], + numbers[2], + numbers[3], + numbers[4], + numbers[5] + ) + elseif typeString == "Ray" then + return Ray.new( + Vector3.new(numbers[1], numbers[2], numbers[3]), + Vector3.new(numbers[4], numbers[5], numbers[6]) + ) + elseif typeString == "Rect" then + return Rect.new(numbers[1], numbers[2], numbers[3], numbers[4]) + elseif typeString == "Region3" then + -- FUTURE: support rotated Region3s if/when they become constructable + local position = Vector3.new(numbers[1], numbers[2], numbers[3]) + local halfSize = + Vector3.new(numbers[4] / 2, numbers[5] / 2, numbers[6] / 2) + return Region3.new(position - halfSize, position + halfSize) + elseif typeString == "Region3int16" then + return Region3int16.new( + Vector3int16.new(numbers[1], numbers[2], numbers[3]), + Vector3int16.new(numbers[4], numbers[5], numbers[6]) + ) + elseif typeString == "UDim" then + return UDim.new(numbers[1], numbers[2]) + elseif typeString == "UDim2" then + return UDim2.new(numbers[1], numbers[2], numbers[3], numbers[4]) + elseif typeString == "Vector2" then + return Vector2.new(numbers[1], numbers[2]) + elseif typeString == "Vector2int16" then + return Vector2int16.new(numbers[1], numbers[2]) + elseif typeString == "Vector3" then + return Vector3.new(numbers[1], numbers[2], numbers[3]) + elseif typeString == "Vector3int16" then + return Vector3int16.new(numbers[1], numbers[2], numbers[3]) + else + return nil + end +end + +return packType diff --git a/Libraries/Fusion/Animation/springCoefficients.luau b/Libraries/Fusion/Animation/springCoefficients.luau new file mode 100644 index 0000000..e49b60c --- /dev/null +++ b/Libraries/Fusion/Animation/springCoefficients.luau @@ -0,0 +1,86 @@ +--!strict + +--[[ + Returns a 2x2 matrix of coefficients for a given time, damping and speed. + Specifically, this returns four coefficients - posPos, posVel, velPos, and + velVel - which can be multiplied with position and velocity like so: + + local newPosition = oldPosition * posPos + oldVelocity * posVel + local newVelocity = oldPosition * velPos + oldVelocity * velVel + + Special thanks to AxisAngle for helping to improve numerical precision. +]] + +local function springCoefficients( + time: number, + damping: number, + speed: number +): (number, number, number, number) + -- if time or speed is 0, then the spring won't move + if time == 0 or speed == 0 then + return 1, 0, 0, 1 + end + local posPos, posVel, velPos, velVel + + if damping > 1 then + -- overdamped spring + -- solution to the characteristic equation: + -- z = -ζω ± Sqrt[ζ^2 - 1] ω + -- x[t] -> x0(e^(t z2) z1 - e^(t z1) z2)/(z1 - z2) + -- + v0(e^(t z1) - e^(t z2))/(z1 - z2) + -- v[t] -> x0(z1 z2(-e^(t z1) + e^(t z2)))/(z1 - z2) + -- + v0(z1 e^(t z1) - z2 e^(t z2))/(z1 - z2) + + local scaledTime = time * speed + local alpha = math.sqrt(damping ^ 2 - 1) + local scaledInvAlpha = -0.5 / alpha + local z1 = -alpha - damping + local z2 = 1 / z1 + local expZ1 = math.exp(scaledTime * z1) + local expZ2 = math.exp(scaledTime * z2) + + posPos = (expZ2 * z1 - expZ1 * z2) * scaledInvAlpha + posVel = (expZ1 - expZ2) * scaledInvAlpha / speed + velPos = (expZ2 - expZ1) * scaledInvAlpha * speed + velVel = (expZ1 * z1 - expZ2 * z2) * scaledInvAlpha + elseif damping == 1 then + -- critically damped spring + -- x[t] -> x0(e^-tω)(1+tω) + v0(e^-tω)t + -- v[t] -> x0(t ω^2)(-e^-tω) + v0(1 - tω)(e^-tω) + + local scaledTime = time * speed + local expTerm = math.exp(-scaledTime) + + posPos = expTerm * (1 + scaledTime) + posVel = expTerm * time + velPos = expTerm * (-scaledTime * speed) + velVel = expTerm * (1 - scaledTime) + else + -- underdamped spring + -- factored out of the solutions to the characteristic equation: + -- α = Sqrt[1 - ζ^2] + -- x[t] -> x0(e^-tζω)(α Cos[tα] + ζω Sin[tα])/α + -- + v0(e^-tζω)(Sin[tα])/α + -- v[t] -> x0(-e^-tζω)(α^2 + ζ^2 ω^2)(Sin[tα])/α + -- + v0(e^-tζω)(α Cos[tα] - ζω Sin[tα])/α + + local scaledTime = time * speed + local alpha = math.sqrt(1 - damping ^ 2) + local invAlpha = 1 / alpha + local alphaTime = alpha * scaledTime + local expTerm = math.exp(-scaledTime * damping) + local sinTerm = expTerm * math.sin(alphaTime) + local cosTerm = expTerm * math.cos(alphaTime) + local sinInvAlpha = sinTerm * invAlpha + local sinInvAlphaDamp = sinInvAlpha * damping + + posPos = sinInvAlphaDamp + cosTerm + posVel = sinInvAlpha + velPos = -(sinInvAlphaDamp * damping + sinTerm * alpha) + velVel = cosTerm - sinInvAlphaDamp + end + + return posPos, posVel, velPos, velVel +end + +return springCoefficients diff --git a/Libraries/Fusion/Animation/unpackType.luau b/Libraries/Fusion/Animation/unpackType.luau new file mode 100644 index 0000000..770e8f8 --- /dev/null +++ b/Libraries/Fusion/Animation/unpackType.luau @@ -0,0 +1,92 @@ +--!strict + +--[[ + Unpacks an animatable type into an array of numbers. + If the type is not animatable, an empty array will be returned. + + FIXME: This function uses a lot of redefinitions to suppress false positives + from the Luau typechecker - ideally these wouldn't be required + + FUTURE: When Luau supports singleton types, those could be used in + conjunction with intersection types to make this function fully statically + type checkable. +]] + +local Oklab = require "../Colour/Oklab" + +local function unpackType(value: any, typeString: string): { number } + if typeString == "number" then + return { value } + elseif typeString == "CFrame" then + -- FUTURE: is there a better way of doing this? doing distance + -- calculations on `angle` may be incorrect + local axis, angle = value:ToAxisAngle() + return { value.X, value.Y, value.Z, axis.X, axis.Y, axis.Z, angle } + elseif typeString == "Color3" then + local lab = Oklab.to(value) + return { lab.X, lab.Y, lab.Z } + elseif typeString == "ColorSequenceKeypoint" then + local lab = Oklab.to(value.Value) + return { lab.X, lab.Y, lab.Z, value.Time } + elseif typeString == "DateTime" then + return { value.UnixTimestampMillis } + elseif typeString == "NumberRange" then + return { value.Min, value.Max } + elseif typeString == "NumberSequenceKeypoint" then + return { value.Value, value.Time, value.Envelope } + elseif typeString == "PhysicalProperties" then + return { + value.Density, + value.Friction, + value.Elasticity, + value.FrictionWeight, + value.ElasticityWeight, + } + elseif typeString == "Ray" then + return { + value.Origin.X, + value.Origin.Y, + value.Origin.Z, + value.Direction.X, + value.Direction.Y, + value.Direction.Z, + } + elseif typeString == "Rect" then + return { value.Min.X, value.Min.Y, value.Max.X, value.Max.Y } + elseif typeString == "Region3" then + -- FUTURE: support rotated Region3s if/when they become constructable + return { + value.CFrame.X, + value.CFrame.Y, + value.CFrame.Z, + value.Size.X, + value.Size.Y, + value.Size.Z, + } + elseif typeString == "Region3int16" then + return { + value.Min.X, + value.Min.Y, + value.Min.Z, + value.Max.X, + value.Max.Y, + value.Max.Z, + } + elseif typeString == "UDim" then + return { value.Scale, value.Offset } + elseif typeString == "UDim2" then + return { value.X.Scale, value.X.Offset, value.Y.Scale, value.Y.Offset } + elseif typeString == "Vector2" then + return { value.X, value.Y } + elseif typeString == "Vector2int16" then + return { value.X, value.Y } + elseif typeString == "Vector3" then + return { value.X, value.Y, value.Z } + elseif typeString == "Vector3int16" then + return { value.X, value.Y, value.Z } + else + return {} + end +end + +return unpackType diff --git a/Libraries/Fusion/Colour/Oklab.luau b/Libraries/Fusion/Colour/Oklab.luau new file mode 100644 index 0000000..46fb01e --- /dev/null +++ b/Libraries/Fusion/Colour/Oklab.luau @@ -0,0 +1,56 @@ +--!strict + +--[[ + Provides functions for converting Color3s into Oklab space, for more + perceptually uniform colour blending. + + See: https://bottosson.github.io/posts/oklab/ +]] + +local Oklab = {} + +-- Converts a Color3 in RGB space to a Vector3 in Oklab space. +function Oklab.to(rgb: Color3): Vector3 + local l = rgb.r * 0.4122214708 + rgb.g * 0.5363325363 + rgb.b * 0.0514459929 + local m = rgb.r * 0.2119034982 + rgb.g * 0.6806995451 + rgb.b * 0.1073969566 + local s = rgb.r * 0.0883024619 + rgb.g * 0.2817188376 + rgb.b * 0.6299787005 + + local lRoot = l ^ (1 / 3) + local mRoot = m ^ (1 / 3) + local sRoot = s ^ (1 / 3) + + return Vector3.new( + lRoot * 0.2104542553 + mRoot * 0.7936177850 - sRoot * 0.0040720468, + lRoot * 1.9779984951 - mRoot * 2.4285922050 + sRoot * 0.4505937099, + lRoot * 0.0259040371 + mRoot * 0.7827717662 - sRoot * 0.8086757660 + ) +end + +-- Converts a Vector3 in CIELAB space to a Color3 in RGB space. +-- The Color3 will be clamped by default unless specified otherwise. +function Oklab.from(lab: Vector3, unclamped: boolean?): Color3 + local lRoot = lab.X + lab.Y * 0.3963377774 + lab.Z * 0.2158037573 + local mRoot = lab.X - lab.Y * 0.1055613458 - lab.Z * 0.0638541728 + local sRoot = lab.X - lab.Y * 0.0894841775 - lab.Z * 1.2914855480 + + local l = lRoot ^ 3 + local m = mRoot ^ 3 + local s = sRoot ^ 3 + + local red = l * 4.0767416621 - m * 3.3077115913 + s * 0.2309699292 + local green = l * -1.2684380046 + m * 2.6097574011 - s * 0.3413193965 + local blue = l * -0.0041960863 - m * 0.7034186147 + s * 1.7076147010 + + if not unclamped then + -- red = math.clamp(red, 0, 1) + -- green = math.clamp(green, 0, 1) + -- blue = math.clamp(blue, 0, 1) + red = math.max(0, math.min(red, 1)) + green = math.max(0, math.min(green, 1)) + blue = math.max(0, math.min(blue, 1)) + end + + return Color3.new(red, green, blue) +end + +return Oklab diff --git a/Libraries/Fusion/External.luau b/Libraries/Fusion/External.luau new file mode 100644 index 0000000..d620f8c --- /dev/null +++ b/Libraries/Fusion/External.luau @@ -0,0 +1,97 @@ +--!strict +--[[ + Abstraction layer between Fusion internals and external environments, + allowing for flexible integration with schedulers and test mocks. +]] + +local logError = require "./Logging/logError" + +local External = {} + +export type Scheduler = { + doTaskImmediate: (resume: () -> ()) -> (), + doTaskDeferred: (resume: () -> ()) -> (), + startScheduler: () -> (), + stopScheduler: () -> (), +} + +local updateStepCallbacks = {} +local currentScheduler: Scheduler? = nil +local lastUpdateStep = 0 + +--[[ + Sets the external scheduler that Fusion will use for queuing async tasks. + Returns the previous scheduler so it can be reset later. +]] +function External.setExternalScheduler(newScheduler: Scheduler?): Scheduler? + local oldScheduler = currentScheduler + if oldScheduler ~= nil then + oldScheduler.stopScheduler() + end + currentScheduler = newScheduler + if newScheduler ~= nil then + newScheduler.startScheduler() + end + return oldScheduler +end + +--[[ + Sends an immediate task to the external scheduler. Throws if none is set. +]] +function External.doTaskImmediate(resume: () -> ()) + if currentScheduler == nil then + logError "noTaskScheduler" + else + currentScheduler.doTaskImmediate(resume) + end +end + +--[[ + Sends a deferred task to the external scheduler. Throws if none is set. +]] +function External.doTaskDeferred(resume: () -> ()) + if currentScheduler == nil then + logError "noTaskScheduler" + else + currentScheduler.doTaskDeferred(resume) + end +end + +--[[ + Registers a callback to the update step of the external scheduler. + Returns a function that can be used to disconnect later. + + Callbacks are given the current number of seconds since an arbitrary epoch. + + TODO: This epoch may change between schedulers. We could investigate ways + of allowing schedulers to co-operate to keep the epoch the same, so that + monotonicity can be better preserved. +]] +function External.bindToUpdateStep(callback: (now: number) -> ()): () -> () + local uniqueIdentifier = {} + updateStepCallbacks[uniqueIdentifier] = callback + return function() + updateStepCallbacks[uniqueIdentifier] = nil + end +end + +--[[ + Steps time-dependent systems with the current number of seconds since an + arbitrary epoch. This should be called as early as possible in the external + scheduler's update cycle. +]] +function External.performUpdateStep(now: number) + lastUpdateStep = now + for _, callback in pairs(updateStepCallbacks) do + callback(now) + end +end + +--[[ + Returns the timestamp of the last update step. +]] +function External.lastUpdateStep() + return lastUpdateStep +end + +return External diff --git a/Libraries/Fusion/Instances/Children.luau b/Libraries/Fusion/Instances/Children.luau new file mode 100644 index 0000000..68d32aa --- /dev/null +++ b/Libraries/Fusion/Instances/Children.luau @@ -0,0 +1,157 @@ +--!strict + +--[[ + A special key for property tables, which parents any given descendants into + an instance. +]] + +local PubTypes = require "../PubTypes" +local External = require "../External" +local logWarn = require "../Logging/logWarn" +local Observer = require "../State/Observer" +local peek = require "../State/peek" +local isState = require "../State/isState" +local typeof = require "../Polyfill/typeof" + +type Set = { [T]: boolean } + +-- Experimental flag: name children based on the key used in the [Children] table +local EXPERIMENTAL_AUTO_NAMING = false + +local Children = {} +Children.type = "SpecialKey" +Children.kind = "Children" +Children.stage = "descendants" + +function Children:apply( + propValue: any, + applyTo: Instance, + cleanupTasks: { PubTypes.Task } +) + local newParented: Set = {} + local oldParented: Set = {} + + -- save disconnection functions for state object observers + local newDisconnects: { [PubTypes.StateObject]: () -> () } = {} + local oldDisconnects: { [PubTypes.StateObject]: () -> () } = {} + + local updateQueued = false + local queueUpdate: () -> () + + -- Rescans this key's value to find new instances to parent and state objects + -- to observe for changes; then unparents instances no longer found and + -- disconnects observers for state objects no longer present. + local function updateChildren() + if not updateQueued then + return -- this update may have been canceled by destruction, etc. + end + updateQueued = false + + oldParented, newParented = newParented, oldParented + oldDisconnects, newDisconnects = newDisconnects, oldDisconnects + -- table.clear(newParented) + for i, _ in pairs(newParented) do + newParented[i] = nil + end + -- table.clear(newDisconnects) + for i, _ in pairs(newDisconnects) do + newDisconnects[i] = nil + end + + local function processChild(child: any, autoName: string?) + local childType = typeof(child) + + if childType == "Instance" then + -- case 1; single instance + + newParented[child] = true + if oldParented[child] == nil then + -- wasn't previously present + + -- TODO: check for ancestry conflicts here + child.Parent = applyTo + else + -- previously here; we want to reuse, so remove from old + -- set so we don't encounter it during unparenting + oldParented[child] = nil + end + + if EXPERIMENTAL_AUTO_NAMING and autoName ~= nil then + child.Name = autoName + end + elseif isState(child) then + -- case 2; state object + + local value = peek(child) + -- allow nil to represent the absence of a child + if value ~= nil then + processChild(value, autoName) + end + + local disconnect = oldDisconnects[child] + if disconnect == nil then + -- wasn't previously present + disconnect = Observer(child):onChange(queueUpdate) + else + -- previously here; we want to reuse, so remove from old + -- set so we don't encounter it during unparenting + oldDisconnects[child] = nil + end + + newDisconnects[child] = disconnect + elseif childType == "table" then + -- case 3; table of objects + + for key, subChild in pairs(child) do + local keyType = typeof(key) + local subAutoName: string? = nil + + if keyType == "string" then + subAutoName = key + elseif keyType == "number" and autoName ~= nil then + subAutoName = autoName .. "_" .. key + end + + processChild(subChild, subAutoName) + end + else + logWarn("unrecognisedChildType", childType) + end + end + + if propValue ~= nil then + -- `propValue` is set to nil on cleanup, so we don't process children + -- in that case + processChild(propValue) + end + + -- unparent any children that are no longer present + for oldInstance in pairs(oldParented) do + oldInstance.Parent = nil + end + + -- disconnect observers which weren't reused + for _, disconnect in pairs(oldDisconnects) do + disconnect() + end + end + + queueUpdate = function() + if not updateQueued then + updateQueued = true + External.doTaskDeferred(updateChildren) + end + end + + table.insert(cleanupTasks, function() + propValue = nil + updateQueued = true + updateChildren() + end) + + -- perform initial child parenting + updateQueued = true + updateChildren() +end + +return Children :: PubTypes.SpecialKey diff --git a/Libraries/Fusion/Instances/Cleanup.luau b/Libraries/Fusion/Instances/Cleanup.luau new file mode 100644 index 0000000..0f676aa --- /dev/null +++ b/Libraries/Fusion/Instances/Cleanup.luau @@ -0,0 +1,23 @@ +--!strict + +--[[ + A special key for property tables, which adds user-specified tasks to be run + when the instance is destroyed. +]] + +local PubTypes = require "../PubTypes" + +local Cleanup = {} +Cleanup.type = "SpecialKey" +Cleanup.kind = "Cleanup" +Cleanup.stage = "observer" + +function Cleanup:apply( + userTask: any, + _: Instance, + cleanupTasks: { PubTypes.Task } +) + table.insert(cleanupTasks, userTask) +end + +return Cleanup diff --git a/Libraries/Fusion/Instances/Hydrate.luau b/Libraries/Fusion/Instances/Hydrate.luau new file mode 100644 index 0000000..79b53ce --- /dev/null +++ b/Libraries/Fusion/Instances/Hydrate.luau @@ -0,0 +1,18 @@ +--!strict + +--[[ + Processes and returns an existing instance, with options for setting + properties, event handlers and other attributes on the instance. +]] + +local PubTypes = require "../PubTypes" +local applyInstanceProps = require "../Instances/applyInstanceProps" + +local function Hydrate(target: Instance) + return function(props: PubTypes.PropertyTable): Instance + applyInstanceProps(props, target) + return target + end +end + +return Hydrate diff --git a/Libraries/Fusion/Instances/New.luau b/Libraries/Fusion/Instances/New.luau new file mode 100644 index 0000000..3b1502e --- /dev/null +++ b/Libraries/Fusion/Instances/New.luau @@ -0,0 +1,34 @@ +--!strict + +--[[ + Constructs and returns a new instance, with options for setting properties, + event handlers and other attributes on the instance right away. +]] + +local PubTypes = require "../PubTypes" +local defaultProps = require "../Instances/defaultProps" +local applyInstanceProps = require "../Instances/applyInstanceProps" +local logError = require "../Logging/logError" + +local function New(className: string) + return function(props: PubTypes.PropertyTable): Instance + local ok, instance = pcall(Instance.new, className) + + if not ok then + logError("cannotCreateClass", nil, className) + end + + local classDefaults = defaultProps[className] + if classDefaults ~= nil then + for defaultProp, defaultValue in pairs(classDefaults) do + instance[defaultProp] = defaultValue + end + end + + applyInstanceProps(props, instance) + + return instance + end +end + +return New diff --git a/Libraries/Fusion/Instances/OnChange.luau b/Libraries/Fusion/Instances/OnChange.luau new file mode 100644 index 0000000..1960848 --- /dev/null +++ b/Libraries/Fusion/Instances/OnChange.luau @@ -0,0 +1,53 @@ +--!strict + +--[[ + Constructs special keys for property tables which connect property change + listeners to an instance. +]] + +local PubTypes = require "../PubTypes" +local logError = require "../Logging/logError" +local typeof = require "../Polyfill/typeof" + +local function OnChange(propertyName: string): PubTypes.SpecialKey + local changeKey = {} + changeKey.type = "SpecialKey" + changeKey.kind = "OnChange" + changeKey.stage = "observer" + + function changeKey:apply( + callback: any, + applyTo: Instance, + cleanupTasks: { PubTypes.Task } + ) + -- local ok, event = + -- pcall(applyTo.GetPropertyChangedSignal, applyTo, propertyName) + local ok, event = pcall(function() + return applyTo.Changed + end) + + if not ok then + logError( + "cannotConnectChange", + nil, + applyTo.ClassName, + propertyName + ) + elseif typeof(callback) ~= "function" then + logError("invalidChangeHandler", nil, propertyName) + else + table.insert( + cleanupTasks, + event:connect(function(prop) + if prop == propertyName then + callback((applyTo :: any)[propertyName]) + end + end) + ) + end + end + + return changeKey +end + +return OnChange diff --git a/Libraries/Fusion/Instances/OnEvent.luau b/Libraries/Fusion/Instances/OnEvent.luau new file mode 100644 index 0000000..ecd54b2 --- /dev/null +++ b/Libraries/Fusion/Instances/OnEvent.luau @@ -0,0 +1,40 @@ +--!strict + +--[[ + Constructs special keys for property tables which connect event listeners to + an instance. +]] + +local PubTypes = require "../PubTypes" +local logError = require "../Logging/logError" +local typeof = require "../Polyfill/typeof" + +local function getProperty_unsafe(instance: Instance, property: string) + return (instance :: any)[property] +end + +local function OnEvent(eventName: string): PubTypes.SpecialKey + local eventKey = {} + eventKey.type = "SpecialKey" + eventKey.kind = "OnEvent" + eventKey.stage = "observer" + + function eventKey:apply( + callback: any, + applyTo: Instance, + cleanupTasks: { PubTypes.Task } + ) + local ok, event = pcall(getProperty_unsafe, applyTo, eventName) + if not ok or typeof(event) ~= "RBXScriptSignal" then + logError("cannotConnectEvent", nil, applyTo.ClassName, eventName) + elseif typeof(callback) ~= "function" then + logError("invalidEventHandler", nil, eventName) + else + table.insert(cleanupTasks, event:connect(callback)) + end + end + + return eventKey +end + +return OnEvent diff --git a/Libraries/Fusion/Instances/Out.luau b/Libraries/Fusion/Instances/Out.luau new file mode 100644 index 0000000..d41df31 --- /dev/null +++ b/Libraries/Fusion/Instances/Out.luau @@ -0,0 +1,52 @@ +--!strict + +--[[ + A special key for property tables, which allows users to extract values from + an instance into an automatically-updated Value object. +]] + +local PubTypes = require "../PubTypes" +local logError = require "../Logging/logError" +local xtypeof = require "../Utility/xtypeof" + +local function Out(propertyName: string): PubTypes.SpecialKey + local outKey = {} + outKey.type = "SpecialKey" + outKey.kind = "Out" + outKey.stage = "observer" + + function outKey:apply( + outState: any, + applyTo: Instance, + cleanupTasks: { PubTypes.Task } + ) + -- local ok, event = + -- pcall(applyTo.GetPropertyChangedSignal, applyTo, propertyName) + local ok, event = pcall(function() + return applyTo.Changed + end) + + if not ok then + logError("invalidOutProperty", nil, applyTo.ClassName, propertyName) + elseif xtypeof(outState) ~= "State" or outState.kind ~= "Value" then + logError "invalidOutType" + else + outState:set((applyTo :: any)[propertyName]) + table.insert( + cleanupTasks, + event:connect(function(prop) + if prop == propertyName then + outState:set((applyTo :: any)[propertyName]) + end + end) + ) + table.insert(cleanupTasks, function() + outState:set(nil) + end) + end + end + + return outKey +end + +return Out diff --git a/Libraries/Fusion/Instances/Ref.luau b/Libraries/Fusion/Instances/Ref.luau new file mode 100644 index 0000000..0a033a7 --- /dev/null +++ b/Libraries/Fusion/Instances/Ref.luau @@ -0,0 +1,32 @@ +--!strict + +--[[ + A special key for property tables, which stores a reference to the instance + in a user-provided Value object. +]] + +local PubTypes = require "../PubTypes" +local logError = require "../Logging/logError" +local xtypeof = require "../Utility/xtypeof" + +local Ref = {} +Ref.type = "SpecialKey" +Ref.kind = "Ref" +Ref.stage = "observer" + +function Ref:apply( + refState: any, + applyTo: Instance, + cleanupTasks: { PubTypes.Task } +) + if xtypeof(refState) ~= "State" or refState.kind ~= "Value" then + logError "invalidRefType" + else + refState:set(applyTo) + table.insert(cleanupTasks, function() + refState:set(nil) + end) + end +end + +return Ref diff --git a/Libraries/Fusion/Instances/applyInstanceProps.luau b/Libraries/Fusion/Instances/applyInstanceProps.luau new file mode 100644 index 0000000..0e554bd --- /dev/null +++ b/Libraries/Fusion/Instances/applyInstanceProps.luau @@ -0,0 +1,160 @@ +--!strict + +--[[ + Applies a table of properties to an instance, including binding to any + given state objects and applying any special keys. + + No strong reference is kept by default - special keys should take care not + to accidentally hold strong references to instances forever. + + If a key is used twice, an error will be thrown. This is done to avoid + double assignments or double bindings. However, some special keys may want + to enable such assignments - in which case unique keys should be used for + each occurence. +]] + +local PubTypes = require "../PubTypes" +local External = require "../External" +local cleanup = require "../Utility/cleanup" +local xtypeof = require "../Utility/xtypeof" +local logError = require "../Logging/logError" +local Observer = require "../State/Observer" +local peek = require "../State/peek" +local typeof = require "../Polyfill/typeof" + +local function setProperty_unsafe( + instance: Instance, + property: string, + value: any +) + (instance :: any)[property] = value +end + +local function testPropertyAssignable(instance: Instance, property: string) + (instance :: any)[property] = (instance :: any)[property] +end + +local function setProperty(instance: Instance, property: string, value: any) + if not pcall(setProperty_unsafe, instance, property, value) then + if not pcall(testPropertyAssignable, instance, property) then + if instance == nil then + -- reference has been lost + logError("setPropertyNilRef", nil, property, tostring(value)) + else + -- property is not assignable + logError( + "cannotAssignProperty", + nil, + instance.ClassName, + property + ) + end + else + -- property is assignable, but this specific assignment failed + -- this typically implies the wrong type was received + local givenType = typeof(value) + local expectedType = typeof((instance :: any)[property]) + logError( + "invalidPropertyType", + nil, + instance.ClassName, + property, + expectedType, + givenType + ) + end + end +end + +local function bindProperty( + instance: Instance, + property: string, + value: PubTypes.CanBeState, + cleanupTasks: { PubTypes.Task } +) + if xtypeof(value) == "State" then + -- value is a state object - assign and observe for changes + local willUpdate = false + local function updateLater() + if not willUpdate then + willUpdate = true + External.doTaskDeferred(function() + willUpdate = false + setProperty(instance, property, peek(value)) + end) + end + end + + setProperty(instance, property, peek(value)) + table.insert(cleanupTasks, Observer(value :: any):onChange(updateLater)) + else + -- value is a constant - assign once only + setProperty(instance, property, value) + end +end + +local function applyInstanceProps( + props: PubTypes.PropertyTable, + applyTo: Instance +) + local specialKeys = { + self = {} :: { [PubTypes.SpecialKey]: any }, + descendants = {} :: { [PubTypes.SpecialKey]: any }, + ancestor = {} :: { [PubTypes.SpecialKey]: any }, + observer = {} :: { [PubTypes.SpecialKey]: any }, + } + local cleanupTasks = {} + + for key, value in pairs(props) do + local keyType = xtypeof(key) + + if keyType == "string" then + if key ~= "Parent" then + bindProperty(applyTo, key :: string, value, cleanupTasks) + end + elseif keyType == "SpecialKey" then + local stage = (key :: PubTypes.SpecialKey).stage + local keys = specialKeys[stage] + if keys == nil then + logError("unrecognisedPropertyStage", nil, stage) + else + keys[key] = value + end + else + -- we don't recognise what this key is supposed to be + logError("unrecognisedPropertyKey", nil, xtypeof(key)) + end + end + + for key, value in pairs(specialKeys.self) do + key:apply(value, applyTo, cleanupTasks) + end + for key, value in pairs(specialKeys.descendants) do + key:apply(value, applyTo, cleanupTasks) + end + + if props.Parent ~= nil then + bindProperty(applyTo, "Parent", props.Parent, cleanupTasks) + end + + for key, value in pairs(specialKeys.ancestor) do + key:apply(value, applyTo, cleanupTasks) + end + for key, value in pairs(specialKeys.observer) do + key:apply(value, applyTo, cleanupTasks) + end + + -- applyTo.Destroying:connect(function() + -- cleanup(cleanupTasks) + -- end) + + if applyTo.Parent then -- close enough? + game.DescendantRemoving:connect(function(descendant) + if descendant == applyTo then + cleanup(cleanupTasks) + end + end) + end +end + +return applyInstanceProps diff --git a/Libraries/Fusion/Instances/defaultProps.luau b/Libraries/Fusion/Instances/defaultProps.luau new file mode 100644 index 0000000..10f6e98 --- /dev/null +++ b/Libraries/Fusion/Instances/defaultProps.luau @@ -0,0 +1,117 @@ +--!strict + +--[[ + Stores 'sensible default' properties to be applied to instances created by + the New function. +]] + +return { + BillboardGui = { + Active = true, + }, + + Frame = { + BackgroundColor3 = Color3.new(1, 1, 1), + BorderColor3 = Color3.new(0, 0, 0), + BorderSizePixel = 0, + }, + + TextLabel = { + BackgroundColor3 = Color3.new(1, 1, 1), + BorderColor3 = Color3.new(0, 0, 0), + BorderSizePixel = 0, + + Font = Enum.Font.SourceSans, + Text = "", + TextColor3 = Color3.new(0, 0, 0), + FontSize = Enum.FontSize.Size14, + }, + + TextButton = { + BackgroundColor3 = Color3.new(1, 1, 1), + BorderColor3 = Color3.new(0, 0, 0), + BorderSizePixel = 0, + + AutoButtonColor = false, + + Font = Enum.Font.SourceSans, + Text = "", + TextColor3 = Color3.new(0, 0, 0), + FontSize = Enum.FontSize.Size14, + }, + + TextBox = { + BackgroundColor3 = Color3.new(1, 1, 1), + BorderColor3 = Color3.new(0, 0, 0), + BorderSizePixel = 0, + + ClearTextOnFocus = false, + + Font = Enum.Font.SourceSans, + Text = "", + TextColor3 = Color3.new(0, 0, 0), + FontSize = Enum.FontSize.Size14, + }, + + ImageLabel = { + BackgroundColor3 = Color3.new(1, 1, 1), + BorderColor3 = Color3.new(0, 0, 0), + BorderSizePixel = 0, + }, + + ImageButton = { + BackgroundColor3 = Color3.new(1, 1, 1), + BorderColor3 = Color3.new(0, 0, 0), + BorderSizePixel = 0, + + AutoButtonColor = false, + }, + + SpawnLocation = { + Duration = 0, + }, + + Part = { + Anchored = true, + Size = Vector3.new(1, 1, 1), + FrontSurface = Enum.SurfaceType.Smooth, + BackSurface = Enum.SurfaceType.Smooth, + LeftSurface = Enum.SurfaceType.Smooth, + RightSurface = Enum.SurfaceType.Smooth, + TopSurface = Enum.SurfaceType.Smooth, + BottomSurface = Enum.SurfaceType.Smooth, + }, + + TrussPart = { + Anchored = true, + Size = Vector3.new(2, 2, 2), + FrontSurface = Enum.SurfaceType.Smooth, + BackSurface = Enum.SurfaceType.Smooth, + LeftSurface = Enum.SurfaceType.Smooth, + RightSurface = Enum.SurfaceType.Smooth, + TopSurface = Enum.SurfaceType.Smooth, + BottomSurface = Enum.SurfaceType.Smooth, + }, + + CornerWedgePart = { + Anchored = true, + Size = Vector3.new(1, 1, 1), + FrontSurface = Enum.SurfaceType.Smooth, + BackSurface = Enum.SurfaceType.Smooth, + LeftSurface = Enum.SurfaceType.Smooth, + RightSurface = Enum.SurfaceType.Smooth, + TopSurface = Enum.SurfaceType.Smooth, + BottomSurface = Enum.SurfaceType.Smooth, + }, + + VehicleSeat = { + Anchored = true, + Size = Vector3.new(1, 1, 1), + FrontSurface = Enum.SurfaceType.Smooth, + BackSurface = Enum.SurfaceType.Smooth, + LeftSurface = Enum.SurfaceType.Smooth, + RightSurface = Enum.SurfaceType.Smooth, + TopSurface = Enum.SurfaceType.Smooth, + BottomSurface = Enum.SurfaceType.Smooth, + }, +} diff --git a/Libraries/Fusion/Logging/logError.luau b/Libraries/Fusion/Logging/logError.luau new file mode 100644 index 0000000..49f7323 --- /dev/null +++ b/Libraries/Fusion/Logging/logError.luau @@ -0,0 +1,43 @@ +--!strict + +--[[ + Utility function to log a Fusion-specific error. +]] + +local Types = require "../Types" +local messages = require "../Logging/messages" + +local function logError(messageID: string, errObj: Types.Error?, ...) + local formatString: string + + if messages[messageID] ~= nil then + formatString = messages[messageID] + else + messageID = "unknownMessage" + formatString = messages[messageID] + end + + local errorString + if errObj == nil then + errorString = string.format( + "[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")", + ... + ) + else + formatString = + formatString:gsub("ERROR_MESSAGE", tostring(errObj.message)) + errorString = string.format( + "[Fusion] " + .. formatString + .. "\n(ID: " + .. messageID + .. ")\n---- Stack trace ----\n" + .. tostring(errObj.trace), + ... + ) + end + + error(errorString:gsub("\n", "\n "), 0) +end + +return logError diff --git a/Libraries/Fusion/Logging/logErrorNonFatal.luau b/Libraries/Fusion/Logging/logErrorNonFatal.luau new file mode 100644 index 0000000..9d59733 --- /dev/null +++ b/Libraries/Fusion/Logging/logErrorNonFatal.luau @@ -0,0 +1,43 @@ +--!strict + +--[[ + Utility function to log a Fusion-specific error, without halting execution. +]] + +local Types = require "../Types" +local messages = require "../Logging/messages" + +local function logErrorNonFatal(messageID: string, errObj: Types.Error?, ...) + local formatString: string + + if messages[messageID] ~= nil then + formatString = messages[messageID] + else + messageID = "unknownMessage" + formatString = messages[messageID] + end + + local errorString + if errObj == nil then + errorString = + string.format(`[Fusion] {formatString}\n(ID: {messageID})`, ...) + else + formatString = + formatString:gsub("ERROR_MESSAGE", tostring(errObj.message)) + errorString = string.format( + "[Fusion] " + .. formatString + .. "\n(ID: " + .. messageID + .. ")\n---- Stack trace ----\n" + .. tostring(errObj.trace), + ... + ) + end + + coroutine.wrap(function() + error(errorString:gsub("\n", "\n "), 0) + end)() +end + +return logErrorNonFatal diff --git a/Libraries/Fusion/Logging/logWarn.luau b/Libraries/Fusion/Logging/logWarn.luau new file mode 100644 index 0000000..ee6f5d6 --- /dev/null +++ b/Libraries/Fusion/Logging/logWarn.luau @@ -0,0 +1,22 @@ +--!strict + +--[[ + Utility function to log a Fusion-specific warning. +]] + +local messages = require "../Logging/messages" + +local function logWarn(messageID, ...) + local formatString: string + + if messages[messageID] ~= nil then + formatString = messages[messageID] + else + messageID = "unknownMessage" + formatString = messages[messageID] + end + + warn(string.format(`[Fusion] {formatString}\n(ID: {messageID})`, ...)) +end + +return logWarn diff --git a/Libraries/Fusion/Logging/messages.luau b/Libraries/Fusion/Logging/messages.luau new file mode 100644 index 0000000..0a8ebea --- /dev/null +++ b/Libraries/Fusion/Logging/messages.luau @@ -0,0 +1,55 @@ +--!strict + +--[[ + Stores templates for different kinds of logging messages. +]] + +return { + -- attributeNameNil = "Attribute name cannot be nil", + cannotAssignProperty = "The class type '%s' has no assignable property '%s'.", + cannotConnectChange = "The %s class doesn't have a property called '%s'.", + -- cannotConnectAttributeChange = "The %s class doesn't have an attribute called '%s'.", + cannotConnectEvent = "The %s class doesn't have an event called '%s'.", + cannotCreateClass = "Can't create a new instance of class '%s'.", + computedCallbackError = "Computed callback error: ERROR_MESSAGE", + contextualCallbackError = "Contextual callback error: ERROR_MESSAGE", + destructorNeededValue = "To save instances into Values, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", + destructorNeededComputed = "To return instances from Computeds, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", + multiReturnComputed = "Returning multiple values from Computeds is discouraged, as behaviour will change soon - see discussion #189 on GitHub.", + destructorNeededForKeys = "To return instances from ForKeys, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", + destructorNeededForValues = "To return instances from ForValues, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", + destructorNeededForPairs = "To return instances from ForPairs, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.", + forKeysProcessorError = "ForKeys callback error: ERROR_MESSAGE", + forKeysKeyCollision = "ForKeys should only write to output key '%s' once when processing key changes, but it wrote to it twice. Previously input key: '%s'; New input key: '%s'", + forKeysDestructorError = "ForKeys destructor error: ERROR_MESSAGE", + forPairsDestructorError = "ForPairs destructor error: ERROR_MESSAGE", + forPairsKeyCollision = "ForPairs should only write to output key '%s' once when processing key changes, but it wrote to it twice. Previous input pair: '[%s] = %s'; New input pair: '[%s] = %s'", + forPairsProcessorError = "ForPairs callback error: ERROR_MESSAGE", + forValuesProcessorError = "ForValues callback error: ERROR_MESSAGE", + forValuesDestructorError = "ForValues destructor error: ERROR_MESSAGE", + invalidChangeHandler = "The change handler for the '%s' property must be a function.", + -- invalidAttributeChangeHandler = "The change handler for the '%s' attribute must be a function.", + invalidEventHandler = "The handler for the '%s' event must be a function.", + invalidPropertyType = "'%s.%s' expected a '%s' type, but got a '%s' type.", + invalidRefType = "Instance refs must be Value objects.", + invalidOutType = "[Out] properties must be given Value objects.", + -- invalidAttributeOutType = "[AttributeOut] properties must be given Value objects.", + invalidOutProperty = "The %s class doesn't have a property called '%s'.", + -- invalidOutAttributeName = "The %s class doesn't have an attribute called '%s'.", + invalidSpringDamping = "The damping ratio for a spring must be >= 0. (damping was %.2f)", + invalidSpringSpeed = "The speed of a spring must be >= 0. (speed was %.2f)", + mistypedSpringDamping = "The damping ratio for a spring must be a number. (got a %s)", + mistypedSpringSpeed = "The speed of a spring must be a number. (got a %s)", + mistypedTweenInfo = "The tween info of a tween must be a TweenInfo. (got a %s)", + noTaskScheduler = "Fusion is not connected to an external task scheduler.", + springTypeMismatch = "The type '%s' doesn't match the spring's type '%s'.", + stateGetWasRemoved = "`StateObject:get()` has been replaced by `use()` and `peek()` - see discussion #217 on GitHub.", + strictReadError = "'%s' is not a valid member of '%s'.", + unknownMessage = "Unknown error: ERROR_MESSAGE", + unrecognisedChildType = "'%s' type children aren't accepted by `[Children]`.", + unrecognisedPropertyKey = "'%s' keys aren't accepted in property tables.", + unrecognisedPropertyStage = "'%s' isn't a valid stage for a special key to be applied at.", + + invalidEasingStyle = "The easing style must be a valid Enum.EasingStyle or a string of 'Linear', 'Quad', 'Cubic', 'Quart', 'Quint', 'Sine', 'Exponential', 'Circular', 'Elastic', 'Back', 'Bounce'. (got %s)", + invalidEasingDirection = "The easing direction must be a valid Enum.EasingDirection or a string of 'In', 'Out', 'InOut', 'OutIn'. (got %s)", +} diff --git a/Libraries/Fusion/Logging/parseError.luau b/Libraries/Fusion/Logging/parseError.luau new file mode 100644 index 0000000..366f6b5 --- /dev/null +++ b/Libraries/Fusion/Logging/parseError.luau @@ -0,0 +1,24 @@ +--!strict + +--[[ + An xpcall() error handler to collect and parse useful information about + errors, such as clean messages and stack traces. +]] + +local Types = require "../Types" + +local function parseError(err: string): Types.Error + local trace + if debug and debug.traceback then + trace = debug.traceback(nil, 2) + end + + return { + type = "Error", + raw = err, + message = err:gsub("^.+:%d+:%s*", ""), + trace = trace or "Traceback not available", + } +end + +return parseError diff --git a/Libraries/Fusion/MercuryExternal.luau b/Libraries/Fusion/MercuryExternal.luau new file mode 100644 index 0000000..b6e9b4b --- /dev/null +++ b/Libraries/Fusion/MercuryExternal.luau @@ -0,0 +1,75 @@ +--!strict +--[[ + Roblox implementation for Fusion's abstract scheduler layer. +]] + +local RunService = game:GetService "RunService" + +local External = require "./External" + +local MercuryExternal = {} + +--[[ + Sends an immediate task to the external scheduler. Throws if none is set. +]] +function MercuryExternal.doTaskImmediate(resume: () -> ()) + Spawn(resume) +end + +--[[ + Sends a deferred task to the external scheduler. Throws if none is set. +]] +function MercuryExternal.doTaskDeferred(resume: () -> ()) + coroutine.resume(coroutine.create(resume)) +end + +--[[ + Sends an update step to Fusion using the Roblox clock time. +]] +local function performUpdateStep() + External.performUpdateStep(time()) +end + +--[[ + Binds Fusion's update step to RunService step events. +]] +local stopSchedulerFunc: () -> ()? = nil +function MercuryExternal.startScheduler() + if stopSchedulerFunc ~= nil then + return + end + -- if RunService:IsClient() then + -- In cases where multiple Fusion modules are running simultaneously, + -- -- this prevents collisions. + -- local id = "FusionUpdateStep_" .. HttpService:GenerateGUID() + -- RunService:BindToRenderStep( + -- id, + -- Enum.RenderPriority.First.Value, + -- performUpdateStep + -- ) + -- stopSchedulerFunc = function() + -- RunService:UnbindFromRenderStep(id) + -- end + local conn = RunService.RenderStepped:connect(performUpdateStep) + stopSchedulerFunc = function() + conn:disconnect() + end + -- else + -- local connection = RunService.Heartbeat:connect(performUpdateStep) + -- stopSchedulerFunc = function() + -- connection:Disconnect() + -- end + -- end +end + +--[[ + Unbinds Fusion's update step from RunService step events. +]] +function MercuryExternal.stopScheduler() + if stopSchedulerFunc ~= nil then + stopSchedulerFunc() + stopSchedulerFunc = nil + end +end + +return MercuryExternal diff --git a/Libraries/Fusion/Polyfill/TweenInfo.luau b/Libraries/Fusion/Polyfill/TweenInfo.luau new file mode 100644 index 0000000..1ffb808 --- /dev/null +++ b/Libraries/Fusion/Polyfill/TweenInfo.luau @@ -0,0 +1,112 @@ +-- A basic polyfill for the TweenInfo.new function, +-- allows using Enum.EasingStyle/Direction or strings instead + +local logError = require "../Logging/logError" +local TweenInfo = {} + +function TweenInfo.new( + time: number?, + easingStyle: Enum.EasingStyle | string?, + easingDirection: Enum.EasingDirection | string?, + repeatCount: number?, + reverses: boolean?, + delayTime: number? +) + local proxy = newproxy(true) + local mt = getmetatable(proxy) + + -- if easingStyle or easingDirection is an enum, + -- convert it to a string + if type(easingStyle) ~= "string" then + if easingStyle then + easingStyle = tostring(easingStyle):gsub("Enum.%w+.", "") + end + else + local ok + for _, s in ipairs { + "Linear", + "Quad", + "Cubic", + "Quart", + "Quint", + "Sine", + "Exponential", + "Circular", + "Elastic", + "Back", + "Bounce", + } do + if easingStyle == s then + ok = true + break + end + end + if not ok then + logError("invalidEasingStyle", nil, easingStyle) + end + end + + if type(easingDirection) ~= "string" then + if easingDirection then + easingDirection = tostring(easingDirection):gsub("Enum.%w+.", "") + end + else + local ok + for _, d in ipairs { + "In", + "Out", + "InOut", + "OutIn", + } do + if easingDirection == d then + ok = true + break + end + end + if not ok then + logError("invalidEasingDirection", nil, easingDirection) + end + end + + time = time or 1 + easingStyle = easingStyle or "Quad" + easingDirection = easingDirection or "Out" + repeatCount = repeatCount or 0 + reverses = reverses or false + delayTime = delayTime or 0 + + mt.__index = { + Time = time, + EasingStyle = easingStyle, + EasingDirection = easingDirection, + RepeatCount = repeatCount, + Reverses = reverses, + DelayTime = delayTime, + } + + -- When attempting to assign to properties, throw an error + mt.__newindex = function(_, prop) + error(prop .. " cannot be assigned to", math.huge) -- lmfao + end + + mt.__tostring = function() + return "Time:" + .. tostring(time) + .. " DelayTime:" + .. tostring(delayTime) + .. " RepeatCount:" + .. tostring(repeatCount) + .. " Reverses:" + .. (reverses and "True" or "False") + .. " EasingDirection:" + .. easingDirection + .. " EasingStyle:" + .. easingStyle + end + + mt.__metatable = "The metatable is locked" + + return proxy +end + +return TweenInfo diff --git a/Libraries/Fusion/Polyfill/easing.luau b/Libraries/Fusion/Polyfill/easing.luau new file mode 100644 index 0000000..33a1ce7 --- /dev/null +++ b/Libraries/Fusion/Polyfill/easing.luau @@ -0,0 +1,347 @@ +-- +-- Adapted from +-- Tweener's easing functions (Penner's Easing Equations) +-- and http://code.google.com/p/tweener/ (jstweener javascript version) +-- + +-- For all easing functions: +-- t = elapsed time +-- b = begin +-- c = change == ending - beginning + +local pow = math.pow +local sin = math.sin +local cos = math.cos +local pi = math.pi +local sqrt = math.sqrt +local abs = math.abs +local asin = math.asin + +local easing = { + Linear = {}, + Quad = {}, + Cubic = {}, + Quart = {}, + Quint = {}, + Sine = {}, + Exponential = {}, + Circular = {}, + Elastic = {}, + Back = {}, + Bounce = {}, +} + +local linear = function(t, b, c) + return c * t + b +end + +easing.Linear.In = linear +easing.Linear.Out = linear +easing.Linear.InOut = linear +easing.Linear.OutIn = linear + +easing.Quad.In = function(t, b, c) + return c * pow(t, 2) + b +end + +easing.Quad.Out = function(t, b, c) + return -c * t * (t - 2) + b +end + +easing.Quad.InOut = function(t, b, c) + t *= 2 + if t < 1 then + return c / 2 * pow(t, 2) + b + end + return -c / 2 * ((t - 1) * (t - 3) - 1) + b +end + +easing.Quad.OutIn = function(t, b, c) + if t < 0.5 then + return easing.Quad.Out(t * 2, b, c / 2) + end + return easing.Quad.In((t * 2) - 1, b + c / 2, c / 2) +end + +easing.Cubic.In = function(t, b, c) + return c * pow(t, 3) + b +end + +easing.Cubic.Out = function(t, b, c) + t -= 1 + return c * (pow(t, 3) + 1) + b +end + +easing.Cubic.InOut = function(t, b, c) + t *= 2 + if t < 1 then + return c / 2 * t * t * t + b + end + t -= 2 + return c / 2 * (t * t * t + 2) + b +end + +easing.Cubic.OutIn = function(t, b, c) + if t < 0.5 then + return easing.Cubic.Out(t * 2, b, c / 2) + end + return easing.Cubic.In((t * 2) - 1, b + c / 2, c / 2) +end + +easing.Quart.In = function(t, b, c) + return c * pow(t, 4) + b +end + +easing.Quart.Out = function(t, b, c) + t -= 1 + return -c * (pow(t, 4) - 1) + b +end + +easing.Quart.InOut = function(t, b, c) + t *= 2 + if t < 1 then + return c / 2 * pow(t, 4) + b + end + t -= 2 + return -c / 2 * (pow(t, 4) - 2) + b +end + +easing.Quart.OutIn = function(t, b, c) + if t < 0.5 then + return easing.Quart.Out(t * 2, b, c / 2) + end + return easing.Quart.In((t * 2) - 1, b + c / 2, c / 2) +end + +easing.Quint.In = function(t, b, c) + return c * pow(t, 5) + b +end + +easing.Quint.Out = function(t, b, c) + t -= 1 + return c * (pow(t, 5) + 1) + b +end + +easing.Quint.InOut = function(t, b, c) + t *= 2 + if t < 1 then + return c / 2 * pow(t, 5) + b + end + t -= 2 + return c / 2 * (pow(t, 5) + 2) + b +end + +easing.Quint.OutIn = function(t, b, c) + if t < 0.5 then + return easing.Quint.Out(t * 2, b, c / 2) + end + return easing.Quint.In((t * 2) - 1, b + c / 2, c / 2) +end + +easing.Sine.In = function(t, b, c) + return -c * cos(t * (pi / 2)) + c + b +end + +easing.Sine.Out = function(t, b, c) + return c * sin(t * (pi / 2)) + b +end + +easing.Sine.InOut = function(t, b, c) + return -c / 2 * (cos(pi * t) - 1) + b +end + +easing.Sine.OutIn = function(t, b, c) + if t < 0.5 then + return easing.Sine.Out(t * 2, b, c / 2) + end + return easing.Sine.In((t * 2) - 1, b + c / 2, c / 2) +end + +easing.Exponential.In = function(t, b, c) + if t == 0 then + return b + end + return c * pow(2, 10 * (t - 1)) + b - c * 0.001 +end + +easing.Exponential.Out = function(t, b, c) + if t == 1 then + return b + c + end + return c * 1.001 * (-pow(2, -10 * t) + 1) + b +end + +easing.Exponential.InOut = function(t, b, c) + if t == 0 then + return b + elseif t == 1 then + return b + c + end + t *= 2 + if t < 1 then + return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 + end + t -= 1 + return c / 2 * 1.0005 * (-pow(2, -10 * t) + 2) + b +end + +easing.Exponential.OutIn = function(t, b, c) + if t < 0.5 then + return t.Exponential.Out(t * 2, b, c / 2) + end + return t.Exponential.In((t * 2) - 1, b + c / 2, c / 2) +end + +easing.Circular.In = function(t, b, c) + return (-c * (sqrt(1 - pow(t, 2)) - 1) + b) +end + +easing.Circular.Out = function(t, b, c) + t -= 1 + return (c * sqrt(1 - pow(t, 2)) + b) +end + +easing.Circular.InOut = function(t, b, c) + t *= 2 + if t < 1 then + return -c / 2 * (sqrt(1 - t * t) - 1) + b + end + t -= 2 + return c / 2 * (sqrt(1 - t * t) + 1) + b +end + +easing.Circular.OutIn = function(t, b, c) + if t < 0.5 then + return easing.Circular.Out(t * 2, b, c / 2) + end + return easing.Circular.In((t * 2) - 1, b + c / 2, c / 2) +end + +easing.Elastic.In = function(t, b, c) --, a, p) + if t == 0 then + return b + elseif t == 1 then + return b + c + end + + local p = 0.3 + local s + + s = p / 4 + + t -= 1 + + return -(c * pow(2, 10 * t) * sin((t * 1 - s) * (2 * pi) / p)) + b +end + +easing.Elastic.Out = function(t, b, c) --, a, p) + if t == 0 then + return b + elseif t == 1 then + return b + c + end + + local p = 0.3 + local s + s = p / 4 + + return c * pow(2, -10 * t) * sin((t - s) * (2 * pi) / p) + c + b +end + +easing.Elastic.InOut = function(t, b, c) --, a, p) + if t == 0 then + return b + end + + t *= 2 + + if t == 2 then + return b + c + end + + local p = 0.45 + local a = 0 + local s + + if not a or a < abs(c) then + a = c + s = p / 4 + else + s = p / (2 * pi) * asin(c / a) + end + + t -= 1 + if t < 1 then + return -0.5 * (a * pow(2, 10 * t) * sin((t - s) * (2 * pi) / p)) + b + end + return a * pow(2, -10 * t) * sin((t - s) * (2 * pi) / p) * 0.5 + c + b +end + +easing.Elastic.OutIn = function(t, b, c) --, a, p) + if t < 0.5 then + return easing.Elastic.Out(t * 2, b, c / 2) + end + return easing.Elastic.In((t * 2) - 1, b + c / 2, c / 2) +end + +easing.Back.In = function(t, b, c) --, s) + local s = 1.70158 + return c * t * t * ((s + 1) * t - s) + b +end + +easing.Back.Out = function(t, b, c) --, s) + local s = 1.70158 + t -= 1 + return c * (t * t * ((s + 1) * t + s) + 1) + b +end + +easing.Back.InOut = function(t, b, c) --, s) + local s = 2.5949095 + t *= 2 + if t < 1 then + return c / 2 * (t * t * ((s + 1) * t - s)) + b + end + t -= 2 + return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b +end + +easing.Back.OutIn = function(t, b, c) --, s) + if t < 0.5 then + return easing.Back.Out(t * 2, b, c / 2) + end + return easing.Back.In((t * 2) - 1, b + c / 2, c / 2) +end + +easing.Bounce.Out = function(t, b, c) + if t < 1 / 2.75 then + return c * (7.5625 * t * t) + b + elseif t < 2 / 2.75 then + t -= 1.5 / 2.75 + return c * (7.5625 * t * t + 0.75) + b + elseif t < 2.5 / 2.75 then + t -= 2.25 / 2.75 + return c * (7.5625 * t * t + 0.9375) + b + end + t -= 2.625 / 2.75 + return c * (7.5625 * t * t + 0.984375) + b +end + +easing.Bounce.In = function(t, b, c) + return c - easing.Bounce.Out(1 - t, 0, c) + b +end + +easing.Bounce.InOut = function(t, b, c) + if t < 0.5 then + return easing.Bounce.In(t * 2, 0, c) * 0.5 + b + end + return easing.Bounce.Out(t * 2 - 1, 0, c) * 0.5 + c * 0.5 + b +end + +easing.Bounce.OutIn = function(t, b, c) + if t < 0.5 then + return easing.Bounce.Out(t * 2, b, c / 2) + end + return easing.Bounce.In((t * 2) - 1, b + c / 2, c / 2) +end + +return easing diff --git a/Libraries/Fusion/Polyfill/typeof.luau b/Libraries/Fusion/Polyfill/typeof.luau new file mode 100644 index 0000000..170ab2c --- /dev/null +++ b/Libraries/Fusion/Polyfill/typeof.luau @@ -0,0 +1,181 @@ +-- A basic polyfill for the typeof function + +return function(value) + local basicType = type(value) + + if + basicType == "nil" + or basicType == "boolean" + or basicType == "number" + or basicType == "string" + or basicType == "function" + or basicType == "thread" + or basicType == "table" + then + return basicType + end + + -- Will short-circuit + --[[ + { + name of type to check, + { list of required properties }, + } + ]] + local tests = { + { + "Instance", + { "ClassName" }, + }, + { + "EnumItem", + { "EnumType", "Name", "Value" }, + }, + { + "Enum", + { "GetEnumItems" }, + }, + { + "Enums", + { "MembershipType" }, -- lmao + }, + { + "RBXScriptSignal", + { + "connect", + -- "connected", + -- "connectFirst", + -- "connectLast", + "wait", + }, + }, + { + "RBXScriptConnection", + { + "connected", + "disconnect", + }, + }, + { + "TweenInfo", + { + "EasingDirection", + -- "Time", + -- "DelayTime", + "RepeatCount", + "EasingStyle", + -- "Reverses", + }, + }, + { + "CFrame", + { + "p", + "x", + "y", + "z", + "lookVector", + }, + }, + { + "Vector3", + { + "Lerp", + -- "Cross", + -- "Dot", + "unit", + "magnitude", + "x", + "y", + "z", + }, + }, + { + "Vector3int16", + { "z", "x", "y" }, + }, + { + "Vector2", + { "unit", "magnitude", "x", "y" }, + }, + { + "Vector2int16", + { "x", "y" }, + }, + { + "Region3", + { "CFrame", "Size" }, + }, + { + "Region3int16", + { "Min", "Max" }, + }, + { + "Ray", + { + "Origin", + "Direction", + "Unit", + "ClosestPoint", + "Distance", + }, + }, + { + "UDim", + { "Scale", "Offset" }, + }, + { + "Axes", + { "Z", "X", "Y" }, + }, + { + "UDim2", + { "X", "Y" }, + }, + { + "BrickColor", + { + "Number", + "Name", + "Color", + "r", + "g", + "b", + }, + }, + { + "Color3", + { "r", "g", "b" }, + }, + { + "Faces", + { + "Right", + "Top", + "Back", + -- "Left", + -- "Bottom", + -- "Front", + }, + }, + } + + for _, v in ipairs(tests) do + local t, test = v[1], v[2] + + local ok, result = pcall(function() + for _, prop in ipairs(test) do + if value[prop] == nil then + return false + end + -- Cannot throw if the property does not exist, + -- as userdatas may allow nil indexing + end + return true + end) + + if ok and result then + return t + end + end +end diff --git a/Libraries/Fusion/PubTypes.luau b/Libraries/Fusion/PubTypes.luau new file mode 100644 index 0000000..6a1a3ff --- /dev/null +++ b/Libraries/Fusion/PubTypes.luau @@ -0,0 +1,162 @@ +--!strict + +--[[ + Stores common public-facing type information for Fusion APIs. +]] + +type Set = { [T]: any } + +--[[ + General use types +]] + +-- A unique symbolic value. +export type Symbol = { + type: "Symbol", + name: string, +} + +-- Types that can be expressed as vectors of numbers, and so can be animated. +export type Animatable = + number + | CFrame + | Color3 + | ColorSequenceKeypoint + | DateTime + | NumberRange + | NumberSequenceKeypoint + | PhysicalProperties + | Ray + | Rect + | Region3 + | Region3int16 + | UDim + | UDim2 + | Vector2 + | Vector2int16 + | Vector3 + | Vector3int16 + +-- A task which can be accepted for cleanup. +export type Task = + Instance + | RBXScriptConnection + | () -> () | { destroy: (any) -> () } | { Destroy: (any) -> () } | { Task } + +-- Script-readable version information. +export type Version = { + major: number, + minor: number, + isRelease: boolean, +} + +-- An object which stores a value scoped in time. +export type Contextual = { + type: "Contextual", + now: (Contextual) -> T, + is: (Contextual, T) -> ContextualIsMethods, +} + +type ContextualIsMethods = { + during: (ContextualIsMethods, (A...) -> T, A...) -> T, +} + +--[[ + Generic reactive graph types +]] + +-- A graph object which can have dependents. +export type Dependency = { + dependentSet: Set, +} + +-- A graph object which can have dependencies. +export type Dependent = { + update: (Dependent) -> boolean, + dependencySet: Set, +} + +-- An object which stores a piece of reactive state. +export type StateObject = Dependency & { + type: "State", + kind: string, + _typeIdentifier: T, +} + +-- Either a constant value of type T, or a state object containing type T. +export type CanBeState = StateObject | T + +-- Function signature for use callbacks. +export type Use = (target: CanBeState) -> T + +--[[ + Specific reactive graph types +]] + +-- A state object whose value can be set at any time by the user. +export type Value = StateObject & { + kind: "State", + set: (Value, newValue: any, force: boolean?) -> (), +} + +-- A state object whose value is derived from other objects using a callback. +export type Computed = StateObject & Dependent & { + kind: "Computed", +} + +-- A state object whose value is derived from other objects using a callback. +export type ForPairs = StateObject<{ [KO]: VO }> & Dependent & { + kind: "ForPairs", +} +-- A state object whose value is derived from other objects using a callback. +export type ForKeys = StateObject<{ [KO]: V }> & Dependent & { + kind: "ForKeys", +} +-- A state object whose value is derived from other objects using a callback. +export type ForValues = StateObject<{ [K]: VO }> & Dependent & { + kind: "ForKeys", +} + +-- A state object which follows another state object using tweens. +export type Tween = StateObject & Dependent & { + kind: "Tween", +} + +-- A state object which follows another state object using spring simulation. +export type Spring = StateObject & Dependent & { + kind: "Spring", + setPosition: (Spring, newPosition: Animatable) -> (), + setVelocity: (Spring, newVelocity: Animatable) -> (), + addVelocity: (Spring, deltaVelocity: Animatable) -> (), +} + +-- An object which can listen for updates on another state object. +export type Observer = Dependent & { + kind: "Observer", + onChange: (Observer, callback: () -> ()) -> (() -> ()), +} + +--[[ + Instance related types +]] + +-- Denotes children instances in an instance or component's property table. +export type SpecialKey = { + type: "SpecialKey", + kind: string, + stage: "self" | "descendants" | "ancestor" | "observer", + apply: ( + SpecialKey, + value: any, + applyTo: Instance, + cleanupTasks: { Task } + ) -> (), +} + +-- A collection of instances that may be parented to another instance. +export type Children = Instance | StateObject | { [any]: Children } + +-- A table that defines an instance's properties, handlers and children. +export type PropertyTable = { [string | SpecialKey]: any } + +return nil diff --git a/Libraries/Fusion/State/Computed.luau b/Libraries/Fusion/State/Computed.luau new file mode 100644 index 0000000..ced3fd6 --- /dev/null +++ b/Libraries/Fusion/State/Computed.luau @@ -0,0 +1,125 @@ +--!nonstrict + +--[[ + Constructs and returns objects which can be used to model derived reactive + state. +]] + +local Types = require "../Types" +-- Logging +local logError = require "../Logging/logError" +local logErrorNonFatal = require "../Logging/logErrorNonFatal" +local logWarn = require "../Logging/logWarn" +local parseError = require "../Logging/parseError" +-- Utility +local isSimilar = require "../Utility/isSimilar" +local needsDestruction = require "../Utility/needsDestruction" +-- State +local makeUseCallback = require "../State/makeUseCallback" + +local class = {} + +local CLASS_METATABLE = { __index = class } +local WEAK_KEYS_METATABLE = { __mode = "k" } + +--[[ + Recalculates this Computed's cached value and dependencies. + Returns true if it changed, or false if it's identical. +]] +function class:update(): boolean + -- remove this object from its dependencies' dependent sets + for dependency in pairs(self.dependencySet) do + dependency.dependentSet[self] = nil + end + + -- we need to create a new, empty dependency set to capture dependencies + -- into, but in case there's an error, we want to restore our old set of + -- dependencies. by using this table-swapping solution, we can avoid the + -- overhead of allocating new tables each update. + self._oldDependencySet, self.dependencySet = + self.dependencySet, self._oldDependencySet + -- table.clear(self.dependencySet) + for i, _ in pairs(self.dependencySet) do + self.dependencySet[i] = nil + end + + local use = makeUseCallback(self.dependencySet) + -- local ok, newValue, newMetaValue = + -- xpcall(self._processor, parseError, use) + local ok, newValue, newMetaValue = pcall(self._processor, use) + + if ok then + if self._destructor == nil and needsDestruction(newValue) then + logWarn "destructorNeededComputed" + end + + if newMetaValue ~= nil then + logWarn "multiReturnComputed" + end + + local oldValue = self._value + local similar = isSimilar(oldValue, newValue) + if self._destructor ~= nil then + self._destructor(oldValue) + end + self._value = newValue + + -- add this object to the dependencies' dependent sets + for dependency in pairs(self.dependencySet) do + dependency.dependentSet[self] = true + end + + return not similar + else + -- this needs to be non-fatal, because otherwise it'd disrupt the + -- update process + logErrorNonFatal("computedCallbackError", parseError(newValue)) + + -- restore old dependencies, because the new dependencies may be corrupt + self._oldDependencySet, self.dependencySet = + self.dependencySet, self._oldDependencySet + + -- restore this object in the dependencies' dependent sets + for dependency in pairs(self.dependencySet) do + dependency.dependentSet[self] = true + end + + return false + end +end + +--[[ + Returns the interior value of this state object. +]] +function class:_peek(): any + return self._value +end + +function class:get() + logError "stateGetWasRemoved" +end + +local function Computed( + processor: () -> T, + destructor: ((T) -> ())? +): Types.Computed + local dependencySet = {} + local self = setmetatable({ + type = "State", + kind = "Computed", + dependencySet = dependencySet, + -- if we held strong references to the dependents, then they wouldn't be + -- able to get garbage collected when they fall out of scope + dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), + _oldDependencySet = {}, + _processor = processor, + _destructor = destructor, + _value = nil, + }, CLASS_METATABLE) + + self:update() + + return self +end + +return Computed diff --git a/Libraries/Fusion/State/ForKeys.luau b/Libraries/Fusion/State/ForKeys.luau new file mode 100644 index 0000000..98087d6 --- /dev/null +++ b/Libraries/Fusion/State/ForKeys.luau @@ -0,0 +1,274 @@ +--!nonstrict + +--[[ + Constructs a new ForKeys state object which maps keys of an array using + a `processor` function. + + Optionally, a `destructor` function can be specified for cleaning up + calculated keys. If omitted, the default cleanup function will be used instead. + + Optionally, a `meta` value can be returned in the processor function as the + second value to pass data from the processor to the destructor. +]] + +local PubTypes = require "../PubTypes" +local Types = require "../Types" +-- Logging +local parseError = require "../Logging/parseError" +local logErrorNonFatal = require "../Logging/logErrorNonFatal" +local logError = require "../Logging/logError" +local logWarn = require "../Logging/logWarn" +-- Utility +local cleanup = require "../Utility/cleanup" +local needsDestruction = require "../Utility/needsDestruction" +-- State +local peek = require "../State/peek" +local makeUseCallback = require "../State/makeUseCallback" +local isState = require "../State/isState" + +local class = {} + +local CLASS_METATABLE = { __index = class } +local WEAK_KEYS_METATABLE = { __mode = "k" } + +--[[ + Called when the original table is changed. + + This will firstly find any keys meeting any of the following criteria: + + - they were not previously present + - a dependency used during generation of this value has changed + + It will recalculate those key pairs, storing information about any + dependencies used in the processor callback during output key generation, + and save the new key to the output array with the same value. If it is + overwriting an older value, that older value will be passed to the + destructor for cleanup. + + Finally, this function will find keys that are no longer present, and remove + their output keys from the output table and pass them to the destructor. +]] + +function class:update(): boolean + local inputIsState = self._inputIsState + local newInputTable = peek(self._inputTable) + local oldInputTable = self._oldInputTable + local outputTable = self._outputTable + + local keyOIMap = self._keyOIMap + local keyIOMap = self._keyIOMap + local meta = self._meta + + local didChange = false + + -- clean out main dependency set + for dependency in pairs(self.dependencySet) do + dependency.dependentSet[self] = nil + end + + self._oldDependencySet, self.dependencySet = + self.dependencySet, self._oldDependencySet + -- table.clear(self.dependencySet) + for i, _ in pairs(self.dependencySet) do + self.dependencySet[i] = nil + end + + -- if the input table is a state object, add it as a dependency + if inputIsState then + self._inputTable.dependentSet[self] = true + self.dependencySet[self._inputTable] = true + end + + -- STEP 1: find keys that changed or were not previously present + for newInKey, value in pairs(newInputTable) do + -- get or create key data + local keyData = self._keyData[newInKey] + + if keyData == nil then + keyData = { + dependencySet = setmetatable({}, WEAK_KEYS_METATABLE), + oldDependencySet = setmetatable({}, WEAK_KEYS_METATABLE), + dependencyValues = setmetatable({}, WEAK_KEYS_METATABLE), + } + self._keyData[newInKey] = keyData + end + + -- check if the key is new + local shouldRecalculate = oldInputTable[newInKey] == nil + + -- check if the key's dependencies have changed + if shouldRecalculate == false then + for dependency, oldValue in pairs(keyData.dependencyValues) do + if oldValue ~= peek(dependency) then + shouldRecalculate = true + break + end + end + end + + -- recalculate the output key if necessary + if shouldRecalculate then + keyData.oldDependencySet, keyData.dependencySet = + keyData.dependencySet, keyData.oldDependencySet + -- table.clear(keyData.dependencySet) + for i, _ in pairs(keyData.dependencySet) do + keyData.dependencySet[i] = nil + end + + local use = makeUseCallback(keyData.dependencySet) + -- local processOK, newOutKey, newMetaValue = + -- xpcall(self._processor, parseError, use, newInKey) + local processOK, newOutKey, newMetaValue = + pcall(self._processor, use, newInKey) + + if processOK then + if + self._destructor == nil + and ( + needsDestruction(newOutKey) + or needsDestruction(newMetaValue) + ) + then + logWarn "destructorNeededForKeys" + end + + local oldInKey = keyOIMap[newOutKey] + local oldOutKey = keyIOMap[newInKey] + + -- check for key collision + if oldInKey ~= newInKey and newInputTable[oldInKey] ~= nil then + logError( + "forKeysKeyCollision", + nil, + tostring(newOutKey), + tostring(oldInKey), + tostring(newOutKey) + ) + end + + -- check for a changed output key + if + oldOutKey ~= newOutKey + and keyOIMap[oldOutKey] == newInKey + then + -- clean up the old calculated value + local oldMetaValue = meta[oldOutKey] + + local destructOK, err = pcall( + self._destructor or cleanup, + oldOutKey, + oldMetaValue + ) + if not destructOK then + logErrorNonFatal( + "forKeysDestructorError", + parseError(err) + ) + end + + keyOIMap[oldOutKey] = nil + outputTable[oldOutKey] = nil + meta[oldOutKey] = nil + end + + -- update the stored data for this key + oldInputTable[newInKey] = value + meta[newOutKey] = newMetaValue + keyOIMap[newOutKey] = newInKey + keyIOMap[newInKey] = newOutKey + outputTable[newOutKey] = value + + -- if we had to recalculate the output, then we did change + didChange = true + else + -- restore old dependencies, because the new dependencies may be corrupt + keyData.oldDependencySet, keyData.dependencySet = + keyData.dependencySet, keyData.oldDependencySet + + logErrorNonFatal("forKeysProcessorError", parseError(newOutKey)) + end + end + + -- save dependency values and add to main dependency set + for dependency in pairs(keyData.dependencySet) do + keyData.dependencyValues[dependency] = peek(dependency) + + self.dependencySet[dependency] = true + dependency.dependentSet[self] = true + end + end + + -- STEP 2: find keys that were removed + for outputKey, inputKey in pairs(keyOIMap) do + if newInputTable[inputKey] == nil then + -- clean up the old calculated value + local oldMetaValue = meta[outputKey] + + local destructOK, err = + pcall(self._destructor or cleanup, outputKey, oldMetaValue) + if not destructOK then + logErrorNonFatal("forKeysDestructorError", parseError(err)) + end + + -- remove data + oldInputTable[inputKey] = nil + meta[outputKey] = nil + keyOIMap[outputKey] = nil + keyIOMap[inputKey] = nil + outputTable[outputKey] = nil + self._keyData[inputKey] = nil + + -- if we removed a key, then the table/state changed + didChange = true + end + end + + return didChange +end + +--[[ + Returns the interior value of this state object. +]] +function class:_peek(): any + return self._outputTable +end + +function class:get() + logError "stateGetWasRemoved" +end + +local function ForKeys( + inputTable: PubTypes.CanBeState<{ [KI]: any }>, + processor: (KI) -> (KO, M?), + destructor: (KO, M?) -> ()? +): Types.ForKeys + local inputIsState = isState(inputTable) + + local self = setmetatable({ + type = "State", + kind = "ForKeys", + dependencySet = {}, + -- if we held strong references to the dependents, then they wouldn't be + -- able to get garbage collected when they fall out of scope + dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), + _oldDependencySet = {}, + + _processor = processor, + _destructor = destructor, + _inputIsState = inputIsState, + + _inputTable = inputTable, + _oldInputTable = {}, + _outputTable = {}, + _keyOIMap = {}, + _keyIOMap = {}, + _keyData = {}, + _meta = {}, + }, CLASS_METATABLE) + + self:update() + + return self +end + +return ForKeys diff --git a/Libraries/Fusion/State/ForPairs.luau b/Libraries/Fusion/State/ForPairs.luau new file mode 100644 index 0000000..03ef607 --- /dev/null +++ b/Libraries/Fusion/State/ForPairs.luau @@ -0,0 +1,340 @@ +--!nonstrict + +--[[ + Constructs a new ForPairs object which maps pairs of a table using + a `processor` function. + + Optionally, a `destructor` function can be specified for cleaning up values. + If omitted, the default cleanup function will be used instead. + + Additionally, a `meta` table/value can optionally be returned to pass data created + when running the processor to the destructor when the created object is cleaned up. +]] + +local PubTypes = require "../PubTypes" +local Types = require "../Types" +-- Logging +local parseError = require "../Logging/parseError" +local logErrorNonFatal = require "../Logging/logErrorNonFatal" +local logError = require "../Logging/logError" +local logWarn = require "../Logging/logWarn" +-- Utility +local cleanup = require "../Utility/cleanup" +local needsDestruction = require "../Utility/needsDestruction" +-- State +local peek = require "../State/peek" +local makeUseCallback = require "../State/makeUseCallback" +local isState = require "../State/isState" + +local class = {} + +local CLASS_METATABLE = { __index = class } +local WEAK_KEYS_METATABLE = { __mode = "k" } + +--[[ + Called when the original table is changed. + + This will firstly find any keys meeting any of the following criteria: + + - they were not previously present + - their associated value has changed + - a dependency used during generation of this value has changed + + It will recalculate those key/value pairs, storing information about any + dependencies used in the processor callback during value generation, and + save the new key/value pair to the output array. If it is overwriting an + older key/value pair, that older pair will be passed to the destructor + for cleanup. + + Finally, this function will find keys that are no longer present, and remove + their key/value pairs from the output table and pass them to the destructor. +]] +function class:update(): boolean + local inputIsState = self._inputIsState + local newInputTable = peek(self._inputTable) + local oldInputTable = self._oldInputTable + + local keyIOMap = self._keyIOMap + local meta = self._meta + + local didChange = false + + -- clean out main dependency set + for dependency in pairs(self.dependencySet) do + dependency.dependentSet[self] = nil + end + + self._oldDependencySet, self.dependencySet = + self.dependencySet, self._oldDependencySet + -- table.clear(self.dependencySet) + for i, _ in pairs(self.dependencySet) do + self.dependencySet[i] = nil + end + + -- if the input table is a state object, add it as a dependency + if inputIsState then + self._inputTable.dependentSet[self] = true + self.dependencySet[self._inputTable] = true + end + + -- clean out output table + self._oldOutputTable, self._outputTable = + self._outputTable, self._oldOutputTable + + local oldOutputTable = self._oldOutputTable + local newOutputTable = self._outputTable + -- table.clear(newOutputTable) + for i, _ in pairs(newOutputTable) do + newOutputTable[i] = nil + end + + -- Step 1: find key/value pairs that changed or were not previously present + + for newInKey, newInValue in pairs(newInputTable) do + -- get or create key data + local keyData = self._keyData[newInKey] + + if keyData == nil then + keyData = { + dependencySet = setmetatable({}, WEAK_KEYS_METATABLE), + oldDependencySet = setmetatable({}, WEAK_KEYS_METATABLE), + dependencyValues = setmetatable({}, WEAK_KEYS_METATABLE), + } + self._keyData[newInKey] = keyData + end + + -- check if the pair is new or changed + local shouldRecalculate = oldInputTable[newInKey] ~= newInValue + + -- check if the pair's dependencies have changed + if shouldRecalculate == false then + for dependency, oldValue in pairs(keyData.dependencyValues) do + if oldValue ~= peek(dependency) then + shouldRecalculate = true + break + end + end + end + + -- recalculate the output pair if necessary + if shouldRecalculate then + keyData.oldDependencySet, keyData.dependencySet = + keyData.dependencySet, keyData.oldDependencySet + -- table.clear(keyData.dependencySet) + for i, _ in pairs(keyData.dependencySet) do + keyData.dependencySet[i] = nil + end + + local use = makeUseCallback(keyData.dependencySet) + -- local processOK, newOutKey, newOutValue, newMetaValue = + -- xpcall(self._processor, parseError, use, newInKey, newInValue) + local processOK, newOutKey, newOutValue, newMetaValue = + pcall(self._processor, use, newInKey, newInValue) + + if processOK then + if + self._destructor == nil + and ( + needsDestruction(newOutKey) + or needsDestruction(newOutValue) + or needsDestruction(newMetaValue) + ) + then + logWarn "destructorNeededForPairs" + end + + -- if this key was already written to on this run-through, throw a fatal error. + if newOutputTable[newOutKey] ~= nil then + -- figure out which key/value pair previously wrote to this key + local previousNewKey, previousNewValue + for inKey, outKey in pairs(keyIOMap) do + if outKey == newOutKey then + previousNewValue = newInputTable[inKey] + if previousNewValue ~= nil then + previousNewKey = inKey + break + end + end + end + + if previousNewKey ~= nil then + logError( + "forPairsKeyCollision", + nil, + tostring(newOutKey), + tostring(previousNewKey), + tostring(previousNewValue), + tostring(newInKey), + tostring(newInValue) + ) + end + end + + local oldOutValue = oldOutputTable[newOutKey] + + if oldOutValue ~= newOutValue then + local oldMetaValue = meta[newOutKey] + if oldOutValue ~= nil then + local destructOK, err = pcall( + self._destructor or cleanup, + newOutKey, + oldOutValue, + oldMetaValue + ) + if not destructOK then + logErrorNonFatal( + "forPairsDestructorError", + parseError(err) + ) + end + end + + oldOutputTable[newOutKey] = nil + end + + -- update the stored data for this key/value pair + oldInputTable[newInKey] = newInValue + keyIOMap[newInKey] = newOutKey + meta[newOutKey] = newMetaValue + newOutputTable[newOutKey] = newOutValue + + -- if we had to recalculate the output, then we did change + didChange = true + else + -- restore old dependencies, because the new dependencies may be corrupt + keyData.oldDependencySet, keyData.dependencySet = + keyData.dependencySet, keyData.oldDependencySet + + logErrorNonFatal( + "forPairsProcessorError", + parseError(newOutKey) + ) + end + else + local storedOutKey = keyIOMap[newInKey] + + -- check for key collision + if newOutputTable[storedOutKey] ~= nil then + -- figure out which key/value pair previously wrote to this key + local previousNewKey, previousNewValue + for inKey, outKey in pairs(keyIOMap) do + if storedOutKey == outKey then + previousNewValue = newInputTable[inKey] + + if previousNewValue ~= nil then + previousNewKey = inKey + break + end + end + end + + if previousNewKey ~= nil then + logError( + "forPairsKeyCollision", + nil, + tostring(storedOutKey), + tostring(previousNewKey), + tostring(previousNewValue), + tostring(newInKey), + tostring(newInValue) + ) + end + end + + -- copy the stored key/value pair into the new output table + newOutputTable[storedOutKey] = oldOutputTable[storedOutKey] + end + + -- save dependency values and add to main dependency set + for dependency in pairs(keyData.dependencySet) do + keyData.dependencyValues[dependency] = peek(dependency) + + self.dependencySet[dependency] = true + dependency.dependentSet[self] = true + end + end + + -- STEP 2: find keys that were removed + for oldOutKey, oldOutValue in pairs(oldOutputTable) do + -- check if this key/value pair is in the new output table + if newOutputTable[oldOutKey] ~= oldOutValue then + -- clean up the old output pair + local oldMetaValue = meta[oldOutKey] + if oldOutValue ~= nil then + local destructOK, err = pcall( + self._destructor or cleanup, + oldOutKey, + oldOutValue, + oldMetaValue + ) + if not destructOK then + logErrorNonFatal("forPairsDestructorError", parseError(err)) + end + end + + -- check if the key was completely removed from the output table + if newOutputTable[oldOutKey] == nil then + meta[oldOutKey] = nil + self._keyData[oldOutKey] = nil + end + + didChange = true + end + end + + for key in pairs(oldInputTable) do + if newInputTable[key] == nil then + oldInputTable[key] = nil + keyIOMap[key] = nil + end + end + + return didChange +end + +--[[ + Returns the interior value of this state object. +]] +function class:_peek(): any + return self._outputTable +end + +function class:get() + logError "stateGetWasRemoved" +end + +local function ForPairs( + inputTable: PubTypes.CanBeState<{ [KI]: VI }>, + processor: (KI, VI) -> (KO, VO, M?), + destructor: (KO, VO, M?) -> ()? +): Types.ForPairs + local inputIsState = isState(inputTable) + + local self = setmetatable({ + type = "State", + kind = "ForPairs", + dependencySet = {}, + -- if we held strong references to the dependents, then they wouldn't be + -- able to get garbage collected when they fall out of scope + dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), + _oldDependencySet = {}, + + _processor = processor, + _destructor = destructor, + _inputIsState = inputIsState, + + _inputTable = inputTable, + _oldInputTable = {}, + _outputTable = {}, + _oldOutputTable = {}, + _keyIOMap = {}, + _keyData = {}, + _meta = {}, + }, CLASS_METATABLE) + + self:update() + + return self +end + +return ForPairs diff --git a/Libraries/Fusion/State/ForValues.luau b/Libraries/Fusion/State/ForValues.luau new file mode 100644 index 0000000..036101c --- /dev/null +++ b/Libraries/Fusion/State/ForValues.luau @@ -0,0 +1,272 @@ +--!nonstrict + +--[[ + Constructs a new ForValues object which maps values of a table using + a `processor` function. + + Optionally, a `destructor` function can be specified for cleaning up values. + If omitted, the default cleanup function will be used instead. + + Additionally, a `meta` table/value can optionally be returned to pass data created + when running the processor to the destructor when the created object is cleaned up. +]] +local PubTypes = require "../PubTypes" +local Types = require "../Types" +-- Logging +local parseError = require "../Logging/parseError" +local logError = require "../Logging/logError" +local logErrorNonFatal = require "../Logging/logErrorNonFatal" +local logWarn = require "../Logging/logWarn" +-- Utility +local cleanup = require "../Utility/cleanup" +local needsDestruction = require "../Utility/needsDestruction" +-- State +local peek = require "../State/peek" +local makeUseCallback = require "../State/makeUseCallback" +local isState = require "../State/isState" + +local class = {} + +local CLASS_METATABLE = { __index = class } +local WEAK_KEYS_METATABLE = { __mode = "k" } + +--[[ + Called when the original table is changed. + + This will firstly find any values meeting any of the following criteria: + + - they were not previously present + - a dependency used during generation of this value has changed + + It will recalculate those values, storing information about any dependencies + used in the processor callback during value generation, and save the new value + to the output array with the same key. If it is overwriting an older value, + that older value will be passed to the destructor for cleanup. + + Finally, this function will find values that are no longer present, and remove + their values from the output table and pass them to the destructor. You can re-use + the same value multiple times and this will function will update them as little as + possible; reusing the same values where possible. +]] +function class:update(): boolean + local inputIsState = self._inputIsState + local inputTable = peek(self._inputTable) + local outputValues = {} + + local didChange = false + + -- clean out value cache + self._oldValueCache, self._valueCache = + self._valueCache, self._oldValueCache + local newValueCache = self._valueCache + local oldValueCache = self._oldValueCache + -- table.clear(newValueCache) + for i, _ in pairs(newValueCache) do + newValueCache[i] = nil + end + + -- clean out main dependency set + for dependency in pairs(self.dependencySet) do + dependency.dependentSet[self] = nil + end + self._oldDependencySet, self.dependencySet = + self.dependencySet, self._oldDependencySet + -- table.clear(self.dependencySet) + for i, _ in pairs(self.dependencySet) do + self.dependencySet[i] = nil + end + + -- if the input table is a state object, add it as a dependency + if inputIsState then + self._inputTable.dependentSet[self] = true + self.dependencySet[self._inputTable] = true + end + + -- STEP 1: find values that changed or were not previously present + for inKey, inValue in pairs(inputTable) do + -- check if the value is new or changed + local oldCachedValues = oldValueCache[inValue] + local shouldRecalculate = oldCachedValues == nil + + -- get a cached value and its dependency/meta data if available + local value, valueData, meta + + if type(oldCachedValues) == "table" and #oldCachedValues > 0 then + local valueInfo = table.remove(oldCachedValues, #oldCachedValues) + value = valueInfo.value + valueData = valueInfo.valueData + meta = valueInfo.meta + + if #oldCachedValues <= 0 then + oldValueCache[inValue] = nil + end + elseif oldCachedValues ~= nil then + oldValueCache[inValue] = nil + shouldRecalculate = true + end + + if valueData == nil then + valueData = { + dependencySet = setmetatable({}, WEAK_KEYS_METATABLE), + oldDependencySet = setmetatable({}, WEAK_KEYS_METATABLE), + dependencyValues = setmetatable({}, WEAK_KEYS_METATABLE), + } + end + + -- check if the value's dependencies have changed + if shouldRecalculate == false then + for dependency, oldValue in pairs(valueData.dependencyValues) do + if oldValue ~= peek(dependency) then + shouldRecalculate = true + break + end + end + end + + -- recalculate the output value if necessary + if shouldRecalculate then + valueData.oldDependencySet, valueData.dependencySet = + valueData.dependencySet, valueData.oldDependencySet + -- table.clear(valueData.dependencySet) + for i, _ in pairs(valueData.dependencySet) do + valueData.dependencySet[i] = nil + end + + local use = makeUseCallback(valueData.dependencySet) + -- local processOK, newOutValue, newMetaValue = + -- xpcall(self._processor, parseError, use, inValue) + local processOK, newOutValue, newMetaValue = + pcall(self._processor, use, inValue) + + if processOK then + if + self._destructor == nil + and ( + needsDestruction(newOutValue) + or needsDestruction(newMetaValue) + ) + then + logWarn "destructorNeededForValues" + end + + -- pass the old value to the destructor if it exists + if value ~= nil then + local destructOK, err = + pcall(self._destructor or cleanup, value, meta) + if not destructOK then + logErrorNonFatal( + "forValuesDestructorError", + parseError(err) + ) + end + end + + -- store the new value and meta data + value = newOutValue + meta = newMetaValue + didChange = true + else + -- restore old dependencies, because the new dependencies may be corrupt + valueData.oldDependencySet, valueData.dependencySet = + valueData.dependencySet, valueData.oldDependencySet + logErrorNonFatal( + "forValuesProcessorError", + parseError(newOutValue) + ) + end + end + + -- store the value and its dependency/meta data + local newCachedValues = newValueCache[inValue] + if newCachedValues == nil then + newCachedValues = {} + newValueCache[inValue] = newCachedValues + end + + table.insert(newCachedValues, { + value = value, + valueData = valueData, + meta = meta, + }) + + outputValues[inKey] = value + + -- save dependency values and add to main dependency set + for dependency in pairs(valueData.dependencySet) do + valueData.dependencyValues[dependency] = peek(dependency) + + self.dependencySet[dependency] = true + dependency.dependentSet[self] = true + end + end + + -- STEP 2: find values that were removed + -- for tables of data, we just need to check if it's still in the cache + for _oldInValue, oldCachedValueInfo in pairs(oldValueCache) do + for _, valueInfo in ipairs(oldCachedValueInfo) do + local oldValue = valueInfo.value + local oldMetaValue = valueInfo.meta + + local destructOK, err = + pcall(self._destructor or cleanup, oldValue, oldMetaValue) + if not destructOK then + logErrorNonFatal("forValuesDestructorError", parseError(err)) + end + + didChange = true + end + + -- table.clear(oldCachedValueInfo) + for i, _ in pairs(oldCachedValueInfo) do + oldCachedValueInfo[i] = nil + end + end + + self._outputTable = outputValues + + return didChange +end + +--[[ + Returns the interior value of this state object. +]] +function class:_peek(): any + return self._outputTable +end + +function class:get() + logError "stateGetWasRemoved" +end + +local function ForValues( + inputTable: PubTypes.CanBeState<{ [any]: VI }>, + processor: (VI) -> (VO, M?), + destructor: (VO, M?) -> ()? +): Types.ForValues + local inputIsState = isState(inputTable) + + local self = setmetatable({ + type = "State", + kind = "ForValues", + dependencySet = {}, + -- if we held strong references to the dependents, then they wouldn't be + -- able to get garbage collected when they fall out of scope + dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), + _oldDependencySet = {}, + + _processor = processor, + _destructor = destructor, + _inputIsState = inputIsState, + + _inputTable = inputTable, + _outputTable = {}, + _valueCache = {}, + _oldValueCache = {}, + }, CLASS_METATABLE) + + self:update() + + return self +end + +return ForValues diff --git a/Libraries/Fusion/State/Observer.luau b/Libraries/Fusion/State/Observer.luau new file mode 100644 index 0000000..f457be2 --- /dev/null +++ b/Libraries/Fusion/State/Observer.luau @@ -0,0 +1,90 @@ +--!nonstrict + +--[[ + Constructs a new state object which can listen for updates on another state + object. + + FIXME: enabling strict types here causes free types to leak +]] + +local PubTypes = require "../PubTypes" +local Types = require "../Types" +local External = require "../External" + +type Set = { [T]: any } + +local class = {} +local CLASS_METATABLE = { __index = class } + +-- Table used to hold Observer objects in memory. +local strongRefs: Set = {} + +--[[ + Called when the watched state changes value. +]] +function class:update(): boolean + for _, callback in pairs(self._changeListeners) do + External.doTaskImmediate(callback) + end + return false +end + +--[[ + Adds a change listener. When the watched state changes value, the listener + will be fired. + + Returns a function which, when called, will disconnect the change listener. + As long as there is at least one active change listener, this Observer + will be held in memory, preventing GC, so disconnecting is important. +]] +function class:onChange(callback: () -> ()): () -> () + local uniqueIdentifier = {} + + self._numChangeListeners += 1 + self._changeListeners[uniqueIdentifier] = callback + + -- disallow gc (this is important to make sure changes are received) + strongRefs[self] = true + + local disconnected = false + return function() + if disconnected then + return + end + disconnected = true + self._changeListeners[uniqueIdentifier] = nil + self._numChangeListeners -= 1 + + if self._numChangeListeners == 0 then + -- allow gc if all listeners are disconnected + strongRefs[self] = nil + end + end +end + +--[[ + Similar to `class:onChange()`, however it runs the provided callback + immediately. +]] +function class:onBind(callback: () -> ()): () -> () + External.doTaskImmediate(callback) + return self:onChange(callback) +end + +local function Observer(watchedState: PubTypes.Value): Types.Observer + local self = setmetatable({ + type = "State", + kind = "Observer", + dependencySet = { [watchedState] = true }, + dependentSet = {}, + _changeListeners = {}, + _numChangeListeners = 0, + }, CLASS_METATABLE) + + -- add this object to the watched state's dependent set + watchedState.dependentSet[self] = true + + return self +end + +return Observer diff --git a/Libraries/Fusion/State/Value.luau b/Libraries/Fusion/State/Value.luau new file mode 100644 index 0000000..01da6b1 --- /dev/null +++ b/Libraries/Fusion/State/Value.luau @@ -0,0 +1,60 @@ +--!nonstrict + +--[[ + Constructs and returns objects which can be used to model independent + reactive state. +]] + +local Types = require "../Types" +-- Logging +local logError = require "../Logging/logError" +-- State +local updateAll = require "../State/updateAll" +-- Utility +local isSimilar = require "../Utility/isSimilar" + +local class = {} + +local CLASS_METATABLE = { __index = class } +local WEAK_KEYS_METATABLE = { __mode = "k" } + +--[[ + Updates the value stored in this State object. + + If `force` is enabled, this will skip equality checks and always update the + state object and any dependents - use this with care as this can lead to + unnecessary updates. +]] +function class:set(newValue: any, force: boolean?) + local oldValue = self._value + if force or not isSimilar(oldValue, newValue) then + self._value = newValue + updateAll(self) + end +end + +--[[ + Returns the interior value of this state object. +]] +function class:_peek(): any + return self._value +end + +function class:get() + logError "stateGetWasRemoved" +end + +local function Value(initialValue: T): Types.State + local self = setmetatable({ + type = "State", + kind = "Value", + -- if we held strong references to the dependents, then they wouldn't be + -- able to get garbage collected when they fall out of scope + dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), + _value = initialValue, + }, CLASS_METATABLE) + + return self +end + +return Value diff --git a/Libraries/Fusion/State/isState.luau b/Libraries/Fusion/State/isState.luau new file mode 100644 index 0000000..45c5a35 --- /dev/null +++ b/Libraries/Fusion/State/isState.luau @@ -0,0 +1,11 @@ +--!strict + +--[[ + Returns true if the given value can be assumed to be a valid state object. +]] + +local function isState(target: any): boolean + return type(target) == "table" and type(target._peek) == "function" +end + +return isState diff --git a/Libraries/Fusion/State/makeUseCallback.luau b/Libraries/Fusion/State/makeUseCallback.luau new file mode 100644 index 0000000..3120430 --- /dev/null +++ b/Libraries/Fusion/State/makeUseCallback.luau @@ -0,0 +1,25 @@ +--!strict + +--[[ + Constructs a 'use callback' for the purposes of collecting dependencies. +]] + +local PubTypes = require "../PubTypes" +local Types = require "../Types" +-- State +local isState = require "../State/isState" + +type Set = { [T]: any } + +local function makeUseCallback(dependencySet: Set) + local function use(target: PubTypes.CanBeState): T + if isState(target) then + dependencySet[target] = true + return (target :: Types.StateObject):_peek() + end + return target + end + return use +end + +return makeUseCallback diff --git a/Libraries/Fusion/State/peek.luau b/Libraries/Fusion/State/peek.luau new file mode 100644 index 0000000..2fd67ab --- /dev/null +++ b/Libraries/Fusion/State/peek.luau @@ -0,0 +1,19 @@ +--!strict + +--[[ + A common interface for accessing the values of state objects or constants. +]] + +local PubTypes = require "../PubTypes" +local Types = require "../Types" +-- State +local isState = require "../State/isState" + +local function peek(target: PubTypes.CanBeState): T + if isState(target) then + return (target :: Types.StateObject):_peek() + end + return target +end + +return peek diff --git a/Libraries/Fusion/State/updateAll.luau b/Libraries/Fusion/State/updateAll.luau new file mode 100644 index 0000000..865e649 --- /dev/null +++ b/Libraries/Fusion/State/updateAll.luau @@ -0,0 +1,67 @@ +--!strict + +--[[ + Given a reactive object, updates all dependent reactive objects. + Objects are only ever updated after all of their dependencies are updated, + are only ever updated once, and won't be updated if their dependencies are + unchanged. +]] + +local PubTypes = require "../PubTypes" + +type Set = { [T]: any } +type Descendant = (PubTypes.Dependent & PubTypes.Dependency) | PubTypes.Dependent + +-- Credit: https://blog.elttob.uk/2022/11/07/sets-efficient-topological-search.html +local function updateAll(root: PubTypes.Dependency) + local counters: { [Descendant]: number } = {} + local flags: { [Descendant]: boolean } = {} + local queue: { Descendant } = {} + local queueSize = 0 + local queuePos = 1 + + for object in pairs(root.dependentSet) do + queueSize += 1 + queue[queueSize] = object + flags[object] = true + end + + -- Pass 1: counting up + while queuePos <= queueSize do + local next = queue[queuePos] + local counter = counters[next] + if counter == nil then + counters[next] = 1 + else + counters[next] = counter + 1 + end + if next.dependentSet ~= nil then + for object in pairs(next.dependentSet) do + queueSize += 1 + queue[queueSize] = object + end + end + queuePos += 1 + end + + -- Pass 2: counting down + processing + queuePos = 1 + while queuePos <= queueSize do + local next = queue[queuePos] + local counter = counters[next] - 1 + counters[next] = counter + if + counter == 0 + and flags[next] + and next:update() + and next.dependentSet ~= nil + then + for object in pairs(next.dependentSet) do + flags[object] = true + end + end + queuePos += 1 + end +end + +return updateAll diff --git a/Libraries/Fusion/Types.luau b/Libraries/Fusion/Types.luau new file mode 100644 index 0000000..8470b30 --- /dev/null +++ b/Libraries/Fusion/Types.luau @@ -0,0 +1,159 @@ +--!strict + +--[[ + Stores common type information used internally. + + These types may be used internally so Fusion code can type-check, but + should never be exposed to public users, as these definitions are fair game + for breaking changes. +]] + +local PubTypes = require "./PubTypes" + +type Set = { [T]: any } + +--[[ + General use types +]] + +-- A symbol that represents the absence of a value. +export type None = PubTypes.Symbol & { + -- name: "None" (add this when Luau supports singleton types) +} + +-- Stores useful information about Luau errors. +export type Error = { + type: string, -- replace with "Error" when Luau supports singleton types + raw: string, + message: string, + trace: string, +} + +-- An object which stores a value scoped in time. +export type Contextual = PubTypes.Contextual & { + _valuesNow: { [thread]: { value: T } }, + _defaultValue: T, +} + +--[[ + Generic reactive graph types +]] + +export type StateObject = PubTypes.StateObject & { + _peek: (StateObject) -> T, +} + +--[[ + Specific reactive graph types +]] + +-- A state object whose value can be set at any time by the user. +export type State = PubTypes.Value & { + _value: T, +} + +-- A state object whose value is derived from other objects using a callback. +export type Computed = PubTypes.Computed & { + _oldDependencySet: Set, + _callback: (PubTypes.Use) -> T, + _value: T, +} + +-- A state object whose value is derived from other objects using a callback. +export type ForPairs = PubTypes.ForPairs & { + _oldDependencySet: Set, + _processor: (PubTypes.Use, KI, VI) -> (KO, VO), + _destructor: (VO, M?) -> (), + _inputIsState: boolean, + _inputTable: PubTypes.CanBeState<{ [KI]: VI }>, + _oldInputTable: { [KI]: VI }, + _outputTable: { [KO]: VO }, + _oldOutputTable: { [KO]: VO }, + _keyIOMap: { [KI]: KO }, + _meta: { [KO]: M? }, + _keyData: { + [KI]: { + dependencySet: Set, + oldDependencySet: Set, + dependencyValues: { [PubTypes.Dependency]: any }, + }, + }, +} + +-- A state object whose value is derived from other objects using a callback. +export type ForKeys = PubTypes.ForKeys & { + _oldDependencySet: Set, + _processor: (PubTypes.Use, KI) -> (KO), + _destructor: (KO, M?) -> (), + _inputIsState: boolean, + _inputTable: PubTypes.CanBeState<{ [KI]: KO }>, + _oldInputTable: { [KI]: KO }, + _outputTable: { [KO]: any }, + _keyOIMap: { [KO]: KI }, + _meta: { [KO]: M? }, + _keyData: { + [KI]: { + dependencySet: Set, + oldDependencySet: Set, + dependencyValues: { [PubTypes.Dependency]: any }, + }, + }, +} + +-- A state object whose value is derived from other objects using a callback. +export type ForValues = PubTypes.ForValues & { + _oldDependencySet: Set, + _processor: (PubTypes.Use, VI) -> (VO), + _destructor: (VO, M?) -> (), + _inputIsState: boolean, + _inputTable: PubTypes.CanBeState<{ [VI]: VO }>, + _outputTable: { [any]: VI }, + _valueCache: { [VO]: any }, + _oldValueCache: { [VO]: any }, + _meta: { [VO]: M? }, + _valueData: { + [VI]: { + dependencySet: Set, + oldDependencySet: Set, + dependencyValues: { [PubTypes.Dependency]: any }, + }, + }, +} + +-- A state object which follows another state object using tweens. +export type Tween = PubTypes.Tween & { + _goalState: State, + _tweenInfo: TweenInfo, + _prevValue: T, + _nextValue: T, + _currentValue: T, + _currentTweenInfo: TweenInfo, + _currentTweenDuration: number, + _currentTweenStartTime: number, + _currentlyAnimating: boolean, +} + +-- A state object which follows another state object using spring simulation. +export type Spring = PubTypes.Spring & { + _speed: PubTypes.CanBeState, + _speedIsState: boolean, + _lastSpeed: number, + _damping: PubTypes.CanBeState, + _dampingIsState: boolean, + _lastDamping: number, + _goalState: State, + _goalValue: T, + _currentType: string, + _currentValue: T, + _springPositions: { number }, + _springGoals: { number }, + _springVelocities: { number }, +} + +-- An object which can listen for updates on another state object. +export type Observer = PubTypes.Observer & { + _changeListeners: Set<() -> ()>, + _numChangeListeners: number, +} + +return nil diff --git a/Libraries/Fusion/Utility/Contextual.luau b/Libraries/Fusion/Utility/Contextual.luau new file mode 100644 index 0000000..193df50 --- /dev/null +++ b/Libraries/Fusion/Utility/Contextual.luau @@ -0,0 +1,74 @@ +--!strict +--!nolint LocalShadow + +--[[ + Time-based contextual values, to allow for transparently passing values down + the call stack. +]] + +local Types = require "../Types" +-- Logging +local logError = require "../Logging/logError" +local parseError = require "../Logging/parseError" + +local class = {} + +local CLASS_METATABLE = { __index = class } +local WEAK_KEYS_METATABLE = { __mode = "k" } + +--[[ + Returns the current value of this contextual. +]] +function class:now(): unknown + local thread = coroutine.running() + local value = self._valuesNow[thread] + if typeof(value) ~= "table" then + return self._defaultValue + else + return value.value :: unknown + end +end + +--[[ + Temporarily assigns a value to this contextual. +]] +function class:is(newValue: unknown) + local methods = {} + -- Methods use colon `:` syntax for consistency and autocomplete but we + -- actually want them to operate on the `self` from this outer lexical scope + local contextual = self + + function methods:during(callback: (A...) -> T, ...: A...): T + local thread = coroutine.running() + local prevValue = contextual._valuesNow[thread] + -- Storing the value in this format allows us to distinguish storing + -- `nil` from not calling `:during()` at all. + contextual._valuesNow[thread] = { value = newValue } + -- local ok, value = xpcall(callback, parseError, ...) + local ok, value = pcall(callback, ...) + + contextual._valuesNow[thread] = prevValue + if ok then + return value + else + logError("contextualCallbackError", parseError(value)) + end + end + + return methods +end + +local function Contextual(defaultValue: T): Types.Contextual + local self = setmetatable({ + type = "Contextual", + -- if we held strong references to threads here, then if a thread was + -- killed before this contextual had a chance to finish executing its + -- callback, it would be held strongly in this table forever + _valuesNow = setmetatable({}, WEAK_KEYS_METATABLE), + _defaultValue = defaultValue, + }, CLASS_METATABLE) + + return self +end + +return Contextual diff --git a/Libraries/Fusion/Utility/None.luau b/Libraries/Fusion/Utility/None.luau new file mode 100644 index 0000000..6a40a49 --- /dev/null +++ b/Libraries/Fusion/Utility/None.luau @@ -0,0 +1,12 @@ +--!strict + +--[[ + A symbol for representing nil values in contexts where nil is not usable. +]] + +local Types = require "../Types" + +return { + type = "Symbol", + name = "None", +} :: Types.None diff --git a/Libraries/Fusion/Utility/cleanup.luau b/Libraries/Fusion/Utility/cleanup.luau new file mode 100644 index 0000000..b67c299 --- /dev/null +++ b/Libraries/Fusion/Utility/cleanup.luau @@ -0,0 +1,54 @@ +--!strict + +--[[ + Cleans up the tasks passed in as the arguments. + A task can be any of the following: + + - an Instance - will be destroyed + - an RBXScriptConnection - will be disconnected + - a function - will be run + - a table with a `Destroy` or `destroy` function - will be called + - an array - `cleanup` will be called on each item +]] + +local typeof = require "../Polyfill/typeof" + +local function cleanupOne(task: any) + local taskType = typeof(task) + + -- case 1: Instance + if taskType == "Instance" then + task:Destroy() + + -- case 2: RBXScriptConnection + elseif taskType == "RBXScriptConnection" then + task:disconnect() + + -- case 3: callback + elseif taskType == "function" then + task() + elseif taskType == "table" then + -- case 4: destroy() function + if type(task.destroy) == "function" then + task:destroy() + + -- case 5: Destroy() function + elseif type(task.Destroy) == "function" then + task:Destroy() + + -- case 6: array of tasks + elseif task[1] ~= nil then + for _, subtask in ipairs(task) do + cleanupOne(subtask) + end + end + end +end + +local function cleanup(...: any) + for index = 1, select("#", ...) do + cleanupOne(select(index, ...)) + end +end + +return cleanup diff --git a/Libraries/Fusion/Utility/doNothing.luau b/Libraries/Fusion/Utility/doNothing.luau new file mode 100644 index 0000000..b2cd5bc --- /dev/null +++ b/Libraries/Fusion/Utility/doNothing.luau @@ -0,0 +1,9 @@ +--!strict + +--[[ + An empty function. Often used as a destructor to indicate no destruction. +]] + +local function doNothing(...: any) end + +return doNothing diff --git a/Libraries/Fusion/Utility/isSimilar.luau b/Libraries/Fusion/Utility/isSimilar.luau new file mode 100644 index 0000000..ac93baf --- /dev/null +++ b/Libraries/Fusion/Utility/isSimilar.luau @@ -0,0 +1,17 @@ +--!strict +--[[ + Returns true if A and B are 'similar' - i.e. any user of A would not need + to recompute if it changed to B. +]] + +local function isSimilar(a: any, b: any): boolean + -- HACK: because tables are mutable data structures, don't make assumptions + -- about similarity from equality for now (see issue #44) + if type(a) == "table" then + return false + end + -- NaN does not equal itself but is the same + return a == b or a ~= a and b ~= b +end + +return isSimilar diff --git a/Libraries/Fusion/Utility/needsDestruction.luau b/Libraries/Fusion/Utility/needsDestruction.luau new file mode 100644 index 0000000..1ee6eb0 --- /dev/null +++ b/Libraries/Fusion/Utility/needsDestruction.luau @@ -0,0 +1,14 @@ +--!strict + +--[[ + Returns true if the given value is not automatically memory managed, and + requires manual cleanup. +]] + +local typeof = require "../Polyfill/typeof" + +local function needsDestruction(x: any): boolean + return typeof(x) == "Instance" +end + +return needsDestruction diff --git a/Libraries/Fusion/Utility/restrictRead.luau b/Libraries/Fusion/Utility/restrictRead.luau new file mode 100644 index 0000000..9a279cc --- /dev/null +++ b/Libraries/Fusion/Utility/restrictRead.luau @@ -0,0 +1,27 @@ +--!strict + +--[[ + Restricts the reading of missing members for a table. +]] + +local logError = require "../Logging/logError" + +type table = { [any]: any } + +local function restrictRead(tableName: string, strictTable: table): table + -- FIXME: Typed Luau doesn't recognise this correctly yet + local metatable = getmetatable(strictTable :: any) + + if metatable == nil then + metatable = {} + setmetatable(strictTable, metatable) + end + + function metatable:__index(memberName) + logError("strictReadError", nil, tostring(memberName), tableName) + end + + return strictTable +end + +return restrictRead diff --git a/Libraries/Fusion/Utility/xtypeof.luau b/Libraries/Fusion/Utility/xtypeof.luau new file mode 100644 index 0000000..19187f1 --- /dev/null +++ b/Libraries/Fusion/Utility/xtypeof.luau @@ -0,0 +1,21 @@ +--!strict + +--[[ + Extended typeof, designed for identifying custom objects. + If given a table with a `type` string, returns that. + Otherwise, returns `typeof()` the argument. +]] + +local typeof = require "../Polyfill/typeof" + +local function xtypeof(x: any) + local typeString = typeof(x) + + if typeString == "table" and type(x.type) == "string" then + return x.type + else + return typeString + end +end + +return xtypeof diff --git a/Libraries/Fusion/init.luau b/Libraries/Fusion/init.luau new file mode 100644 index 0000000..43ac0e8 --- /dev/null +++ b/Libraries/Fusion/init.luau @@ -0,0 +1,115 @@ +--!strict + +--[[ + The entry point for the Fusion library. +]] + +local PubTypes = require "./PubTypes" +local External = require "./External" +local restrictRead = require "./Utility/restrictRead" + +-- Down the line, this will be conditional based on whether Fusion is being +-- compiled for Mercury. +do + local MercuryExternal = require "./MercuryExternal" + External.setExternalScheduler(MercuryExternal) +end + +local Fusion = restrictRead("Fusion", { + version = { major = 0, minor = 3, isRelease = false }, + + New = require "./Instances/New", + Hydrate = require "./Instances/Hydrate", + Ref = require "./Instances/Ref", + Out = require "./Instances/Out", + Cleanup = require "./Instances/Cleanup", + Children = require "./Instances/Children", + OnEvent = require "./Instances/OnEvent", + OnChange = require "./Instances/OnChange", + + Value = require "./State/Value", + Computed = require "./State/Computed", + ForPairs = require "./State/ForPairs", + ForKeys = require "./State/ForKeys", + ForValues = require "./State/ForValues", + Observer = require "./State/Observer", + + Tween = require "./Animation/Tween", + Spring = require "./Animation/Spring", + + Contextual = require "./Utility/Contextual", + cleanup = require "./Utility/cleanup", + doNothing = require "./Utility/doNothing", + peek = require "./State/peek", + + typeof = require "./Polyfill/typeof", + TweenInfo = require "./Polyfill/TweenInfo", + + Help = function() + return "See https://elttob.uk/Fusion/0.3/ for more information." + end, +}) :: Fusion + +export type StateObject = PubTypes.StateObject +export type CanBeState = PubTypes.CanBeState +export type Symbol = PubTypes.Symbol +export type Value = PubTypes.Value +export type Computed = PubTypes.Computed +export type ForPairs = PubTypes.ForPairs +export type ForKeys = PubTypes.ForKeys +export type ForValues = PubTypes.ForKeys +export type Observer = PubTypes.Observer +export type Tween = PubTypes.Tween +export type Spring = PubTypes.Spring +export type Use = PubTypes.Use +export type Contextual = PubTypes.Contextual + +type Fusion = { + version: PubTypes.Version, + + New: ( + className: string + ) -> ((propertyTable: PubTypes.PropertyTable) -> Instance), + Hydrate: ( + target: Instance + ) -> ((propertyTable: PubTypes.PropertyTable) -> Instance), + Ref: PubTypes.SpecialKey, + Cleanup: PubTypes.SpecialKey, + Children: PubTypes.SpecialKey, + Out: (propertyName: string) -> PubTypes.SpecialKey, + OnEvent: (eventName: string) -> PubTypes.SpecialKey, + OnChange: (propertyName: string) -> PubTypes.SpecialKey, + + Value: (initialValue: T) -> Value, + Computed: (callback: (Use) -> T, destructor: (T) -> ()?) -> Computed, + ForPairs: ( + inputTable: CanBeState<{ [KI]: VI }>, + processor: (Use, KI, VI) -> (KO, VO, M?), + destructor: (KO, VO, M?) -> ()? + ) -> ForPairs, + ForKeys: ( + inputTable: CanBeState<{ [KI]: any }>, + processor: (Use, KI) -> (KO, M?), + destructor: (KO, M?) -> ()? + ) -> ForKeys, + ForValues: ( + inputTable: CanBeState<{ [any]: VI }>, + processor: (Use, VI) -> (VO, M?), + destructor: (VO, M?) -> ()? + ) -> ForValues, + Observer: (watchedState: StateObject) -> Observer, + + Tween: (goalState: StateObject, tweenInfo: TweenInfo?) -> Tween, + Spring: ( + goalState: StateObject, + speed: CanBeState?, + damping: CanBeState? + ) -> Spring, + + Contextual: (defaultValue: T) -> Contextual, + cleanup: (...any) -> (), + doNothing: (...any) -> (), + peek: Use, +} + +return Fusion diff --git a/README.md b/README.md index 1f1a387..43cdb7d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # Corescripts -After installing Aftman and running `aftman install`, run `./compile.sh` to compile the corescripts from ./luau/\*.luau to ./processed/\*.lua. +After installing Aftman and running `aftman install`, run `./corescripts/compile.sh` to compile the corescripts and libraries from this directory to ./corescripts/processed. diff --git a/compile.sh b/compile.sh index bfd0355..dd505b5 100644 --- a/compile.sh +++ b/compile.sh @@ -3,6 +3,9 @@ for file in ./corescripts/luau/[0-9]*.luau; do darklua process -c dense.json5 $file ./corescripts/processed/$(basename "${file::-1}") done +echo "Processing libraries..." +darklua process -c dense.json5 ./corescripts/Libraries/Fusion/init.luau ./corescripts/processed/10000001.lua + echo "Processing other corescripts..." for file in ./corescripts/luau/[a-z]*.luau; do darklua process -c lines.json5 $file ./corescripts/processed/$(basename "${file::-1}") diff --git a/luau/10000001.luau b/luau/10000001.luau deleted file mode 100644 index 1c6cc5a..0000000 --- a/luau/10000001.luau +++ /dev/null @@ -1,3417 +0,0 @@ -local __DARKLUA_BUNDLE_MODULES = {} - -do - __DARKLUA_BUNDLE_MODULES.c = { - cannotAssignProperty = "The class type '%s' has no assignable property '%s'.", - cannotConnectChange = "The %s class doesn't have a property called '%s'.", - cannotConnectEvent = "The %s class doesn't have an event called '%s'.", - cannotCreateClass = "Can't create a new instance of class '%s'.", - computedCallbackError = "Computed callback error: ERROR_MESSAGE", - destructorNeededValue = [[To save instances into Values, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.]], - destructorNeededComputed = [[To return instances from Computeds, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.]], - multiReturnComputed = [[Returning multiple values from Computeds is discouraged, as behaviour will change soon - see discussion #189 on GitHub.]], - destructorNeededForKeys = [[To return instances from ForKeys, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.]], - destructorNeededForValues = [[To return instances from ForValues, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.]], - destructorNeededForPairs = [[To return instances from ForPairs, provide a destructor function. This will be an error soon - see discussion #183 on GitHub.]], - forKeysProcessorError = "ForKeys callback error: ERROR_MESSAGE", - forKeysKeyCollision = [[ForKeys should only write to output key '%s' once when processing key changes, but it wrote to it twice. Previously input key: '%s'; New input key: '%s']], - forKeysDestructorError = "ForKeys destructor error: ERROR_MESSAGE", - forPairsDestructorError = "ForPairs destructor error: ERROR_MESSAGE", - forPairsKeyCollision = [[ForPairs should only write to output key '%s' once when processing key changes, but it wrote to it twice. Previous input pair: '[%s] = %s'; New input pair: '[%s] = %s']], - forPairsProcessorError = "ForPairs callback error: ERROR_MESSAGE", - forValuesProcessorError = "ForValues callback error: ERROR_MESSAGE", - forValuesDestructorError = "ForValues destructor error: ERROR_MESSAGE", - invalidChangeHandler = [[The change handler for the '%s' property must be a function.]], - invalidEventHandler = "The handler for the '%s' event must be a function.", - invalidPropertyType = "'%s.%s' expected a '%s' type, but got a '%s' type.", - invalidRefType = "Instance refs must be Value objects.", - invalidOutType = "[Out] properties must be given Value objects.", - invalidOutProperty = "The %s class doesn't have a property called '%s'.", - invalidSpringDamping = [[The damping ratio for a spring must be >= 0. (damping was %.2f)]], - invalidSpringSpeed = "The speed of a spring must be >= 0. (speed was %.2f)", - mistypedSpringDamping = "The damping ratio for a spring must be a number. (got a %s)", - mistypedSpringSpeed = "The speed of a spring must be a number. (got a %s)", - mistypedTweenInfo = "The tween info of a tween must be a TweenInfo. (got a %s)", - noTaskScheduler = "Fusion is not connected to an external task scheduler.", - springTypeMismatch = "The type '%s' doesn't match the spring's type '%s'.", - stateGetWasRemoved = [[`StateObject:get()` has been replaced by `use()` and `peek()` - see discussion #217 on GitHub.]], - strictReadError = "'%s' is not a valid member of '%s'.", - unknownMessage = "Unknown error: ERROR_MESSAGE", - unrecognisedChildType = "'%s' type children aren't accepted by `[Children]`.", - unrecognisedPropertyKey = "'%s' keys aren't accepted in property tables.", - unrecognisedPropertyStage = [['%s' isn't a valid stage for a special key to be applied at.]], - invalidEasingStyle = [[The easing style must be a valid Enum.EasingStyle or a string of 'Linear', 'Quad', 'Cubic', 'Quart', 'Quint', 'Sine', 'Exponential', 'Circular', 'Elastic', 'Back', 'Bounce'. (got %s)]], - invalidEasingDirection = [[The easing direction must be a valid Enum.EasingDirection or a string of 'In', 'Out', 'InOut', 'OutIn'. (got %s)]], - } -end -do - local messages = __DARKLUA_BUNDLE_MODULES.c - - local function logError(messageID, errObj, ...) - local formatString - - if messages[messageID] ~= nil then - formatString = messages[messageID] - else - messageID = "unknownMessage" - formatString = messages[messageID] - end - - local errorString - - if errObj == nil then - errorString = string.format( - "[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")", - ... - ) - else - formatString = - formatString:gsub("ERROR_MESSAGE", tostring(errObj.message)) - errorString = string.format( - "[Fusion] " - .. formatString - .. "\n(ID: " - .. messageID - .. ")\n---- Stack trace ----\n" - .. tostring(errObj.trace), - ... - ) - end - - error(errorString:gsub("\n", "\n "), 0) - end - - __DARKLUA_BUNDLE_MODULES.d = logError -end -do - local logError = __DARKLUA_BUNDLE_MODULES.d - local External = {} - local updateStepCallbacks = {} - local currentScheduler = nil - local lastUpdateStep = 0 - - function External.setExternalScheduler(newScheduler) - local oldScheduler = currentScheduler - - if oldScheduler ~= nil then - oldScheduler.stopScheduler() - end - - currentScheduler = newScheduler - - if newScheduler ~= nil then - newScheduler.startScheduler() - end - - return oldScheduler - end - function External.doTaskImmediate(resume) - if currentScheduler == nil then - logError "noTaskScheduler" - else - currentScheduler.doTaskImmediate(resume) - end - end - function External.doTaskDeferred(resume) - if currentScheduler == nil then - logError "noTaskScheduler" - else - currentScheduler.doTaskDeferred(resume) - end - end - function External.bindToUpdateStep(callback) - local uniqueIdentifier = {} - - updateStepCallbacks[uniqueIdentifier] = callback - - return function() - updateStepCallbacks[uniqueIdentifier] = nil - end - end - function External.performUpdateStep(now) - lastUpdateStep = now - - for _, callback in pairs(updateStepCallbacks) do - callback(now) - end - end - function External.lastUpdateStep() - return lastUpdateStep - end - - __DARKLUA_BUNDLE_MODULES.e = External -end -do - local logError = __DARKLUA_BUNDLE_MODULES.d - - local function restrictRead(tableName, strictTable) - local metatable = getmetatable(strictTable) - - if metatable == nil then - metatable = {} - - setmetatable(strictTable, metatable) - end - - function metatable:__index(memberName) - logError("strictReadError", nil, tostring(memberName), tableName) - end - - return strictTable - end - - __DARKLUA_BUNDLE_MODULES.f = restrictRead -end -do - local RunService = game:GetService "RunService" - local External = __DARKLUA_BUNDLE_MODULES.e - local MercuryExternal = {} - - function MercuryExternal.doTaskImmediate(resume) - Spawn(resume) - end - function MercuryExternal.doTaskDeferred(resume) - coroutine.resume(coroutine.create(resume)) - end - - local function performUpdateStep() - External.performUpdateStep(tick()) - end - - local stopSchedulerFunc = nil - - function MercuryExternal.startScheduler() - if stopSchedulerFunc ~= nil then - return - end - - local conn = RunService.RenderStepped:connect(performUpdateStep) - - stopSchedulerFunc = function() - conn:disconnect() - end - end - function MercuryExternal.stopScheduler() - if stopSchedulerFunc ~= nil then - stopSchedulerFunc() - - stopSchedulerFunc = nil - end - end - - __DARKLUA_BUNDLE_MODULES.g = MercuryExternal -end -do - __DARKLUA_BUNDLE_MODULES.h = { - BillboardGui = { Active = true }, - Frame = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - }, - TextLabel = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - Font = Enum.Font.SourceSans, - Text = "", - TextColor3 = Color3.new(0, 0, 0), - FontSize = Enum.FontSize.Size14, - }, - TextButton = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - AutoButtonColor = false, - Font = Enum.Font.SourceSans, - Text = "", - TextColor3 = Color3.new(0, 0, 0), - FontSize = Enum.FontSize.Size14, - }, - TextBox = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - ClearTextOnFocus = false, - Font = Enum.Font.SourceSans, - Text = "", - TextColor3 = Color3.new(0, 0, 0), - FontSize = Enum.FontSize.Size14, - }, - ImageLabel = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - }, - ImageButton = { - BackgroundColor3 = Color3.new(1, 1, 1), - BorderColor3 = Color3.new(0, 0, 0), - BorderSizePixel = 0, - AutoButtonColor = false, - }, - SpawnLocation = { Duration = 0 }, - Part = { - Anchored = true, - Size = Vector3.new(1, 1, 1), - FrontSurface = Enum.SurfaceType.Smooth, - BackSurface = Enum.SurfaceType.Smooth, - LeftSurface = Enum.SurfaceType.Smooth, - RightSurface = Enum.SurfaceType.Smooth, - TopSurface = Enum.SurfaceType.Smooth, - BottomSurface = Enum.SurfaceType.Smooth, - }, - TrussPart = { - Anchored = true, - Size = Vector3.new(2, 2, 2), - FrontSurface = Enum.SurfaceType.Smooth, - BackSurface = Enum.SurfaceType.Smooth, - LeftSurface = Enum.SurfaceType.Smooth, - RightSurface = Enum.SurfaceType.Smooth, - TopSurface = Enum.SurfaceType.Smooth, - BottomSurface = Enum.SurfaceType.Smooth, - }, - CornerWedgePart = { - Anchored = true, - Size = Vector3.new(1, 1, 1), - FrontSurface = Enum.SurfaceType.Smooth, - BackSurface = Enum.SurfaceType.Smooth, - LeftSurface = Enum.SurfaceType.Smooth, - RightSurface = Enum.SurfaceType.Smooth, - TopSurface = Enum.SurfaceType.Smooth, - BottomSurface = Enum.SurfaceType.Smooth, - }, - VehicleSeat = { - Anchored = true, - Size = Vector3.new(1, 1, 1), - FrontSurface = Enum.SurfaceType.Smooth, - BackSurface = Enum.SurfaceType.Smooth, - LeftSurface = Enum.SurfaceType.Smooth, - RightSurface = Enum.SurfaceType.Smooth, - TopSurface = Enum.SurfaceType.Smooth, - BottomSurface = Enum.SurfaceType.Smooth, - }, - } -end -do - __DARKLUA_BUNDLE_MODULES.i = function(value) - local basicType = type(value) - - if - basicType == "nil" - or basicType == "boolean" - or basicType == "number" - or basicType == "string" - or basicType == "function" - or basicType == "thread" - or basicType == "table" - then - return basicType - end - - local tests = { - { - "Instance", - { - "ClassName", - }, - }, - { - "EnumItem", - { - "EnumType", - "Name", - "Value", - }, - }, - { - "Enum", - { - "GetEnumItems", - }, - }, - { - "Enums", - { - "MembershipType", - }, - }, - { - "RBXScriptSignal", - { - "connect", - "wait", - }, - }, - { - "RBXScriptConnection", - { - "connected", - "disconnect", - }, - }, - { - "TweenInfo", - { - "EasingDirection", - "RepeatCount", - "EasingStyle", - }, - }, - { - "CFrame", - { - "p", - "x", - "y", - "z", - "lookVector", - }, - }, - { - "Vector3", - { - "Lerp", - "unit", - "magnitude", - "x", - "y", - "z", - }, - }, - { - "Vector3int16", - { - "z", - "x", - "y", - }, - }, - { - "Vector2", - { - "unit", - "magnitude", - "x", - "y", - }, - }, - { - "Vector2int16", - { - "x", - "y", - }, - }, - { - "Region3", - { - "CFrame", - "Size", - }, - }, - { - "Region3int16", - { - "Min", - "Max", - }, - }, - { - "Ray", - { - "Origin", - "Direction", - "Unit", - "ClosestPoint", - "Distance", - }, - }, - { - "UDim", - { - "Scale", - "Offset", - }, - }, - { - "Axes", - { - "Z", - "X", - "Y", - }, - }, - { - "UDim2", - { - "X", - "Y", - }, - }, - { - "BrickColor", - { - "Number", - "Name", - "Color", - "r", - "g", - "b", - }, - }, - { - "Color3", - { - "r", - "g", - "b", - }, - }, - { - "Faces", - { - "Right", - "Top", - "Back", - }, - }, - } - - for _, v in ipairs(tests) do - local t, test = v[1], v[2] - local ok, result = pcall(function() - for _, prop in ipairs(test) do - if value[prop] == nil then - return false - end - end - - return true - end) - - if ok and result then - return t - end - end - end -end -do - local typeof = __DARKLUA_BUNDLE_MODULES.i - - local function cleanupOne(task) - local taskType = typeof(task) - - if taskType == "Instance" then - task:Destroy() - elseif taskType == "RBXScriptConnection" then - task:disconnect() - elseif taskType == "function" then - task() - elseif taskType == "table" then - if type(task.destroy) == "function" then - task:destroy() - elseif type(task.Destroy) == "function" then - task:Destroy() - elseif task[1] ~= nil then - for _, subtask in ipairs(task) do - cleanupOne(subtask) - end - end - end - end - local function cleanup(...) - for index = 1, select("#", ...) do - cleanupOne(select(index, ...)) - end - end - - __DARKLUA_BUNDLE_MODULES.j = cleanup -end -do - local typeof = __DARKLUA_BUNDLE_MODULES.i - - local function xtypeof(x) - local typeString = typeof(x) - - if typeString == "table" and type(x.type) == "string" then - return x.type - else - return typeString - end - end - - __DARKLUA_BUNDLE_MODULES.k = xtypeof -end -do - local External = __DARKLUA_BUNDLE_MODULES.e - local class = {} - local CLASS_METATABLE = { __index = class } - local strongRefs = {} - - function class:update() - for _, callback in pairs(self._changeListeners) do - External.doTaskImmediate(callback) - end - - return false - end - function class:onChange(callback) - local uniqueIdentifier = {} - - self._numChangeListeners = self._numChangeListeners + 1 - self._changeListeners[uniqueIdentifier] = callback - strongRefs[self] = true - - local disconnected = false - - return function() - if disconnected then - return - end - - disconnected = true - self._changeListeners[uniqueIdentifier] = nil - self._numChangeListeners = self._numChangeListeners - 1 - - if self._numChangeListeners == 0 then - strongRefs[self] = nil - end - end - end - function class:onBind(callback) - External.doTaskImmediate(callback) - - return self:onChange(callback) - end - - local function Observer(watchedState) - local self = setmetatable({ - type = "State", - kind = "Observer", - dependencySet = { [watchedState] = true }, - dependentSet = {}, - _changeListeners = {}, - _numChangeListeners = 0, - }, CLASS_METATABLE) - - watchedState.dependentSet[self] = true - - return self - end - - __DARKLUA_BUNDLE_MODULES.l = Observer -end -do - local function isState(target) - return type(target) == "table" and type(target._peek) == "function" - end - - __DARKLUA_BUNDLE_MODULES.m = isState -end -do - local isState = __DARKLUA_BUNDLE_MODULES.m - - local function peek(target) - if isState(target) then - return (target):_peek() - end - - return target - end - - __DARKLUA_BUNDLE_MODULES.n = peek -end -do - local External = __DARKLUA_BUNDLE_MODULES.e - local cleanup = __DARKLUA_BUNDLE_MODULES.j - local xtypeof = __DARKLUA_BUNDLE_MODULES.k - local logError = __DARKLUA_BUNDLE_MODULES.d - local Observer = __DARKLUA_BUNDLE_MODULES.l - local peek = __DARKLUA_BUNDLE_MODULES.n - local typeof = __DARKLUA_BUNDLE_MODULES.i - - local function setProperty_unsafe(instance, property, value) - (instance)[property] = value - end - local function testPropertyAssignable(instance, property) - (instance)[property] = (instance)[property] - end - local function setProperty(instance, property, value) - if not pcall(setProperty_unsafe, instance, property, value) then - if not pcall(testPropertyAssignable, instance, property) then - if instance == nil then - logError( - "setPropertyNilRef", - nil, - property, - tostring(value) - ) - else - logError( - "cannotAssignProperty", - nil, - instance.ClassName, - property - ) - end - else - local givenType = typeof(value) - local expectedType = typeof((instance)[property]) - - logError( - "invalidPropertyType", - nil, - instance.ClassName, - property, - expectedType, - givenType - ) - end - end - end - local function bindProperty(instance, property, value, cleanupTasks) - if xtypeof(value) == "State" then - local willUpdate = false - - local function updateLater() - if not willUpdate then - willUpdate = true - - External.doTaskDeferred(function() - willUpdate = false - - setProperty(instance, property, peek(value)) - end) - end - end - - setProperty(instance, property, peek(value)) - table.insert(cleanupTasks, Observer(value):onChange(updateLater)) - else - setProperty(instance, property, value) - end - end - local function applyInstanceProps(props, applyTo) - local specialKeys = { - self = {}, - descendants = {}, - ancestor = {}, - observer = {}, - } - local cleanupTasks = {} - - for key, value in pairs(props) do - local keyType = xtypeof(key) - - if keyType == "string" then - if key ~= "Parent" then - bindProperty(applyTo, key, value, cleanupTasks) - end - elseif keyType == "SpecialKey" then - local stage = (key).stage - local keys = specialKeys[stage] - - if keys == nil then - logError("unrecognisedPropertyStage", nil, stage) - else - keys[key] = value - end - else - logError("unrecognisedPropertyKey", nil, xtypeof(key)) - end - end - for key, value in pairs(specialKeys.self) do - key:apply(value, applyTo, cleanupTasks) - end - for key, value in pairs(specialKeys.descendants) do - key:apply(value, applyTo, cleanupTasks) - end - - if props.Parent ~= nil then - bindProperty(applyTo, "Parent", props.Parent, cleanupTasks) - end - - for key, value in pairs(specialKeys.ancestor) do - key:apply(value, applyTo, cleanupTasks) - end - for key, value in pairs(specialKeys.observer) do - key:apply(value, applyTo, cleanupTasks) - end - - if applyTo.Parent then - game.DescendantRemoving:connect(function(descendant) - if descendant == applyTo then - cleanup(cleanupTasks) - end - end) - end - end - - __DARKLUA_BUNDLE_MODULES.o = applyInstanceProps -end -do - local defaultProps = __DARKLUA_BUNDLE_MODULES.h - local applyInstanceProps = __DARKLUA_BUNDLE_MODULES.o - local logError = __DARKLUA_BUNDLE_MODULES.d - - local function New(className) - return function(props) - local ok, instance = pcall(Instance.new, className) - - if not ok then - logError("cannotCreateClass", nil, className) - end - - local classDefaults = defaultProps[className] - - if classDefaults ~= nil then - for defaultProp, defaultValue in pairs(classDefaults) do - instance[defaultProp] = defaultValue - end - end - - applyInstanceProps(props, instance) - - return instance - end - end - - __DARKLUA_BUNDLE_MODULES.p = New -end -do - local applyInstanceProps = __DARKLUA_BUNDLE_MODULES.o - - local function Hydrate(target) - return function(props) - applyInstanceProps(props, target) - - return target - end - end - - __DARKLUA_BUNDLE_MODULES.q = Hydrate -end -do - local logError = __DARKLUA_BUNDLE_MODULES.d - local xtypeof = __DARKLUA_BUNDLE_MODULES.k - local Ref = {} - - Ref.type = "SpecialKey" - Ref.kind = "Ref" - Ref.stage = "observer" - - function Ref:apply(refState, applyTo, cleanupTasks) - if xtypeof(refState) ~= "State" or refState.kind ~= "Value" then - logError "invalidRefType" - else - refState:set(applyTo) - table.insert(cleanupTasks, function() - refState:set(nil) - end) - end - end - - __DARKLUA_BUNDLE_MODULES.r = Ref -end -do - local logError = __DARKLUA_BUNDLE_MODULES.d - local xtypeof = __DARKLUA_BUNDLE_MODULES.k - - local function Out(propertyName) - local outKey = {} - - outKey.type = "SpecialKey" - outKey.kind = "Out" - outKey.stage = "observer" - - function outKey:apply(outState, applyTo, cleanupTasks) - local ok, event = pcall(function() - return applyTo.Changed - end) - - if not ok then - logError( - "invalidOutProperty", - nil, - applyTo.ClassName, - propertyName - ) - elseif xtypeof(outState) ~= "State" or outState.kind ~= "Value" then - logError "invalidOutType" - else - outState:set((applyTo)[propertyName]) - table.insert( - cleanupTasks, - event:connect(function(prop) - if prop == propertyName then - outState:set((applyTo)[propertyName]) - end - end) - ) - table.insert(cleanupTasks, function() - outState:set(nil) - end) - end - end - - return outKey - end - - __DARKLUA_BUNDLE_MODULES.s = Out -end -do - local Cleanup = {} - - Cleanup.type = "SpecialKey" - Cleanup.kind = "Cleanup" - Cleanup.stage = "observer" - - function Cleanup:apply(userTask, _, cleanupTasks) - table.insert(cleanupTasks, userTask) - end - - __DARKLUA_BUNDLE_MODULES.t = Cleanup -end -do - local messages = __DARKLUA_BUNDLE_MODULES.c - - local function logWarn(messageID, ...) - local formatString - - if messages[messageID] ~= nil then - formatString = messages[messageID] - else - messageID = "unknownMessage" - formatString = messages[messageID] - end - - warn( - string.format( - "[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")", - ... - ) - ) - end - - __DARKLUA_BUNDLE_MODULES.u = logWarn -end -do - local External = __DARKLUA_BUNDLE_MODULES.e - local logWarn = __DARKLUA_BUNDLE_MODULES.u - local Observer = __DARKLUA_BUNDLE_MODULES.l - local peek = __DARKLUA_BUNDLE_MODULES.n - local isState = __DARKLUA_BUNDLE_MODULES.m - local typeof = __DARKLUA_BUNDLE_MODULES.i - local EXPERIMENTAL_AUTO_NAMING = false - local Children = {} - - Children.type = "SpecialKey" - Children.kind = "Children" - Children.stage = "descendants" - - function Children:apply(propValue, applyTo, cleanupTasks) - local newParented = {} - local oldParented = {} - local newDisconnects = {} - local oldDisconnects = {} - local updateQueued = false - local queueUpdate - - local function updateChildren() - if not updateQueued then - return - end - - updateQueued = false - oldParented, newParented = newParented, oldParented - oldDisconnects, newDisconnects = newDisconnects, oldDisconnects - - for i, _ in pairs(newParented) do - newParented[i] = nil - end - for i, _ in pairs(newDisconnects) do - newDisconnects[i] = nil - end - - local function processChild(child, autoName) - local childType = typeof(child) - - if childType == "Instance" then - newParented[child] = true - - if oldParented[child] == nil then - child.Parent = applyTo - else - oldParented[child] = nil - end - if EXPERIMENTAL_AUTO_NAMING and autoName ~= nil then - child.Name = autoName - end - elseif isState(child) then - local value = peek(child) - - if value ~= nil then - processChild(value, autoName) - end - - local disconnect = oldDisconnects[child] - - if disconnect == nil then - disconnect = Observer(child):onChange(queueUpdate) - else - oldDisconnects[child] = nil - end - - newDisconnects[child] = disconnect - elseif childType == "table" then - for key, subChild in pairs(child) do - local keyType = typeof(key) - local subAutoName = nil - - if keyType == "string" then - subAutoName = key - elseif keyType == "number" and autoName ~= nil then - subAutoName = autoName .. "_" .. key - end - - processChild(subChild, subAutoName) - end - else - logWarn("unrecognisedChildType", childType) - end - end - - if propValue ~= nil then - processChild(propValue) - end - - for oldInstance in pairs(oldParented) do - oldInstance.Parent = nil - end - for _, disconnect in pairs(oldDisconnects) do - disconnect() - end - end - - queueUpdate = function() - if not updateQueued then - updateQueued = true - - External.doTaskDeferred(updateChildren) - end - end - - table.insert(cleanupTasks, function() - propValue = nil - updateQueued = true - - updateChildren() - end) - - updateQueued = true - - updateChildren() - end - - __DARKLUA_BUNDLE_MODULES.v = Children -end -do - local logError = __DARKLUA_BUNDLE_MODULES.d - local typeof = __DARKLUA_BUNDLE_MODULES.i - - local function getProperty_unsafe(instance, property) - return (instance)[property] - end - local function OnEvent(eventName) - local eventKey = {} - - eventKey.type = "SpecialKey" - eventKey.kind = "OnEvent" - eventKey.stage = "observer" - - function eventKey:apply(callback, applyTo, cleanupTasks) - local ok, event = pcall(getProperty_unsafe, applyTo, eventName) - - if not ok or typeof(event) ~= "RBXScriptSignal" then - logError( - "cannotConnectEvent", - nil, - applyTo.ClassName, - eventName - ) - elseif typeof(callback) ~= "function" then - logError("invalidEventHandler", nil, eventName) - else - table.insert(cleanupTasks, event:connect(callback)) - end - end - - return eventKey - end - - __DARKLUA_BUNDLE_MODULES.w = OnEvent -end -do - local logError = __DARKLUA_BUNDLE_MODULES.d - local typeof = __DARKLUA_BUNDLE_MODULES.i - - local function OnChange(propertyName) - local changeKey = {} - - changeKey.type = "SpecialKey" - changeKey.kind = "OnChange" - changeKey.stage = "observer" - - function changeKey:apply(callback, applyTo, cleanupTasks) - local ok, event = pcall(function() - return applyTo.Changed - end) - - if not ok then - logError( - "cannotConnectChange", - nil, - applyTo.ClassName, - propertyName - ) - elseif typeof(callback) ~= "function" then - logError("invalidChangeHandler", nil, propertyName) - else - table.insert( - cleanupTasks, - event:connect(function(prop) - if prop == propertyName then - callback((applyTo)[propertyName]) - end - end) - ) - end - end - - return changeKey - end - - __DARKLUA_BUNDLE_MODULES.x = OnChange -end -do - local function updateAll(root) - local counters = {} - local flags = {} - local queue = {} - local queueSize = 0 - local queuePos = 1 - - for object in pairs(root.dependentSet) do - queueSize = queueSize + 1 - queue[queueSize] = object - flags[object] = true - end - - while queuePos <= queueSize do - local next = queue[queuePos] - local counter = counters[next] - - counters[next] = (function() - if counter == nil then - return 1 - else - return counter + 1 - end - end)() - - if next.dependentSet ~= nil then - for object in pairs(next.dependentSet) do - queueSize = queueSize + 1 - queue[queueSize] = object - end - end - - queuePos = queuePos + 1 - end - - queuePos = 1 - - while queuePos <= queueSize do - local next = queue[queuePos] - local counter = counters[next] - 1 - - counters[next] = counter - - if - counter == 0 - and flags[next] - and next:update() - and next.dependentSet ~= nil - then - for object in pairs(next.dependentSet) do - flags[object] = true - end - end - - queuePos = queuePos + 1 - end - end - - __DARKLUA_BUNDLE_MODULES.y = updateAll -end -do - local function isSimilar(a, b) - if type(a) == "table" then - return false - end - - return a == b - end - - __DARKLUA_BUNDLE_MODULES.z = isSimilar -end -do - local logError = __DARKLUA_BUNDLE_MODULES.d - local updateAll = __DARKLUA_BUNDLE_MODULES.y - local isSimilar = __DARKLUA_BUNDLE_MODULES.z - local class = {} - local CLASS_METATABLE = { __index = class } - local WEAK_KEYS_METATABLE = { - __mode = "k", - } - - function class:set(newValue, force) - local oldValue = self._value - - if force or not isSimilar(oldValue, newValue) then - self._value = newValue - - updateAll(self) - end - end - function class:_peek() - return self._value - end - function class:get() - logError "stateGetWasRemoved" - end - - local function Value(initialValue) - local self = setmetatable({ - type = "State", - kind = "Value", - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _value = initialValue, - }, CLASS_METATABLE) - - return self - end - - __DARKLUA_BUNDLE_MODULES.A = Value -end -do - local messages = __DARKLUA_BUNDLE_MODULES.c - - local function logErrorNonFatal(messageID, errObj, ...) - local formatString - - if messages[messageID] ~= nil then - formatString = messages[messageID] - else - messageID = "unknownMessage" - formatString = messages[messageID] - end - - local errorString - - if errObj == nil then - errorString = string.format( - "[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")", - ... - ) - else - formatString = - formatString:gsub("ERROR_MESSAGE", tostring(errObj.message)) - errorString = string.format( - "[Fusion] " - .. formatString - .. "\n(ID: " - .. messageID - .. ")\n---- Stack trace ----\n" - .. tostring(errObj.trace), - ... - ) - end - - coroutine.wrap(function() - error(errorString:gsub("\n", "\n "), 0) - end)() - end - - __DARKLUA_BUNDLE_MODULES.B = logErrorNonFatal -end -do - local function parseError(err) - local trace = "Traceback not available" - - if debug and debug.traceback then - trace = debug.traceback(nil, 2) - end - - return { - type = "Error", - raw = err, - message = err:gsub("^.+:%d+:%s*", ""), - trace = trace, - } - end - - __DARKLUA_BUNDLE_MODULES.C = parseError -end -do - local typeof = __DARKLUA_BUNDLE_MODULES.i - - local function needsDestruction(x) - return typeof(x) == "Instance" - end - - __DARKLUA_BUNDLE_MODULES.D = needsDestruction -end -do - local isState = __DARKLUA_BUNDLE_MODULES.m - - local function makeUseCallback(dependencySet) - local function use(target) - if isState(target) then - dependencySet[target] = true - - return (target):_peek() - end - - return target - end - - return use - end - - __DARKLUA_BUNDLE_MODULES.E = makeUseCallback -end -do - local logError = __DARKLUA_BUNDLE_MODULES.d - local logErrorNonFatal = __DARKLUA_BUNDLE_MODULES.B - local logWarn = __DARKLUA_BUNDLE_MODULES.u - local parseError = __DARKLUA_BUNDLE_MODULES.C - local isSimilar = __DARKLUA_BUNDLE_MODULES.z - local needsDestruction = __DARKLUA_BUNDLE_MODULES.D - local makeUseCallback = __DARKLUA_BUNDLE_MODULES.E - local class = {} - local CLASS_METATABLE = { __index = class } - local WEAK_KEYS_METATABLE = { - __mode = "k", - } - - function class:update() - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = nil - end - - self._oldDependencySet, self.dependencySet = - self.dependencySet, self._oldDependencySet - - for i, _ in pairs(self.dependencySet) do - self.dependencySet[i] = nil - end - - local use = makeUseCallback(self.dependencySet) - local ok, newValue, newMetaValue = pcall(self._processor, use) - - if ok then - if self._destructor == nil and needsDestruction(newValue) then - logWarn "destructorNeededComputed" - end - if newMetaValue ~= nil then - logWarn "multiReturnComputed" - end - - local oldValue = self._value - local similar = isSimilar(oldValue, newValue) - - if self._destructor ~= nil then - self._destructor(oldValue) - end - - self._value = newValue - - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = true - end - - return not similar - else - logErrorNonFatal("computedCallbackError", parseError(newValue)) - - self._oldDependencySet, self.dependencySet = - self.dependencySet, self._oldDependencySet - - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = true - end - - return false - end - end - function class:_peek() - return self._value - end - function class:get() - logError "stateGetWasRemoved" - end - - local function Computed(processor, destructor) - local dependencySet = {} - local self = setmetatable({ - type = "State", - kind = "Computed", - dependencySet = dependencySet, - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _oldDependencySet = {}, - _processor = processor, - _destructor = destructor, - _value = nil, - }, CLASS_METATABLE) - - self:update() - - return self - end - - __DARKLUA_BUNDLE_MODULES.F = Computed -end -do - local parseError = __DARKLUA_BUNDLE_MODULES.C - local logErrorNonFatal = __DARKLUA_BUNDLE_MODULES.B - local logError = __DARKLUA_BUNDLE_MODULES.d - local logWarn = __DARKLUA_BUNDLE_MODULES.u - local cleanup = __DARKLUA_BUNDLE_MODULES.j - local needsDestruction = __DARKLUA_BUNDLE_MODULES.D - local peek = __DARKLUA_BUNDLE_MODULES.n - local makeUseCallback = __DARKLUA_BUNDLE_MODULES.E - local isState = __DARKLUA_BUNDLE_MODULES.m - local class = {} - local CLASS_METATABLE = { __index = class } - local WEAK_KEYS_METATABLE = { - __mode = "k", - } - - function class:update() - local inputIsState = self._inputIsState - local newInputTable = peek(self._inputTable) - local oldInputTable = self._oldInputTable - local keyIOMap = self._keyIOMap - local meta = self._meta - local didChange = false - - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = nil - end - - self._oldDependencySet, self.dependencySet = - self.dependencySet, self._oldDependencySet - - for i, _ in pairs(self.dependencySet) do - self.dependencySet[i] = nil - end - - if inputIsState then - self._inputTable.dependentSet[self] = true - self.dependencySet[self._inputTable] = true - end - - self._oldOutputTable, self._outputTable = - self._outputTable, self._oldOutputTable - - local oldOutputTable = self._oldOutputTable - local newOutputTable = self._outputTable - - for i, _ in pairs(newOutputTable) do - newOutputTable[i] = nil - end - for newInKey, newInValue in pairs(newInputTable) do - local keyData = self._keyData[newInKey] - - if keyData == nil then - keyData = { - dependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - oldDependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - dependencyValues = setmetatable({}, WEAK_KEYS_METATABLE), - } - self._keyData[newInKey] = keyData - end - - local shouldRecalculate = oldInputTable[newInKey] ~= newInValue - - if shouldRecalculate == false then - for dependency, oldValue in pairs(keyData.dependencyValues) do - if oldValue ~= peek(dependency) then - shouldRecalculate = true - - break - end - end - end - if shouldRecalculate then - keyData.oldDependencySet, keyData.dependencySet = - keyData.dependencySet, keyData.oldDependencySet - - for i, _ in pairs(keyData.dependencySet) do - keyData.dependencySet[i] = nil - end - - local use = makeUseCallback(keyData.dependencySet) - local processOK, newOutKey, newOutValue, newMetaValue = - pcall(self._processor, use, newInKey, newInValue) - - if processOK then - if - self._destructor == nil - and ( - needsDestruction(newOutKey) - or needsDestruction(newOutValue) - or needsDestruction(newMetaValue) - ) - then - logWarn "destructorNeededForPairs" - end - if newOutputTable[newOutKey] ~= nil then - local previousNewKey, previousNewValue - - for inKey, outKey in pairs(keyIOMap) do - if outKey == newOutKey then - previousNewValue = newInputTable[inKey] - - if previousNewValue ~= nil then - previousNewKey = inKey - - break - end - end - end - - if previousNewKey ~= nil then - logError( - "forPairsKeyCollision", - nil, - tostring(newOutKey), - tostring(previousNewKey), - tostring(previousNewValue), - tostring(newInKey), - tostring(newInValue) - ) - end - end - - local oldOutValue = oldOutputTable[newOutKey] - - if oldOutValue ~= newOutValue then - local oldMetaValue = meta[newOutKey] - - if oldOutValue ~= nil then - local destructOK, err = pcall( - self._destructor or cleanup, - newOutKey, - oldOutValue, - oldMetaValue - ) - - if not destructOK then - logErrorNonFatal( - "forPairsDestructorError", - parseError(err) - ) - end - end - - oldOutputTable[newOutKey] = nil - end - - oldInputTable[newInKey] = newInValue - keyIOMap[newInKey] = newOutKey - meta[newOutKey] = newMetaValue - newOutputTable[newOutKey] = newOutValue - didChange = true - else - keyData.oldDependencySet, keyData.dependencySet = - keyData.dependencySet, keyData.oldDependencySet - - logErrorNonFatal( - "forPairsProcessorError", - parseError(newOutKey) - ) - end - else - local storedOutKey = keyIOMap[newInKey] - - if newOutputTable[storedOutKey] ~= nil then - local previousNewKey, previousNewValue - - for inKey, outKey in pairs(keyIOMap) do - if storedOutKey == outKey then - previousNewValue = newInputTable[inKey] - - if previousNewValue ~= nil then - previousNewKey = inKey - - break - end - end - end - - if previousNewKey ~= nil then - logError( - "forPairsKeyCollision", - nil, - tostring(storedOutKey), - tostring(previousNewKey), - tostring(previousNewValue), - tostring(newInKey), - tostring(newInValue) - ) - end - end - - newOutputTable[storedOutKey] = oldOutputTable[storedOutKey] - end - - for dependency in pairs(keyData.dependencySet) do - keyData.dependencyValues[dependency] = peek(dependency) - self.dependencySet[dependency] = true - dependency.dependentSet[self] = true - end - end - for oldOutKey, oldOutValue in pairs(oldOutputTable) do - if newOutputTable[oldOutKey] ~= oldOutValue then - local oldMetaValue = meta[oldOutKey] - - if oldOutValue ~= nil then - local destructOK, err = pcall( - self._destructor or cleanup, - oldOutKey, - oldOutValue, - oldMetaValue - ) - - if not destructOK then - logErrorNonFatal( - "forPairsDestructorError", - parseError(err) - ) - end - end - if newOutputTable[oldOutKey] == nil then - meta[oldOutKey] = nil - self._keyData[oldOutKey] = nil - end - - didChange = true - end - end - for key in pairs(oldInputTable) do - if newInputTable[key] == nil then - oldInputTable[key] = nil - keyIOMap[key] = nil - end - end - - return didChange - end - function class:_peek() - return self._outputTable - end - function class:get() - logError "stateGetWasRemoved" - end - - local function ForPairs(inputTable, processor, destructor) - local inputIsState = isState(inputTable) - local self = setmetatable({ - type = "State", - kind = "ForPairs", - dependencySet = {}, - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _oldDependencySet = {}, - _processor = processor, - _destructor = destructor, - _inputIsState = inputIsState, - _inputTable = inputTable, - _oldInputTable = {}, - _outputTable = {}, - _oldOutputTable = {}, - _keyIOMap = {}, - _keyData = {}, - _meta = {}, - }, CLASS_METATABLE) - - self:update() - - return self - end - - __DARKLUA_BUNDLE_MODULES.G = ForPairs -end -do - local parseError = __DARKLUA_BUNDLE_MODULES.C - local logErrorNonFatal = __DARKLUA_BUNDLE_MODULES.B - local logError = __DARKLUA_BUNDLE_MODULES.d - local logWarn = __DARKLUA_BUNDLE_MODULES.u - local cleanup = __DARKLUA_BUNDLE_MODULES.j - local needsDestruction = __DARKLUA_BUNDLE_MODULES.D - local peek = __DARKLUA_BUNDLE_MODULES.n - local makeUseCallback = __DARKLUA_BUNDLE_MODULES.E - local isState = __DARKLUA_BUNDLE_MODULES.m - local class = {} - local CLASS_METATABLE = { __index = class } - local WEAK_KEYS_METATABLE = { - __mode = "k", - } - - function class:update() - local inputIsState = self._inputIsState - local newInputTable = peek(self._inputTable) - local oldInputTable = self._oldInputTable - local outputTable = self._outputTable - local keyOIMap = self._keyOIMap - local keyIOMap = self._keyIOMap - local meta = self._meta - local didChange = false - - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = nil - end - - self._oldDependencySet, self.dependencySet = - self.dependencySet, self._oldDependencySet - - for i, _ in pairs(self.dependencySet) do - self.dependencySet[i] = nil - end - - if inputIsState then - self._inputTable.dependentSet[self] = true - self.dependencySet[self._inputTable] = true - end - - for newInKey, value in pairs(newInputTable) do - local keyData = self._keyData[newInKey] - - if keyData == nil then - keyData = { - dependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - oldDependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - dependencyValues = setmetatable({}, WEAK_KEYS_METATABLE), - } - self._keyData[newInKey] = keyData - end - - local shouldRecalculate = oldInputTable[newInKey] == nil - - if shouldRecalculate == false then - for dependency, oldValue in pairs(keyData.dependencyValues) do - if oldValue ~= peek(dependency) then - shouldRecalculate = true - - break - end - end - end - if shouldRecalculate then - keyData.oldDependencySet, keyData.dependencySet = - keyData.dependencySet, keyData.oldDependencySet - - for i, _ in pairs(keyData.dependencySet) do - keyData.dependencySet[i] = nil - end - - local use = makeUseCallback(keyData.dependencySet) - local processOK, newOutKey, newMetaValue = - pcall(self._processor, use, newInKey) - - if processOK then - if - self._destructor == nil - and ( - needsDestruction(newOutKey) - or needsDestruction(newMetaValue) - ) - then - logWarn "destructorNeededForKeys" - end - - local oldInKey = keyOIMap[newOutKey] - local oldOutKey = keyIOMap[newInKey] - - if - oldInKey ~= newInKey - and newInputTable[oldInKey] ~= nil - then - logError( - "forKeysKeyCollision", - nil, - tostring(newOutKey), - tostring(oldInKey), - tostring(newOutKey) - ) - end - if - oldOutKey ~= newOutKey - and keyOIMap[oldOutKey] == newInKey - then - local oldMetaValue = meta[oldOutKey] - local destructOK, err = pcall( - self._destructor or cleanup, - oldOutKey, - oldMetaValue - ) - - if not destructOK then - logErrorNonFatal( - "forKeysDestructorError", - parseError(err) - ) - end - - keyOIMap[oldOutKey] = nil - outputTable[oldOutKey] = nil - meta[oldOutKey] = nil - end - - oldInputTable[newInKey] = value - meta[newOutKey] = newMetaValue - keyOIMap[newOutKey] = newInKey - keyIOMap[newInKey] = newOutKey - outputTable[newOutKey] = value - didChange = true - else - keyData.oldDependencySet, keyData.dependencySet = - keyData.dependencySet, keyData.oldDependencySet - - logErrorNonFatal( - "forKeysProcessorError", - parseError(newOutKey) - ) - end - end - - for dependency in pairs(keyData.dependencySet) do - keyData.dependencyValues[dependency] = peek(dependency) - self.dependencySet[dependency] = true - dependency.dependentSet[self] = true - end - end - for outputKey, inputKey in pairs(keyOIMap) do - if newInputTable[inputKey] == nil then - local oldMetaValue = meta[outputKey] - local destructOK, err = - pcall(self._destructor or cleanup, outputKey, oldMetaValue) - - if not destructOK then - logErrorNonFatal("forKeysDestructorError", parseError(err)) - end - - oldInputTable[inputKey] = nil - meta[outputKey] = nil - keyOIMap[outputKey] = nil - keyIOMap[inputKey] = nil - outputTable[outputKey] = nil - self._keyData[inputKey] = nil - didChange = true - end - end - - return didChange - end - function class:_peek() - return self._outputTable - end - function class:get() - logError "stateGetWasRemoved" - end - - local function ForKeys(inputTable, processor, destructor) - local inputIsState = isState(inputTable) - local self = setmetatable({ - type = "State", - kind = "ForKeys", - dependencySet = {}, - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _oldDependencySet = {}, - _processor = processor, - _destructor = destructor, - _inputIsState = inputIsState, - _inputTable = inputTable, - _oldInputTable = {}, - _outputTable = {}, - _keyOIMap = {}, - _keyIOMap = {}, - _keyData = {}, - _meta = {}, - }, CLASS_METATABLE) - - self:update() - - return self - end - - __DARKLUA_BUNDLE_MODULES.H = ForKeys -end -do - local parseError = __DARKLUA_BUNDLE_MODULES.C - local logError = __DARKLUA_BUNDLE_MODULES.d - local logErrorNonFatal = __DARKLUA_BUNDLE_MODULES.B - local logWarn = __DARKLUA_BUNDLE_MODULES.u - local cleanup = __DARKLUA_BUNDLE_MODULES.j - local needsDestruction = __DARKLUA_BUNDLE_MODULES.D - local peek = __DARKLUA_BUNDLE_MODULES.n - local makeUseCallback = __DARKLUA_BUNDLE_MODULES.E - local isState = __DARKLUA_BUNDLE_MODULES.m - local class = {} - local CLASS_METATABLE = { __index = class } - local WEAK_KEYS_METATABLE = { - __mode = "k", - } - - function class:update() - local inputIsState = self._inputIsState - local inputTable = peek(self._inputTable) - local outputValues = {} - local didChange = false - - self._oldValueCache, self._valueCache = - self._valueCache, self._oldValueCache - - local newValueCache = self._valueCache - local oldValueCache = self._oldValueCache - - for i, _ in pairs(newValueCache) do - newValueCache[i] = nil - end - for dependency in pairs(self.dependencySet) do - dependency.dependentSet[self] = nil - end - - self._oldDependencySet, self.dependencySet = - self.dependencySet, self._oldDependencySet - - for i, _ in pairs(self.dependencySet) do - self.dependencySet[i] = nil - end - - if inputIsState then - self._inputTable.dependentSet[self] = true - self.dependencySet[self._inputTable] = true - end - - for inKey, inValue in pairs(inputTable) do - local oldCachedValues = oldValueCache[inValue] - local shouldRecalculate = oldCachedValues == nil - local value, valueData, meta - - if type(oldCachedValues) == "table" and #oldCachedValues > 0 then - local valueInfo = - table.remove(oldCachedValues, #oldCachedValues) - - value = valueInfo.value - valueData = valueInfo.valueData - meta = valueInfo.meta - - if #oldCachedValues <= 0 then - oldValueCache[inValue] = nil - end - elseif oldCachedValues ~= nil then - oldValueCache[inValue] = nil - shouldRecalculate = true - end - if valueData == nil then - valueData = { - dependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - oldDependencySet = setmetatable({}, WEAK_KEYS_METATABLE), - dependencyValues = setmetatable({}, WEAK_KEYS_METATABLE), - } - end - if shouldRecalculate == false then - for dependency, oldValue in pairs(valueData.dependencyValues) do - if oldValue ~= peek(dependency) then - shouldRecalculate = true - - break - end - end - end - if shouldRecalculate then - valueData.oldDependencySet, valueData.dependencySet = - valueData.dependencySet, valueData.oldDependencySet - - for i, _ in pairs(valueData.dependencySet) do - valueData.dependencySet[i] = nil - end - - local use = makeUseCallback(valueData.dependencySet) - local processOK, newOutValue, newMetaValue = - pcall(self._processor, use, inValue) - - if processOK then - if - self._destructor == nil - and ( - needsDestruction(newOutValue) - or needsDestruction(newMetaValue) - ) - then - logWarn "destructorNeededForValues" - end - if value ~= nil then - local destructOK, err = - pcall(self._destructor or cleanup, value, meta) - - if not destructOK then - logErrorNonFatal( - "forValuesDestructorError", - parseError(err) - ) - end - end - - value = newOutValue - meta = newMetaValue - didChange = true - else - valueData.oldDependencySet, valueData.dependencySet = - valueData.dependencySet, valueData.oldDependencySet - - logErrorNonFatal( - "forValuesProcessorError", - parseError(newOutValue) - ) - end - end - - local newCachedValues = newValueCache[inValue] - - if newCachedValues == nil then - newCachedValues = {} - newValueCache[inValue] = newCachedValues - end - - table.insert(newCachedValues, { - value = value, - valueData = valueData, - meta = meta, - }) - - outputValues[inKey] = value - - for dependency in pairs(valueData.dependencySet) do - valueData.dependencyValues[dependency] = peek(dependency) - self.dependencySet[dependency] = true - dependency.dependentSet[self] = true - end - end - for _oldInValue, oldCachedValueInfo in pairs(oldValueCache) do - for _, valueInfo in ipairs(oldCachedValueInfo) do - local oldValue = valueInfo.value - local oldMetaValue = valueInfo.meta - local destructOK, err = - pcall(self._destructor or cleanup, oldValue, oldMetaValue) - - if not destructOK then - logErrorNonFatal( - "forValuesDestructorError", - parseError(err) - ) - end - - didChange = true - end - for i, _ in pairs(oldCachedValueInfo) do - oldCachedValueInfo[i] = nil - end - end - - self._outputTable = outputValues - - return didChange - end - function class:_peek() - return self._outputTable - end - function class:get() - logError "stateGetWasRemoved" - end - - local function ForValues(inputTable, processor, destructor) - local inputIsState = isState(inputTable) - local self = setmetatable({ - type = "State", - kind = "ForValues", - dependencySet = {}, - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _oldDependencySet = {}, - _processor = processor, - _destructor = destructor, - _inputIsState = inputIsState, - _inputTable = inputTable, - _outputTable = {}, - _valueCache = {}, - _oldValueCache = {}, - }, CLASS_METATABLE) - - self:update() - - return self - end - - __DARKLUA_BUNDLE_MODULES.I = ForValues -end -do - local Oklab = {} - - function Oklab.to(rgb) - local l = rgb.r * 0.4122214708 - + rgb.g * 0.5363325363 - + rgb.b * 0.0514459929 - local m = rgb.r * 0.2119034982 - + rgb.g * 0.6806995451 - + rgb.b * 0.1073969566 - local s = rgb.r * 0.0883024619 - + rgb.g * 0.2817188376 - + rgb.b * 0.6299787005 - local lRoot = l ^ 0.3333333333333333 - local mRoot = m ^ 0.3333333333333333 - local sRoot = s ^ 0.3333333333333333 - - return Vector3.new( - lRoot * 0.2104542553 + mRoot * 0.793617785 - sRoot * 0.0040720468, - lRoot * 1.9779984951 - mRoot * 2.428592205 + sRoot * 0.4505937099, - lRoot * 0.0259040371 + mRoot * 0.7827717662 - sRoot * 0.808675766 - ) - end - function Oklab.from(lab, unclamped) - local lRoot = lab.X + lab.Y * 0.3963377774 + lab.Z * 0.2158037573 - local mRoot = lab.X - lab.Y * 0.1055613458 - lab.Z * 0.0638541728 - local sRoot = lab.X - lab.Y * 0.0894841775 - lab.Z * 1.291485548 - local l = lRoot ^ 3 - local m = mRoot ^ 3 - local s = sRoot ^ 3 - local red = l * 4.0767416621 - m * 3.3077115913 + s * 0.2309699292 - local green = l * -1.2684380046 + m * 2.6097574011 - s * 0.3413193965 - local blue = l * -4.196086299999999E-3 - - m * 0.7034186147 - + s * 1.707614701 - - if not unclamped then - red = math.max(0, math.min(red, 1)) - green = math.max(0, math.min(green, 1)) - blue = math.max(0, math.min(blue, 1)) - end - - return Color3.new(red, green, blue) - end - - __DARKLUA_BUNDLE_MODULES.J = Oklab -end -do - local Oklab = __DARKLUA_BUNDLE_MODULES.J - local typeof = __DARKLUA_BUNDLE_MODULES.i - - local function lerpType(from, to, ratio) - local typeString = typeof(from) - - if typeof(to) == typeString then - if typeString == "number" then - return (to - from) * ratio + from - elseif typeString == "CFrame" then - return from:Lerp(to, ratio) - elseif typeString == "Color3" then - local fromLab = Oklab.to(from) - local toLab = Oklab.to(to) - - return Oklab.from(fromLab:Lerp(toLab, ratio), false) - elseif typeString == "Ray" then - return Ray.new( - from.Origin:Lerp(to.Origin, ratio), - from.Direction:Lerp(to.Direction, ratio) - ) - elseif typeString == "Region3" then - local position = - from.CFrame.Position:Lerp(to.CFrame.Position, ratio) - local halfSize = from.Size:Lerp(to.Size, ratio) / 2 - - return Region3.new(position - halfSize, position + halfSize) - elseif typeString == "Region3int16" then - return Region3int16.new( - Vector3int16.new( - (to.Min.X - from.Min.X) * ratio + from.Min.X, - (to.Min.Y - from.Min.Y) * ratio + from.Min.Y, - (to.Min.Z - from.Min.Z) * ratio + from.Min.Z - ), - Vector3int16.new( - (to.Max.X - from.Max.X) * ratio + from.Max.X, - (to.Max.Y - from.Max.Y) * ratio + from.Max.Y, - (to.Max.Z - from.Max.Z) * ratio + from.Max.Z - ) - ) - elseif typeString == "UDim" then - return UDim.new( - (to.Scale - from.Scale) * ratio + from.Scale, - (to.Offset - from.Offset) * ratio + from.Offset - ) - elseif typeString == "UDim2" then - return UDim2.new( - (to.X.Scale - from.X.Scale) * ratio + from.X.Scale, - (to.X.Offset - from.X.Offset) * ratio + from.X.Offset, - (to.Y.Scale - from.Y.Scale) * ratio + from.Y.Scale, - (to.Y.Offset - from.Y.Offset) * ratio + from.Y.Offset - ) - elseif typeString == "Vector2" or typeString == "Vector3" then - return from:Lerp(to, ratio) - elseif typeString == "Vector2int16" then - return Vector2int16.new( - (to.X - from.X) * ratio + from.X, - (to.Y - from.Y) * ratio + from.Y - ) - elseif typeString == "Vector3int16" then - return Vector3int16.new( - (to.X - from.X) * ratio + from.X, - (to.Y - from.Y) * ratio + from.Y, - (to.Z - from.Z) * ratio + from.Z - ) - end - end - if ratio < 0.5 then - return from - end - - return to - end - - __DARKLUA_BUNDLE_MODULES.K = lerpType -end -do - local pow = math.pow - local sin = math.sin - local cos = math.cos - local pi = math.pi - local sqrt = math.sqrt - local abs = math.abs - local asin = math.asin - local easing = { - Linear = {}, - Quad = {}, - Cubic = {}, - Quart = {}, - Quint = {}, - Sine = {}, - Exponential = {}, - Circular = {}, - Elastic = {}, - Back = {}, - Bounce = {}, - } - local linear = function(t, b, c) - return c * t + b - end - - easing.Linear.In = linear - easing.Linear.Out = linear - easing.Linear.InOut = linear - easing.Linear.OutIn = linear - easing.Quad.In = function(t, b, c) - return c * pow(t, 2) + b - end - easing.Quad.Out = function(t, b, c) - return -c * t * (t - 2) + b - end - easing.Quad.InOut = function(t, b, c) - t = t * 2 - - if t < 1 then - return c / 2 * pow(t, 2) + b - end - - return -c / 2 * ((t - 1) * (t - 3) - 1) + b - end - easing.Quad.OutIn = function(t, b, c) - if t < 0.5 then - return easing.Quad.Out(t * 2, b, c / 2) - end - - return easing.Quad.In((t * 2) - 1, b + c / 2, c / 2) - end - easing.Cubic.In = function(t, b, c) - return c * pow(t, 3) + b - end - easing.Cubic.Out = function(t, b, c) - t = t - 1 - - return c * (pow(t, 3) + 1) + b - end - easing.Cubic.InOut = function(t, b, c) - t = t * 2 - - if t < 1 then - return c / 2 * t * t * t + b - end - - t = t - 2 - - return c / 2 * (t * t * t + 2) + b - end - easing.Cubic.OutIn = function(t, b, c) - if t < 0.5 then - return easing.Cubic.Out(t * 2, b, c / 2) - end - - return easing.Cubic.In((t * 2) - 1, b + c / 2, c / 2) - end - easing.Quart.In = function(t, b, c) - return c * pow(t, 4) + b - end - easing.Quart.Out = function(t, b, c) - t = t - 1 - - return -c * (pow(t, 4) - 1) + b - end - easing.Quart.InOut = function(t, b, c) - t = t * 2 - - if t < 1 then - return c / 2 * pow(t, 4) + b - end - - t = t - 2 - - return -c / 2 * (pow(t, 4) - 2) + b - end - easing.Quart.OutIn = function(t, b, c) - if t < 0.5 then - return easing.Quart.Out(t * 2, b, c / 2) - end - - return easing.Quart.In((t * 2) - 1, b + c / 2, c / 2) - end - easing.Quint.In = function(t, b, c) - return c * pow(t, 5) + b - end - easing.Quint.Out = function(t, b, c) - t = t - 1 - - return c * (pow(t, 5) + 1) + b - end - easing.Quint.InOut = function(t, b, c) - t = t * 2 - - if t < 1 then - return c / 2 * pow(t, 5) + b - end - - t = t - 2 - - return c / 2 * (pow(t, 5) + 2) + b - end - easing.Quint.OutIn = function(t, b, c) - if t < 0.5 then - return easing.Quint.Out(t * 2, b, c / 2) - end - - return easing.Quint.In((t * 2) - 1, b + c / 2, c / 2) - end - easing.Sine.In = function(t, b, c) - return -c * cos(t * (pi / 2)) + c + b - end - easing.Sine.Out = function(t, b, c) - return c * sin(t * (pi / 2)) + b - end - easing.Sine.InOut = function(t, b, c) - return -c / 2 * (cos(pi * t) - 1) + b - end - easing.Sine.OutIn = function(t, b, c) - if t < 0.5 then - return easing.Sine.Out(t * 2, b, c / 2) - end - - return easing.Sine.In((t * 2) - 1, b + c / 2, c / 2) - end - easing.Exponential.In = function(t, b, c) - if t == 0 then - return b - end - - return c * pow(2, 10 * (t - 1)) + b - c * 0.001 - end - easing.Exponential.Out = function(t, b, c) - if t == 1 then - return b + c - end - - return c * 1.001 * (-pow(2, -10 * t) + 1) + b - end - easing.Exponential.InOut = function(t, b, c) - if t == 0 then - return b - elseif t == 1 then - return b + c - end - - t = t * 2 - - if t < 1 then - return c / 2 * pow(2, 10 * (t - 1)) + b - c * 0.0005 - end - - t = t - 1 - - return c / 2 * 1.0005 * (-pow(2, -10 * t) + 2) + b - end - easing.Exponential.OutIn = function(t, b, c) - if t < 0.5 then - return t.Exponential.Out(t * 2, b, c / 2) - end - - return t.Exponential.In((t * 2) - 1, b + c / 2, c / 2) - end - easing.Circular.In = function(t, b, c) - return (-c * (sqrt(1 - pow(t, 2)) - 1) + b) - end - easing.Circular.Out = function(t, b, c) - t = t - 1 - - return (c * sqrt(1 - pow(t, 2)) + b) - end - easing.Circular.InOut = function(t, b, c) - t = t * 2 - - if t < 1 then - return -c / 2 * (sqrt(1 - t * t) - 1) + b - end - - t = t - 2 - - return c / 2 * (sqrt(1 - t * t) + 1) + b - end - easing.Circular.OutIn = function(t, b, c) - if t < 0.5 then - return easing.Circular.Out(t * 2, b, c / 2) - end - - return easing.Circular.In((t * 2) - 1, b + c / 2, c / 2) - end - easing.Elastic.In = function(t, b, c) - if t == 0 then - return b - elseif t == 1 then - return b + c - end - - local p = 0.3 - local s - - s = p / 4 - t = t - 1 - - return -(c * pow(2, 10 * t) * sin((t * 1 - s) * (2 * pi) / p)) + b - end - easing.Elastic.Out = function(t, b, c) - if t == 0 then - return b - elseif t == 1 then - return b + c - end - - local p = 0.3 - local s - - s = p / 4 - - return c * pow(2, -10 * t) * sin((t - s) * (2 * pi) / p) + c + b - end - easing.Elastic.InOut = function(t, b, c) - if t == 0 then - return b - end - - t = t * 2 - - if t == 2 then - return b + c - end - - local p = 0.45 - local a = 0 - local s - - if not a or a < abs(c) then - a = c - s = p / 4 - else - s = p / (2 * pi) * asin(c / a) - end - - t = t - 1 - - if t < 1 then - return -0.5 * (a * pow(2, 10 * t) * sin((t - s) * (2 * pi) / p)) + b - end - - return a * pow(2, -10 * t) * sin((t - s) * (2 * pi) / p) * 0.5 + c + b - end - easing.Elastic.OutIn = function(t, b, c) - if t < 0.5 then - return easing.Elastic.Out(t * 2, b, c / 2) - end - - return easing.Elastic.In((t * 2) - 1, b + c / 2, c / 2) - end - easing.Back.In = function(t, b, c) - local s = 1.70158 - - return c * t * t * ((s + 1) * t - s) + b - end - easing.Back.Out = function(t, b, c) - local s = 1.70158 - - t = t - 1 - - return c * (t * t * ((s + 1) * t + s) + 1) + b - end - easing.Back.InOut = function(t, b, c) - local s = 2.5949095 - - t = t * 2 - - if t < 1 then - return c / 2 * (t * t * ((s + 1) * t - s)) + b - end - - t = t - 2 - - return c / 2 * (t * t * ((s + 1) * t + s) + 2) + b - end - easing.Back.OutIn = function(t, b, c) - if t < 0.5 then - return easing.Back.Out(t * 2, b, c / 2) - end - - return easing.Back.In((t * 2) - 1, b + c / 2, c / 2) - end - easing.Bounce.Out = function(t, b, c) - if t < 0.36363636363636365 then - return c * (7.5625 * t * t) + b - elseif t < 0.7272727272727273 then - t = t - 0.5454545454545454 - - return c * (7.5625 * t * t + 0.75) + b - elseif t < 0.9090909090909091 then - t = t - 0.8181818181818182 - - return c * (7.5625 * t * t + 0.9375) + b - end - - t = t - 0.9545454545454546 - - return c * (7.5625 * t * t + 0.984375) + b - end - easing.Bounce.In = function(t, b, c) - return c - easing.Bounce.Out(1 - t, 0, c) + b - end - easing.Bounce.InOut = function(t, b, c) - if t < 0.5 then - return easing.Bounce.In(t * 2, 0, c) * 0.5 + b - end - - return easing.Bounce.Out(t * 2 - 1, 0, c) * 0.5 + c * 0.5 + b - end - easing.Bounce.OutIn = function(t, b, c) - if t < 0.5 then - return easing.Bounce.Out(t * 2, b, c / 2) - end - - return easing.Bounce.In((t * 2) - 1, b + c / 2, c / 2) - end - __DARKLUA_BUNDLE_MODULES.L = easing -end -do - local easing = __DARKLUA_BUNDLE_MODULES.L - - local function getTweenRatio(tweenInfo, currentTime) - local delay = tweenInfo.DelayTime - local duration = tweenInfo.Time - local reverses = tweenInfo.Reverses - local numCycles = 1 + tweenInfo.RepeatCount - local easeStyle = tweenInfo.EasingStyle - local easeDirection = tweenInfo.EasingDirection - local cycleDuration = delay + duration - - if reverses then - cycleDuration = cycleDuration + duration - end - if - currentTime >= cycleDuration * numCycles - and tweenInfo.RepeatCount > -1 - then - return 1 - end - - local cycleTime = currentTime % cycleDuration - - if cycleTime <= delay then - return 0 - end - - local tweenProgress = (cycleTime - delay) / duration - - if tweenProgress > 1 then - tweenProgress = 2 - tweenProgress - end - - return easing[easeStyle][easeDirection](tweenProgress, 0, 1) - end - - __DARKLUA_BUNDLE_MODULES.M = getTweenRatio -end -do - local External = __DARKLUA_BUNDLE_MODULES.e - local lerpType = __DARKLUA_BUNDLE_MODULES.K - local getTweenRatio = __DARKLUA_BUNDLE_MODULES.M - local updateAll = __DARKLUA_BUNDLE_MODULES.y - local TweenScheduler = {} - local WEAK_KEYS_METATABLE = { - __mode = "k", - } - local allTweens = {} - - setmetatable(allTweens, WEAK_KEYS_METATABLE) - - function TweenScheduler.add(tween) - allTweens[tween] = true - end - function TweenScheduler.remove(tween) - allTweens[tween] = nil - end - - local function updateAllTweens(now) - for tween in pairs(allTweens) do - local currentTime = now - tween._currentTweenStartTime - - if - currentTime > tween._currentTweenDuration - and tween._currentTweenInfo.RepeatCount > -1 - then - if tween._currentTweenInfo.Reverses then - tween._currentValue = tween._prevValue - else - tween._currentValue = tween._nextValue - end - - tween._currentlyAnimating = false - - updateAll(tween) - TweenScheduler.remove(tween) - else - local ratio = - getTweenRatio(tween._currentTweenInfo, currentTime) - local currentValue = - lerpType(tween._prevValue, tween._nextValue, ratio) - - tween._currentValue = currentValue - tween._currentlyAnimating = true - - updateAll(tween) - end - end - end - - External.bindToUpdateStep(updateAllTweens) - - __DARKLUA_BUNDLE_MODULES.N = TweenScheduler -end -do - local External = __DARKLUA_BUNDLE_MODULES.e - local TweenScheduler = __DARKLUA_BUNDLE_MODULES.N - local logError = __DARKLUA_BUNDLE_MODULES.d - local logErrorNonFatal = __DARKLUA_BUNDLE_MODULES.B - local xtypeof = __DARKLUA_BUNDLE_MODULES.k - local peek = __DARKLUA_BUNDLE_MODULES.n - local typeof = __DARKLUA_BUNDLE_MODULES.i - local class = {} - local CLASS_METATABLE = { __index = class } - local WEAK_KEYS_METATABLE = { - __mode = "k", - } - - function class:update() - local goalValue = peek(self._goalState) - - if goalValue == self._nextValue and not self._currentlyAnimating then - return false - end - - local tweenInfo = peek(self._tweenInfo) - - if typeof(tweenInfo) ~= "TweenInfo" then - logErrorNonFatal("mistypedTweenInfo", nil, typeof(tweenInfo)) - - return false - end - - self._prevValue = self._currentValue - self._nextValue = goalValue - self._currentTweenStartTime = External.lastUpdateStep() - self._currentTweenInfo = tweenInfo - - local tweenDuration = tweenInfo.DelayTime + tweenInfo.Time - - if tweenInfo.Reverses then - tweenDuration = tweenDuration + tweenInfo.Time - end - - tweenDuration = tweenDuration * (tweenInfo.RepeatCount + 1) - self._currentTweenDuration = tweenDuration - - TweenScheduler.add(self) - - return false - end - function class:_peek() - return self._currentValue - end - function class:get() - logError "stateGetWasRemoved" - end - - local function Tween(goalState, tweenInfo) - local currentValue = peek(goalState) - - if tweenInfo == nil then - tweenInfo = TweenInfo.new() - end - - local dependencySet = { [goalState] = true } - local tweenInfoIsState = xtypeof(tweenInfo) == "State" - - if tweenInfoIsState then - dependencySet[tweenInfo] = true - end - - local startingTweenInfo = peek(tweenInfo) - - if typeof(startingTweenInfo) ~= "TweenInfo" then - logError("mistypedTweenInfo", nil, typeof(startingTweenInfo)) - end - - local self = setmetatable({ - type = "State", - kind = "Tween", - dependencySet = dependencySet, - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _goalState = goalState, - _tweenInfo = tweenInfo, - _tweenInfoIsState = tweenInfoIsState, - _prevValue = currentValue, - _nextValue = currentValue, - _currentValue = currentValue, - _currentTweenInfo = tweenInfo, - _currentTweenDuration = 0, - _currentTweenStartTime = 0, - _currentlyAnimating = false, - }, CLASS_METATABLE) - - goalState.dependentSet[self] = true - - return self - end - - __DARKLUA_BUNDLE_MODULES.O = Tween -end -do - local Oklab = __DARKLUA_BUNDLE_MODULES.J - - local function unpackType(value, typeString) - if typeString == "number" then - return { value } - elseif typeString == "CFrame" then - local axis, angle = value:ToAxisAngle() - - return { - value.X, - value.Y, - value.Z, - axis.X, - axis.Y, - axis.Z, - angle, - } - elseif typeString == "Color3" then - local lab = Oklab.to(value) - - return { - lab.X, - lab.Y, - lab.Z, - } - elseif typeString == "ColorSequenceKeypoint" then - local lab = Oklab.to(value.Value) - - return { - lab.X, - lab.Y, - lab.Z, - value.Time, - } - elseif typeString == "DateTime" then - return { - value.UnixTimestampMillis, - } - elseif typeString == "NumberRange" then - return { - value.Min, - value.Max, - } - elseif typeString == "NumberSequenceKeypoint" then - return { - value.Value, - value.Time, - value.Envelope, - } - elseif typeString == "PhysicalProperties" then - return { - value.Density, - value.Friction, - value.Elasticity, - value.FrictionWeight, - value.ElasticityWeight, - } - elseif typeString == "Ray" then - return { - value.Origin.X, - value.Origin.Y, - value.Origin.Z, - value.Direction.X, - value.Direction.Y, - value.Direction.Z, - } - elseif typeString == "Rect" then - return { - value.Min.X, - value.Min.Y, - value.Max.X, - value.Max.Y, - } - elseif typeString == "Region3" then - return { - value.CFrame.X, - value.CFrame.Y, - value.CFrame.Z, - value.Size.X, - value.Size.Y, - value.Size.Z, - } - elseif typeString == "Region3int16" then - return { - value.Min.X, - value.Min.Y, - value.Min.Z, - value.Max.X, - value.Max.Y, - value.Max.Z, - } - elseif typeString == "UDim" then - return { - value.Scale, - value.Offset, - } - elseif typeString == "UDim2" then - return { - value.X.Scale, - value.X.Offset, - value.Y.Scale, - value.Y.Offset, - } - elseif typeString == "Vector2" then - return { - value.X, - value.Y, - } - elseif typeString == "Vector2int16" then - return { - value.X, - value.Y, - } - elseif typeString == "Vector3" then - return { - value.X, - value.Y, - value.Z, - } - elseif typeString == "Vector3int16" then - return { - value.X, - value.Y, - value.Z, - } - else - return {} - end - end - - __DARKLUA_BUNDLE_MODULES.P = unpackType -end -do - local Oklab = __DARKLUA_BUNDLE_MODULES.J - - local function packType(numbers, typeString) - if typeString == "number" then - return numbers[1] - elseif typeString == "CFrame" then - return CFrame.new(numbers[1], numbers[2], numbers[3]) - * CFrame.fromAxisAngle( - Vector3.new(numbers[4], numbers[5], numbers[6]).Unit, - numbers[7] - ) - elseif typeString == "Color3" then - return Oklab.from( - Vector3.new(numbers[1], numbers[2], numbers[3]), - false - ) - elseif typeString == "ColorSequenceKeypoint" then - return ColorSequenceKeypoint.new( - numbers[4], - Oklab.from( - Vector3.new(numbers[1], numbers[2], numbers[3]), - false - ) - ) - elseif typeString == "DateTime" then - return DateTime.fromUnixTimestampMillis(numbers[1]) - elseif typeString == "NumberRange" then - return NumberRange.new(numbers[1], numbers[2]) - elseif typeString == "NumberSequenceKeypoint" then - return NumberSequenceKeypoint.new( - numbers[2], - numbers[1], - numbers[3] - ) - elseif typeString == "PhysicalProperties" then - return PhysicalProperties.new( - numbers[1], - numbers[2], - numbers[3], - numbers[4], - numbers[5] - ) - elseif typeString == "Ray" then - return Ray.new( - Vector3.new(numbers[1], numbers[2], numbers[3]), - Vector3.new(numbers[4], numbers[5], numbers[6]) - ) - elseif typeString == "Rect" then - return Rect.new(numbers[1], numbers[2], numbers[3], numbers[4]) - elseif typeString == "Region3" then - local position = Vector3.new(numbers[1], numbers[2], numbers[3]) - local halfSize = - Vector3.new(numbers[4] / 2, numbers[5] / 2, numbers[6] / 2) - - return Region3.new(position - halfSize, position + halfSize) - elseif typeString == "Region3int16" then - return Region3int16.new( - Vector3int16.new(numbers[1], numbers[2], numbers[3]), - Vector3int16.new(numbers[4], numbers[5], numbers[6]) - ) - elseif typeString == "UDim" then - return UDim.new(numbers[1], numbers[2]) - elseif typeString == "UDim2" then - return UDim2.new(numbers[1], numbers[2], numbers[3], numbers[4]) - elseif typeString == "Vector2" then - return Vector2.new(numbers[1], numbers[2]) - elseif typeString == "Vector2int16" then - return Vector2int16.new(numbers[1], numbers[2]) - elseif typeString == "Vector3" then - return Vector3.new(numbers[1], numbers[2], numbers[3]) - elseif typeString == "Vector3int16" then - return Vector3int16.new(numbers[1], numbers[2], numbers[3]) - else - return nil - end - end - - __DARKLUA_BUNDLE_MODULES.Q = packType -end -do - local function springCoefficients(time, damping, speed) - if time == 0 or speed == 0 then - return 1, 0, 0, 1 - end - - local posPos, posVel, velPos, velVel - - if damping > 1 then - local scaledTime = time * speed - local alpha = math.sqrt(damping ^ 2 - 1) - local scaledInvAlpha = -0.5 / alpha - local z1 = -alpha - damping - local z2 = 1 / z1 - local expZ1 = math.exp(scaledTime * z1) - local expZ2 = math.exp(scaledTime * z2) - - posPos = (expZ2 * z1 - expZ1 * z2) * scaledInvAlpha - posVel = (expZ1 - expZ2) * scaledInvAlpha / speed - velPos = (expZ2 - expZ1) * scaledInvAlpha * speed - velVel = (expZ1 * z1 - expZ2 * z2) * scaledInvAlpha - elseif damping == 1 then - local scaledTime = time * speed - local expTerm = math.exp(-scaledTime) - - posPos = expTerm * (1 + scaledTime) - posVel = expTerm * time - velPos = expTerm * (-scaledTime * speed) - velVel = expTerm * (1 - scaledTime) - else - local scaledTime = time * speed - local alpha = math.sqrt(1 - damping ^ 2) - local invAlpha = 1 / alpha - local alphaTime = alpha * scaledTime - local expTerm = math.exp(-scaledTime * damping) - local sinTerm = expTerm * math.sin(alphaTime) - local cosTerm = expTerm * math.cos(alphaTime) - local sinInvAlpha = sinTerm * invAlpha - local sinInvAlphaDamp = sinInvAlpha * damping - - posPos = sinInvAlphaDamp + cosTerm - posVel = sinInvAlpha - velPos = -(sinInvAlphaDamp * damping + sinTerm * alpha) - velVel = cosTerm - sinInvAlphaDamp - end - - return posPos, posVel, velPos, velVel - end - - __DARKLUA_BUNDLE_MODULES.R = springCoefficients -end -do - local External = __DARKLUA_BUNDLE_MODULES.e - local packType = __DARKLUA_BUNDLE_MODULES.Q - local springCoefficients = __DARKLUA_BUNDLE_MODULES.R - local updateAll = __DARKLUA_BUNDLE_MODULES.y - local SpringScheduler = {} - local EPSILON = 0.0001 - local activeSprings = {} - local lastUpdateTime = External.lastUpdateStep() - - function SpringScheduler.add(spring) - spring._lastSchedule = lastUpdateTime - spring._startDisplacements = {} - spring._startVelocities = {} - - for index, goal in ipairs(spring._springGoals) do - spring._startDisplacements[index] = spring._springPositions[index] - - goal - spring._startVelocities[index] = spring._springVelocities[index] - end - - activeSprings[spring] = true - end - function SpringScheduler.remove(spring) - activeSprings[spring] = nil - end - - local function updateAllSprings(now) - local springsToSleep = {} - - lastUpdateTime = now - - for spring in pairs(activeSprings) do - local posPos, posVel, velPos, velVel = springCoefficients( - lastUpdateTime - spring._lastSchedule, - spring._currentDamping, - spring._currentSpeed - ) - local positions = spring._springPositions - local velocities = spring._springVelocities - local startDisplacements = spring._startDisplacements - local startVelocities = spring._startVelocities - local isMoving = false - - for index, goal in ipairs(spring._springGoals) do - local oldDisplacement = startDisplacements[index] - local oldVelocity = startVelocities[index] - local newDisplacement = oldDisplacement * posPos - + oldVelocity * posVel - local newVelocity = oldDisplacement * velPos - + oldVelocity * velVel - - if - math.abs(newDisplacement) > EPSILON - or math.abs(newVelocity) > EPSILON - then - isMoving = true - end - - positions[index] = newDisplacement + goal - velocities[index] = newVelocity - end - - if not isMoving then - springsToSleep[spring] = true - end - end - for spring in pairs(activeSprings) do - spring._currentValue = - packType(spring._springPositions, spring._currentType) - - updateAll(spring) - end - for spring in pairs(springsToSleep) do - activeSprings[spring] = nil - spring._currentValue = - packType(spring._springGoals, spring._currentType) - end - end - - External.bindToUpdateStep(updateAllSprings) - - __DARKLUA_BUNDLE_MODULES.S = SpringScheduler -end -do - local logError = __DARKLUA_BUNDLE_MODULES.d - local logErrorNonFatal = __DARKLUA_BUNDLE_MODULES.B - local unpackType = __DARKLUA_BUNDLE_MODULES.P - local SpringScheduler = __DARKLUA_BUNDLE_MODULES.S - local updateAll = __DARKLUA_BUNDLE_MODULES.y - local xtypeof = __DARKLUA_BUNDLE_MODULES.k - local peek = __DARKLUA_BUNDLE_MODULES.n - local typeof = __DARKLUA_BUNDLE_MODULES.i - local class = {} - local CLASS_METATABLE = { __index = class } - local WEAK_KEYS_METATABLE = { - __mode = "k", - } - - function class:setPosition(newValue) - local newType = typeof(newValue) - - if newType ~= self._currentType then - logError("springTypeMismatch", nil, newType, self._currentType) - end - - self._springPositions = unpackType(newValue, newType) - self._currentValue = newValue - - SpringScheduler.add(self) - updateAll(self) - end - function class:setVelocity(newValue) - local newType = typeof(newValue) - - if newType ~= self._currentType then - logError("springTypeMismatch", nil, newType, self._currentType) - end - - self._springVelocities = unpackType(newValue, newType) - - SpringScheduler.add(self) - end - function class:addVelocity(deltaValue) - local deltaType = typeof(deltaValue) - - if deltaType ~= self._currentType then - logError("springTypeMismatch", nil, deltaType, self._currentType) - end - - local springDeltas = unpackType(deltaValue, deltaType) - - for index, delta in ipairs(springDeltas) do - do - local __DARKLUA_VAR = self._springVelocities - - __DARKLUA_VAR[index] = __DARKLUA_VAR[index] + delta - end - end - - SpringScheduler.add(self) - end - function class:update() - local goalValue = peek(self._goalState) - - if goalValue == self._goalValue then - local damping = peek(self._damping) - - if typeof(damping) ~= "number" then - logErrorNonFatal("mistypedSpringDamping", nil, typeof(damping)) - elseif damping < 0 then - logErrorNonFatal("invalidSpringDamping", nil, damping) - else - self._currentDamping = damping - end - - local speed = peek(self._speed) - - if typeof(speed) ~= "number" then - logErrorNonFatal("mistypedSpringSpeed", nil, typeof(speed)) - elseif speed < 0 then - logErrorNonFatal("invalidSpringSpeed", nil, speed) - else - self._currentSpeed = speed - end - - return false - else - self._goalValue = goalValue - - local oldType = self._currentType - local newType = typeof(goalValue) - - self._currentType = newType - - local springGoals = unpackType(goalValue, newType) - local numSprings = #springGoals - - self._springGoals = springGoals - - if newType ~= oldType then - self._currentValue = self._goalValue - - local springPositions = {} - - for i = 1, numSprings do - springPositions[i] = 0 - end - - local springVelocities = {} - - for i = 1, numSprings do - springVelocities[i] = 0 - end - - for index, springGoal in ipairs(springGoals) do - springPositions[index] = springGoal - end - - self._springPositions = springPositions - self._springVelocities = springVelocities - - SpringScheduler.remove(self) - - return true - elseif numSprings == 0 then - self._currentValue = self._goalValue - - return true - else - SpringScheduler.add(self) - - return false - end - end - end - function class:_peek() - return self._currentValue - end - function class:get() - logError "stateGetWasRemoved" - end - - local function Spring(goalState, speed, damping) - if speed == nil then - speed = 10 - end - if damping == nil then - damping = 1 - end - - local dependencySet = { [goalState] = true } - - if xtypeof(speed) == "State" then - dependencySet[speed] = true - end - if xtypeof(damping) == "State" then - dependencySet[damping] = true - end - - local self = setmetatable({ - type = "State", - kind = "Spring", - dependencySet = dependencySet, - dependentSet = setmetatable({}, WEAK_KEYS_METATABLE), - _speed = speed, - _damping = damping, - _goalState = goalState, - _goalValue = nil, - _currentType = nil, - _currentValue = nil, - _currentSpeed = peek(speed), - _currentDamping = peek(damping), - _springPositions = nil, - _springGoals = nil, - _springVelocities = nil, - }, CLASS_METATABLE) - - goalState.dependentSet[self] = true - - self:update() - - return self - end - - __DARKLUA_BUNDLE_MODULES.T = Spring -end -do - local function doNothing() end - - __DARKLUA_BUNDLE_MODULES.U = doNothing -end -do - local logError = __DARKLUA_BUNDLE_MODULES.d - local TweenInfo = {} - - function TweenInfo.new( - time, - easingStyle, - easingDirection, - repeatCount, - reverses, - delayTime - ) - local proxy = newproxy(true) - local mt = getmetatable(proxy) - - if type(easingStyle) ~= "string" then - if easingStyle then - easingStyle = tostring(easingStyle):gsub("Enum.%w+.", "") - end - else - local ok - - for _, s in ipairs { - "Linear", - "Quad", - "Cubic", - "Quart", - "Quint", - "Sine", - "Exponential", - "Circular", - "Elastic", - "Back", - "Bounce", - } do - if easingStyle == s then - ok = true - - break - end - end - - if not ok then - logError("invalidEasingStyle", nil, easingStyle) - end - end - if type(easingDirection) ~= "string" then - if easingDirection then - easingDirection = - tostring(easingDirection):gsub("Enum.%w+.", "") - end - else - local ok - - for _, d in ipairs { - "In", - "Out", - "InOut", - "OutIn", - } do - if easingDirection == d then - ok = true - - break - end - end - - if not ok then - logError("invalidEasingDirection", nil, easingDirection) - end - end - - time = time or 1 - easingStyle = easingStyle or "Quad" - easingDirection = easingDirection or "Out" - repeatCount = repeatCount or 0 - reverses = reverses or false - delayTime = delayTime or 0 - mt.__index = { - Time = time, - EasingStyle = easingStyle, - EasingDirection = easingDirection, - RepeatCount = repeatCount, - Reverses = reverses, - DelayTime = delayTime, - } - mt.__newindex = function(_, prop) - error(prop .. " cannot be assigned to", math.huge) - end - mt.__tostring = function() - return "Time:" - .. tostring(time) - .. " DelayTime:" - .. tostring(delayTime) - .. " RepeatCount:" - .. tostring(repeatCount) - .. " Reverses:" - .. (reverses and "True" or "False") - .. " EasingDirection:" - .. easingDirection - .. " EasingStyle:" - .. easingStyle - end - mt.__metatable = "The metatable is locked" - - return proxy - end - - __DARKLUA_BUNDLE_MODULES.V = TweenInfo -end - -local External = __DARKLUA_BUNDLE_MODULES.e -local restrictRead = __DARKLUA_BUNDLE_MODULES.f - -do - local MercuryExternal = __DARKLUA_BUNDLE_MODULES.g - - External.setExternalScheduler(MercuryExternal) -end - -local Fusion = restrictRead("Fusion", { - version = { - major = 0, - minor = 3, - isRelease = false, - }, - New = __DARKLUA_BUNDLE_MODULES.p, - Hydrate = __DARKLUA_BUNDLE_MODULES.q, - Ref = __DARKLUA_BUNDLE_MODULES.r, - Out = __DARKLUA_BUNDLE_MODULES.s, - Cleanup = __DARKLUA_BUNDLE_MODULES.t, - Children = __DARKLUA_BUNDLE_MODULES.v, - OnEvent = __DARKLUA_BUNDLE_MODULES.w, - OnChange = __DARKLUA_BUNDLE_MODULES.x, - Value = __DARKLUA_BUNDLE_MODULES.A, - Computed = __DARKLUA_BUNDLE_MODULES.F, - ForPairs = __DARKLUA_BUNDLE_MODULES.G, - ForKeys = __DARKLUA_BUNDLE_MODULES.H, - ForValues = __DARKLUA_BUNDLE_MODULES.I, - Observer = __DARKLUA_BUNDLE_MODULES.l, - Tween = __DARKLUA_BUNDLE_MODULES.O, - Spring = __DARKLUA_BUNDLE_MODULES.T, - cleanup = __DARKLUA_BUNDLE_MODULES.j, - doNothing = __DARKLUA_BUNDLE_MODULES.U, - peek = __DARKLUA_BUNDLE_MODULES.n, - typeof = __DARKLUA_BUNDLE_MODULES.i, - TweenInfo = __DARKLUA_BUNDLE_MODULES.V, - Help = function() - return "See https://elttob.uk/Fusion/0.3/ for more information." - end, -}) - -return Fusion diff --git a/luau/46295863.luau b/luau/46295863.luau index f15dbda..17832b2 100644 --- a/luau/46295863.luau +++ b/luau/46295863.luau @@ -2166,13 +2166,13 @@ local createReportAbuseDialog = function() frame.Active = true frame.Parent = shield - settingsFrame = Instance.new "Frame" - settingsFrame.Name = "ReportAbuseStyle" - settingsFrame.Size = UDim2.new(1, 0, 1, 0) - settingsFrame.Style = Enum.FrameStyle.RobloxRound - settingsFrame.Active = true - settingsFrame.ZIndex = baseZIndex + 1 - settingsFrame.Parent = frame + local reportAbuseFrame = Instance.new "Frame" + reportAbuseFrame.Name = "ReportAbuseStyle" + reportAbuseFrame.Size = UDim2.new(1, 0, 1, 0) + reportAbuseFrame.Style = Enum.FrameStyle.RobloxRound + reportAbuseFrame.Active = true + reportAbuseFrame.ZIndex = baseZIndex + 1 + reportAbuseFrame.Parent = frame local title = Instance.new "TextLabel" title.Name = "Title" @@ -2182,7 +2182,7 @@ local createReportAbuseDialog = function() title.Font = Enum.Font.ArialBold title.FontSize = Enum.FontSize.Size36 title.ZIndex = baseZIndex + 2 - title.Parent = settingsFrame + title.Parent = reportAbuseFrame local description = Instance.new "TextLabel" description.Name = "Description" @@ -2198,7 +2198,7 @@ local createReportAbuseDialog = function() description.ZIndex = baseZIndex + 2 description.TextXAlignment = Enum.TextXAlignment.Left description.TextYAlignment = Enum.TextYAlignment.Top - description.Parent = settingsFrame + description.Parent = reportAbuseFrame local playerLabel = Instance.new "TextLabel" playerLabel.Name = "PlayerLabel" @@ -2211,7 +2211,7 @@ local createReportAbuseDialog = function() playerLabel.TextColor3 = Color3I(255, 255, 255) playerLabel.TextXAlignment = Enum.TextXAlignment.Left playerLabel.ZIndex = baseZIndex + 2 - playerLabel.Parent = settingsFrame + playerLabel.Parent = reportAbuseFrame local abusingPlayer local abuse @@ -2261,7 +2261,7 @@ local createReportAbuseDialog = function() abuseLabel.TextColor3 = Color3I(255, 255, 255) abuseLabel.TextXAlignment = Enum.TextXAlignment.Left abuseLabel.ZIndex = baseZIndex + 2 - abuseLabel.Parent = settingsFrame + abuseLabel.Parent = reportAbuseFrame local abuses = { "Swearing", @@ -2288,7 +2288,7 @@ local createReportAbuseDialog = function() abuseDropDown.ZIndex = baseZIndex + 2 abuseDropDown.Position = UDim2.new(0.425, 0, 0, 142) abuseDropDown.Size = UDim2.new(0.55, 0, 0, 32) - abuseDropDown.Parent = settingsFrame + abuseDropDown.Parent = reportAbuseFrame local shortDescriptionLabel = Instance.new "TextLabel" shortDescriptionLabel.Name = "ShortDescriptionLabel" @@ -2301,7 +2301,7 @@ local createReportAbuseDialog = function() shortDescriptionLabel.TextXAlignment = Enum.TextXAlignment.Left shortDescriptionLabel.BackgroundTransparency = 1 shortDescriptionLabel.ZIndex = baseZIndex + 2 - shortDescriptionLabel.Parent = settingsFrame + shortDescriptionLabel.Parent = reportAbuseFrame local shortDescriptionWrapper = Instance.new "Frame" shortDescriptionWrapper.Name = "ShortDescriptionWrapper" @@ -2310,7 +2310,7 @@ local createReportAbuseDialog = function() shortDescriptionWrapper.BackgroundColor3 = Color3I(0, 0, 0) shortDescriptionWrapper.BorderSizePixel = 0 shortDescriptionWrapper.ZIndex = baseZIndex + 2 - shortDescriptionWrapper.Parent = settingsFrame + shortDescriptionWrapper.Parent = reportAbuseFrame local shortDescriptionBox = Instance.new "TextBox" shortDescriptionBox.Name = "TextBox" @@ -2342,7 +2342,7 @@ local createReportAbuseDialog = function() submitReportButton.Text = "Submit Report" submitReportButton.TextColor3 = Color3I(255, 255, 255) submitReportButton.ZIndex = baseZIndex + 2 - submitReportButton.Parent = settingsFrame + submitReportButton.Parent = reportAbuseFrame submitReportButton.MouseButton1Click:connect(function() if submitReportButton.Active then @@ -2377,11 +2377,11 @@ local createReportAbuseDialog = function() cancelButton.Text = "Cancel" cancelButton.TextColor3 = Color3I(255, 255, 255) cancelButton.ZIndex = baseZIndex + 2 - cancelButton.Parent = settingsFrame + cancelButton.Parent = reportAbuseFrame closeAndResetDialog = function() --Delete old player combo box - local oldComboBox = settingsFrame:FindFirstChild "PlayersComboBox" + local oldComboBox = reportAbuseFrame:FindFirstChild "PlayersComboBox" if oldComboBox then oldComboBox.Parent = nil end @@ -2404,7 +2404,7 @@ local createReportAbuseDialog = function() cancelButton.MouseButton1Click:connect(closeAndResetDialog) reportAbuseButton.MouseButton1Click:connect(function() - createPlayersDropDown().Parent = settingsFrame + createPlayersDropDown().Parent = reportAbuseFrame table.insert(centerDialogs, shield) game.GuiService:AddCenterDialog( shield, diff --git a/luau/host.luau b/luau/host.luau index 31f649f..2a7a35a 100644 --- a/luau/host.luau +++ b/luau/host.luau @@ -1,5 +1,5 @@ +-- Start Game print "[Mercury]: Loaded Host corescript" --- Start Game Script Arguments local InsertService = game:GetService "InsertService" local BadgeService = game:GetService "BadgeService" @@ -15,57 +15,13 @@ local Visit = game:GetService "Visit" local NetworkServer = game:GetService "NetworkServer" -- StartGame -- --- pcall(function() --- ScriptContext:AddStarterScript(injectScriptAssetID) --- end) + RunService:Run() -- REQUIRES: StartGanmeSharedArgs.txt -- REQUIRES: MonitorGameStatus.txt -------------------- UTILITY FUNCTIONS -------------------------- - -local function waitForChild(parent, childName) - while true do - local child = parent:findFirstChild(childName) - if child then - return child - end - parent.ChildAdded:wait() - end -end - --- returns the player object that killed this humanoid --- returns nil if the killer is no longer in the game -local function getKillerOfHumanoidIfStillInGame(humanoid) - -- check for kill tag on humanoid - may be more than one - todo: deal with this - local tag = humanoid:findFirstChild "creator" - - -- find player with name on tag - if tag then - local killer = tag.Value - if killer.Parent then -- killer still in game - return killer - end - end - - return nil -end - --- send kill and death stats when a player dies -local function onDied(victim, humanoid) - local killer = getKillerOfHumanoidIfStillInGame(humanoid) - local victorId = 0 - if killer then - victorId = killer.userId - print(`STAT: kill by {victorId} of {victim.userId}`) - game:HttpGet(`{url}/Game/Knockouts.ashx?UserID={victorId}`) - end - print(`STAT: death of {victim.userId} by {victorId}`) - game:HttpGet(`{url}/Game/Wipeouts.ashx?UserID={victim.userId}`) -end - ------------------------------------END UTILITY FUNCTIONS ------------------------- +local url = _BASE_URL -----------------------------------"CUSTOM" SHARED CODE---------------------------------- @@ -91,8 +47,6 @@ end) -----------------------------------START GAME SHARED SCRIPT------------------------------ -local url = "_BASE_URL" - -- pcall(function() -- ScriptContext:AddStarterScript(libraryRegistrationScriptAssetID) -- end) @@ -111,34 +65,7 @@ if url ~= nil then pcall(function() ContentProvider:SetBaseUrl(`{url}/`) end) - -- pcall(function() - -- Players:SetChatFilterUrl( - -- `{url}/Game/ChatFilter.ashx` - -- ) - -- end) - -- BadgeService:SetPlaceId(placeId) - -- if access ~= nil then - -- BadgeService:SetAwardBadgeUrl( - -- `{url}/Game/Badge/AwardBadge.ashx?UserID=%d&BadgeID=%d&PlaceID=%d&{access}` - -- ) - -- BadgeService:SetHasBadgeUrl( - -- `{url}/Game/Badge/HasBadge.ashx?UserID=%d&BadgeID=%d&{access}` - -- ) - -- BadgeService:SetIsBadgeDisabledUrl( - -- `{url}/Game/Badge/IsBadgeDisabled.ashx?BadgeID=%d&PlaceID=%d&{access}` - -- ) - - -- FriendService:SetMakeFriendUrl( - -- `{servicesUrl}/Friend/CreateFriend?firstUserId=%d&secondUserId=%d&{access}` - -- ) - -- FriendService:SetBreakFriendUrl( - -- `{servicesUrl}/Friend/BreakFriend?firstUserId=%d&secondUserId=%d&{access}` - -- ) - -- FriendService:SetGetFriendsUrl( - -- `{servicesUrl}/Friend/AreFriends?userId={access}` - -- ) - -- end BadgeService:SetIsBadgeLegalUrl "" InsertService:SetBaseSetsUrl( `{url}/Game/Tools/InsertAsset.ashx?nsets=10&type=base` @@ -149,18 +76,6 @@ if url ~= nil then InsertService:SetCollectionUrl(`{url}/Game/Tools/InsertAsset.ashx?sid=%d`) InsertService:SetAssetUrl(`{url}/asset?id=%d`) InsertService:SetAssetVersionUrl(`{url}/Asset/?assetversionid=%d`) - - -- pcall(function() - -- loadfile(`{url}/Game/LoadPlaceInfo.ashx?PlaceId={placeId}`)() - -- end) - - -- pcall(function() - -- if access then - -- loadfile( - -- `{url}/Game/PlaceSpecificScript.ashx?PlaceId={placeId}&{access}` - -- )() - -- end - -- end) end pcall(function() @@ -168,72 +83,26 @@ pcall(function() end) settings().Diagnostics.LuaRamLimit = 0 -if placeId ~= nil and killID ~= nil and deathID ~= nil and url ~= nil then - -- listen for the death of a Player - function createDeathMonitor(player) - -- we don't need to clean up old monitors or connections since the Character will be destroyed soon - if player.Character then - local humanoid = waitForChild(player.Character, "Humanoid") - humanoid.Died:connect(function() - onDied(player, humanoid) - end) - end - end - - -- listen to all Players' Characters - Players.ChildAdded:connect(function(player) - createDeathMonitor(player) - player.Changed:connect(function(property) - if property == "Character" then - createDeathMonitor(player) - end - end) - end) -end - Players.PlayerAdded:connect(function(player) print(`Player {player.userId} added`) - - -- if url and access and placeId and player and player.userId then - -- game:HttpGet( - -- `{url}/Game/ClientPresence.ashx?action=connect&{access}&PlaceID={placeId}&UserID={player.userId}` - -- ) - -- game:HttpGet( - -- `{url}/Game/PlaceVisit.ashx?UserID={player.userId}&AssociatedPlaceID={placeId}&{access}` - -- ) - -- end end) Players.PlayerRemoving:connect(function(player) print(`Player {player.userId} leaving`) - - -- if url and access and placeId and player and player.userId then - -- game:HttpGet( - -- `{url}/Game/ClientPresence.ashx?action=disconnect&{access}&PlaceID={placeId}&UserID={player.userId}` - -- ) - -- end end) --- if placeId ~= nil and url ~= nil then --- -- yield so that file load happens in the heartbeat thread --- wait() - --- -- load the game --- game:Load(`{url}/asset/?id={placeId}`) --- end - if _MAP_LOCATION_EXISTS then -- yield so that file load happens in the heartbeat thread wait() -- load the game - game:Load "_MAP_LOCATION" + game:Load(_MAP_LOCATION) end -- Now start the connection NetworkServer:Start(_SERVER_PORT) -Visit:SetPing("_SERVER_PRESENCE_URL", 30) +Visit:SetPing(_SERVER_PRESENCE_URL, 30) -- if timeout then -- ScriptContext:SetTimeout(timeout) diff --git a/luau/join.luau b/luau/join.luau index 4385577..58729ee 100644 --- a/luau/join.luau +++ b/luau/join.luau @@ -160,7 +160,7 @@ local function onConnectionAccepted(_, replicator) local waitingForMarker = true local success, err = pcall(function() - Visit:SetPing("_PING_URL", 30) + Visit:SetPing(_PING_URL, 30) loadingState += 1 game:SetMessageBrickCount() @@ -257,7 +257,7 @@ local success, err = pcall(function() pcall(function() player.Name = [========[_USER_NAME]========] end) - player.CharacterAppearance = "_CHAR_APPEARANCE" + player.CharacterAppearance = _CHAR_APPEARANCE Visit:SetUploadUrl "" end) From b0e1d53df53de799839d9b5bf6981ac37f94fe2b Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Sun, 28 Jan 2024 17:09:33 +0000 Subject: [PATCH 2/8] Move Luau polyfills into separate modules folder for better organisation --- Libraries/Fusion/Animation/Spring.luau | 2 +- Libraries/Fusion/Animation/Tween.luau | 2 +- Libraries/Fusion/Animation/getTweenRatio.luau | 2 +- Libraries/Fusion/Animation/lerpType.luau | 2 +- Libraries/Fusion/Instances/Children.luau | 2 +- Libraries/Fusion/Instances/OnChange.luau | 2 +- Libraries/Fusion/Instances/OnEvent.luau | 2 +- .../Fusion/Instances/applyInstanceProps.luau | 2 +- Libraries/Fusion/Logging/logError.luau | 16 ++++++--------- .../Fusion/Logging/logErrorNonFatal.luau | 14 ++++++------- Libraries/Fusion/Logging/messages.luau | 4 +--- Libraries/Fusion/MercuryExternal.luau | 6 +++--- Libraries/Fusion/PubTypes.luau | 2 +- Libraries/Fusion/Types.luau | 4 ++-- Libraries/Fusion/Utility/cleanup.luau | 2 +- .../Fusion/Utility/needsDestruction.luau | 2 +- Libraries/Fusion/Utility/xtypeof.luau | 2 +- Libraries/Fusion/init.luau | 8 ++++---- .../Polyfill/TweenInfo.luau | 20 ++++++++++++------- .../Fusion => Modules}/Polyfill/easing.luau | 0 .../Fusion => Modules}/Polyfill/typeof.luau | 1 + 21 files changed, 48 insertions(+), 49 deletions(-) rename {Libraries/Fusion => Modules}/Polyfill/TweenInfo.luau (77%) rename {Libraries/Fusion => Modules}/Polyfill/easing.luau (100%) rename {Libraries/Fusion => Modules}/Polyfill/typeof.luau (99%) diff --git a/Libraries/Fusion/Animation/Spring.luau b/Libraries/Fusion/Animation/Spring.luau index 5b0744f..e11a5c8 100644 --- a/Libraries/Fusion/Animation/Spring.luau +++ b/Libraries/Fusion/Animation/Spring.luau @@ -14,7 +14,7 @@ local SpringScheduler = require "../Animation/SpringScheduler" local updateAll = require "../State/updateAll" local xtypeof = require "../Utility/xtypeof" local peek = require "../State/peek" -local typeof = require "../Polyfill/typeof" +local typeof = require "../../../Modules/Polyfill/typeof" local class = {} diff --git a/Libraries/Fusion/Animation/Tween.luau b/Libraries/Fusion/Animation/Tween.luau index 5bd62a8..2240ce8 100644 --- a/Libraries/Fusion/Animation/Tween.luau +++ b/Libraries/Fusion/Animation/Tween.luau @@ -13,7 +13,7 @@ local logError = require "../Logging/logError" local logErrorNonFatal = require "../Logging/logErrorNonFatal" local xtypeof = require "../Utility/xtypeof" local peek = require "../State/peek" -local typeof = require "../Polyfill/typeof" +local typeof = require "../../../Modules/Polyfill/typeof" local class = {} diff --git a/Libraries/Fusion/Animation/getTweenRatio.luau b/Libraries/Fusion/Animation/getTweenRatio.luau index 8ec5aac..859f5a7 100644 --- a/Libraries/Fusion/Animation/getTweenRatio.luau +++ b/Libraries/Fusion/Animation/getTweenRatio.luau @@ -6,7 +6,7 @@ ]] -- local TweenService = game:GetService "TweenService" -local easing = require "../Polyfill/easing" +local easing = require "../../../Modules/Polyfill/easing" local function getTweenRatio(tweenInfo: TweenInfo, currentTime: number): number local delay = tweenInfo.DelayTime diff --git a/Libraries/Fusion/Animation/lerpType.luau b/Libraries/Fusion/Animation/lerpType.luau index 5067588..2846692 100644 --- a/Libraries/Fusion/Animation/lerpType.luau +++ b/Libraries/Fusion/Animation/lerpType.luau @@ -10,7 +10,7 @@ ]] local Oklab = require "../Colour/Oklab" -local typeof = require "../Polyfill/typeof" +local typeof = require "../../../Modules/Polyfill/typeof" local function lerpType(from: any, to: any, ratio: number): any local typeString = typeof(from) diff --git a/Libraries/Fusion/Instances/Children.luau b/Libraries/Fusion/Instances/Children.luau index 68d32aa..8cf161b 100644 --- a/Libraries/Fusion/Instances/Children.luau +++ b/Libraries/Fusion/Instances/Children.luau @@ -11,7 +11,7 @@ local logWarn = require "../Logging/logWarn" local Observer = require "../State/Observer" local peek = require "../State/peek" local isState = require "../State/isState" -local typeof = require "../Polyfill/typeof" +local typeof = require "../../../Modules/Polyfill/typeof" type Set = { [T]: boolean } diff --git a/Libraries/Fusion/Instances/OnChange.luau b/Libraries/Fusion/Instances/OnChange.luau index 1960848..2abab9a 100644 --- a/Libraries/Fusion/Instances/OnChange.luau +++ b/Libraries/Fusion/Instances/OnChange.luau @@ -7,7 +7,7 @@ local PubTypes = require "../PubTypes" local logError = require "../Logging/logError" -local typeof = require "../Polyfill/typeof" +local typeof = require "../../../Modules/Polyfill/typeof" local function OnChange(propertyName: string): PubTypes.SpecialKey local changeKey = {} diff --git a/Libraries/Fusion/Instances/OnEvent.luau b/Libraries/Fusion/Instances/OnEvent.luau index ecd54b2..9350f19 100644 --- a/Libraries/Fusion/Instances/OnEvent.luau +++ b/Libraries/Fusion/Instances/OnEvent.luau @@ -7,7 +7,7 @@ local PubTypes = require "../PubTypes" local logError = require "../Logging/logError" -local typeof = require "../Polyfill/typeof" +local typeof = require "../../../Modules/Polyfill/typeof" local function getProperty_unsafe(instance: Instance, property: string) return (instance :: any)[property] diff --git a/Libraries/Fusion/Instances/applyInstanceProps.luau b/Libraries/Fusion/Instances/applyInstanceProps.luau index 0e554bd..1b891af 100644 --- a/Libraries/Fusion/Instances/applyInstanceProps.luau +++ b/Libraries/Fusion/Instances/applyInstanceProps.luau @@ -20,7 +20,7 @@ local xtypeof = require "../Utility/xtypeof" local logError = require "../Logging/logError" local Observer = require "../State/Observer" local peek = require "../State/peek" -local typeof = require "../Polyfill/typeof" +local typeof = require "../../../Modules/Polyfill/typeof" local function setProperty_unsafe( instance: Instance, diff --git a/Libraries/Fusion/Logging/logError.luau b/Libraries/Fusion/Logging/logError.luau index 49f7323..c6b2878 100644 --- a/Libraries/Fusion/Logging/logError.luau +++ b/Libraries/Fusion/Logging/logError.luau @@ -20,24 +20,20 @@ local function logError(messageID: string, errObj: Types.Error?, ...) local errorString if errObj == nil then errorString = string.format( - "[Fusion] " .. formatString .. "\n(ID: " .. messageID .. ")", - ... + string.format(`[Fusion] {formatString}\n(ID: {messageID})`, ...) ) else formatString = - formatString:gsub("ERROR_MESSAGE", tostring(errObj.message)) + string.gsub(formatString, "ERROR_MESSAGE", tostring(errObj.message)) errorString = string.format( - "[Fusion] " - .. formatString - .. "\n(ID: " - .. messageID - .. ")\n---- Stack trace ----\n" - .. tostring(errObj.trace), + `[Fusion] {formatString}\n(ID: {messageID})\n---- Stack trace ----\n{tostring( + errObj.trace + )}`, ... ) end - error(errorString:gsub("\n", "\n "), 0) + error(string.gsub(errorString, "\n", "\n "), 0) end return logError diff --git a/Libraries/Fusion/Logging/logErrorNonFatal.luau b/Libraries/Fusion/Logging/logErrorNonFatal.luau index 9d59733..aedc120 100644 --- a/Libraries/Fusion/Logging/logErrorNonFatal.luau +++ b/Libraries/Fusion/Logging/logErrorNonFatal.luau @@ -19,18 +19,16 @@ local function logErrorNonFatal(messageID: string, errObj: Types.Error?, ...) local errorString if errObj == nil then - errorString = + errorString = string.format( string.format(`[Fusion] {formatString}\n(ID: {messageID})`, ...) + ) else formatString = - formatString:gsub("ERROR_MESSAGE", tostring(errObj.message)) + string.gsub(formatString, "ERROR_MESSAGE", tostring(errObj.message)) errorString = string.format( - "[Fusion] " - .. formatString - .. "\n(ID: " - .. messageID - .. ")\n---- Stack trace ----\n" - .. tostring(errObj.trace), + `[Fusion] {formatString}\n(ID: {messageID})\n---- Stack trace ----\n{tostring( + errObj.trace + )}`, ... ) end diff --git a/Libraries/Fusion/Logging/messages.luau b/Libraries/Fusion/Logging/messages.luau index 0a8ebea..6e218ed 100644 --- a/Libraries/Fusion/Logging/messages.luau +++ b/Libraries/Fusion/Logging/messages.luau @@ -23,6 +23,7 @@ return { forKeysKeyCollision = "ForKeys should only write to output key '%s' once when processing key changes, but it wrote to it twice. Previously input key: '%s'; New input key: '%s'", forKeysDestructorError = "ForKeys destructor error: ERROR_MESSAGE", forPairsDestructorError = "ForPairs destructor error: ERROR_MESSAGE", + -- logError(invalidEasingStyle, easingStyle) forPairsKeyCollision = "ForPairs should only write to output key '%s' once when processing key changes, but it wrote to it twice. Previous input pair: '[%s] = %s'; New input pair: '[%s] = %s'", forPairsProcessorError = "ForPairs callback error: ERROR_MESSAGE", forValuesProcessorError = "ForValues callback error: ERROR_MESSAGE", @@ -49,7 +50,4 @@ return { unrecognisedChildType = "'%s' type children aren't accepted by `[Children]`.", unrecognisedPropertyKey = "'%s' keys aren't accepted in property tables.", unrecognisedPropertyStage = "'%s' isn't a valid stage for a special key to be applied at.", - - invalidEasingStyle = "The easing style must be a valid Enum.EasingStyle or a string of 'Linear', 'Quad', 'Cubic', 'Quart', 'Quint', 'Sine', 'Exponential', 'Circular', 'Elastic', 'Back', 'Bounce'. (got %s)", - invalidEasingDirection = "The easing direction must be a valid Enum.EasingDirection or a string of 'In', 'Out', 'InOut', 'OutIn'. (got %s)", } diff --git a/Libraries/Fusion/MercuryExternal.luau b/Libraries/Fusion/MercuryExternal.luau index b6e9b4b..4a9c412 100644 --- a/Libraries/Fusion/MercuryExternal.luau +++ b/Libraries/Fusion/MercuryExternal.luau @@ -1,6 +1,6 @@ --!strict --[[ - Roblox implementation for Fusion's abstract scheduler layer. + Mercury implementation for Fusion's abstract scheduler layer. ]] local RunService = game:GetService "RunService" @@ -24,7 +24,7 @@ function MercuryExternal.doTaskDeferred(resume: () -> ()) end --[[ - Sends an update step to Fusion using the Roblox clock time. + Sends an update step to Fusion using the Mercury clock time. ]] local function performUpdateStep() External.performUpdateStep(time()) @@ -38,7 +38,7 @@ function MercuryExternal.startScheduler() if stopSchedulerFunc ~= nil then return end - -- if RunService:IsClient() then + -- if not _SERVER then -- In cases where multiple Fusion modules are running simultaneously, -- -- this prevents collisions. -- local id = "FusionUpdateStep_" .. HttpService:GenerateGUID() diff --git a/Libraries/Fusion/PubTypes.luau b/Libraries/Fusion/PubTypes.luau index 6a1a3ff..90c5f36 100644 --- a/Libraries/Fusion/PubTypes.luau +++ b/Libraries/Fusion/PubTypes.luau @@ -133,7 +133,7 @@ export type Spring = StateObject & Dependent & { -- An object which can listen for updates on another state object. export type Observer = Dependent & { kind: "Observer", - onChange: (Observer, callback: () -> ()) -> (() -> ()), + onChange: (Observer, callback: () -> ()) -> () -> (), } --[[ diff --git a/Libraries/Fusion/Types.luau b/Libraries/Fusion/Types.luau index 8470b30..de8fb7f 100644 --- a/Libraries/Fusion/Types.luau +++ b/Libraries/Fusion/Types.luau @@ -83,7 +83,7 @@ export type ForPairs = PubTypes.ForPairs & { -- A state object whose value is derived from other objects using a callback. export type ForKeys = PubTypes.ForKeys & { _oldDependencySet: Set, - _processor: (PubTypes.Use, KI) -> (KO), + _processor: (PubTypes.Use, KI) -> KO, _destructor: (KO, M?) -> (), _inputIsState: boolean, _inputTable: PubTypes.CanBeState<{ [KI]: KO }>, @@ -103,7 +103,7 @@ export type ForKeys = PubTypes.ForKeys & { -- A state object whose value is derived from other objects using a callback. export type ForValues = PubTypes.ForValues & { _oldDependencySet: Set, - _processor: (PubTypes.Use, VI) -> (VO), + _processor: (PubTypes.Use, VI) -> VO, _destructor: (VO, M?) -> (), _inputIsState: boolean, _inputTable: PubTypes.CanBeState<{ [VI]: VO }>, diff --git a/Libraries/Fusion/Utility/cleanup.luau b/Libraries/Fusion/Utility/cleanup.luau index b67c299..9dc5a17 100644 --- a/Libraries/Fusion/Utility/cleanup.luau +++ b/Libraries/Fusion/Utility/cleanup.luau @@ -11,7 +11,7 @@ - an array - `cleanup` will be called on each item ]] -local typeof = require "../Polyfill/typeof" +local typeof = require "../../../Modules/Polyfill/typeof" local function cleanupOne(task: any) local taskType = typeof(task) diff --git a/Libraries/Fusion/Utility/needsDestruction.luau b/Libraries/Fusion/Utility/needsDestruction.luau index 1ee6eb0..6e265b7 100644 --- a/Libraries/Fusion/Utility/needsDestruction.luau +++ b/Libraries/Fusion/Utility/needsDestruction.luau @@ -5,7 +5,7 @@ requires manual cleanup. ]] -local typeof = require "../Polyfill/typeof" +local typeof = require "../../../Modules/Polyfill/typeof" local function needsDestruction(x: any): boolean return typeof(x) == "Instance" diff --git a/Libraries/Fusion/Utility/xtypeof.luau b/Libraries/Fusion/Utility/xtypeof.luau index 19187f1..438dcf2 100644 --- a/Libraries/Fusion/Utility/xtypeof.luau +++ b/Libraries/Fusion/Utility/xtypeof.luau @@ -6,7 +6,7 @@ Otherwise, returns `typeof()` the argument. ]] -local typeof = require "../Polyfill/typeof" +local typeof = require "../../../Modules/Polyfill/typeof" local function xtypeof(x: any) local typeString = typeof(x) diff --git a/Libraries/Fusion/init.luau b/Libraries/Fusion/init.luau index 43ac0e8..ebb7dcb 100644 --- a/Libraries/Fusion/init.luau +++ b/Libraries/Fusion/init.luau @@ -42,8 +42,8 @@ local Fusion = restrictRead("Fusion", { doNothing = require "./Utility/doNothing", peek = require "./State/peek", - typeof = require "./Polyfill/typeof", - TweenInfo = require "./Polyfill/TweenInfo", + typeof = require "../../Modules/Polyfill/typeof", + TweenInfo = require "../../Modules/Polyfill/TweenInfo", Help = function() return "See https://elttob.uk/Fusion/0.3/ for more information." @@ -69,10 +69,10 @@ type Fusion = { New: ( className: string - ) -> ((propertyTable: PubTypes.PropertyTable) -> Instance), + ) -> (propertyTable: PubTypes.PropertyTable) -> Instance, Hydrate: ( target: Instance - ) -> ((propertyTable: PubTypes.PropertyTable) -> Instance), + ) -> (propertyTable: PubTypes.PropertyTable) -> Instance, Ref: PubTypes.SpecialKey, Cleanup: PubTypes.SpecialKey, Children: PubTypes.SpecialKey, diff --git a/Libraries/Fusion/Polyfill/TweenInfo.luau b/Modules/Polyfill/TweenInfo.luau similarity index 77% rename from Libraries/Fusion/Polyfill/TweenInfo.luau rename to Modules/Polyfill/TweenInfo.luau index 1ffb808..23ca5f2 100644 --- a/Libraries/Fusion/Polyfill/TweenInfo.luau +++ b/Modules/Polyfill/TweenInfo.luau @@ -1,7 +1,6 @@ -- A basic polyfill for the TweenInfo.new function, -- allows using Enum.EasingStyle/Direction or strings instead -local logError = require "../Logging/logError" local TweenInfo = {} function TweenInfo.new( @@ -19,7 +18,7 @@ function TweenInfo.new( -- convert it to a string if type(easingStyle) ~= "string" then if easingStyle then - easingStyle = tostring(easingStyle):gsub("Enum.%w+.", "") + easingStyle = string.gsub(tostring(easingStyle), "Enum.%w+.", "") end else local ok @@ -42,13 +41,17 @@ function TweenInfo.new( end end if not ok then - logError("invalidEasingStyle", nil, easingStyle) + error( + `The easing style must be a valid Enum.EasingStyle or a string of 'Linear', 'Quad', 'Cubic', 'Quart', 'Quint', 'Sine', 'Exponential', 'Circular', 'Elastic', 'Back', 'Bounce'. (got {easingStyle})`, + 0 + ) end end if type(easingDirection) ~= "string" then if easingDirection then - easingDirection = tostring(easingDirection):gsub("Enum.%w+.", "") + easingDirection = + string.gsub(tostring(easingDirection), "Enum.%w+.", "") end else local ok @@ -64,7 +67,10 @@ function TweenInfo.new( end end if not ok then - logError("invalidEasingDirection", nil, easingDirection) + error( + `The easing direction must be a valid Enum.EasingDirection or a string of 'In', 'Out', 'InOut', 'OutIn'. (got {easingDirection})`, + 0 + ) end end @@ -99,9 +105,9 @@ function TweenInfo.new( .. " Reverses:" .. (reverses and "True" or "False") .. " EasingDirection:" - .. easingDirection + .. tostring(easingDirection) .. " EasingStyle:" - .. easingStyle + .. tostring(easingStyle) end mt.__metatable = "The metatable is locked" diff --git a/Libraries/Fusion/Polyfill/easing.luau b/Modules/Polyfill/easing.luau similarity index 100% rename from Libraries/Fusion/Polyfill/easing.luau rename to Modules/Polyfill/easing.luau diff --git a/Libraries/Fusion/Polyfill/typeof.luau b/Modules/Polyfill/typeof.luau similarity index 99% rename from Libraries/Fusion/Polyfill/typeof.luau rename to Modules/Polyfill/typeof.luau index 170ab2c..faa9f2b 100644 --- a/Libraries/Fusion/Polyfill/typeof.luau +++ b/Modules/Polyfill/typeof.luau @@ -178,4 +178,5 @@ return function(value) return t end end + return basicType end From e163e48f88405036373af52075652298100f7d51 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Sun, 28 Jan 2024 17:10:52 +0000 Subject: [PATCH 3/8] Compile Red library from corescripts/Libraries, update types to make Red FULLY TYPE SAFE! --- Libraries/Red/Net/Event.luau | 411 +++++++++ Libraries/Red/Net/Serdes.luau | 76 ++ Libraries/Red/Net/init.luau | 198 +++++ Libraries/Red/RedEvent.luau | 45 + Libraries/Red/Util/Bin.luau | 27 + Libraries/Red/Util/Clock.luau | 67 ++ Libraries/Red/Util/Collection.luau | 41 + Libraries/Red/Util/Promise.luau | 206 +++++ Libraries/Red/Util/Ratelimit.luau | 40 + Libraries/Red/Util/Signal.luau | 74 ++ Libraries/Red/Util/Spawn.luau | 23 + Libraries/Red/init.luau | 33 + Libraries/Red/typeof.luau | 181 ++++ compile.sh | 1 + defs.d.lua | 484 ++++++---- luau/10000002.luau | 1334 ---------------------------- 16 files changed, 1726 insertions(+), 1515 deletions(-) create mode 100644 Libraries/Red/Net/Event.luau create mode 100644 Libraries/Red/Net/Serdes.luau create mode 100644 Libraries/Red/Net/init.luau create mode 100644 Libraries/Red/RedEvent.luau create mode 100644 Libraries/Red/Util/Bin.luau create mode 100644 Libraries/Red/Util/Clock.luau create mode 100644 Libraries/Red/Util/Collection.luau create mode 100644 Libraries/Red/Util/Promise.luau create mode 100644 Libraries/Red/Util/Ratelimit.luau create mode 100644 Libraries/Red/Util/Signal.luau create mode 100644 Libraries/Red/Util/Spawn.luau create mode 100644 Libraries/Red/init.luau create mode 100644 Libraries/Red/typeof.luau delete mode 100644 luau/10000002.luau diff --git a/Libraries/Red/Net/Event.luau b/Libraries/Red/Net/Event.luau new file mode 100644 index 0000000..c23d23d --- /dev/null +++ b/Libraries/Red/Net/Event.luau @@ -0,0 +1,411 @@ +return function(IsServer: boolean) + local RunService = game:GetService "RunService" + + local RedEvent = require "../RedEvent"(IsServer) + local Remote = RedEvent.Remote + + local Serdes = require "./Serdes"(IsServer) + + local Spawn = require "../Util/Spawn" + local Promise = require "../Util/Promise" + local Clock = require "../Util/Clock" + + local Event = {} + local nil_symbol = { __nil = true } + + Event.Callbacks = {} :: { [string]: ((...any) -> ...any)? } + Event.Outgoing = {} :: any + + if not IsServer then + Event.ActiveCalls = {} + end + + function Event.Listen() + -- debug.setmemorycategory "Red.Listen" + if not IsServer then + Remote.OnClientEvent:connect( + function(SingleFire, MultipleFire, IncomingCall) + -- debug.profilebegin "Red.Listen.Incoming" + + -- replace nil symbols with nil + if SingleFire.__nil then + SingleFire = nil + end + if MultipleFire.__nil then + MultipleFire = nil + end + if IncomingCall.__nil then + IncomingCall = nil + end + + if SingleFire then + -- debug.profilebegin "Red.Listen.Incoming.SingleFire" + + for EventId, Call in pairs(SingleFire) do + local Callback = Event.Callbacks[EventId] + + local c = 0 + repeat -- bruh + RunService.Stepped:wait() + Callback = Event.Callbacks[EventId] + c += 1 + until Callback or c > 500 -- random + + if Callback then + if type(Call) == "table" then + Spawn(Callback, unpack(Call)) + else + Spawn(Callback, Call) + end + else + print "[Red]: Callback not found!" + end + end + + -- debug.profileend() + end + + if MultipleFire then + -- debug.profilebegin "Red.Listen.Incoming.Fire" + + for EventId, Calls in pairs(MultipleFire) do + local Callback = Event.Callbacks[EventId] + + if Callback then + for _, Call in ipairs(Calls) do + if type(Call) == "table" then + Spawn(Callback, unpack(Call)) + else + Spawn(Callback, Call) + end + end + end + end + + -- debug.profileend() + end + + if IncomingCall then + -- debug.profilebegin "Red.Listen.Incoming.Call" + + for _, Call in pairs(IncomingCall) do + local CallId = table.remove(Call, 1) + local Success = table.remove(Call, 1) + + if Event.ActiveCalls[CallId] then + if Success then + Event.ActiveCalls[CallId].Resolve( + unpack(Call) + ) + else + Event.ActiveCalls[CallId].Reject( + unpack(Call) + ) + end + + Event.ActiveCalls[CallId] = nil + end + end + + -- debug.profileend() + end + + -- debug.profileend() + end + ) + + Clock.new(1 / 60, function() + -- debug.profilebegin "Red.Listen.Outgoing" + + if not next(Event.Outgoing) then + return + end + + local SingleFire = {} + local SendSingleFire = false + + if Event.Outgoing[1] then + for EventId, Calls in pairs(Event.Outgoing[1]) do + if #Calls == 1 then + SingleFire[EventId] = Calls[1] + Event.Outgoing[1][EventId] = nil + + SendSingleFire = true + end + end + end + + -- nils cannot be sent properly across remoteevents in 2013, + -- so we have to use a symbol to represent nil + local sf = nil_symbol + if SendSingleFire then + sf = SingleFire + end + + local eo1, eo2 = Event.Outgoing[1], Event.Outgoing[2] + if eo1 == nil then + eo1 = nil_symbol + end + if eo2 == nil then + eo2 = nil_symbol + end + + Remote:FireServer(sf, eo1, eo2) + + -- table.clear(Event.Outgoing) + for i, _ in pairs(Event.Outgoing) do + Event.Outgoing[i] = nil + end + + -- debug.profileend() + end) + else + Remote.OnServerEvent:connect( + function(Player, SingleFire, MultipleFire, IncomingCall) + -- debug.profilebegin "Red.Listen.Incoming" + + -- replace nil symbols with nil + if SingleFire.__nil then + SingleFire = nil + end + if MultipleFire.__nil then + MultipleFire = nil + end + if IncomingCall.__nil then + IncomingCall = nil + end + + if SingleFire then + -- debug.profilebegin "Red.Listen.Incoming.SingleFire" + + for EventId, Call in pairs(SingleFire) do + local Callback = Event.Callbacks[EventId] + + if Callback then + if type(Call) == "table" then + Spawn(Callback, Player, unpack(Call)) + else + Spawn(Callback, Player, Call) + end + end + end + + -- debug.profileend() + end + + if MultipleFire then + -- debug.profilebegin "Red.Listen.Incoming.MultipleFire" + + for EventId, Calls in pairs(MultipleFire) do + local Callback = Event.Callbacks[EventId] + + if Callback then + for _, Call in ipairs(Calls) do + if type(Call) == "table" then + Spawn(Callback, Player, unpack(Call)) + else + Spawn(Callback, Player, Call) + end + end + end + end + + -- debug.profileend() + end + + if IncomingCall then + -- debug.profilebegin "Red.Listen.Incoming.Call" + + for EventId, Calls in pairs(IncomingCall) do + if Event.Callbacks[EventId] then + for _, Call in ipairs(Calls) do + Spawn(function() + local CallId = table.remove(Call, 1) + local Result = { + CallId, + pcall( + Event.Callbacks[EventId], + Player, + unpack(Call) + ), + } + + if Event.Outgoing[Player] == nil then + Event.Outgoing[Player] = {} + end + + if Event.Outgoing[Player][2] == nil then + Event.Outgoing[Player][2] = {} + end + + table.insert( + Event.Outgoing[Player][2], + Result + ) + end) + end + else + if Event.Outgoing[Player] == nil then + Event.Outgoing[Player] = {} + end + + if Event.Outgoing[Player][2] == nil then + Event.Outgoing[Player][2] = {} + end + + for _, Call in ipairs(Calls) do + table.insert(Event.Outgoing[Player][2], { + Call[1], + false, + "[Red]: Event not found", + }) + end + end + end + + -- debug.profileend() + end + + -- debug.profileend() + end + ) + + RunService.Heartbeat:connect(function() + -- debug.profilebegin "Red.Listen.Outgoing" + + for Player, Packets in pairs(Event.Outgoing) do + local SingleCall = {} + local SendSingleCall = false + + if Packets[1] then + for EventId, Calls in pairs(Packets[1]) do + if #Calls == 1 then + SingleCall[EventId] = Calls[1] + Packets[1][EventId] = nil + + SendSingleCall = true + end + end + end + + -- nils cannot be sent properly across remoteevents in 2013, + -- so we have to use a symbol to represent nil + local sc = nil_symbol + if SendSingleCall then + sc = SingleCall + end + + local p1, p2 = Packets[1], Packets[2] + + if p1 == nil then + p1 = nil_symbol + end + if p2 == nil then + p2 = nil_symbol + end + + Remote:FireClient(Player, sc, p1, p2) + end + + -- table.clear(Event.Outgoing) + for i, _ in pairs(Event.Outgoing) do + Event.Outgoing[i] = nil + end + + -- debug.profileend() + end) + end + end + + function Event.AddQueue(Queue: { any }, Call: { any }) + local Length = #Call + + if Length == 1 then + local Type = type(Call[1]) + + if Type ~= "table" then + table.insert(Queue, Call[1]) + else + table.insert(Queue, Call) + end + else + table.insert(Queue, Call) + end + end + + function Event.FireClient(Player: Player, EventName: string, ...) + assert(IsServer, "Event.FireClient can only be called from the server") + + local EventId = Serdes.IdentifierAsync(EventName) + + if Event.Outgoing[Player] == nil then + Event.Outgoing[Player] = {} + end + + if Event.Outgoing[Player][1] == nil then + Event.Outgoing[Player][1] = {} + end + + if Event.Outgoing[Player][1][EventId] == nil then + Event.Outgoing[Player][1][EventId] = {} + end + + Event.AddQueue(Event.Outgoing[Player][1][EventId], { ... }) + end + + function Event.FireServer(EventName: string, ...) + assert( + not IsServer, + "Event.FireServer can only be called on the client" + ) + + local Args = { ... } + + return Serdes.Identifier(EventName):Then(function(EventId) + if Event.Outgoing[1] == nil then + Event.Outgoing[1] = {} + end + + if Event.Outgoing[1][EventId] == nil then + Event.Outgoing[1][EventId] = {} + end + + Event.AddQueue(Event.Outgoing[1][EventId], Args) + end) + end + + function Event.Call(EventName: string, ...) + assert(not IsServer, "Event.Call can only be called on the client") + + local Args = { ... } + + return Promise.new(function(Resolve, Reject) + local CallId = Serdes.OneTime() + local EventId = Serdes.IdentifierAsync(EventName) + + if Event.Outgoing[2] == nil then + Event.Outgoing[2] = {} + end + + if Event.Outgoing[2][EventId] == nil then + Event.Outgoing[2][EventId] = {} + end + + table.insert(Args, 1, CallId) + table.insert(Event.Outgoing[2][EventId], Args) + + Event.ActiveCalls[CallId] = { + Resolve = Resolve, + Reject = Reject, + } + end) + end + + function Event.SetCallback(EventName: string, Callback: ((...any) -> any)?) + return Serdes.Identifier(EventName):Then(function(EventId) + Event.Callbacks[EventId] = Callback + end) + end + + return Event +end diff --git a/Libraries/Red/Net/Serdes.luau b/Libraries/Red/Net/Serdes.luau new file mode 100644 index 0000000..1cdcb48 --- /dev/null +++ b/Libraries/Red/Net/Serdes.luau @@ -0,0 +1,76 @@ +return function(IsServer: boolean) + local RedEvent = require "../RedEvent"(IsServer) + local Event = RedEvent.Remote + + local Promise = require "../Util/Promise" + local Serdes = {} + + Serdes.NextId = 1 -- StringValues think null bytes are empty strings + Serdes.NextOT = 1 + + function Serdes.RegisterIdentifier(Name: string) + assert(IsServer, "RegisterIdentifier can only be called on the server") + + local Id = string.char(Serdes.NextId) + Serdes.NextId += 1 + + local e = Event:FindFirstChild(Name) + if e then + e.Value = Id + else + e = Instance.new "StringValue" + e.Name = Name + e.Value = Id + e.Parent = Event + end + + return Id + end + + function Serdes.Identifier(Name: string) + if not IsServer then + return Promise.new(function(Resolve) + local e = Event:WaitForChild(Name) + if e.Value ~= nil then + Resolve(e.Value) + else + local Thread = Delay(5, function() + print( + "[Red.Serdes]: Retrieving identifier exceeded 5 seconds. Make sure '" + .. Name + .. "' is registered on the server." + ) + end) + + e.Changed:Once(function() + coroutine.yield(Thread :: thread) + + Resolve(e.Value) + end) + end + end) + else + local e = Event:FindFirstChild(Name) + if e and e.Value then + return Promise.Resolve(e.Value) + end + return Promise.Resolve(Serdes.RegisterIdentifier(Name)) + end + end + + function Serdes.IdentifierAsync(Name: string) + return Serdes.Identifier(Name):Await() + end + + function Serdes.OneTime() + Serdes.NextOT += 1 + + if Serdes.NextOT == 0xFFFF + 1 then + Serdes.NextOT = 0 + end + + return string.char(Serdes.NextOT) + end + + return Serdes +end diff --git a/Libraries/Red/Net/init.luau b/Libraries/Red/Net/init.luau new file mode 100644 index 0000000..d6dd261 --- /dev/null +++ b/Libraries/Red/Net/init.luau @@ -0,0 +1,198 @@ +return function(IsServer: boolean) + local Players = game:GetService "Players" + + local RedEvent = require "../RedEvent"(IsServer) + local Remote = RedEvent.Remote + local ClientFolder = RedEvent.ClientFolder + + local Serdes = require "./Serdes"(IsServer) + local Event = require "./Event"(IsServer) + + local Server = {} + Server.__index = Server + + function Server.Server(Name: string) + local self = setmetatable({}, Server) + + self.Name = Name + self.FolderInstance = nil :: Folder? + + return self + end + + Server.new = Server.Server + + function Server.Fire(self: Server, Player: Player, EventName: string, ...) + Event.FireClient(Player, self.Name .. "_" .. EventName, ...) + end + + function Server.FireAll(self: Server, EventName: string, ...) + for _, Player in ipairs(Players:GetPlayers()) do + self:Fire(Player, EventName, ...) + end + end + + function Server.FireAllExcept( + self: Server, + Player: Player, + EventName: string, + ... + ) + for _, OtherPlayer in ipairs(Players:GetPlayers()) do + if OtherPlayer ~= Player then + self:Fire(OtherPlayer, EventName, ...) + end + end + end + + function Server.FireList( + self: Server, + PlayerList: { Player }, + EventName: string, + ... + ) + for _, Player in ipairs(PlayerList) do + self:Fire(Player, EventName, ...) + end + end + + function Server.FireWithFilter( + self: Server, + Filter: (Player) -> boolean, + EventName: string, + ... + ) + for _, Player in ipairs(Players:GetPlayers()) do + if Filter(Player) then + self:Fire(Player, EventName, ...) + end + end + end + + function Server.On( + self: Server, + EventName: string, + Callback: ((Player, ...any) -> ...any)? + ) + Event.SetCallback(self.Name .. "_" .. EventName, Callback) + end + + function Server.Folder(self: Server, Player: Player?) + if Player then + ClientFolder = ( + Player:FindFirstChild "PlayerGui" :: any + ).Red :: ScreenGui + + if ClientFolder:FindFirstChild(self.Name) then + return ClientFolder:FindFirstChild(self.Name) :: Model + else + local Folder = Instance.new "Model" + Folder.Name = self.Name + Folder.Parent = ClientFolder + + return Folder :: Model + end + else + if not self.FolderInstance then + local Folder = Instance.new "Model" + Folder.Name = self.Name + Folder.Parent = Remote + + self.FolderInstance = Folder + end + + return self.FolderInstance :: Model + end + end + + export type Server = typeof(Server.Server "") + + local Client = {} + Client.__index = Client + + function Client.Client(Name: string) + local self = setmetatable({}, Client) + + self.Name = Name + self.FolderInstance = nil :: Folder? + self.LocalFolderInstance = nil :: Folder? + + return self + end + + Client.new = Client.Client + + function Client.Fire(self: Client, EventName: string, ...) + return Event.FireServer(self.Name .. "_" .. EventName, ...) + end + + function Client.Call(self: Client, EventName: string, ...) + return Event.Call(self.Name .. "_" .. EventName, ...) + end + + function Client.On( + self: Client, + EventName: string, + Callback: ((...any) -> ())? + ) + return Event.SetCallback(self.Name .. "_" .. EventName, Callback) + end + + function Client.Folder(self: Client) + if not self.FolderInstance then + self.FolderInstance = Remote:WaitForChild(self.Name) :: Model + end + + return self.FolderInstance :: Model + end + + function Client.LocalFolder(self: Client) + if not self.LocalFolderInstance then + self.LocalFolderInstance = + ClientFolder:WaitForChild(self.Name) :: Model + end + + return self.LocalFolderInstance :: Model + end + + export type Client = typeof(Client.Client "") + + local Net = {} + + Net.ServerNamespaceList = {} + Net.ClientNamespaceList = {} + + function Net.Server(Name: string, Definitions: { string }?): Server + assert(IsServer, "Net.Server can only be used on the server") + + if not Net.ServerNamespaceList[Name] then + Net.ServerNamespaceList[Name] = Server.Server(Name) + end + + if Definitions then + for _, Term in ipairs(Definitions) do + Serdes.Identifier((Name :: string) .. "_" .. Term) + end + end + + return Net.ServerNamespaceList[Name] + end + + function Net.Client(Name: string): Client + assert(not IsServer, "Net.Client can only be used on the client") + + if Net.ClientNamespaceList[Name] == nil then + Net.ClientNamespaceList[Name] = Client.Client(Name) + end + + return Net.ClientNamespaceList[Name] + end + + function Net.Identifier(Name: string) + return Serdes.Identifier(Name) + end + + Event.Listen() + + return Net +end diff --git a/Libraries/Red/RedEvent.luau b/Libraries/Red/RedEvent.luau new file mode 100644 index 0000000..197fc24 --- /dev/null +++ b/Libraries/Red/RedEvent.luau @@ -0,0 +1,45 @@ +return function(IsServer: boolean) + local ReplicatedStorage = game:GetService "ReplicatedStorage" + local Players = game:GetService "Players" + + local RedEvent = {} + + local Remote, ClientFolder + + if IsServer then + Remote = ReplicatedStorage:FindFirstChild "RedEvent" :: RemoteEvent + + if not Remote then -- prevent vile promise bugs + Remote = Instance.new "RemoteEvent" + Remote.Name = "RedEvent" + Remote.Parent = ReplicatedStorage + end + + local function PlayerAdded(Player: Player) + ClientFolder = Instance.new "ScreenGui" + + -- ClientFolder.Enabled = false + -- ClientFolder.ResetOnSpawn = false + ClientFolder.Name = "Red" + ClientFolder.Parent = Player:FindFirstChild "PlayerGui" + end + + Players.PlayerAdded:connect(PlayerAdded) + + for _, Player in ipairs(Players:GetPlayers()) do + PlayerAdded(Player) + end + else + Remote = ReplicatedStorage:WaitForChild "RedEvent" :: RemoteEvent + + ClientFolder = ( + Players.LocalPlayer:FindFirstChild "PlayerGui" :: PlayerGui + ):WaitForChild "Red" :: ScreenGui + ClientFolder.Parent = nil + end + + RedEvent.Remote = Remote + RedEvent.ClientFolder = ClientFolder + + return RedEvent +end diff --git a/Libraries/Red/Util/Bin.luau b/Libraries/Red/Util/Bin.luau new file mode 100644 index 0000000..d950fe1 --- /dev/null +++ b/Libraries/Red/Util/Bin.luau @@ -0,0 +1,27 @@ +local Spawn = require "./Spawn" +local typeof = require "../../../Modules/Polyfill/typeof" + +type BinItem = Instance | RBXScriptConnection | () -> ...any + +return function() + local Bin: { BinItem } = {} + + return function(Item: BinItem) + table.insert(Bin, Item) + end, function() + for _, Item in ipairs(Bin) do + if typeof(Item) == "Instance" then + Item:Destroy() + elseif typeof(Item) == "RBXScriptConnection" then + Item:disconnect() + elseif typeof(Item) == "function" then + Spawn(Item) + end + end + + -- table.clear(Bin) + for i, _ in ipairs(Bin) do + Bin[i] = nil + end + end +end diff --git a/Libraries/Red/Util/Clock.luau b/Libraries/Red/Util/Clock.luau new file mode 100644 index 0000000..c01c1dc --- /dev/null +++ b/Libraries/Red/Util/Clock.luau @@ -0,0 +1,67 @@ +-- local ReplicatedStorage = game:GetService "ReplicatedStorage" +local RunService = game:GetService "RunService" + +local function MakeHeartbeatFunction(Clock: Clock) + return function(Delta) + Clock:Advance(Delta) + end +end + +local Clock = {} +Clock.__index = Clock + +function Clock.Clock(Interval: number, Callback: () -> ()) + local self = setmetatable({}, Clock) + + self.Interval = Interval + self.Callback = Callback + self.Delta = 0 + + self.Connection = RunService.Heartbeat:connect(MakeHeartbeatFunction(self)) + + return self +end + +Clock.new = Clock.Clock + +function Clock.Pause(self: Clock) + if self.Connection then + self.Connection:Disconnect() + end +end + +function Clock.Resume(self: Clock) + if self.Connection.Connected then + return + end + + self.Connection = RunService.Heartbeat:connect(MakeHeartbeatFunction(self)) +end + +function Clock.Advance(self: Clock, Delta: number) + self.Delta += Delta + + if self.Delta >= self.Interval * 10 then + local Skipped = math.floor(self.Delta / self.Interval) + self.Delta -= Skipped * self.Interval + + -- if ReplicatedStorage:GetAttribute "RedDebug" then + -- warn( + -- "[Red.Clock]: Clock is falling behind! Skipped " + -- .. Skipped + -- .. " intervals." + -- ) + -- end + + return + end + + if self.Delta >= self.Interval then + self.Delta -= self.Interval + self.Callback() + end +end + +export type Clock = typeof(Clock.Clock(...)) + +return Clock diff --git a/Libraries/Red/Util/Collection.luau b/Libraries/Red/Util/Collection.luau new file mode 100644 index 0000000..f1b6ac9 --- /dev/null +++ b/Libraries/Red/Util/Collection.luau @@ -0,0 +1,41 @@ +local CollectionService = game:GetService "CollectionService" + +local Spawn = require "./Spawn" + +return function( + Tag: string, + Start: (Instance) -> T..., + Stop: (T...) -> () +): () -> () + local InstanceMap = {} + + for _, Instance in ipairs(CollectionService:GetTagged(Tag)) do + Spawn(function() + InstanceMap[Instance] = { Start(Instance) } + end) + end + + local AddConnection = CollectionService:GetInstanceAddedSignal(Tag) + :connect(function(Instance) + InstanceMap[Instance] = { Start(Instance) } + end) + + local RemoveConnection = CollectionService:GetInstanceRemovedSignal(Tag) + :connect(function(Instance) + local Value = InstanceMap[Instance] + + if Value then + InstanceMap[Instance] = nil + Stop(unpack(Value)) + end + end) + + return function() + AddConnection:Disconnect() + RemoveConnection:Disconnect() + + for Instance, Value in pairs(InstanceMap) do + Spawn(Stop, unpack(Value)) + end + end +end diff --git a/Libraries/Red/Util/Promise.luau b/Libraries/Red/Util/Promise.luau new file mode 100644 index 0000000..69804b0 --- /dev/null +++ b/Libraries/Red/Util/Promise.luau @@ -0,0 +1,206 @@ +local RunService = game:GetService "RunService" + +local Spawn = require "./Spawn" + +local Promise = {} +Promise.__index = Promise + +function Promise.Promise(Callback: ( + Resolve: (...any) -> (), + Reject: (...any) -> () +) -> ()) + local self = setmetatable({}, Promise) + + self.Status = "Pending" + self.OnResolve = {} :: { (...any) -> () } + self.OnReject = {} :: { (...any) -> () } + + self.Value = {} :: { any } + + self.Thread = nil :: thread? + self.Thread = coroutine.create(function() + local ok, err = ypcall(Callback, function(...) + self:_Resolve(...) + end, function(...) + self:_Reject(...) + end) + + if not ok then + self:_Reject(err) + end + end) + + coroutine.resume(self.Thread :: thread) + + return self +end + +Promise.new = Promise.Promise :: ( + Callback: (Resolve: (...any) -> (), Reject: (...any) -> ()) -> () +) -> Promise + +function Promise.Resolve(...: any): Promise + local self = setmetatable({}, Promise) + + self.Status = "Resolved" + self.OnResolve = {} :: { (...any) -> () } + self.OnReject = {} :: { (...any) -> () } + self.Value = { ... } :: { any } + self.Thread = nil :: thread? + + return self +end + +function Promise.Reject(...: any): Promise + local self = setmetatable({}, Promise) + + self.Status = "Rejected" + self.OnResolve = {} :: { (...any) -> () } + self.OnReject = {} :: { (...any) -> () } + self.Value = { ... } :: { any } + self.Thread = nil :: thread? + + return self +end + +function Promise._Resolve(self: Promise, ...: any) + assert( + self.Status == "Pending", + "Cannot resolve a promise that is not pending." + ) + + self.Status = "Resolved" + self.Value = { ... } + + for _, Callback in ipairs(self.OnResolve) do + Spawn(Callback, ...) + end + + -- task.defer(task.cancel, self.Thread :: thread) + coroutine.resume(coroutine.create(function() + coroutine.yield(self.Thread :: thread) + end)) +end + +function Promise._Reject(self: Promise, ...: any) + assert( + self.Status == "Pending", + "Cannot reject a promise that is not pending." + ) + + self.Status = "Rejected" + self.Value = { ... } + + for _, Callback in ipairs(self.OnReject) do + Spawn(Callback, ...) + end + + -- task.defer(task.cancel, self.Thread :: thread) + coroutine.resume(coroutine.create(function() + coroutine.yield(self.Thread :: thread) + end)) +end + +function Promise.Then( + self: Promise, + OnResolve: ((...any) -> ...any)?, + OnReject: ((...any) -> ...any)? +): Promise + return Promise.Promise(function(Resolve, Reject) + local function PromiseResolutionProcedure( + Value: Promise | any, + ...: any + ) + if type(Value) == "table" and getmetatable(Value) == Promise then + if Value.Status == "Pending" then + table.insert(Value.OnResolve, Resolve) + table.insert(Value.OnReject, Reject) + elseif Value.Status == "Resolved" then + Resolve(Value.Value) + elseif Value.Status == "Rejected" then + Reject(Value.Value) + end + else + Resolve(Value, ...) + end + end + + if self.Status == "Pending" then + if OnResolve then + table.insert(self.OnResolve, function(...) + PromiseResolutionProcedure(OnResolve(...)) + end) + else + table.insert(self.OnResolve, PromiseResolutionProcedure) + end + + if OnReject then + table.insert(self.OnReject, function(...) + PromiseResolutionProcedure(OnReject(...)) + end) + else + table.insert(self.OnReject, Reject) + end + elseif self.Status == "Resolved" then + if OnResolve then + PromiseResolutionProcedure(OnResolve(unpack(self.Value))) + else + Resolve(unpack(self.Value)) + end + elseif self.Status == "Rejected" then + if OnReject then + PromiseResolutionProcedure(OnReject(unpack(self.Value))) + else + Reject(unpack(self.Value)) + end + end + end) +end + +function Promise.Catch(self: Promise, OnReject: (...any) -> ()) + return self:Then(nil, OnReject) +end + +function Promise.Finally(self: Promise, Finally: () -> ()) + return self:Then(function(...) + Finally() + return ... + end, function(Error) + Finally() + error(Error) + end) +end + +function Promise.Await(self: Promise): ...any + if self.Status == "Resolved" then + return unpack(self.Value) + elseif self.Status == "Rejected" then + return error(unpack(self.Value)) + end + + local c = 0 + repeat -- bruh + RunService.Stepped:wait() + c += 1 + until self.Status ~= "Pending" or c > 500 -- random + + local Current = coroutine.running() + + local function Resume() + coroutine.resume(Current) + end + + table.insert(self.OnResolve, Resume) + table.insert(self.OnReject, Resume) + + coroutine.yield() + + if self.Status == "Resolved" then + return unpack(self.Value) + end + return error(unpack(self.Value)) +end + +export type Promise = typeof(Promise.Promise(...)) + +return Promise diff --git a/Libraries/Red/Util/Ratelimit.luau b/Libraries/Red/Util/Ratelimit.luau new file mode 100644 index 0000000..7435f2c --- /dev/null +++ b/Libraries/Red/Util/Ratelimit.luau @@ -0,0 +1,40 @@ +return function(Limit: number, Interval: number) + assert(Limit > 0, "Limit must be greater than 0") + + local CountMap = {} :: { [T]: number } + local CountKeyless = 0 + + return function(Key: T?) + if Key then + local Count = CountMap[Key] + + if Count == nil then + Count = 0 + + Delay(Interval, function() + CountMap[Key] = nil + end) + end + + if Count >= Limit then + return false + end + + CountMap[Key] = Count + 1 + else + if CountKeyless == 0 then + Delay(Interval, function() + CountKeyless = 0 + end) + end + + if CountKeyless >= Limit then + return false + end + + CountKeyless += 1 + end + + return true + end +end diff --git a/Libraries/Red/Util/Signal.luau b/Libraries/Red/Util/Signal.luau new file mode 100644 index 0000000..6da6de1 --- /dev/null +++ b/Libraries/Red/Util/Signal.luau @@ -0,0 +1,74 @@ +local Promise = require "./Promise" +local Spawn = require "./Spawn" + +type SignalNode = { + Next: SignalNode?, + Callback: (T...) -> (), +} + +export type Signal = { + Root: SignalNode?, + + Connect: (self: Signal, Callback: (T...) -> ()) -> () -> (), + Wait: (self: Signal) -> Promise.Promise, + Fire: (self: Signal, T...) -> (), + DisconnectAll: (self: Signal) -> (), +} + +local Signal = {} +Signal.__index = Signal + +function Signal.new(): Signal + return setmetatable({ + Root = nil, + }, Signal) :: any +end + +function Signal.Connect(self: Signal, Callback: (T...) -> ()): () -> () + local Node = { + Next = self.Root, + Callback = Callback, + } + + self.Root = Node + + return function() + if self.Root == Node then + self.Root = Node.Next + else + local Current = self.Root + while Current do + if Current.Next == Node then + Current.Next = Node.Next + break + end + Current = Current.Next + end + end + end +end + +function Signal.Wait(self: Signal): Promise.Promise + return Promise.new(function(Resolve) + local Disconnect + + Disconnect = self:Connect(function(...) + Disconnect() + Resolve(... :: any) + end) + end) +end + +function Signal.Fire(self: Signal, ...: T...) + local Current = self.Root + while Current do + Spawn(Current.Callback, ...) + Current = Current.Next + end +end + +function Signal.DisconnectAll(self: Signal) + self.Root = nil +end + +return Signal diff --git a/Libraries/Red/Util/Spawn.luau b/Libraries/Red/Util/Spawn.luau new file mode 100644 index 0000000..1dc1317 --- /dev/null +++ b/Libraries/Red/Util/Spawn.luau @@ -0,0 +1,23 @@ +local FreeThread: thread? = nil + +local function FunctionPasser(fn, ...) + local AquiredThread = FreeThread + FreeThread = nil + fn(...) + FreeThread = AquiredThread +end + +local function Yielder() + while true do + FunctionPasser(coroutine.yield()) + end +end + +return function(fn: (T...) -> (), ...: T...) + if not FreeThread then + FreeThread = coroutine.create(Yielder) + coroutine.resume(FreeThread :: any) + end + + coroutine.resume(FreeThread :: thread, fn, ...) +end diff --git a/Libraries/Red/init.luau b/Libraries/Red/init.luau new file mode 100644 index 0000000..92143c7 --- /dev/null +++ b/Libraries/Red/init.luau @@ -0,0 +1,33 @@ +local function Red(_, Script: LuaSourceContainer) + local _SERVER + + if Script:IsA "LocalScript" then + _SERVER = false + elseif Script:IsA "Script" then + _SERVER = true + else + error("Argument must be the script itself", 2) + end + + local Net = require "./Net"(_SERVER) + + return { + Server = Net.Server, + Client = Net.Client, + + Collection = require "./Util/Collection", + Ratelimit = require "./Util/Ratelimit", + Promise = require "./Util/Promise", + Signal = require "./Util/Signal", + Clock = require "./Util/Clock", + Spawn = require "./Util/Spawn", + Bin = require "./Util/Bin", + } +end + +return { + Help = function() + return "See https://redblox.dev/ for more information." + end, + Load = Red, +} diff --git a/Libraries/Red/typeof.luau b/Libraries/Red/typeof.luau new file mode 100644 index 0000000..170ab2c --- /dev/null +++ b/Libraries/Red/typeof.luau @@ -0,0 +1,181 @@ +-- A basic polyfill for the typeof function + +return function(value) + local basicType = type(value) + + if + basicType == "nil" + or basicType == "boolean" + or basicType == "number" + or basicType == "string" + or basicType == "function" + or basicType == "thread" + or basicType == "table" + then + return basicType + end + + -- Will short-circuit + --[[ + { + name of type to check, + { list of required properties }, + } + ]] + local tests = { + { + "Instance", + { "ClassName" }, + }, + { + "EnumItem", + { "EnumType", "Name", "Value" }, + }, + { + "Enum", + { "GetEnumItems" }, + }, + { + "Enums", + { "MembershipType" }, -- lmao + }, + { + "RBXScriptSignal", + { + "connect", + -- "connected", + -- "connectFirst", + -- "connectLast", + "wait", + }, + }, + { + "RBXScriptConnection", + { + "connected", + "disconnect", + }, + }, + { + "TweenInfo", + { + "EasingDirection", + -- "Time", + -- "DelayTime", + "RepeatCount", + "EasingStyle", + -- "Reverses", + }, + }, + { + "CFrame", + { + "p", + "x", + "y", + "z", + "lookVector", + }, + }, + { + "Vector3", + { + "Lerp", + -- "Cross", + -- "Dot", + "unit", + "magnitude", + "x", + "y", + "z", + }, + }, + { + "Vector3int16", + { "z", "x", "y" }, + }, + { + "Vector2", + { "unit", "magnitude", "x", "y" }, + }, + { + "Vector2int16", + { "x", "y" }, + }, + { + "Region3", + { "CFrame", "Size" }, + }, + { + "Region3int16", + { "Min", "Max" }, + }, + { + "Ray", + { + "Origin", + "Direction", + "Unit", + "ClosestPoint", + "Distance", + }, + }, + { + "UDim", + { "Scale", "Offset" }, + }, + { + "Axes", + { "Z", "X", "Y" }, + }, + { + "UDim2", + { "X", "Y" }, + }, + { + "BrickColor", + { + "Number", + "Name", + "Color", + "r", + "g", + "b", + }, + }, + { + "Color3", + { "r", "g", "b" }, + }, + { + "Faces", + { + "Right", + "Top", + "Back", + -- "Left", + -- "Bottom", + -- "Front", + }, + }, + } + + for _, v in ipairs(tests) do + local t, test = v[1], v[2] + + local ok, result = pcall(function() + for _, prop in ipairs(test) do + if value[prop] == nil then + return false + end + -- Cannot throw if the property does not exist, + -- as userdatas may allow nil indexing + end + return true + end) + + if ok and result then + return t + end + end +end diff --git a/compile.sh b/compile.sh index dd505b5..908b4a5 100644 --- a/compile.sh +++ b/compile.sh @@ -5,6 +5,7 @@ done echo "Processing libraries..." darklua process -c dense.json5 ./corescripts/Libraries/Fusion/init.luau ./corescripts/processed/10000001.lua +darklua process -c bundle.json5 ./corescripts/Libraries/Red/init.luau ./corescripts/processed/10000002.lua echo "Processing other corescripts..." for file in ./corescripts/luau/[a-z]*.luau; do diff --git a/defs.d.lua b/defs.d.lua index 541823c..70cc72b 100644 --- a/defs.d.lua +++ b/defs.d.lua @@ -5,8 +5,6 @@ type ProtectedString = string type BinaryString = string type QDir = string type QFont = string -type FloatCurveKey = any -type RotationCurveKey = any declare class Enum function GetEnumItems(self): { any } @@ -188,11 +186,6 @@ declare class EnumAssetFetchStatus_INTERNAL extends Enum Loading: EnumAssetFetchStatus TimedOut: EnumAssetFetchStatus end -declare class EnumAudioSubType extends EnumItem end -declare class EnumAudioSubType_INTERNAL extends Enum - Music: EnumAudioSubType - SoundEffect: EnumAudioSubType -end declare class EnumAudioWindowSize extends EnumItem end declare class EnumAudioWindowSize_INTERNAL extends Enum Small: EnumAudioWindowSize @@ -2526,15 +2519,6 @@ declare class EnumVerticalScrollBarPosition_INTERNAL extends Enum Left: EnumVerticalScrollBarPosition Right: EnumVerticalScrollBarPosition end -declare class EnumVibrationMotor extends EnumItem end -declare class EnumVibrationMotor_INTERNAL extends Enum - Large: EnumVibrationMotor - Small: EnumVibrationMotor - LeftTrigger: EnumVibrationMotor - RightTrigger: EnumVibrationMotor - LeftHand: EnumVibrationMotor - RightHand: EnumVibrationMotor -end declare class EnumViewMode extends EnumItem end declare class EnumViewMode_INTERNAL extends Enum None: EnumViewMode @@ -2542,12 +2526,6 @@ declare class EnumViewMode_INTERNAL extends Enum Transparent: EnumViewMode Decal: EnumViewMode end -declare class EnumVirtualCursorMode extends EnumItem end -declare class EnumVirtualCursorMode_INTERNAL extends Enum - Default: EnumVirtualCursorMode - Disabled: EnumVirtualCursorMode - Enabled: EnumVirtualCursorMode -end declare class EnumVirtualInputMode extends EnumItem end declare class EnumVirtualInputMode_INTERNAL extends Enum Recording: EnumVirtualInputMode @@ -2661,7 +2639,6 @@ type ENUM_LIST = { ApplyStrokeMode: EnumApplyStrokeMode_INTERNAL, AspectType: EnumAspectType_INTERNAL, AssetFetchStatus: EnumAssetFetchStatus_INTERNAL, - AudioSubType: EnumAudioSubType_INTERNAL, AudioWindowSize: EnumAudioWindowSize_INTERNAL, AutoIndentRule: EnumAutoIndentRule_INTERNAL, AutomaticSize: EnumAutomaticSize_INTERNAL, @@ -2957,9 +2934,7 @@ type ENUM_LIST = { VelocityConstraintMode: EnumVelocityConstraintMode_INTERNAL, VerticalAlignment: EnumVerticalAlignment_INTERNAL, VerticalScrollBarPosition: EnumVerticalScrollBarPosition_INTERNAL, - VibrationMotor: EnumVibrationMotor_INTERNAL, ViewMode: EnumViewMode_INTERNAL, - VirtualCursorMode: EnumVirtualCursorMode_INTERNAL, VirtualInputMode: EnumVirtualInputMode_INTERNAL, VolumetricAudio: EnumVolumetricAudio_INTERNAL, WaterDirection: EnumWaterDirection_INTERNAL, @@ -3060,14 +3035,6 @@ declare class Faces Top: boolean end -declare class FloatCurveKey - Interpolation: EnumKeyInterpolationMode - LeftTangent: number - RightTangent: number - Time: number - Value: number -end - declare class Font Bold: boolean Family: string @@ -3258,14 +3225,6 @@ declare class CFrame function __sub(self, other: Vector3): CFrame end -declare class RotationCurveKey - Interpolation: EnumKeyInterpolationMode - LeftTangent: number - RightTangent: number - Time: number - Value: CFrame -end - declare class Region3 CFrame: CFrame Size: Vector3 @@ -3664,7 +3623,6 @@ end declare class AudioSearchParams extends Instance Album: string Artist: string - AudioSubType: EnumAudioSubType MaxDuration: number MinDuration: number SearchKeyword: string @@ -3811,7 +3769,6 @@ declare class StarterGui extends BasePlayerGui RtlTextSupport: EnumRtlTextSupport ScreenOrientation: EnumScreenOrientation ShowDevelopmentGui: boolean - VirtualCursorMode: EnumVirtualCursorMode function GetCore(self, parameterName: string): any function GetCoreGuiEnabled(self, coreGuiType: EnumCoreGuiType): boolean function RegisterGetCore(self, parameterName: string, getFunction: ((...any) -> ...any)): nil @@ -3820,39 +3777,6 @@ declare class StarterGui extends BasePlayerGui function SetCoreGuiEnabled(self, coreGuiType: EnumCoreGuiType, enabled: boolean): nil end -declare class BaseWrap extends Instance - CageMeshId: Content - CageOrigin: CFrame - CageOriginWorld: CFrame - HSRAssetId: Content - ImportOrigin: CFrame - ImportOriginWorld: CFrame - function GetFaces(self, cageType: EnumCageType): { any } - function GetVertices(self, cageType: EnumCageType): { any } - function IsHSRReady(self): boolean - function ModifyVertices(self, cageType: EnumCageType, vertices: { any }): nil -end - -declare class WrapLayer extends BaseWrap - AutoSkin: EnumWrapLayerAutoSkin - BindOffset: CFrame - Color: Color3 - DebugMode: EnumWrapLayerDebugMode - Enabled: boolean - Order: number - Puffiness: number - ReferenceMeshId: Content - ReferenceOrigin: CFrame - ReferenceOriginWorld: CFrame - ShrinkFactor: number -end - -declare class WrapTarget extends BaseWrap - Color: Color3 - DebugMode: EnumWrapTargetDebugMode - Stiffness: number -end - declare class BindableEvent extends Instance Event: RBXScriptSignal<...any> function Fire(self, ...: any): () @@ -4750,15 +4674,6 @@ declare class DynamicImage extends Instance function WritePixels(self, position: Vector2, size: Vector2, pixels: { any }): nil end -declare class EulerRotationCurve extends Instance - RotationOrder: EnumRotationOrder - function GetAnglesAtTime(self, time: number): { any } - function GetRotationAtTime(self, time: number): CFrame - function X(self): FloatCurve - function Y(self): FloatCurve - function Z(self): FloatCurve -end - declare class EventIngestService extends Instance function SendEventDeferred(self, target: string, eventContext: string, eventName: string, additionalArgs: { [any]: any }): nil function SendEventImmediately(self, target: string, eventContext: string, eventName: string, additionalArgs: { [any]: any }): nil @@ -4876,17 +4791,6 @@ end declare class FlagStandService extends Instance end -declare class FloatCurve extends Instance - Length: number - function GetKeyAtIndex(self, index: number): FloatCurveKey - function GetKeyIndicesAtTime(self, time: number): { any } - function GetKeys(self): { any } - function GetValueAtTime(self, time: number): number? - function InsertKey(self, key: FloatCurveKey): { any } - function RemoveKeyAtIndex(self, startingIndex: number, count: number?): number - function SetKeys(self, keys: { any }): number -end - declare class ForceField extends Instance Visible: boolean end @@ -5507,13 +5411,6 @@ end declare class GuidRegistryService extends Instance end -declare class HapticService extends Instance - function GetMotor(self, inputType: EnumUserInputType, vibrationMotor: EnumVibrationMotor): any - function IsMotorSupported(self, inputType: EnumUserInputType, vibrationMotor: EnumVibrationMotor): boolean - function IsVibrationSupported(self, inputType: EnumUserInputType): boolean - function SetMotor(self, inputType: EnumUserInputType, vibrationMotor: EnumVibrationMotor, vibrationValues: any): nil -end - declare class HeightmapImporterService extends Instance ColormapHasUnknownPixels: RBXScriptSignal<> ProgressUpdate: RBXScriptSignal @@ -7083,53 +6980,14 @@ end declare class ReplicatedStorage extends Instance end -declare class RobloxServerStorage extends Instance -end - -declare class RomarkService extends Instance - function EndRemoteRomarkTest(self): nil -end - -declare class RotationCurve extends Instance - Length: number - function GetKeyAtIndex(self, index: number): RotationCurveKey - function GetKeyIndicesAtTime(self, time: number): { any } - function GetKeys(self): { any } - function GetValueAtTime(self, time: number): CFrame? - function InsertKey(self, key: RotationCurveKey): { any } - function RemoveKeyAtIndex(self, startingIndex: number, count: number?): number - function SetKeys(self, keys: { any }): number -end - -declare class RtMessagingService extends Instance -end declare class RunService extends Instance - ClientGitHash: string Heartbeat: RBXScriptSignal - PostSimulation: RBXScriptSignal - PreAnimation: RBXScriptSignal - PreRender: RBXScriptSignal - PreSimulation: RBXScriptSignal RenderStepped: RBXScriptSignal Stepped: RBXScriptSignal - function BindToRenderStep(self, name: string, priority: number, func: ((delta: number) -> ())): () - function GetCoreScriptVersion(self): string - function GetRobloxClientChannel(self): string - function GetRobloxVersion(self): string - function IsClient(self): boolean - function IsEdit(self): boolean - function IsRunMode(self): boolean - function IsRunning(self): boolean - function IsServer(self): boolean - function IsStudio(self): boolean function Pause(self): nil function Run(self): nil - function Set3dRenderingEnabled(self, enable: boolean): nil - function SetRobloxGuiFocused(self, focus: boolean): nil function Stop(self): nil - function UnbindFromRenderStep(self, name: string): nil - function setThrottleFramerateEnabled(self, enable: boolean): nil end declare class RuntimeScriptService extends Instance @@ -7344,7 +7202,6 @@ declare class ServiceProvider extends Instance GuiService: GuiService GuidRegistryService: GuidRegistryService HSRDataContentProvider: HSRDataContentProvider - HapticService: HapticService HeightmapImporterService: HeightmapImporterService HttpRbxApiService: HttpRbxApiService HttpService: HttpService @@ -7396,9 +7253,6 @@ declare class ServiceProvider extends Instance RemoteDebuggerServer: RemoteDebuggerServer RenderSettings: RenderSettings ReplicatedStorage: ReplicatedStorage - RobloxServerStorage: RobloxServerStorage - RomarkService: RomarkService - RtMessagingService: RtMessagingService RunService: RunService RuntimeScriptService: RuntimeScriptService SafetyService: SafetyService @@ -7521,7 +7375,6 @@ declare class ServiceProvider extends Instance function GetService(self, service: "GuiService"): GuiService function GetService(self, service: "GuidRegistryService"): GuidRegistryService function GetService(self, service: "HSRDataContentProvider"): HSRDataContentProvider - function GetService(self, service: "HapticService"): HapticService function GetService(self, service: "HeightmapImporterService"): HeightmapImporterService function GetService(self, service: "Hopper"): Hopper function GetService(self, service: "HttpRbxApiService"): HttpRbxApiService @@ -7576,9 +7429,6 @@ declare class ServiceProvider extends Instance function GetService(self, service: "RemoteDebuggerServer"): RemoteDebuggerServer function GetService(self, service: "RenderSettings"): RenderSettings function GetService(self, service: "ReplicatedStorage"): ReplicatedStorage - function GetService(self, service: "RobloxServerStorage"): RobloxServerStorage - function GetService(self, service: "RomarkService"): RomarkService - function GetService(self, service: "RtMessagingService"): RtMessagingService function GetService(self, service: "RunService"): RunService function GetService(self, service: "RuntimeScriptService"): RuntimeScriptService function GetService(self, service: "SafetyService"): SafetyService @@ -7774,15 +7624,12 @@ declare class SnippetService extends Instance end declare class SocialService extends Instance - GameInvitePromptClosed: RBXScriptSignal - PromptInviteRequested: RBXScriptSignal - SelfViewHidden: RBXScriptSignal<> - SelfViewVisible: RBXScriptSignal - function CanSendGameInviteAsync(self, player: Player, recipientId: number?): boolean - function HideSelfView(self): nil - function InvokeGameInvitePromptClosed(self, player: Instance, recipientIds: { any }): nil - function PromptGameInvite(self, player: Player, experienceInviteOptions: Instance?): nil - function ShowSelfView(self, selfViewPosition: EnumSelfViewPosition?): nil + function SetGroupRankUrl(self, groupRankUrl: string): nil + function SetGroupRoleUrl(self, groupRoleUrl: string): nil + function SetFriendUrl(self, friendUrl: string): nil + function SetBestFriendUrl(self, bestFriendUrl: string): nil + function SetStuffUrl(self, stuffUrl: string): nil + function SetPackageContentsUrl(self, stuffUrl: string): nil end declare class Sound extends Instance @@ -7821,11 +7668,6 @@ declare class Sound extends Instance function Stop(self): nil end -declare class SoundEffect extends Instance - Enabled: boolean - Priority: number -end - declare class SoundService extends Instance AmbientReverb: EnumReverbType DeviceListChanged: RBXScriptSignal @@ -8487,13 +8329,6 @@ declare class Vector3Value extends ValueBase Value: Vector3 end -declare class Vector3Curve extends Instance - function GetValueAtTime(self, time: number): { any } - function X(self): FloatCurve - function Y(self): FloatCurve - function Z(self): FloatCurve -end - declare class VersionControlService extends Instance ScriptCollabEnabled: boolean end @@ -8546,7 +8381,7 @@ declare class Wire extends Instance end declare Instance: { - new: ((className: "Accoutrement", parent: Instance?) -> Accoutrement) & ((className: "Hat", parent: Instance?) -> Hat) & ((className: "AdvancedDragger", parent: Instance?) -> AdvancedDragger) & ((className: "Animation", parent: Instance?) -> Animation) & ((className: "CurveAnimation", parent: Instance?) -> CurveAnimation) & ((className: "KeyframeSequence", parent: Instance?) -> KeyframeSequence) & ((className: "AnimationController", parent: Instance?) -> AnimationController) & ((className: "AnimationRigData", parent: Instance?) -> AnimationRigData) & ((className: "Animator", parent: Instance?) -> Animator) & ((className: "AudioDeviceInput", parent: Instance?) -> AudioDeviceInput) & ((className: "AudioDeviceOutput", parent: Instance?) -> AudioDeviceOutput) & ((className: "AudioDistortion", parent: Instance?) -> AudioDistortion) & ((className: "AudioEcho", parent: Instance?) -> AudioEcho) & ((className: "AudioEmitter", parent: Instance?) -> AudioEmitter) & ((className: "AudioEqualizer", parent: Instance?) -> AudioEqualizer) & ((className: "AudioFader", parent: Instance?) -> AudioFader) & ((className: "AudioFlanger", parent: Instance?) -> AudioFlanger) & ((className: "AudioListener", parent: Instance?) -> AudioListener) & ((className: "AudioPitchShifter", parent: Instance?) -> AudioPitchShifter) & ((className: "AudioPlayer", parent: Instance?) -> AudioPlayer) & ((className: "AudioReverb", parent: Instance?) -> AudioReverb) & ((className: "AudioSearchParams", parent: Instance?) -> AudioSearchParams) & ((className: "Backpack", parent: Instance?) -> Backpack) & ((className: "WrapLayer", parent: Instance?) -> WrapLayer) & ((className: "WrapTarget", parent: Instance?) -> WrapTarget) & ((className: "BindableEvent", parent: Instance?) -> BindableEvent) & ((className: "BindableFunction", parent: Instance?) -> BindableFunction) & ((className: "BodyAngularVelocity", parent: Instance?) -> BodyAngularVelocity) & ((className: "BodyForce", parent: Instance?) -> BodyForce) & ((className: "BodyGyro", parent: Instance?) -> BodyGyro) & ((className: "BodyPosition", parent: Instance?) -> BodyPosition) & ((className: "BodyThrust", parent: Instance?) -> BodyThrust) & ((className: "BodyVelocity", parent: Instance?) -> BodyVelocity) & ((className: "RocketPropulsion", parent: Instance?) -> RocketPropulsion) & ((className: "BubbleChatMessageProperties", parent: Instance?) -> BubbleChatMessageProperties) & ((className: "Camera", parent: Instance?) -> Camera) & ((className: "BodyColors", parent: Instance?) -> BodyColors) & ((className: "CharacterMesh", parent: Instance?) -> CharacterMesh) & ((className: "Pants", parent: Instance?) -> Pants) & ((className: "Shirt", parent: Instance?) -> Shirt) & ((className: "ShirtGraphic", parent: Instance?) -> ShirtGraphic) & ((className: "Skin", parent: Instance?) -> Skin) & ((className: "ClickDetector", parent: Instance?) -> ClickDetector) & ((className: "DragDetector", parent: Instance?) -> DragDetector) & ((className: "Configuration", parent: Instance?) -> Configuration) & ((className: "AngularVelocity", parent: Instance?) -> AngularVelocity) & ((className: "AnimationConstraint", parent: Instance?) -> AnimationConstraint) & ((className: "BallSocketConstraint", parent: Instance?) -> BallSocketConstraint) & ((className: "HingeConstraint", parent: Instance?) -> HingeConstraint) & ((className: "LineForce", parent: Instance?) -> LineForce) & ((className: "LinearVelocity", parent: Instance?) -> LinearVelocity) & ((className: "PlaneConstraint", parent: Instance?) -> PlaneConstraint) & ((className: "Plane", parent: Instance?) -> Plane) & ((className: "RigidConstraint", parent: Instance?) -> RigidConstraint) & ((className: "RodConstraint", parent: Instance?) -> RodConstraint) & ((className: "RopeConstraint", parent: Instance?) -> RopeConstraint) & ((className: "CylindricalConstraint", parent: Instance?) -> CylindricalConstraint) & ((className: "PrismaticConstraint", parent: Instance?) -> PrismaticConstraint) & ((className: "SpringConstraint", parent: Instance?) -> SpringConstraint) & ((className: "Torque", parent: Instance?) -> Torque) & ((className: "TorsionSpringConstraint", parent: Instance?) -> TorsionSpringConstraint) & ((className: "UniversalConstraint", parent: Instance?) -> UniversalConstraint) & ((className: "HumanoidController", parent: Instance?) -> HumanoidController) & ((className: "SkateboardController", parent: Instance?) -> SkateboardController) & ((className: "VehicleController", parent: Instance?) -> VehicleController) & ((className: "AirController", parent: Instance?) -> AirController) & ((className: "ClimbController", parent: Instance?) -> ClimbController) & ((className: "GroundController", parent: Instance?) -> GroundController) & ((className: "SwimController", parent: Instance?) -> SwimController) & ((className: "ControllerManager", parent: Instance?) -> ControllerManager) & ((className: "CustomEvent", parent: Instance?) -> CustomEvent) & ((className: "CustomEventReceiver", parent: Instance?) -> CustomEventReceiver) & ((className: "CylinderMesh", parent: Instance?) -> CylinderMesh) & ((className: "DynamicMesh", parent: Instance?) -> DynamicMesh) & ((className: "FileMesh", parent: Instance?) -> FileMesh) & ((className: "SpecialMesh", parent: Instance?) -> SpecialMesh) & ((className: "DataStoreIncrementOptions", parent: Instance?) -> DataStoreIncrementOptions) & ((className: "DataStoreOptions", parent: Instance?) -> DataStoreOptions) & ((className: "DataStoreSetOptions", parent: Instance?) -> DataStoreSetOptions) & ((className: "DebuggerWatch", parent: Instance?) -> DebuggerWatch) & ((className: "Dialog", parent: Instance?) -> Dialog) & ((className: "DialogChoice", parent: Instance?) -> DialogChoice) & ((className: "Dragger", parent: Instance?) -> Dragger) & ((className: "EulerRotationCurve", parent: Instance?) -> EulerRotationCurve) & ((className: "ExperienceInviteOptions", parent: Instance?) -> ExperienceInviteOptions) & ((className: "Explosion", parent: Instance?) -> Explosion) & ((className: "Decal", parent: Instance?) -> Decal) & ((className: "Texture", parent: Instance?) -> Texture) & ((className: "Hole", parent: Instance?) -> Hole) & ((className: "MotorFeature", parent: Instance?) -> MotorFeature) & ((className: "Fire", parent: Instance?) -> Fire) & ((className: "FloatCurve", parent: Instance?) -> FloatCurve) & ((className: "CSGDictionaryService", parent: Instance?) -> CSGDictionaryService) & ((className: "NonReplicatedCSGDictionaryService", parent: Instance?) -> NonReplicatedCSGDictionaryService) & ((className: "ForceField", parent: Instance?) -> ForceField) & ((className: "FunctionalTest", parent: Instance?) -> FunctionalTest) & ((className: "GetTextBoundsParams", parent: Instance?) -> GetTextBoundsParams) & ((className: "Frame", parent: Instance?) -> Frame) & ((className: "ImageButton", parent: Instance?) -> ImageButton) & ((className: "TextButton", parent: Instance?) -> TextButton) & ((className: "ImageLabel", parent: Instance?) -> ImageLabel) & ((className: "TextLabel", parent: Instance?) -> TextLabel) & ((className: "TextBox", parent: Instance?) -> TextBox) & ((className: "VideoFrame", parent: Instance?) -> VideoFrame) & ((className: "ViewportFrame", parent: Instance?) -> ViewportFrame) & ((className: "BillboardGui", parent: Instance?) -> BillboardGui) & ((className: "ScreenGui", parent: Instance?) -> ScreenGui) & ((className: "GuiMain", parent: Instance?) -> GuiMain) & ((className: "AdGui", parent: Instance?) -> AdGui) & ((className: "SurfaceGui", parent: Instance?) -> SurfaceGui) & ((className: "FloorWire", parent: Instance?) -> FloorWire) & ((className: "SelectionBox", parent: Instance?) -> SelectionBox) & ((className: "BoxHandleAdornment", parent: Instance?) -> BoxHandleAdornment) & ((className: "ConeHandleAdornment", parent: Instance?) -> ConeHandleAdornment) & ((className: "CylinderHandleAdornment", parent: Instance?) -> CylinderHandleAdornment) & ((className: "ImageHandleAdornment", parent: Instance?) -> ImageHandleAdornment) & ((className: "LineHandleAdornment", parent: Instance?) -> LineHandleAdornment) & ((className: "SphereHandleAdornment", parent: Instance?) -> SphereHandleAdornment) & ((className: "WireframeHandleAdornment", parent: Instance?) -> WireframeHandleAdornment) & ((className: "ParabolaAdornment", parent: Instance?) -> ParabolaAdornment) & ((className: "SelectionSphere", parent: Instance?) -> SelectionSphere) & ((className: "ArcHandles", parent: Instance?) -> ArcHandles) & ((className: "Handles", parent: Instance?) -> Handles) & ((className: "SurfaceSelection", parent: Instance?) -> SurfaceSelection) & ((className: "SelectionPartLasso", parent: Instance?) -> SelectionPartLasso) & ((className: "SelectionPointLasso", parent: Instance?) -> SelectionPointLasso) & ((className: "HeightmapImporterService", parent: Instance?) -> HeightmapImporterService) & ((className: "HiddenSurfaceRemovalAsset", parent: Instance?) -> HiddenSurfaceRemovalAsset) & ((className: "Humanoid", parent: Instance?) -> Humanoid) & ((className: "RotateP", parent: Instance?) -> RotateP) & ((className: "RotateV", parent: Instance?) -> RotateV) & ((className: "Glue", parent: Instance?) -> Glue) & ((className: "ManualGlue", parent: Instance?) -> ManualGlue) & ((className: "ManualWeld", parent: Instance?) -> ManualWeld) & ((className: "Motor", parent: Instance?) -> Motor) & ((className: "Motor6D", parent: Instance?) -> Motor6D) & ((className: "Rotate", parent: Instance?) -> Rotate) & ((className: "Snap", parent: Instance?) -> Snap) & ((className: "VelocityMotor", parent: Instance?) -> VelocityMotor) & ((className: "Weld", parent: Instance?) -> Weld) & ((className: "Keyframe", parent: Instance?) -> Keyframe) & ((className: "KeyframeMarker", parent: Instance?) -> KeyframeMarker) & ((className: "PointLight", parent: Instance?) -> PointLight) & ((className: "SpotLight", parent: Instance?) -> SpotLight) & ((className: "SurfaceLight", parent: Instance?) -> SurfaceLight) & ((className: "Script", parent: Instance?) -> Script) & ((className: "LocalScript", parent: Instance?) -> LocalScript) & ((className: "ModuleScript", parent: Instance?) -> ModuleScript) & ((className: "MarkerCurve", parent: Instance?) -> MarkerCurve) & ((className: "MemoryStoreService", parent: Instance?) -> MemoryStoreService) & ((className: "Message", parent: Instance?) -> Message) & ((className: "Hint", parent: Instance?) -> Hint) & ((className: "CornerWedgePart", parent: Instance?) -> CornerWedgePart) & ((className: "Part", parent: Instance?) -> Part) & ((className: "FlagStand", parent: Instance?) -> FlagStand) & ((className: "Seat", parent: Instance?) -> Seat) & ((className: "SkateboardPlatform", parent: Instance?) -> SkateboardPlatform) & ((className: "SpawnLocation", parent: Instance?) -> SpawnLocation) & ((className: "WedgePart", parent: Instance?) -> WedgePart) & ((className: "PartOperation", parent: Instance?) -> PartOperation) & ((className: "IntersectOperation", parent: Instance?) -> IntersectOperation) & ((className: "NegateOperation", parent: Instance?) -> NegateOperation) & ((className: "UnionOperation", parent: Instance?) -> UnionOperation) & ((className: "TrussPart", parent: Instance?) -> TrussPart) & ((className: "VehicleSeat", parent: Instance?) -> VehicleSeat) & ((className: "Model", parent: Instance?) -> Model) & ((className: "HopperBin", parent: Instance?) -> HopperBin) & ((className: "Tool", parent: Instance?) -> Tool) & ((className: "Flag", parent: Instance?) -> Flag) & ((className: "WorldModel", parent: Instance?) -> WorldModel) & ((className: "PartOperationAsset", parent: Instance?) -> PartOperationAsset) & ((className: "PathfindingLink", parent: Instance?) -> PathfindingLink) & ((className: "PathfindingModifier", parent: Instance?) -> PathfindingModifier) & ((className: "Player", parent: Instance?) -> Player) & ((className: "PluginAction", parent: Instance?) -> PluginAction) & ((className: "PluginCapabilities", parent: Instance?) -> PluginCapabilities) & ((className: "NumberPose", parent: Instance?) -> NumberPose) & ((className: "Pose", parent: Instance?) -> Pose) & ((className: "ReflectionMetadata", parent: Instance?) -> ReflectionMetadata) & ((className: "ReflectionMetadataCallbacks", parent: Instance?) -> ReflectionMetadataCallbacks) & ((className: "ReflectionMetadataClasses", parent: Instance?) -> ReflectionMetadataClasses) & ((className: "ReflectionMetadataEnums", parent: Instance?) -> ReflectionMetadataEnums) & ((className: "ReflectionMetadataEvents", parent: Instance?) -> ReflectionMetadataEvents) & ((className: "ReflectionMetadataFunctions", parent: Instance?) -> ReflectionMetadataFunctions) & ((className: "ReflectionMetadataClass", parent: Instance?) -> ReflectionMetadataClass) & ((className: "ReflectionMetadataEnum", parent: Instance?) -> ReflectionMetadataEnum) & ((className: "ReflectionMetadataEnumItem", parent: Instance?) -> ReflectionMetadataEnumItem) & ((className: "ReflectionMetadataMember", parent: Instance?) -> ReflectionMetadataMember) & ((className: "ReflectionMetadataProperties", parent: Instance?) -> ReflectionMetadataProperties) & ((className: "ReflectionMetadataYieldFunctions", parent: Instance?) -> ReflectionMetadataYieldFunctions) & ((className: "RemoteEvent", parent: Instance?) -> RemoteEvent) & ((className: "RemoteFunction", parent: Instance?) -> RemoteFunction) & ((className: "RenderingTest", parent: Instance?) -> RenderingTest) & ((className: "RotationCurve", parent: Instance?) -> RotationCurve) & ((className: "BuoyancySensor", parent: Instance?) -> BuoyancySensor) & ((className: "ControllerPartSensor", parent: Instance?) -> ControllerPartSensor) & ((className: "Sky", parent: Instance?) -> Sky) & ((className: "Smoke", parent: Instance?) -> Smoke) & ((className: "Sound", parent: Instance?) -> Sound) & ((className: "Sparkles", parent: Instance?) -> Sparkles) & ((className: "StandalonePluginScripts", parent: Instance?) -> StandalonePluginScripts) & ((className: "StarterGear", parent: Instance?) -> StarterGear) & ((className: "StudioCallout", parent: Instance?) -> StudioCallout) & ((className: "StudioObjectBase", parent: Instance?) -> StudioObjectBase) & ((className: "StudioWidget", parent: Instance?) -> StudioWidget) & ((className: "StyleDerive", parent: Instance?) -> StyleDerive) & ((className: "StyleLink", parent: Instance?) -> StyleLink) & ((className: "SurfaceAppearance", parent: Instance?) -> SurfaceAppearance) & ((className: "Team", parent: Instance?) -> Team) & ((className: "TeleportOptions", parent: Instance?) -> TeleportOptions) & ((className: "TerrainDetail", parent: Instance?) -> TerrainDetail) & ((className: "TerrainRegion", parent: Instance?) -> TerrainRegion) & ((className: "TestService", parent: Instance?) -> TestService) & ((className: "TextChannel", parent: Instance?) -> TextChannel) & ((className: "TextChatCommand", parent: Instance?) -> TextChatCommand) & ((className: "TextChatMessageProperties", parent: Instance?) -> TextChatMessageProperties) & ((className: "TrackerStreamAnimation", parent: Instance?) -> TrackerStreamAnimation) & ((className: "BinaryStringValue", parent: Instance?) -> BinaryStringValue) & ((className: "BoolValue", parent: Instance?) -> BoolValue) & ((className: "BrickColorValue", parent: Instance?) -> BrickColorValue) & ((className: "CFrameValue", parent: Instance?) -> CFrameValue) & ((className: "Color3Value", parent: Instance?) -> Color3Value) & ((className: "DoubleConstrainedValue", parent: Instance?) -> DoubleConstrainedValue) & ((className: "IntConstrainedValue", parent: Instance?) -> IntConstrainedValue) & ((className: "IntValue", parent: Instance?) -> IntValue) & ((className: "NumberValue", parent: Instance?) -> NumberValue) & ((className: "ObjectValue", parent: Instance?) -> ObjectValue) & ((className: "RayValue", parent: Instance?) -> RayValue) & ((className: "StringValue", parent: Instance?) -> StringValue) & ((className: "Vector3Value", parent: Instance?) -> Vector3Value) & ((className: "Vector3Curve", parent: Instance?) -> Vector3Curve) & ((className: "Wire", parent: Instance?) -> Wire) & ((className: string, parent: Instance?) -> Instance), + new: ((className: "Accoutrement", parent: Instance?) -> Accoutrement) & ((className: "Hat", parent: Instance?) -> Hat) & ((className: "AdvancedDragger", parent: Instance?) -> AdvancedDragger) & ((className: "Animation", parent: Instance?) -> Animation) & ((className: "CurveAnimation", parent: Instance?) -> CurveAnimation) & ((className: "KeyframeSequence", parent: Instance?) -> KeyframeSequence) & ((className: "AnimationController", parent: Instance?) -> AnimationController) & ((className: "AnimationRigData", parent: Instance?) -> AnimationRigData) & ((className: "Animator", parent: Instance?) -> Animator) & ((className: "AudioDeviceInput", parent: Instance?) -> AudioDeviceInput) & ((className: "AudioDeviceOutput", parent: Instance?) -> AudioDeviceOutput) & ((className: "AudioDistortion", parent: Instance?) -> AudioDistortion) & ((className: "AudioEcho", parent: Instance?) -> AudioEcho) & ((className: "AudioEmitter", parent: Instance?) -> AudioEmitter) & ((className: "AudioEqualizer", parent: Instance?) -> AudioEqualizer) & ((className: "AudioFader", parent: Instance?) -> AudioFader) & ((className: "AudioFlanger", parent: Instance?) -> AudioFlanger) & ((className: "AudioListener", parent: Instance?) -> AudioListener) & ((className: "AudioPitchShifter", parent: Instance?) -> AudioPitchShifter) & ((className: "AudioPlayer", parent: Instance?) -> AudioPlayer) & ((className: "AudioReverb", parent: Instance?) -> AudioReverb) & ((className: "AudioSearchParams", parent: Instance?) -> AudioSearchParams) & ((className: "Backpack", parent: Instance?) -> Backpack) & ((className: "BindableEvent", parent: Instance?) -> BindableEvent) & ((className: "BindableFunction", parent: Instance?) -> BindableFunction) & ((className: "BodyAngularVelocity", parent: Instance?) -> BodyAngularVelocity) & ((className: "BodyForce", parent: Instance?) -> BodyForce) & ((className: "BodyGyro", parent: Instance?) -> BodyGyro) & ((className: "BodyPosition", parent: Instance?) -> BodyPosition) & ((className: "BodyThrust", parent: Instance?) -> BodyThrust) & ((className: "BodyVelocity", parent: Instance?) -> BodyVelocity) & ((className: "RocketPropulsion", parent: Instance?) -> RocketPropulsion) & ((className: "BubbleChatMessageProperties", parent: Instance?) -> BubbleChatMessageProperties) & ((className: "Camera", parent: Instance?) -> Camera) & ((className: "BodyColors", parent: Instance?) -> BodyColors) & ((className: "CharacterMesh", parent: Instance?) -> CharacterMesh) & ((className: "Pants", parent: Instance?) -> Pants) & ((className: "Shirt", parent: Instance?) -> Shirt) & ((className: "ShirtGraphic", parent: Instance?) -> ShirtGraphic) & ((className: "Skin", parent: Instance?) -> Skin) & ((className: "ClickDetector", parent: Instance?) -> ClickDetector) & ((className: "DragDetector", parent: Instance?) -> DragDetector) & ((className: "Configuration", parent: Instance?) -> Configuration) & ((className: "AngularVelocity", parent: Instance?) -> AngularVelocity) & ((className: "AnimationConstraint", parent: Instance?) -> AnimationConstraint) & ((className: "BallSocketConstraint", parent: Instance?) -> BallSocketConstraint) & ((className: "HingeConstraint", parent: Instance?) -> HingeConstraint) & ((className: "LineForce", parent: Instance?) -> LineForce) & ((className: "LinearVelocity", parent: Instance?) -> LinearVelocity) & ((className: "PlaneConstraint", parent: Instance?) -> PlaneConstraint) & ((className: "Plane", parent: Instance?) -> Plane) & ((className: "RigidConstraint", parent: Instance?) -> RigidConstraint) & ((className: "RodConstraint", parent: Instance?) -> RodConstraint) & ((className: "RopeConstraint", parent: Instance?) -> RopeConstraint) & ((className: "CylindricalConstraint", parent: Instance?) -> CylindricalConstraint) & ((className: "PrismaticConstraint", parent: Instance?) -> PrismaticConstraint) & ((className: "SpringConstraint", parent: Instance?) -> SpringConstraint) & ((className: "Torque", parent: Instance?) -> Torque) & ((className: "TorsionSpringConstraint", parent: Instance?) -> TorsionSpringConstraint) & ((className: "UniversalConstraint", parent: Instance?) -> UniversalConstraint) & ((className: "HumanoidController", parent: Instance?) -> HumanoidController) & ((className: "SkateboardController", parent: Instance?) -> SkateboardController) & ((className: "VehicleController", parent: Instance?) -> VehicleController) & ((className: "AirController", parent: Instance?) -> AirController) & ((className: "ClimbController", parent: Instance?) -> ClimbController) & ((className: "GroundController", parent: Instance?) -> GroundController) & ((className: "SwimController", parent: Instance?) -> SwimController) & ((className: "ControllerManager", parent: Instance?) -> ControllerManager) & ((className: "CustomEvent", parent: Instance?) -> CustomEvent) & ((className: "CustomEventReceiver", parent: Instance?) -> CustomEventReceiver) & ((className: "CylinderMesh", parent: Instance?) -> CylinderMesh) & ((className: "DynamicMesh", parent: Instance?) -> DynamicMesh) & ((className: "FileMesh", parent: Instance?) -> FileMesh) & ((className: "SpecialMesh", parent: Instance?) -> SpecialMesh) & ((className: "DataStoreIncrementOptions", parent: Instance?) -> DataStoreIncrementOptions) & ((className: "DataStoreOptions", parent: Instance?) -> DataStoreOptions) & ((className: "DataStoreSetOptions", parent: Instance?) -> DataStoreSetOptions) & ((className: "DebuggerWatch", parent: Instance?) -> DebuggerWatch) & ((className: "Dialog", parent: Instance?) -> Dialog) & ((className: "DialogChoice", parent: Instance?) -> DialogChoice) & ((className: "Dragger", parent: Instance?) -> Dragger) & ((className: "ExperienceInviteOptions", parent: Instance?) -> ExperienceInviteOptions) & ((className: "Explosion", parent: Instance?) -> Explosion) & ((className: "Decal", parent: Instance?) -> Decal) & ((className: "Texture", parent: Instance?) -> Texture) & ((className: "Hole", parent: Instance?) -> Hole) & ((className: "MotorFeature", parent: Instance?) -> MotorFeature) & ((className: "Fire", parent: Instance?) -> Fire) & ((className: "CSGDictionaryService", parent: Instance?) -> CSGDictionaryService) & ((className: "NonReplicatedCSGDictionaryService", parent: Instance?) -> NonReplicatedCSGDictionaryService) & ((className: "ForceField", parent: Instance?) -> ForceField) & ((className: "FunctionalTest", parent: Instance?) -> FunctionalTest) & ((className: "GetTextBoundsParams", parent: Instance?) -> GetTextBoundsParams) & ((className: "Frame", parent: Instance?) -> Frame) & ((className: "ImageButton", parent: Instance?) -> ImageButton) & ((className: "TextButton", parent: Instance?) -> TextButton) & ((className: "ImageLabel", parent: Instance?) -> ImageLabel) & ((className: "TextLabel", parent: Instance?) -> TextLabel) & ((className: "TextBox", parent: Instance?) -> TextBox) & ((className: "VideoFrame", parent: Instance?) -> VideoFrame) & ((className: "ViewportFrame", parent: Instance?) -> ViewportFrame) & ((className: "BillboardGui", parent: Instance?) -> BillboardGui) & ((className: "ScreenGui", parent: Instance?) -> ScreenGui) & ((className: "GuiMain", parent: Instance?) -> GuiMain) & ((className: "AdGui", parent: Instance?) -> AdGui) & ((className: "SurfaceGui", parent: Instance?) -> SurfaceGui) & ((className: "FloorWire", parent: Instance?) -> FloorWire) & ((className: "SelectionBox", parent: Instance?) -> SelectionBox) & ((className: "BoxHandleAdornment", parent: Instance?) -> BoxHandleAdornment) & ((className: "ConeHandleAdornment", parent: Instance?) -> ConeHandleAdornment) & ((className: "CylinderHandleAdornment", parent: Instance?) -> CylinderHandleAdornment) & ((className: "ImageHandleAdornment", parent: Instance?) -> ImageHandleAdornment) & ((className: "LineHandleAdornment", parent: Instance?) -> LineHandleAdornment) & ((className: "SphereHandleAdornment", parent: Instance?) -> SphereHandleAdornment) & ((className: "WireframeHandleAdornment", parent: Instance?) -> WireframeHandleAdornment) & ((className: "ParabolaAdornment", parent: Instance?) -> ParabolaAdornment) & ((className: "SelectionSphere", parent: Instance?) -> SelectionSphere) & ((className: "ArcHandles", parent: Instance?) -> ArcHandles) & ((className: "Handles", parent: Instance?) -> Handles) & ((className: "SurfaceSelection", parent: Instance?) -> SurfaceSelection) & ((className: "SelectionPartLasso", parent: Instance?) -> SelectionPartLasso) & ((className: "SelectionPointLasso", parent: Instance?) -> SelectionPointLasso) & ((className: "HeightmapImporterService", parent: Instance?) -> HeightmapImporterService) & ((className: "HiddenSurfaceRemovalAsset", parent: Instance?) -> HiddenSurfaceRemovalAsset) & ((className: "Humanoid", parent: Instance?) -> Humanoid) & ((className: "RotateP", parent: Instance?) -> RotateP) & ((className: "RotateV", parent: Instance?) -> RotateV) & ((className: "Glue", parent: Instance?) -> Glue) & ((className: "ManualGlue", parent: Instance?) -> ManualGlue) & ((className: "ManualWeld", parent: Instance?) -> ManualWeld) & ((className: "Motor", parent: Instance?) -> Motor) & ((className: "Motor6D", parent: Instance?) -> Motor6D) & ((className: "Rotate", parent: Instance?) -> Rotate) & ((className: "Snap", parent: Instance?) -> Snap) & ((className: "VelocityMotor", parent: Instance?) -> VelocityMotor) & ((className: "Weld", parent: Instance?) -> Weld) & ((className: "Keyframe", parent: Instance?) -> Keyframe) & ((className: "KeyframeMarker", parent: Instance?) -> KeyframeMarker) & ((className: "PointLight", parent: Instance?) -> PointLight) & ((className: "SpotLight", parent: Instance?) -> SpotLight) & ((className: "SurfaceLight", parent: Instance?) -> SurfaceLight) & ((className: "Script", parent: Instance?) -> Script) & ((className: "LocalScript", parent: Instance?) -> LocalScript) & ((className: "ModuleScript", parent: Instance?) -> ModuleScript) & ((className: "MarkerCurve", parent: Instance?) -> MarkerCurve) & ((className: "MemoryStoreService", parent: Instance?) -> MemoryStoreService) & ((className: "Message", parent: Instance?) -> Message) & ((className: "Hint", parent: Instance?) -> Hint) & ((className: "CornerWedgePart", parent: Instance?) -> CornerWedgePart) & ((className: "Part", parent: Instance?) -> Part) & ((className: "FlagStand", parent: Instance?) -> FlagStand) & ((className: "Seat", parent: Instance?) -> Seat) & ((className: "SkateboardPlatform", parent: Instance?) -> SkateboardPlatform) & ((className: "SpawnLocation", parent: Instance?) -> SpawnLocation) & ((className: "WedgePart", parent: Instance?) -> WedgePart) & ((className: "PartOperation", parent: Instance?) -> PartOperation) & ((className: "IntersectOperation", parent: Instance?) -> IntersectOperation) & ((className: "NegateOperation", parent: Instance?) -> NegateOperation) & ((className: "UnionOperation", parent: Instance?) -> UnionOperation) & ((className: "TrussPart", parent: Instance?) -> TrussPart) & ((className: "VehicleSeat", parent: Instance?) -> VehicleSeat) & ((className: "Model", parent: Instance?) -> Model) & ((className: "HopperBin", parent: Instance?) -> HopperBin) & ((className: "Tool", parent: Instance?) -> Tool) & ((className: "Flag", parent: Instance?) -> Flag) & ((className: "WorldModel", parent: Instance?) -> WorldModel) & ((className: "PartOperationAsset", parent: Instance?) -> PartOperationAsset) & ((className: "PathfindingLink", parent: Instance?) -> PathfindingLink) & ((className: "PathfindingModifier", parent: Instance?) -> PathfindingModifier) & ((className: "Player", parent: Instance?) -> Player) & ((className: "PluginAction", parent: Instance?) -> PluginAction) & ((className: "PluginCapabilities", parent: Instance?) -> PluginCapabilities) & ((className: "NumberPose", parent: Instance?) -> NumberPose) & ((className: "Pose", parent: Instance?) -> Pose) & ((className: "ReflectionMetadata", parent: Instance?) -> ReflectionMetadata) & ((className: "ReflectionMetadataCallbacks", parent: Instance?) -> ReflectionMetadataCallbacks) & ((className: "ReflectionMetadataClasses", parent: Instance?) -> ReflectionMetadataClasses) & ((className: "ReflectionMetadataEnums", parent: Instance?) -> ReflectionMetadataEnums) & ((className: "ReflectionMetadataEvents", parent: Instance?) -> ReflectionMetadataEvents) & ((className: "ReflectionMetadataFunctions", parent: Instance?) -> ReflectionMetadataFunctions) & ((className: "ReflectionMetadataClass", parent: Instance?) -> ReflectionMetadataClass) & ((className: "ReflectionMetadataEnum", parent: Instance?) -> ReflectionMetadataEnum) & ((className: "ReflectionMetadataEnumItem", parent: Instance?) -> ReflectionMetadataEnumItem) & ((className: "ReflectionMetadataMember", parent: Instance?) -> ReflectionMetadataMember) & ((className: "ReflectionMetadataProperties", parent: Instance?) -> ReflectionMetadataProperties) & ((className: "ReflectionMetadataYieldFunctions", parent: Instance?) -> ReflectionMetadataYieldFunctions) & ((className: "RemoteEvent", parent: Instance?) -> RemoteEvent) & ((className: "RemoteFunction", parent: Instance?) -> RemoteFunction) & ((className: "RenderingTest", parent: Instance?) -> RenderingTest) & ((className: "BuoyancySensor", parent: Instance?) -> BuoyancySensor) & ((className: "ControllerPartSensor", parent: Instance?) -> ControllerPartSensor) & ((className: "Sky", parent: Instance?) -> Sky) & ((className: "Smoke", parent: Instance?) -> Smoke) & ((className: "Sound", parent: Instance?) -> Sound) & ((className: "Sparkles", parent: Instance?) -> Sparkles) & ((className: "StandalonePluginScripts", parent: Instance?) -> StandalonePluginScripts) & ((className: "StarterGear", parent: Instance?) -> StarterGear) & ((className: "StudioCallout", parent: Instance?) -> StudioCallout) & ((className: "StudioObjectBase", parent: Instance?) -> StudioObjectBase) & ((className: "StudioWidget", parent: Instance?) -> StudioWidget) & ((className: "StyleDerive", parent: Instance?) -> StyleDerive) & ((className: "StyleLink", parent: Instance?) -> StyleLink) & ((className: "SurfaceAppearance", parent: Instance?) -> SurfaceAppearance) & ((className: "Team", parent: Instance?) -> Team) & ((className: "TeleportOptions", parent: Instance?) -> TeleportOptions) & ((className: "TerrainDetail", parent: Instance?) -> TerrainDetail) & ((className: "TerrainRegion", parent: Instance?) -> TerrainRegion) & ((className: "TestService", parent: Instance?) -> TestService) & ((className: "TextChannel", parent: Instance?) -> TextChannel) & ((className: "TextChatCommand", parent: Instance?) -> TextChatCommand) & ((className: "TextChatMessageProperties", parent: Instance?) -> TextChatMessageProperties) & ((className: "TrackerStreamAnimation", parent: Instance?) -> TrackerStreamAnimation) & ((className: "BinaryStringValue", parent: Instance?) -> BinaryStringValue) & ((className: "BoolValue", parent: Instance?) -> BoolValue) & ((className: "BrickColorValue", parent: Instance?) -> BrickColorValue) & ((className: "CFrameValue", parent: Instance?) -> CFrameValue) & ((className: "Color3Value", parent: Instance?) -> Color3Value) & ((className: "DoubleConstrainedValue", parent: Instance?) -> DoubleConstrainedValue) & ((className: "IntConstrainedValue", parent: Instance?) -> IntConstrainedValue) & ((className: "IntValue", parent: Instance?) -> IntValue) & ((className: "NumberValue", parent: Instance?) -> NumberValue) & ((className: "ObjectValue", parent: Instance?) -> ObjectValue) & ((className: "RayValue", parent: Instance?) -> RayValue) & ((className: "StringValue", parent: Instance?) -> StringValue) & ((className: "Vector3Value", parent: Instance?) -> Vector3Value) & ((className: "Wire", parent: Instance?) -> Wire) & ((className: string, parent: Instance?) -> Instance), Lock: (instance: Instance, player: Player) -> nil, Unlock: (instance: Instance) -> nil, @@ -8713,14 +8548,6 @@ declare Font: { fromId: ((id: number, weight: EnumFontWeight?, style: EnumFontStyle?) -> Font), } -declare FloatCurveKey: { - new: ((time: number, value: number, Interpolation: EnumKeyInterpolationMode) -> FloatCurveKey), -} - -declare RotationCurveKey: { - new: ((time: number, value: CFrame, Interpolation: EnumKeyInterpolationMode) -> RotationCurveKey), -} - declare class GlobalSettings extends GenericSettings Lua: LuaSettings @@ -8782,7 +8609,302 @@ declare TeleportService: TeleportService declare plugin: Plugin declare script: LuaSourceContainer declare function loadfile(file: string): any -declare function LoadLibrary(libraryName: string): { any } + +-- fusion + +-- pubtypes + +--[[ + Stores common public-facing type information for Fusion APIs. +]] + +type Set = { [T]: any } + +--[[ + General use types +]] + +-- A unique symbolic value. +type Symbol = { + type: "Symbol", + name: string, +} + +-- Types that can be expressed as vectors of numbers, and so can be animated. +type Animatable = + number + | CFrame + | Color3 + | ColorSequenceKeypoint + | DateTime + | NumberRange + | NumberSequenceKeypoint + | PhysicalProperties + | Ray + | Rect + | Region3 + | Region3int16 + | UDim + | UDim2 + | Vector2 + | Vector2int16 + | Vector3 + | Vector3int16 + +-- A task which can be accepted for cleanup. +type Task = + Instance + | RBXScriptConnection + | () -> () | { destroy: (any) -> () } | { Destroy: (any) -> () } | { Task } + +-- Script-readable version information. +type Version = { + major: number, + minor: number, + isRelease: boolean, +} + +-- An object which stores a value scoped in time. +type Contextual = { + type: "Contextual", + now: (Contextual) -> T, + is: (Contextual, T) -> ContextualIsMethods, +} + +type ContextualIsMethods = { + during: (ContextualIsMethods, (A...) -> T, A...) -> T, +} + +--[[ + Generic reactive graph types +]] + +-- A graph object which can have dependents. +type Dependency = { + dependentSet: Set, +} + +-- A graph object which can have dependencies. +type Dependent = { + update: (Dependent) -> boolean, + dependencySet: Set, +} + +-- An object which stores a piece of reactive state. +type StateObject = Dependency & { + type: "State", + kind: string, + _typeIdentifier: T, +} + +-- Either a constant value of type T, or a state object containing type T. +type CanBeState = StateObject | T + +-- Function signature for use callbacks. +type Use = (target: CanBeState) -> T + +--[[ + Specific reactive graph types +]] + +-- A state object whose value can be set at any time by the user. +type Value = StateObject & { + kind: "State", + set: (Value, newValue: any, force: boolean?) -> (), +} + +-- A state object whose value is derived from other objects using a callback. +type Computed = StateObject & Dependent & { + kind: "Computed", +} + +-- A state object whose value is derived from other objects using a callback. +type ForPairs = StateObject<{ [KO]: VO }> & Dependent & { + kind: "ForPairs", +} +-- A state object whose value is derived from other objects using a callback. +type ForKeys = StateObject<{ [KO]: V }> & Dependent & { + kind: "ForKeys", +} +-- A state object whose value is derived from other objects using a callback. +type ForValues = StateObject<{ [K]: VO }> & Dependent & { + kind: "ForKeys", +} + +-- A state object which follows another state object using tweens. +type Tween = StateObject & Dependent & { + kind: "Tween", +} + +-- A state object which follows another state object using spring simulation. +type Spring = StateObject & Dependent & { + kind: "Spring", + setPosition: (Spring, newPosition: Animatable) -> (), + setVelocity: (Spring, newVelocity: Animatable) -> (), + addVelocity: (Spring, deltaVelocity: Animatable) -> (), +} + +-- An object which can listen for updates on another state object. +type Observer = Dependent & { + kind: "Observer", + onChange: (Observer, callback: () -> ()) -> (() -> ()), +} + +--[[ + Instance related types +]] + +-- Denotes children instances in an instance or component's property table. +type SpecialKey = { + type: "SpecialKey", + kind: string, + stage: "self" | "descendants" | "ancestor" | "observer", + apply: ( + SpecialKey, + value: any, + applyTo: Instance, + cleanupTasks: { Task } + ) -> (), +} + +-- A collection of instances that may be parented to another instance. +type Children = Instance | StateObject | { [any]: Children } + +-- A table that defines an instance's properties, handlers and children. +type PropertyTable = { [string | SpecialKey]: any } + +-- init + +export type Fusion = { + version: Version, + + New: ( + className: string + ) -> ((propertyTable: PropertyTable) -> Instance), + Hydrate: ( + target: Instance + ) -> ((propertyTable: PropertyTable) -> Instance), + Ref: SpecialKey, + Cleanup: SpecialKey, + Children: SpecialKey, + Out: (propertyName: string) -> SpecialKey, + OnEvent: (eventName: string) -> SpecialKey, + OnChange: (propertyName: string) -> SpecialKey, + + Value: (initialValue: T) -> Value, + Computed: (callback: (Use) -> T, destructor: (T) -> ()?) -> Computed, + ForPairs: ( + inputTable: CanBeState<{ [KI]: VI }>, + processor: (Use, KI, VI) -> (KO, VO, M?), + destructor: (KO, VO, M?) -> ()? + ) -> ForPairs, + ForKeys: ( + inputTable: CanBeState<{ [KI]: any }>, + processor: (Use, KI) -> (KO, M?), + destructor: (KO, M?) -> ()? + ) -> ForKeys, + ForValues: ( + inputTable: CanBeState<{ [any]: VI }>, + processor: (Use, VI) -> (VO, M?), + destructor: (VO, M?) -> ()? + ) -> ForValues, + Observer: (watchedState: StateObject) -> Observer, + + Tween: (goalState: StateObject, tweenInfo: TweenInfo?) -> Tween, + Spring: ( + goalState: StateObject, + speed: CanBeState?, + damping: CanBeState? + ) -> Spring, + + Contextual: (defaultValue: T) -> Contextual, + cleanup: (...any) -> (), + doNothing: (...any) -> (), + peek: Use, +} + +export type Server = { + new: (Name: string) -> Server, + Server: (Name: string) -> Server, + Fire: (self: Server, Player: Player, EventName: string, ...any) -> (), + FireAll: (self: Server, EventName: string, ...any) -> (), + FireAllExcept: (self: Server, Player: Player, EventName: string, ...any) -> (), + FireList: (self: Server, Players: {Player}, EventName: string, ...any) -> (), + FireWithFilter: (self: Server, Filter: (Player) -> boolean, EventName: string, ...any) -> (), + On: (self: Server, EventName: string, Callback: ((Player, ...any) -> ...any?)) -> (), + Folder: (self: Server, Player: Player?) -> Model, +} + +export type Client = { + new: (self: Client, Name: string) -> Client, + Client: (self: Client, Name: string) -> Client, + Fire: (self: Client, EventName: string, ...any) -> Promise, + Call: (self: Client, EventName: string, ...any) -> Promise, + On: (self: Client, EventName: string, Callback: ((...any) -> ())?) -> Promise, + Folder: (self: Client) -> Model, + LocalFolder: (self: Client) -> Model, +} + +export type Promise = { + new: (Callback: (Resolve: (...any) -> (), Reject: (...any) -> ()) -> ()) -> Promise, + Promise: (Callback: (Resolve: (...any) -> (), Reject: (...any) -> ()) -> ()) -> Promise, + Reject: (a: any, b: any, c: any, d: any, e: any) -> Promise, + _Resolve: (self: Promise, ...any) -> (), + _Reject: (self: Promise, ...any) -> (), + Then: (self: Promise, OnResolve: ((...any) -> ...any)?, OnReject: ((...any) -> ...any)?) -> Promise, + Catch: (self: Promise, OnReject: ((...any) -> ())) -> ...any, + Finally: (self: Promise, Finally: (() -> ())) -> ...any, + Await: (self: Promise) -> ...any, +} + +type SignalNode = { + Next: SignalNode?, + Callback: (T...) -> (), +} + +export type Signal = { + Root: SignalNode?, + + Connect: (self: Signal, Callback: (T...) -> ()) -> () -> (), + Wait: (self: Signal) -> (Callback: (Resolve: (...any) -> (), Reject: (...any) -> ()) -> ()) -> Promise, + Fire: (self: Signal, T...) -> (), + DisconnectAll: (self: Signal) -> (), +} + +export type Clock = { + new: (Interval: number, Callback: () -> ()) -> Clock, + Clock: (Interval: number, Callback: () -> ()) -> Clock, + Pause: (self: Clock) -> (), + Resume: (self: Clock) -> (), + Advance: (self: Clock, Delta: number) -> (), +} + +type RedCore = { + Server: (Name: string, Definitions: {string}?) -> Server, + Client: (Name: string) -> Client, + + Collection: (Tag: string, Start: (Instance) -> (T...), Stop: (T...) -> ()) -> () -> (), + Ratelimit: (Limit: number, Interval: number) -> (Key: T?) -> boolean, + Promise: Promise, + Signal: { + new: () -> Signal, + Connect: (self: Signal, Callback: (T...) -> ()) -> () -> (), + Wait: (self: Signal) -> Promise, + Fire: (self: Signal, T...) -> (), + DisconnectAll: (self: Signal) -> (), + }, + Clock: Clock, + Spawn: (fn: (T...) -> (), T...) -> (), -- variadics SUCK + Bin: () -> ((Item: (() -> ...any) | Instance | RBXScriptConnection) -> (), () -> ()) +} + +export type Red = { + Help: () -> string, + Load: (self: Red, Script: LuaSourceContainer) -> RedCore, +} + +declare LoadLibrary: ((libraryName: "RbxFusion") -> Fusion) & ((libraryName: "RbxRed") -> Red) + declare function settings(): GlobalSettings declare function UserSettings(): UserSettings declare function PluginManager(): PluginManager diff --git a/luau/10000002.luau b/luau/10000002.luau deleted file mode 100644 index 1f6c3e0..0000000 --- a/luau/10000002.luau +++ /dev/null @@ -1,1334 +0,0 @@ -local function Red(Script) - local _SERVER, _CLIENT - - if Script:IsA "LocalScript" then - _CLIENT = true - elseif Script:IsA "Script" then - _SERVER = true - else - error("Argument must be the script itself", 2) - end - - local success, result = ypcall(function() - local __DARKLUA_BUNDLE_MODULES = {} - - do - local ReplicatedStorage = game:GetService "ReplicatedStorage" - local Players = game:GetService "Players" - local RedEvent = {} - local Remote, ClientFolder - - if _SERVER then - Remote = Instance.new "RemoteEvent" - Remote.Name = "RedEvent" - Remote.Parent = ReplicatedStorage - - local function PlayerAdded(Player) - ClientFolder = Instance.new "ScreenGui" - ClientFolder.Name = "Red" - ClientFolder.Parent = Player:FindFirstChild "PlayerGui" - end - - Players.PlayerAdded:connect(PlayerAdded) - - for _, Player in ipairs(Players:GetPlayers()) do - PlayerAdded(Player) - end - else - Remote = ReplicatedStorage:WaitForChild "RedEvent" - ClientFolder = (Players.LocalPlayer:FindFirstChild "PlayerGui"):WaitForChild "Red" - ClientFolder.Parent = nil - end - - RedEvent.Remote = Remote - RedEvent.ClientFolder = ClientFolder - __DARKLUA_BUNDLE_MODULES.a = RedEvent - end - do - local FreeThread = nil - - local function FunctionPasser(fn, ...) - local AquiredThread = FreeThread - - FreeThread = nil - - fn(...) - - FreeThread = AquiredThread - end - local function Yielder() - while true do - FunctionPasser(coroutine.yield()) - end - end - - __DARKLUA_BUNDLE_MODULES.b = function(fn, ...) - if not FreeThread then - FreeThread = coroutine.create(Yielder) - - coroutine.resume(FreeThread) - end - - coroutine.resume(FreeThread, fn, ...) - end - end - do - local RunService = game:GetService "RunService" - local Spawn = __DARKLUA_BUNDLE_MODULES.b - local Promise = {} - - Promise.__index = Promise - - function Promise.Promise(Callback) - local self = setmetatable({}, Promise) - - self.Status = "Pending" - self.OnResolve = {} - self.OnReject = {} - self.Value = {} - self.Thread = nil - self.Thread = coroutine.create(function() - local ok, err = ypcall(Callback, function(...) - self:_Resolve(...) - end, function(...) - self:_Reject(...) - end) - - if not ok then - self:_Reject(err) - end - end) - - coroutine.resume(self.Thread) - - return self - end - - Promise.new = Promise.Promise - - function Promise.Resolve(...) - local self = setmetatable({}, Promise) - - self.Status = "Resolved" - self.OnResolve = {} - self.OnReject = {} - self.Value = { ... } - self.Thread = nil - - return self - end - function Promise.Reject(...) - local self = setmetatable({}, Promise) - - self.Status = "Rejected" - self.OnResolve = {} - self.OnReject = {} - self.Value = { ... } - self.Thread = nil - - return self - end - function Promise._Resolve(self, ...) - assert( - self.Status == "Pending", - "Cannot resolve a promise that is not pending." - ) - - self.Status = "Resolved" - self.Value = { ... } - - for _, Callback in ipairs(self.OnResolve) do - Spawn(Callback, ...) - end - - coroutine.resume(coroutine.create(function() - coroutine.yield(self.Thread) - end)) - end - function Promise._Reject(self, ...) - assert( - self.Status == "Pending", - "Cannot reject a promise that is not pending." - ) - - self.Status = "Rejected" - self.Value = { ... } - - for _, Callback in ipairs(self.OnReject) do - Spawn(Callback, ...) - end - - coroutine.resume(coroutine.create(function() - coroutine.yield(self.Thread) - end)) - end - function Promise.Then(self, OnResolve, OnReject) - return Promise.Promise(function(Resolve, Reject) - local function PromiseResolutionProcedure(Value, ...) - if - type(Value) == "table" - and getmetatable(Value) == Promise - then - if Value.Status == "Pending" then - table.insert(Value.OnResolve, Resolve) - table.insert(Value.OnReject, Reject) - elseif Value.Status == "Resolved" then - Resolve(Value.Value) - elseif Value.Status == "Rejected" then - Reject(Value.Value) - end - else - Resolve(Value, ...) - end - end - - if self.Status == "Pending" then - if OnResolve then - table.insert(self.OnResolve, function(...) - PromiseResolutionProcedure(OnResolve(...)) - end) - else - table.insert( - self.OnResolve, - PromiseResolutionProcedure - ) - end - if OnReject then - table.insert(self.OnReject, function(...) - PromiseResolutionProcedure(OnReject(...)) - end) - else - table.insert(self.OnReject, Reject) - end - elseif self.Status == "Resolved" then - if OnResolve then - PromiseResolutionProcedure( - OnResolve(unpack(self.Value)) - ) - else - Resolve(unpack(self.Value)) - end - elseif self.Status == "Rejected" then - if OnReject then - PromiseResolutionProcedure( - OnReject(unpack(self.Value)) - ) - else - Reject(unpack(self.Value)) - end - end - end) - end - function Promise.Catch(self, OnReject) - return self:Then(nil, OnReject) - end - function Promise.Finally(self, Finally) - return self:Then(function(...) - Finally() - - return ... - end, function(Error) - Finally() - error(Error) - end) - end - function Promise.Await(self) - if self.Status == "Resolved" then - return unpack(self.Value) - elseif self.Status == "Rejected" then - return error(unpack(self.Value)) - end - - local c = 0 - - repeat - RunService.Stepped:wait() - - c = c + 1 - until self.Status ~= "Pending" or c > 500 - - local Current = coroutine.running() - - local function Resume() - coroutine.resume(Current) - end - - table.insert(self.OnResolve, Resume) - table.insert(self.OnReject, Resume) - coroutine.yield() - - if self.Status == "Resolved" then - return unpack(self.Value) - end - - return error(unpack(self.Value)) - end - - __DARKLUA_BUNDLE_MODULES.c = Promise - end - do - local RedEvent = __DARKLUA_BUNDLE_MODULES.a - local Event = RedEvent.Remote - local Promise = __DARKLUA_BUNDLE_MODULES.c - local Serdes = {} - - Serdes.NextId = 1 - Serdes.NextOT = 1 - - function Serdes.RegisterIdentifier(Name) - assert( - _SERVER, - "RegisterIdentifier can only be called on the server" - ) - - local Id = string.char(Serdes.NextId) - - Serdes.NextId = Serdes.NextId + 1 - - local e = Event:FindFirstChild(Name) - - if e then - e.Value = Id - else - e = Instance.new "StringValue" - e.Name = Name - e.Value = Id - e.Parent = Event - end - - return Id - end - function Serdes.Identifier(Name) - local e - - if _CLIENT then - return Promise.new(function(Resolve) - e = Event:WaitForChild(Name) - - if e.Value ~= nil then - Resolve(e.Value) - else - local Thread = Delay(5, function() - print( - [[[Red.Serdes]: Retrieving identifier exceeded 5 seconds. Make sure ']] - .. Name - .. "' is registered on the server." - ) - end) - - e.Changed:Once(function() - coroutine.yield(Thread) - Resolve(e.Value) - end) - end - end) - else - e = Event:FindFirstChild(Name) - - if e and e.Value then - return Promise.Resolve(e.Value) - end - - return Promise.Resolve(Serdes.RegisterIdentifier(Name)) - end - end - function Serdes.IdentifierAsync(Name) - return Serdes.Identifier(Name):Await() - end - function Serdes.OneTime() - Serdes.NextOT = Serdes.NextOT + 1 - - if Serdes.NextOT == 0xffff + 1 then - Serdes.NextOT = 0 - end - - return string.char(Serdes.NextOT) - end - - __DARKLUA_BUNDLE_MODULES.d = Serdes - end - do - local RunService = game:GetService "RunService" - - local function MakeHeartbeatFunction(Clock) - return function(Delta) - Clock:Advance(Delta) - end - end - - local Clock = {} - - Clock.__index = Clock - - function Clock.Clock(Interval, Callback) - local self = setmetatable({}, Clock) - - self.Interval = Interval - self.Callback = Callback - self.Delta = 0 - self.Connection = - RunService.Heartbeat:connect(MakeHeartbeatFunction(self)) - - return self - end - - Clock.new = Clock.Clock - - function Clock.Pause(self) - if self.Connection then - self.Connection:Disconnect() - end - end - function Clock.Resume(self) - if self.Connection.Connected then - return - end - - self.Connection = - RunService.Heartbeat:connect(MakeHeartbeatFunction(self)) - end - function Clock.Advance(self, Delta) - self.Delta = self.Delta + Delta - - if self.Delta >= self.Interval * 10 then - local Skipped = math.floor(self.Delta / self.Interval) - - self.Delta = self.Delta - Skipped * self.Interval - - return - end - if self.Delta >= self.Interval then - self.Delta = self.Delta - self.Interval - - self.Callback() - end - end - - __DARKLUA_BUNDLE_MODULES.e = Clock - end - do - __DARKLUA_BUNDLE_MODULES.f = __DARKLUA_BUNDLE_MODULES.e - end - do - local RunService = game:GetService "RunService" - local RedEvent = __DARKLUA_BUNDLE_MODULES.a - local Remote = RedEvent.Remote - local Serdes = __DARKLUA_BUNDLE_MODULES.d - local Spawn = __DARKLUA_BUNDLE_MODULES.b - local Promise = __DARKLUA_BUNDLE_MODULES.c - local Clock = __DARKLUA_BUNDLE_MODULES.f - local Event = {} - local nil_symbol = { __nil = true } - - Event.Callbacks = {} - Event.Outgoing = {} - - if _CLIENT then - Event.ActiveCalls = {} - end - - function Event.Listen() - if _CLIENT then - Remote.OnClientEvent:connect( - function(SingleFire, MultipleFire, IncomingCall) - if SingleFire.__nil then - SingleFire = nil - end - if MultipleFire.__nil then - MultipleFire = nil - end - if IncomingCall.__nil then - IncomingCall = nil - end - if SingleFire then - for EventId, Call in pairs(SingleFire) do - local Callback = Event.Callbacks[EventId] - local c = 0 - - repeat - RunService.Stepped:wait() - - Callback = Event.Callbacks[EventId] - c = c + 1 - until Callback or c > 500 - - if Callback then - if type(Call) == "table" then - Spawn(Callback, unpack(Call)) - else - Spawn(Callback, Call) - end - else - print "[Red]: Callback not found!" - end - end - end - if MultipleFire then - for EventId, Calls in pairs(MultipleFire) do - local Callback = Event.Callbacks[EventId] - - if Callback then - for _, Call in ipairs(Calls) do - if type(Call) == "table" then - Spawn(Callback, unpack(Call)) - else - Spawn(Callback, Call) - end - end - end - end - end - if IncomingCall then - for _, Call in pairs(IncomingCall) do - local CallId = table.remove(Call, 1) - local Success = table.remove(Call, 1) - - if Event.ActiveCalls[CallId] then - if Success then - Event.ActiveCalls[CallId].Resolve( - unpack(Call) - ) - else - Event.ActiveCalls[CallId].Reject( - unpack(Call) - ) - end - - Event.ActiveCalls[CallId] = nil - end - end - end - end - ) - Clock.new(1 / 60, function() - if not next(Event.Outgoing) then - return - end - - local SingleFire = {} - local SendSingleFire = false - - if Event.Outgoing[1] then - for EventId, Calls in pairs(Event.Outgoing[1]) do - if #Calls == 1 then - SingleFire[EventId] = Calls[1] - Event.Outgoing[1][EventId] = nil - SendSingleFire = true - end - end - end - - local sf = nil_symbol - - if SendSingleFire then - sf = SingleFire - end - - local eo1, eo2 = Event.Outgoing[1], Event.Outgoing[2] - - if eo1 == nil then - eo1 = nil_symbol - end - if eo2 == nil then - eo2 = nil_symbol - end - - Remote:FireServer(sf, eo1, eo2) - - for i, _ in pairs(Event.Outgoing) do - Event.Outgoing[i] = nil - end - end) - else - Remote.OnServerEvent:connect( - function(Player, SingleFire, MultipleFire, IncomingCall) - if SingleFire.__nil then - SingleFire = nil - end - if MultipleFire.__nil then - MultipleFire = nil - end - if IncomingCall.__nil then - IncomingCall = nil - end - if SingleFire then - for EventId, Call in pairs(SingleFire) do - local Callback = Event.Callbacks[EventId] - - if Callback then - if type(Call) == "table" then - Spawn( - Callback, - Player, - unpack(Call) - ) - else - Spawn(Callback, Player, Call) - end - end - end - end - if MultipleFire then - for EventId, Calls in pairs(MultipleFire) do - local Callback = Event.Callbacks[EventId] - - if Callback then - for _, Call in ipairs(Calls) do - if type(Call) == "table" then - Spawn( - Callback, - Player, - unpack(Call) - ) - else - Spawn(Callback, Player, Call) - end - end - end - end - end - if IncomingCall then - for EventId, Calls in pairs(IncomingCall) do - if Event.Callbacks[EventId] then - for _, Call in ipairs(Calls) do - Spawn(function() - local CallId = - table.remove(Call, 1) - local Result = { - CallId, - pcall( - Event.Callbacks[EventId], - Player, - unpack(Call) - ), - } - - if - Event.Outgoing[Player] - == nil - then - Event.Outgoing[Player] = {} - end - if - Event.Outgoing[Player][2] - == nil - then - Event.Outgoing[Player][2] = - {} - end - - table.insert( - Event.Outgoing[Player][2], - Result - ) - end) - end - else - if Event.Outgoing[Player] == nil then - Event.Outgoing[Player] = {} - end - if Event.Outgoing[Player][2] == nil then - Event.Outgoing[Player][2] = {} - end - - for _, Call in ipairs(Calls) do - table.insert( - Event.Outgoing[Player][2], - { - Call[1], - false, - "[Red]: Event not found", - } - ) - end - end - end - end - end - ) - RunService.Heartbeat:connect(function() - for Player, Packets in pairs(Event.Outgoing) do - local SingleCall = {} - local SendSingleCall = false - - if Packets[1] then - for EventId, Calls in pairs(Packets[1]) do - if #Calls == 1 then - SingleCall[EventId] = Calls[1] - Packets[1][EventId] = nil - SendSingleCall = true - end - end - end - - local sc = nil_symbol - - if SendSingleCall then - sc = SingleCall - end - - local p1, p2 = Packets[1], Packets[2] - - if p1 == nil then - p1 = nil_symbol - end - if p2 == nil then - p2 = nil_symbol - end - - Remote:FireClient(Player, sc, p1, p2) - end - for i, _ in pairs(Event.Outgoing) do - Event.Outgoing[i] = nil - end - end) - end - end - function Event.AddQueue(Queue, Call) - local Length = #Call - - if Length == 1 then - local Type = type(Call[1]) - - if Type ~= "table" then - table.insert(Queue, Call[1]) - else - table.insert(Queue, Call) - end - else - table.insert(Queue, Call) - end - end - function Event.FireClient(Player, EventName, ...) - assert( - not _CLIENT, - "Event.FireClient can only be called from the server" - ) - - local EventId = Serdes.IdentifierAsync(EventName) - - if Event.Outgoing[Player] == nil then - Event.Outgoing[Player] = {} - end - if Event.Outgoing[Player][1] == nil then - Event.Outgoing[Player][1] = {} - end - if Event.Outgoing[Player][1][EventId] == nil then - Event.Outgoing[Player][1][EventId] = {} - end - - Event.AddQueue(Event.Outgoing[Player][1][EventId], { ... }) - end - function Event.FireServer(EventName, ...) - assert( - _CLIENT, - "Event.FireServer can only be called on the client" - ) - - local Args = { ... } - - return Serdes.Identifier(EventName):Then(function(EventId) - if Event.Outgoing[1] == nil then - Event.Outgoing[1] = {} - end - if Event.Outgoing[1][EventId] == nil then - Event.Outgoing[1][EventId] = {} - end - - Event.AddQueue(Event.Outgoing[1][EventId], Args) - end) - end - function Event.Call(EventName, ...) - assert(_CLIENT, "Event.Call can only be called on the client") - - local Args = { ... } - - return Promise.new(function(Resolve, Reject) - local CallId = Serdes.OneTime() - local EventId = Serdes.IdentifierAsync(EventName) - - if Event.Outgoing[2] == nil then - Event.Outgoing[2] = {} - end - if Event.Outgoing[2][EventId] == nil then - Event.Outgoing[2][EventId] = {} - end - - table.insert(Args, 1, CallId) - table.insert(Event.Outgoing[2][EventId], Args) - - Event.ActiveCalls[CallId] = { - Resolve = Resolve, - Reject = Reject, - } - end) - end - function Event.SetCallback(EventName, Callback) - return Serdes.Identifier(EventName):Then(function(EventId) - Event.Callbacks[EventId] = Callback - end) - end - - __DARKLUA_BUNDLE_MODULES.g = Event - end - do - local Players = game:GetService "Players" - local RedEvent = __DARKLUA_BUNDLE_MODULES.a - local Remote = RedEvent.Remote - local ClientFolder = RedEvent.ClientFolder - local Serdes = __DARKLUA_BUNDLE_MODULES.d - local Event = __DARKLUA_BUNDLE_MODULES.g - local Server = {} - - Server.__index = Server - - function Server.Server(Name) - local self = setmetatable({}, Server) - - self.Name = Name - self.FolderInstance = nil - - return self - end - - Server.new = Server.Server - - function Server.Fire(self, Player, EventName, ...) - Event.FireClient(Player, self.Name .. "_" .. EventName, ...) - end - function Server.FireAll(self, EventName, ...) - for _, Player in ipairs(Players:GetPlayers()) do - self:Fire(Player, EventName, ...) - end - end - function Server.FireAllExcept(self, Player, EventName, ...) - for _, OtherPlayer in ipairs(Players:GetPlayers()) do - if OtherPlayer ~= Player then - self:Fire(OtherPlayer, EventName, ...) - end - end - end - function Server.FireList(self, PlayerList, EventName, ...) - for _, Player in ipairs(PlayerList) do - self:Fire(Player, EventName, ...) - end - end - function Server.FireWithFilter(self, Filter, EventName, ...) - for _, Player in ipairs(Players:GetPlayers()) do - if Filter(Player) then - self:Fire(Player, EventName, ...) - end - end - end - function Server.On(self, EventName, Callback) - Event.SetCallback(self.Name .. "_" .. EventName, Callback) - end - function Server.Folder(self, Player) - if Player then - ClientFolder = (Player:FindFirstChild "PlayerGui").Red - - if ClientFolder:FindFirstChild(self.Name) then - return ClientFolder:FindFirstChild(self.Name) - else - local Folder = Instance.new "Model" - - Folder.Name = self.Name - Folder.Parent = ClientFolder - - return Folder - end - else - if not self.FolderInstance then - local Folder = Instance.new "Model" - - Folder.Name = self.Name - Folder.Parent = Remote - self.FolderInstance = Folder - end - - return self.FolderInstance - end - end - - local Client = {} - - Client.__index = Client - - function Client.Client(Name) - local self = setmetatable({}, Client) - - self.Name = Name - self.FolderInstance = nil - self.LocalFolderInstance = nil - - return self - end - - Client.new = Client.Client - - function Client.Fire(self, EventName, ...) - return Event.FireServer(self.Name .. "_" .. EventName, ...) - end - function Client.Call(self, EventName, ...) - return Event.Call(self.Name .. "_" .. EventName, ...) - end - function Client.On(self, EventName, Callback) - return Event.SetCallback( - self.Name .. "_" .. EventName, - Callback - ) - end - function Client.Folder(self) - if not self.FolderInstance then - self.FolderInstance = Remote:WaitForChild(self.Name) - end - - return self.FolderInstance - end - function Client.LocalFolder(self) - if not self.LocalFolderInstance then - self.LocalFolderInstance = - ClientFolder:WaitForChild(self.Name) - end - - return self.LocalFolderInstance - end - - local Net = {} - - Net.ServerNamespaceList = {} - Net.ClientNamespaceList = {} - - function Net.Server(Name, Definitions) - assert(_SERVER, "Net.Server can only be used on the server") - - if not Net.ServerNamespaceList[Name] then - Net.ServerNamespaceList[Name] = Server.Server(Name) - end - if Definitions then - for _, Term in ipairs(Definitions) do - Serdes.Identifier(Name .. "_" .. Term) - end - end - - return Net.ServerNamespaceList[Name] - end - function Net.Client(Name) - assert(_CLIENT, "Net.Client can only be used on the client") - - if Net.ClientNamespaceList[Name] == nil then - Net.ClientNamespaceList[Name] = Client.Client(Name) - end - - return Net.ClientNamespaceList[Name] - end - function Net.Identifier(Name) - return Serdes.Identifier(Name) - end - - Event.Listen() - - __DARKLUA_BUNDLE_MODULES.h = Net - end - do - local CollectionService = game:GetService "CollectionService" - local Spawn = __DARKLUA_BUNDLE_MODULES.b - - __DARKLUA_BUNDLE_MODULES.i = function(Tag, Start, Stop) - local InstanceMap = {} - - for _, Instance in ipairs(CollectionService:GetTagged(Tag)) do - Spawn(function() - InstanceMap[Instance] = { - Start(Instance), - } - end) - end - - local AddConnection = CollectionService - :GetInstanceAddedSignal(Tag) - :connect(function(Instance) - InstanceMap[Instance] = { - Start(Instance), - } - end) - local RemoveConnection = CollectionService - :GetInstanceRemovedSignal(Tag) - :connect(function(Instance) - local Value = InstanceMap[Instance] - - if Value then - InstanceMap[Instance] = nil - - Stop(unpack(Value)) - end - end) - - return function() - AddConnection:Disconnect() - RemoveConnection:Disconnect() - - for Instance, Value in pairs(InstanceMap) do - Spawn(Stop, unpack(Value)) - end - end - end - end - do - __DARKLUA_BUNDLE_MODULES.j = function(Limit, Interval) - assert(Limit > 0, "Limit must be greater than 0") - - local CountMap = {} - local CountKeyless = 0 - - return function(Key) - if Key then - local Count = CountMap[Key] - - if Count == nil then - Count = 0 - - Delay(Interval, function() - CountMap[Key] = nil - end) - end - if Count >= Limit then - return false - end - - CountMap[Key] = Count + 1 - else - if CountKeyless == 0 then - Delay(Interval, function() - CountKeyless = 0 - end) - end - if CountKeyless >= Limit then - return false - end - - CountKeyless = CountKeyless + 1 - end - - return true - end - end - end - do - local Promise = __DARKLUA_BUNDLE_MODULES.c - local Spawn = __DARKLUA_BUNDLE_MODULES.b - local Signal = {} - - Signal.__index = Signal - - function Signal.new() - return setmetatable({ Root = nil }, Signal) - end - function Signal.Connect(self, Callback) - local Node = { - Next = self.Root, - Callback = Callback, - } - - self.Root = Node - - return function() - if self.Root == Node then - self.Root = Node.Next - else - local Current = self.Root - - while Current do - if Current.Next == Node then - Current.Next = Node.Next - - break - end - - Current = Current.Next - end - end - end - end - function Signal.Wait(self) - return Promise.new(function(Resolve) - local Disconnect - - Disconnect = self:Connect(function(...) - Disconnect() - Resolve(...) - end) - end) - end - function Signal.Fire(self, ...) - local Current = self.Root - - while Current do - Spawn(Current.Callback, ...) - - Current = Current.Next - end - end - function Signal.DisconnectAll(self) - self.Root = nil - end - - __DARKLUA_BUNDLE_MODULES.k = Signal - end - do - __DARKLUA_BUNDLE_MODULES.l = function(value) - local basicType = type(value) - - if - basicType == "nil" - or basicType == "boolean" - or basicType == "number" - or basicType == "string" - or basicType == "function" - or basicType == "thread" - or basicType == "table" - then - return basicType - end - - local tests = { - { - "Instance", - { - "ClassName", - }, - }, - { - "EnumItem", - { - "EnumType", - "Name", - "Value", - }, - }, - { - "Enum", - { - "GetEnumItems", - }, - }, - { - "Enums", - { - "MembershipType", - }, - }, - { - "RBXScriptSignal", - { - "connect", - "wait", - }, - }, - { - "RBXScriptConnection", - { - "connected", - "disconnect", - }, - }, - { - "TweenInfo", - { - "EasingDirection", - "RepeatCount", - "EasingStyle", - }, - }, - { - "CFrame", - { - "p", - "x", - "y", - "z", - "lookVector", - }, - }, - { - "Vector3", - { - "Lerp", - "unit", - "magnitude", - "x", - "y", - "z", - }, - }, - { - "Vector3int16", - { - "z", - "x", - "y", - }, - }, - { - "Vector2", - { - "unit", - "magnitude", - "x", - "y", - }, - }, - { - "Vector2int16", - { - "x", - "y", - }, - }, - { - "Region3", - { - "CFrame", - "Size", - }, - }, - { - "Region3int16", - { - "Min", - "Max", - }, - }, - { - "Ray", - { - "Origin", - "Direction", - "Unit", - "ClosestPoint", - "Distance", - }, - }, - { - "UDim", - { - "Scale", - "Offset", - }, - }, - { - "Axes", - { - "Z", - "X", - "Y", - }, - }, - { - "UDim2", - { - "X", - "Y", - }, - }, - { - "BrickColor", - { - "Number", - "Name", - "Color", - "r", - "g", - "b", - }, - }, - { - "Color3", - { - "r", - "g", - "b", - }, - }, - { - "Faces", - { - "Right", - "Top", - "Back", - }, - }, - } - - for _, v in ipairs(tests) do - local t, test = v[1], v[2] - local ok, result = pcall(function() - for _, prop in ipairs(test) do - if value[prop] == nil then - return false - end - end - - return true - end) - - if ok and result then - return t - end - end - end - end - do - local Spawn = __DARKLUA_BUNDLE_MODULES.b - local typeof = __DARKLUA_BUNDLE_MODULES.l - - __DARKLUA_BUNDLE_MODULES.m = function() - local Bin = {} - - return function(Item) - table.insert(Bin, Item) - end, function() - for _, Item in ipairs(Bin) do - if typeof(Item) == "Instance" then - Item:Destroy() - elseif typeof(Item) == "RBXScriptConnection" then - Item:disconnect() - elseif typeof(Item) == "function" then - Spawn(Item) - end - end - for i, _ in ipairs(Bin) do - Bin[i] = nil - end - end - end - end - - local Net = __DARKLUA_BUNDLE_MODULES.h - - return { - Server = Net.Server, - Client = Net.Client, - Collection = __DARKLUA_BUNDLE_MODULES.i, - Ratelimit = __DARKLUA_BUNDLE_MODULES.j, - Promise = __DARKLUA_BUNDLE_MODULES.c, - Signal = __DARKLUA_BUNDLE_MODULES.k, - Clock = __DARKLUA_BUNDLE_MODULES.e, - Spawn = __DARKLUA_BUNDLE_MODULES.b, - Bin = __DARKLUA_BUNDLE_MODULES.m, - } - end) - - if not success then - error(result) - end - - return result -end - -return { - Help = function() - return "See https://redblox.dev/ for more information." - end, - Load = Red, -} From 352e4ae8dd0d332c848e821b575a9bbd69bda491 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Mon, 29 Jan 2024 23:49:16 +0000 Subject: [PATCH 4/8] Improve typing and formatting of files --- defs.d.lua | 40 +++++++++------------ luau/45284430.luau | 4 +-- luau/60595411.luau | 88 +++++++++++++++++++++++++--------------------- luau/73157242.luau | 81 ++++++++++++++++++++---------------------- luau/97188756.luau | 42 +++++++++++----------- 5 files changed, 125 insertions(+), 130 deletions(-) diff --git a/defs.d.lua b/defs.d.lua index 70cc72b..1996a47 100644 --- a/defs.d.lua +++ b/defs.d.lua @@ -3149,8 +3149,11 @@ declare class Vector3 Unit: Vector3 unit: Vector3 X: number + x: number Y: number + y: number Z: number + z: number function Angle(self, other: Vector3, axis: Vector3?): number function Cross(self, other: Vector3): Vector3 function Dot(self, other: Vector3): number @@ -3194,31 +3197,18 @@ end declare class CFrame lookVector: Vector3 - LookVector: Vector3 -- ? - Position: Vector3 - RightVector: Vector3 - Rotation: CFrame - UpVector: Vector3 + p: Vector3 X: number - XVector: Vector3 Y: number - YVector: Vector3 Z: number - ZVector: Vector3 - function GetComponents(self): (number, number, number, number, number, number, number, number, number, number, number, number) - function Inverse(self): CFrame - function Lerp(self, goal: CFrame, alpha: number): CFrame - function Orthonormalize(self): CFrame - function PointToObjectSpace(self, v3: Vector3): Vector3 - function PointToWorldSpace(self, v3: Vector3): Vector3 - function ToAxisAngle(self): (Vector3, number) - function ToEulerAnglesXYZ(self): (number, number, number) - function ToEulerAnglesYXZ(self): (number, number, number) - function ToObjectSpace(self, cf: CFrame): CFrame - function ToOrientation(self): (number, number, number) - function ToWorldSpace(self, cf: CFrame): CFrame - function VectorToObjectSpace(self, v3: Vector3): Vector3 - function VectorToWorldSpace(self, v3: Vector3): Vector3 + function inverse(self): CFrame + function pointToObjectSpace(self, v3: Vector3): Vector3 + function pointToWorldSpace(self, v3: Vector3): Vector3 + function toEulerAnglesXYZ(self): (number, number, number) + function toObjectSpace(self, cf: CFrame): CFrame + function toWorldSpace(self, cf: CFrame): CFrame + function vectorToObjectSpace(self, v3: Vector3): Vector3 + function vectorToWorldSpace(self, v3: Vector3): Vector3 function __add(self, other: Vector3): CFrame function __mul(self, other: CFrame): CFrame function __mul(self, other: Vector3): Vector3 @@ -6214,6 +6204,8 @@ declare class Terrain extends BasePart function WriteVoxels(self, region: Region3, resolution: number, materials: { any }, occupancy: { any }): nil function AutoWedgeCell(self, x: number, y: number, z: number): boolean + function SetWaterCell(self, x: number, y: number, z: number, waterForce: EnumWaterForce, waterDirection: EnumWaterDirection): nil + function SetCell(self, x: number, y: number, z: number, material: EnumCellMaterial, orientation: EnumCellOrientation): nil end declare class TriangleMeshPart extends BasePart @@ -7051,6 +7043,8 @@ declare class ScriptContext extends Instance function StopScriptProfiling(self): string function AddCoreScript(self, id: number, player: Player, name: string): nil + function RegisterLibrary(self, name: string, id: string): nil + function LibraryRegistrationComplete(self): nil end declare class ScriptDebugger extends Instance @@ -8903,7 +8897,7 @@ export type Red = { Load: (self: Red, Script: LuaSourceContainer) -> RedCore, } -declare LoadLibrary: ((libraryName: "RbxFusion") -> Fusion) & ((libraryName: "RbxRed") -> Red) +declare LoadLibrary: ((libraryName: "RbxFusion") -> Fusion) & ((libraryName: "RbxRed") -> Red) & ((libraryName: string) -> any) declare function settings(): GlobalSettings declare function UserSettings(): UserSettings diff --git a/luau/45284430.luau b/luau/45284430.luau index 85b65fd..8cc2234 100644 --- a/luau/45284430.luau +++ b/luau/45284430.luau @@ -2404,8 +2404,8 @@ RbxGui.CreateTutorial = function(name, tutorialKey, createButtons) end local function CreateBasicTutorialPage( - name, - handleResize, + name: string, + handleResize: (number, number) -> (), skipTutorial, giveDoneButton ) diff --git a/luau/60595411.luau b/luau/60595411.luau index b577e12..53b8e2b 100644 --- a/luau/60595411.luau +++ b/luau/60595411.luau @@ -111,25 +111,17 @@ end function JsonWriter:ParseString(s) self:Append '"' - self:Append(string.gsub(s, '[%z%c\\"/]', function(n) - local c = self.backslashes[n] - if c then - return c - end - return string.format("\\u%.4X", string.byte(n)) - end)) + local copy = string.gsub(s, '[%z%c\\"/]', function(n) + return self.backslashes[n] or string.format("\\u%.4X", string.byte(n)) + end) + self:Append(copy) self:Append '"' end function JsonWriter:IsArray(t) local count = 0 - local isindex = function(k) - if type(k) == "number" and k > 0 then - if math.floor(k) == k then - return true - end - end - return false + local function isindex(k) + return type(k) == "number" and k > 0 and math.floor(k) == k end for k, _ in pairs(t) do if not isindex(k) then @@ -451,9 +443,9 @@ function Null() end -------------------- End JSON Parser ------------------------ -local t = {} +local RbxUtility = {} -t.DecodeJSON = function(jsonString) +RbxUtility.DecodeJSON = function(jsonString: string) pcall(function() warn "RbxUtility.DecodeJSON is deprecated, please use Game:GetService('HttpService'):JSONDecode() instead." end) @@ -465,7 +457,7 @@ t.DecodeJSON = function(jsonString) return nil end -t.EncodeJSON = function(jsonTable) +RbxUtility.EncodeJSON = function(jsonTable: { [any]: any }) pcall(function() warn "RbxUtility.EncodeJSON is deprecated, please use Game:GetService('HttpService'):JSONEncode() instead." end) @@ -482,17 +474,17 @@ end --makes a wedge at location x, y, z --sets cell x, y, z to default material if parameter is provided, if not sets cell x, y, z to be whatever material it previously w --returns true if made a wedge, false if the cell remains a block -t.MakeWedge = function(x, y, z, _) +RbxUtility.MakeWedge = function(x, y, z, _) return game:GetService("Terrain"):AutoWedgeCell(x, y, z) end -t.SelectTerrainRegion = function( - regionToSelect, - colour, - selectEmptyCells, - selectionParent +RbxUtility.SelectTerrainRegion = function( + regionToSelect: Region3, + colour: BrickColor, + selectEmptyCells: boolean, + selectionParent: Instance ) - local terrain = game.Workspace:FindFirstChild "Terrain" + local terrain = game.Workspace:FindFirstChild "Terrain" :: Terrain if not terrain then return end @@ -500,14 +492,15 @@ t.SelectTerrainRegion = function( assert(regionToSelect) assert(colour) - if type(regionToSelect) ~= "Region3" then + -- can't use typeof + if not (regionToSelect.CFrame and regionToSelect.Size) then error( `regionToSelect (first arg), should be of type Region3, but is type {type( regionToSelect )}` ) end - if type(colour) ~= "BrickColor" then + if not (colour.Number and colour.r and colour.Color) then error( `color (second arg), should be of type BrickColor, but is type {type( colour @@ -669,7 +662,7 @@ t.SelectTerrainRegion = function( adornments.SelectionPart = selectionPart adornments.SelectionBox = selectionBox - updateSelection = function(newRegion, newColour) + updateSelection = function(newRegion: Region3, newColour) if newRegion and newRegion ~= lastRegion then lastRegion = newRegion selectionPart.Size = newRegion.Size @@ -737,7 +730,7 @@ Method :wait() Description: This call blocks until ]] -function t.CreateSignal() +function RbxUtility.CreateSignal() local this = {} local mBindableEvent = Instance.new "BindableEvent" @@ -800,7 +793,7 @@ function t.CreateSignal() end ------------------------------------------------- Sigal class End ------------------------------------------------------ - +-- this ones my favourite - Heliodex ------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------ @@ -894,7 +887,7 @@ Note: It is also perfectly legal to save a reference to the function returned by --the Create function need to be created as a functor, not a function, in order to support the Create.E syntax, so it --will be created in several steps rather than as a single function declaration. -local function Create_PrivImpl(objectType) +local function Create_PrivImpl(objectType: string) if type(objectType) ~= "string" then error("Argument of Create must be a string", 2) end @@ -902,7 +895,7 @@ local function Create_PrivImpl(objectType) --The first function call is a function call using Lua's single-string-argument syntax --The second function call is using Lua's single-table-argument syntax --Both can be chained together for the nice effect. - return function(dat) + return function(dat: { [string | number | { any } | any]: any }) --default to nothing, to handle the no argument given case dat = dat or {} @@ -944,7 +937,7 @@ local function Create_PrivImpl(objectType) obj[k.__eventname]:connect(v) --define constructor function - elseif k == t.Create then + elseif k == RbxUtility.Create then if type(v) ~= "function" then error( `Bad entry in Create body: Key \`[Create]\` should be paired with a constructor function, got: {v}`, @@ -978,7 +971,7 @@ local function Create_PrivImpl(objectType) end --now, create the functor: -t.Create = setmetatable({}, { +RbxUtility.Create = setmetatable({}, { __call = function(_, ...) return Create_PrivImpl(...) end, @@ -986,7 +979,7 @@ t.Create = setmetatable({}, { --and create the "Event.E" syntax stub. Really it's just a stub to construct a table which our Create --function can recognize as special. -t.Create.E = function(eventName) +RbxUtility.Create.E = function(eventName) return { __eventname = eventName } end @@ -1000,19 +993,28 @@ end ------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------ -t.Help = function(funcNameOrFunc) +RbxUtility.Help = function(funcNameOrFunc) --input argument can be a string or a function. Should return a description (of arguments and expected side effects) - if funcNameOrFunc == "DecodeJSON" or funcNameOrFunc == t.DecodeJSON then + if + funcNameOrFunc == "DecodeJSON" + or funcNameOrFunc == RbxUtility.DecodeJSON + then return "Function DecodeJSON. " .. "Arguments: (string). " .. "Side effect: returns a table with all parsed JSON values" end - if funcNameOrFunc == "EncodeJSON" or funcNameOrFunc == t.EncodeJSON then + if + funcNameOrFunc == "EncodeJSON" + or funcNameOrFunc == RbxUtility.EncodeJSON + then return "Function EncodeJSON. " .. "Arguments: (table). " .. "Side effect: returns a string composed of argument table in JSON data format" end - if funcNameOrFunc == "MakeWedge" or funcNameOrFunc == t.MakeWedge then + if + funcNameOrFunc == "MakeWedge" + or funcNameOrFunc == RbxUtility.MakeWedge + then return "Function MakeWedge. " .. "Arguments: (x, y, z, [default material]). " .. "Description: Makes a wedge at location x, y, z. Sets cell x, y, z to default material if " @@ -1021,7 +1023,7 @@ t.Help = function(funcNameOrFunc) end if funcNameOrFunc == "SelectTerrainRegion" - or funcNameOrFunc == t.SelectTerrainRegion + or funcNameOrFunc == RbxUtility.SelectTerrainRegion then return "Function SelectTerrainRegion. " .. "Arguments: (regionToSelect, color, selectEmptyCells, selectionParent). " @@ -1033,7 +1035,10 @@ t.Help = function(funcNameOrFunc) .. "arguments to said function are a new region3 to select, and the adornment color (color arg is optional). " .. "Also returns a second function that takes no arguments and destroys the selection" end - if funcNameOrFunc == "CreateSignal" or funcNameOrFunc == t.CreateSignal then + if + funcNameOrFunc == "CreateSignal" + or funcNameOrFunc == RbxUtility.CreateSignal + then return "Function CreateSignal. " .. "Arguments: None. " .. "Returns: The newly created Signal object. This object is identical to the RBXScriptSignal class " @@ -1082,8 +1087,9 @@ t.Help = function(funcNameOrFunc) .. "Descrition: Create is a very powerfull function, whose description is too long to fit here, and " .. "is best described via example, please see the wiki page for a description of how to use it." end + return "No help available for this function" end --------------------------------------------Documentation Ends---------------------------------------------------------- -return t +return RbxUtility diff --git a/luau/73157242.luau b/luau/73157242.luau index e9b54da..aecfb50 100644 --- a/luau/73157242.luau +++ b/luau/73157242.luau @@ -426,26 +426,17 @@ local function getMouseTargetCFrame(targetPart) return targetPart.CFrame end -local function isBlocker(part) -- returns whether or not we want to cancel the stamp because we're blocked by this part - if not part then +local function isBlocker(part: Instance) -- returns whether or not we want to cancel the stamp because we're blocked by this part + if not part or not part.Parent or part:FindFirstChild "Humanoid" then return false - end - if not part.Parent then - return false - end - if part:FindFirstChild "Humanoid" then - return false - end - if + elseif part:FindFirstChild "RobloxStamper" or part:FindFirstChild "RobloxModel" then return true - end - if part:IsA "Part" and not part.CanCollide then - return false - end - if part == game.Lighting then + elseif + (part:IsA "Part" and not part.CanCollide) or (part == game.Lighting) + then return false end return isBlocker(part.Parent) @@ -453,7 +444,11 @@ end -- helper function to determine if a character can be pushed upwards by a certain amount -- character is 5 studs tall, we'll check a 1.5 x 1.5 x 4.5 box around char, with center .5 studs below torsocenter -local function spaceAboveCharacter(charTorso, newTorsoY, stampData) +local function spaceAboveCharacter( + charTorso: BasePart, + newTorsoY, + stampData: Instance +) local partsAboveChar = game.Workspace:FindPartsInRegion3( Region3.new( Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) @@ -461,7 +456,7 @@ local function spaceAboveCharacter(charTorso, newTorsoY, stampData) Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) + Vector3.new(0.75, 1.75, 0.75) ), - charTorso.Parent, + charTorso.Parent :: Instance, 100 ) @@ -474,30 +469,25 @@ local function spaceAboveCharacter(charTorso, newTorsoY, stampData) end end - if - clusterPartsInRegion( - Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) - - Vector3.new(0.75, 2.75, 0.75), - Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) - + Vector3.new(0.75, 1.75, 0.75) - ) - then - return false - end - - return true + return not clusterPartsInRegion( + Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) + - Vector3.new(0.75, 2.75, 0.75), + Vector3.new(charTorso.Position.X, newTorsoY, charTorso.Position.Z) + + Vector3.new(0.75, 1.75, 0.75) + ) end -local function findConfigAtMouseTarget(Mouse, stampData) +local function findConfigAtMouseTarget(Mouse: Mouse, stampData: Instance) -- *Critical Assumption* : -- This function assumes the target CF axes are orthogonal with the target bounding box faces -- And, it assumes the insert CF axes are orthongonal with the insert bounding box faces -- Therefore, insertion will not work with angled faces on wedges or other "non-block" parts, nor -- will it work for parts in a model that are not orthogonally aligned with the model's CF. + -- This can happen sometimes, return if so if not Mouse then return nil - end -- This can happen sometimes, return if so + end if not stampData then error "findConfigAtMouseTarget: stampData is nil" return nil @@ -572,7 +562,7 @@ local function findConfigAtMouseTarget(Mouse, stampData) if targetPart:IsA "Terrain" then if not cluster then - cluster = game.Workspace:FindFirstChild "Terrain" + cluster = game.Workspace:FindFirstChild "Terrain" :: Terrain end local cellID = cluster:WorldToCellPreferSolid(mouseHitInWorld) if hitPlane then @@ -1025,7 +1015,9 @@ RbxStamper.SetupStamperDragger = function( -- Init all state variables local gInitial90DegreeRotations = 0 - local stampData + local stampData: { + DisabledScripts: { LuaSourceContainer }?, + } local mouseTarget local errorBox = Instance.new "SelectionBox" @@ -1327,6 +1319,8 @@ RbxStamper.SetupStamperDragger = function( minBB += targetCFrame.p - currModelCFrame.p maxBB += targetCFrame.p - currModelCFrame.p + local clusterMat: Instance + -- don't drag into terrain if clusterPartsInRegion( @@ -1391,7 +1385,7 @@ RbxStamper.SetupStamperDragger = function( positionPartsAtCFrame3(targetCFrame, stampData.CurrentParts) lastTarget.CFrame = targetCFrame -- successful positioning, so update 'dat cframe if stampData.CurrentParts:FindFirstChild("ClusterMaterial", true) then - local clusterMat = + clusterMat = stampData.CurrentParts:FindFirstChild("ClusterMaterial", true) if clusterMat:IsA "Vector3Value" then lastTarget.TerrainOrientation = clusterMat.Value.Z @@ -1707,15 +1701,16 @@ RbxStamper.SetupStamperDragger = function( local parts = {} local decals = {} - stampData = {} - stampData.DisabledScripts = {} - stampData.TransparencyTable = {} - stampData.MaterialTable = {} - stampData.CanCollideTable = {} - stampData.AnchoredTable = {} - stampData.ArchivableTable = {} - stampData.DecalTransparencyTable = {} - stampData.SurfaceTypeTable = {} + stampData = { + DisabledScripts = {}, + TransparencyTable = {}, + MaterialTable = {}, + CanCollideTable = {}, + AnchoredTable = {}, + ArchivableTable = {}, + DecalTransparencyTable = {}, + SurfaceTypeTable = {}, + } collectParts(clone, parts, scripts, decals) diff --git a/luau/97188756.luau b/luau/97188756.luau index 6675f76..f1c9d5f 100644 --- a/luau/97188756.luau +++ b/luau/97188756.luau @@ -18,9 +18,10 @@ local function WaitForChild(parent, childName) return parent[childName] end -local function typedef(obj) - return obj -end +-- wtf +-- local function typedef(obj) +-- return obj +-- end local function IsPhone() local cGui = Game:GetService "CoreGui" @@ -51,7 +52,7 @@ while Player.Character == nil do wait(0.03) end local RbxUtility = LoadLibrary "RbxUtility" -local Gui = typedef(RbxUtility) +local Create = RbxUtility.Create local Camera = Game.Workspace.CurrentCamera -- Services @@ -127,7 +128,6 @@ local Input = { --------------------------------------------------- ------------------ Chat class -------------------- local Chat = { - ChatColors = { BrickColor.new "Bright red", BrickColor.new "Bright blue", @@ -1098,7 +1098,7 @@ end function Chat:ComputeSpaceString(pLabel) local nString = " " if not self.TempSpaceLabel then - self.TempSpaceLabel = Gui.Create "TextButton" { + self.TempSpaceLabel = Create "TextButton" { Size = UDim2.new( 0, pLabel.AbsoluteSize.X, @@ -1227,7 +1227,7 @@ function Chat:CreateMessage(cPlayer, message) end --else -- Haven't hit the mark yet, so keep creating - pLabel = Gui.Create "TextLabel" { + pLabel = Create "TextLabel" { Name = pName, Text = `{pName}:`, -- TextColor3 = pColor, @@ -1259,7 +1259,7 @@ function Chat:CreateMessage(cPlayer, message) nString = self.CachedSpaceStrings_List[pName] end - mLabel = Gui.Create "TextLabel" { + mLabel = Create "TextLabel" { Name = `{pName} - message`, -- Max is 3 lines Size = UDim2.new(1, 0, 0.5, 0), @@ -1353,7 +1353,7 @@ function Chat:CreateSafeChatOptions(list, rootButton) rootButton = rootButton or self.SafeChatButton for msg, _ in pairs(list) do if type(msg) == "string" then - local chatText = Gui.Create "TextButton" { + local chatText = Create "TextButton" { Name = msg, Text = msg, Size = UDim2.new(0, 100, 0, 20), @@ -1404,13 +1404,13 @@ function Chat:CreateSafeChatOptions(list, rootButton) end function Chat:CreateSafeChatGui() - self.SafeChatFrame = Gui.Create "Frame" { + self.SafeChatFrame = Create "Frame" { Name = "SafeChatFrame", Size = UDim2.new(1, 0, 1, 0), Parent = self.Gui, BackgroundTransparency = 1, - Gui.Create "ImageButton" { + Create "ImageButton" { Name = "SafeChatButton", Size = UDim2.new(0, 44, 0, 31), Position = UDim2.new(0, 1, 0.35, 0), @@ -1443,21 +1443,21 @@ end -- For touch devices we create a button instead function Chat:CreateTouchButton() - self.ChatTouchFrame = Gui.Create "Frame" { + self.ChatTouchFrame = Create "Frame" { Name = "ChatTouchFrame", Size = UDim2.new(0, 128, 0, 32), Position = UDim2.new(0, 88, 0, 0), BackgroundTransparency = 1, Parent = self.Gui, - Gui.Create "ImageButton" { + Create "ImageButton" { Name = "ChatLabel", Size = UDim2.new(0, 74, 0, 28), Position = UDim2.new(0, 0, 0, 0), BackgroundTransparency = 1, ZIndex = 2.0, }, - Gui.Create "ImageLabel" { + Create "ImageLabel" { Name = "Background", Size = UDim2.new(1, 0, 1, 0), Position = UDim2.new(0, 0, 0, 0), @@ -1468,7 +1468,7 @@ function Chat:CreateTouchButton() self.TapToChatLabel = self.ChatTouchFrame.ChatLabel self.TouchLabelBackground = self.ChatTouchFrame.Background - self.ChatBar = Gui.Create "TextBox" { + self.ChatBar = Create "TextBox" { Name = "ChatBar", Size = UDim2.new(1, 0, 0.2, 0), Position = UDim2.new(0, 0, 0.8, 800), @@ -1500,7 +1500,7 @@ function Chat:CreateChatBar() return GuiService.UseLuaChat end) if forceChatGUI or (status and result) then - self.ClickToChatButton = Gui.Create "TextButton" { + self.ClickToChatButton = Create "TextButton" { Name = "ClickToChat", Size = UDim2.new(1, 0, 0, 20), BackgroundTransparency = 1, @@ -1513,7 +1513,7 @@ function Chat:CreateChatBar() FontSize = Enum.FontSize.Size12, } - self.ChatBar = Gui.Create "TextBox" { + self.ChatBar = Create "TextBox" { Name = "ChatBar", Size = UDim2.new(1, 0, 0, 20), Position = UDim2.new(0, 0, 1, 0), @@ -1554,7 +1554,7 @@ end -- Done only once function Chat:CreateGui() self.Gui = WaitForChild(CoreGuiService, "RobloxGui") - self.Frame = Gui.Create "Frame" { + self.Frame = Create "Frame" { Name = "ChatFrame", --Size = self.Configuration.Size; Size = UDim2.new(0, 500, 0, 120), @@ -1565,7 +1565,7 @@ function Chat:CreateGui() Parent = self.Gui, Active = false, - Gui.Create "ImageLabel" { + Create "ImageLabel" { Name = "Background", Image = "http://banland.xyz/asset/?id=97120937", --96551212'; Size = UDim2.new(1.3, 0, 1.64, 0), @@ -1575,7 +1575,7 @@ function Chat:CreateGui() Visible = false, }, - Gui.Create "Frame" { + Create "Frame" { Name = "Border", Size = UDim2.new(1, 0, 0, 1), Position = UDim2.new(0, 0, 0.8, 0), @@ -1585,7 +1585,7 @@ function Chat:CreateGui() Visible = false, }, - Gui.Create "Frame" { + Create "Frame" { Name = "ChatRenderFrame", Size = UDim2.new(1.02, 0, 1.01, 0), Position = UDim2.new(0, 0, 0, 0), From 630d9aab82c1d1d597e2eba89c4bb2df7556379f Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Sat, 3 Feb 2024 23:53:37 +0000 Subject: [PATCH 5/8] Formatting improvements and fixes --- Libraries/Fusion/Animation/unpackType.luau | 3 +-- Libraries/Fusion/Utility/isSimilar.luau | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Libraries/Fusion/Animation/unpackType.luau b/Libraries/Fusion/Animation/unpackType.luau index 770e8f8..4b025d5 100644 --- a/Libraries/Fusion/Animation/unpackType.luau +++ b/Libraries/Fusion/Animation/unpackType.luau @@ -84,9 +84,8 @@ local function unpackType(value: any, typeString: string): { number } return { value.X, value.Y, value.Z } elseif typeString == "Vector3int16" then return { value.X, value.Y, value.Z } - else - return {} end + return {} end return unpackType diff --git a/Libraries/Fusion/Utility/isSimilar.luau b/Libraries/Fusion/Utility/isSimilar.luau index ac93baf..7767369 100644 --- a/Libraries/Fusion/Utility/isSimilar.luau +++ b/Libraries/Fusion/Utility/isSimilar.luau @@ -1,6 +1,6 @@ --!strict --[[ - Returns true if A and B are 'similar' - i.e. any user of A would not need + Returns true if A and B are 'similar' - ie. any user of A would not need to recompute if it changed to B. ]] From baf73c6c18c6052c057f053e5ec7b720de4e4630 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Sun, 4 Feb 2024 03:00:11 +0000 Subject: [PATCH 6/8] Improve render scripts and formatting --- Modules/Render.luau | 75 ++++++++++++++++++++++++++++++++++++++++ compile.sh | 2 +- luau/renderAvatar.luau | 69 ++++-------------------------------- luau/renderClothing.luau | 63 +++------------------------------ 4 files changed, 88 insertions(+), 121 deletions(-) create mode 100644 Modules/Render.luau diff --git a/Modules/Render.luau b/Modules/Render.luau new file mode 100644 index 0000000..27fd5a6 --- /dev/null +++ b/Modules/Render.luau @@ -0,0 +1,75 @@ +local HttpService = game:GetService "HttpService" +local ScriptContext = game:GetService "ScriptContext" +local Players = game:GetService "Players" +local ContentProvider = game:GetService "ContentProvider" +local InsertService = game:GetService "InsertService" + +local function post(url: string, body: string) + -- We have to lie about the contentType to avoid being nuked by CORS from the website + game:HttpPost(url, body, true, "text/json") +end + +return function(baseUrl: string, thumbnailKey: string) + local Render = {} + + function Render.SetupAvatar( + renderType: string, + assetId: string, + characterAppearance: string + ) + pcall(function() + ContentProvider:SetBaseUrl(baseUrl) + InsertService:SetAssetUrl(`{baseUrl}/asset?id=%d`) + InsertService:SetAssetVersionUrl( + `{baseUrl}/asset?assetversionid=%d` + ) + end) + + HttpService.HttpEnabled = true + ScriptContext.ScriptsDisabled = true + + print( + `[{game.JobId}] Starting new render for {renderType} ID {assetId}` + ) + post( + `{baseUrl}/api/render/update?apiKey={thumbnailKey}&taskID={game.JobId}`, + "Rendering" + ) + + local player = Players:CreateLocalPlayer(0) + player.CharacterAppearance = baseUrl .. characterAppearance .. assetId + player:LoadCharacter(false) + + -- Raise up the character's arm if they have gear. + local gear = player.Backpack:GetChildren()[1] + if gear then + gear.Parent = player.Character + player.Character.Torso["Right Shoulder"].CurrentAngle = math.rad(90) + end + + return player + end + + function Render.Upload(result: string) + for i = 1, 3 do + local ok, err = pcall(function() + post( + `{baseUrl}/api/render/update?apiKey={thumbnailKey}&taskID={game.JobId}`, + result + ) + end) + if ok then + print(`[{game.JobId}] Upload successful! Moving on...`) + break + elseif i == 3 then + print(`[{game.JobId}] An error occurred! ({err}). Giving up...`) + break + end + print( + `[{game.JobId}] An error occurred! ({err}). Uploading again...` + ) + end + end + + return Render +end diff --git a/compile.sh b/compile.sh index 908b4a5..b2bc33c 100644 --- a/compile.sh +++ b/compile.sh @@ -5,7 +5,7 @@ done echo "Processing libraries..." darklua process -c dense.json5 ./corescripts/Libraries/Fusion/init.luau ./corescripts/processed/10000001.lua -darklua process -c bundle.json5 ./corescripts/Libraries/Red/init.luau ./corescripts/processed/10000002.lua +darklua process -c dense.json5 ./corescripts/Libraries/Red/init.luau ./corescripts/processed/10000002.lua echo "Processing other corescripts..." for file in ./corescripts/luau/[a-z]*.luau; do diff --git a/luau/renderAvatar.luau b/luau/renderAvatar.luau index ad1f7a9..477ef29 100644 --- a/luau/renderAvatar.luau +++ b/luau/renderAvatar.luau @@ -1,54 +1,18 @@ --- Avatar v1.1.0 --- This is the thumbnail script for R6 avatars. Straight up and down, with the right arm out if they have a gear. - -local baseUrl = _BASE_URL -local thumbnailKey = _THUMBNAIL_KEY -local renderType = _RENDER_TYPE -local assetId = _ASSET_ID +-- Render script for R6 avatars local ThumbnailGenerator = game:GetService "ThumbnailGenerator" -local ContentProvider = game:GetService "ContentProvider" -local InsertService = game:GetService "InsertService" -local HttpService = game:GetService "HttpService" -local ScriptContext = game:GetService "ScriptContext" +local Render = require "../Modules/Render.luau"(_BASE_URL, _THUMBNAIL_KEY) -pcall(function() - ContentProvider:SetBaseUrl(baseUrl) - InsertService:SetAssetUrl(`{baseUrl}/asset?id=%d`) - InsertService:SetAssetVersionUrl(`{baseUrl}/asset?assetversionid=%d`) -end) +local player = + Render.SetupAvatar(_RENDER_TYPE, _ASSET_ID, `/asset/characterfetch?userID=`) -HttpService.HttpEnabled = true -ScriptContext.ScriptsDisabled = true - -print(`[{game.JobId}] Starting new render for {renderType} ID {assetId}`) -game:HttpPost( - `{baseUrl}/api/render/update?apiKey={thumbnailKey}&taskID={game.JobId}`, - "Rendering", - true, - "text/json" -) - -local player = game:GetService("Players"):CreateLocalPlayer(0) -player.CharacterAppearance = `{baseUrl}/asset/characterfetch?userID={assetId}` -player:LoadCharacter(false) - --- Raise up the character's arm if they have gear. -local gear = player.Backpack:GetChildren()[1] -if gear then - gear.Parent = player.Character - player.Character.Torso["Right Shoulder"].CurrentAngle = math.rad(90) -end - -local clickBody = ThumbnailGenerator:Click("PNG", 2048, 2048, true) +local clickBody = ThumbnailGenerator:Click("PNG", 1680, 1680, true) print(`[{game.JobId}] Rendered bodyshot`) player.Character.Torso["Right Shoulder"].CurrentAngle = 0 -- Headshot Camera -local FOV = 52.5 - local CameraAngle = player.Character.Head.CFrame local CameraPosition = CameraAngle + CFrame.Angles(0, math.pi, 0).lookVector.unit * 2.75 @@ -57,31 +21,12 @@ local Camera = Instance.new "Camera" Camera.Name = "ThumbnailCamera" Camera.CameraType = Enum.CameraType.Scriptable Camera.CoordinateFrame = CFrame.new(CameraPosition.p, CameraAngle.p) -Camera.FieldOfView = FOV +Camera.FieldOfView = 52.5 Camera.Parent = player.Character workspace.CurrentCamera = Camera local clickHead = ThumbnailGenerator:Click("PNG", 300, 300, true) -local result = `Completed\n{clickBody}\n{clickHead}` - print(`[{game.JobId}] Rendered headshot`) -for i = 1, 3 do - local ok, err = pcall(function() - game:HttpPost( - `{baseUrl}/api/render/update?apiKey={thumbnailKey}&taskID={game.JobId}`, - result, - true, - "text/json" - ) - end) - if ok then - print(`[{game.JobId}] Upload successful! Moving on...`) - break - elseif i == 3 then - print(`[{game.JobId}] An error occurred! ({err}). Giving up...`) - break - end - print(`[{game.JobId}] An error occurred! ({err}). Uploading again...`) -end +Render.Upload(`Completed\n{clickBody}\n{clickHead}`) diff --git a/luau/renderClothing.luau b/luau/renderClothing.luau index c5e0520..5908ddb 100644 --- a/luau/renderClothing.luau +++ b/luau/renderClothing.luau @@ -1,65 +1,12 @@ --- Avatar v1.1.0 --- This is the thumbnail script for R6 avatars. Straight up and down, with the right arm out if they have a gear. - -local baseUrl = _BASE_URL -local thumbnailKey = _THUMBNAIL_KEY -local renderType = _RENDER_TYPE -local assetId = _ASSET_ID +-- Render script for R6 clothing local ThumbnailGenerator = game:GetService "ThumbnailGenerator" -local ContentProvider = game:GetService "ContentProvider" -local InsertService = game:GetService "InsertService" -local HttpService = game:GetService "HttpService" -local ScriptContext = game:GetService "ScriptContext" +local Render = require "../Modules/Render.luau"(_BASE_URL, _THUMBNAIL_KEY) -pcall(function() - ContentProvider:SetBaseUrl(baseUrl) - InsertService:SetAssetUrl(`{baseUrl}/asset?id=%d`) - InsertService:SetAssetVersionUrl(`{baseUrl}/asset?assetversionid=%d`) -end) +Render.SetupAvatar(_RENDER_TYPE, _ASSET_ID, `/api/render/characterasset?id=`) -HttpService.HttpEnabled = true -ScriptContext.ScriptsDisabled = true +local click = ThumbnailGenerator:Click("PNG", 1680, 1680, true) -print(`[{game.JobId}] Starting new render for {renderType} ID {assetId}`) -game:HttpPost( - `{baseUrl}/api/render/update?apiKey={thumbnailKey}&taskID={game.JobId}`, - "Rendering", - true, - "text/json" -) - -local player = game:GetService("Players"):CreateLocalPlayer(0) -player.CharacterAppearance = `{baseUrl}/api/render/characterasset?id={assetId}` -player:LoadCharacter(false) - --- Raise up the character's arm if they have gear. -local gear = player.Backpack:GetChildren()[1] -if gear then - gear.Parent = player.Character - player.Character.Torso["Right Shoulder"].CurrentAngle = math.rad(90) -end - -local click = ThumbnailGenerator:Click("PNG", 2048, 2048, true) - -local result = "Completed\n" .. tostring(click) print(`[{game.JobId}] Successfully rendered, moving on...`) -for i = 1, 3 do - local ok, err = pcall(function() - game:HttpPost( - `{baseUrl}/api/render/update?apiKey={thumbnailKey}&taskID={game.JobId}`, - result, - true, - "text/json" - ) - end) - if ok then - print(`[{game.JobId}] Upload successful! Moving on...`) - break - elseif i == 3 then - print(`[{game.JobId}] An error occurred! ({err}). Giving up...`) - break - end - print(`[{game.JobId}] An error occurred! ({err}). Uploading again...`) -end +Render.Upload(`Completed\n{click}`) From af42c0b18ccc0830dce0b03574fa8dc467325315 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 6 Feb 2024 04:10:06 +0000 Subject: [PATCH 7/8] Further improve revamped about page and other formatting improvements --- luau/48488235.luau | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/luau/48488235.luau b/luau/48488235.luau index 02f48f9..e52f53f 100644 --- a/luau/48488235.luau +++ b/luau/48488235.luau @@ -1645,7 +1645,7 @@ end tframes the team entries to unroll outframes the list to unroll these entries into --]] -local function UnrollTeams(tframes: table, outframes: table) +local function UnrollTeams(tframes: { any }, outframes: { any }) local numEntries = 0 if NeutralTeam and not NeutralTeam.IsHidden then for _, val in ipairs(NeutralTeam.MyPlayers) do @@ -2615,23 +2615,23 @@ local function AddMiddleBGFrame() local nBGFrame = MiddleBGTemplate:Clone() nBGFrame.Position = UDim2.new(0.5, 0, (#MiddleFrameBackgrounds * nBGFrame.Size.Y.Scale), 0) + + local function applyImage(id: string) + nBGFrame.Background.Image = `http://banland.xyz/asset?id={id}` + end + if (#MiddleFrameBackgrounds + 1) % 2 ~= 1 then if IsMaximized.Value then - nBGFrame.Background.Image = "http://banland.xyz/asset?id=" - .. Images.LargeDark + applyImage(Images.LargeDark) else - nBGFrame.Background.Image = "http://banland.xyz/asset?id=" - .. Images.midDark + applyImage(Images.midDark) end + elseif IsMaximized.Value then + applyImage(Images.LargeLight) else - if IsMaximized.Value then - nBGFrame.Background.Image = "http://banland.xyz/asset?id=" - .. Images.LargeLight - else - nBGFrame.Background.Image = "http://banland.xyz/asset?id=" - .. Images.midLight - end + applyImage(Images.midLight) end + nBGFrame.Parent = ListFrame table.insert(MiddleFrameBackgrounds, nBGFrame) From 3f4932750d84d80767dd6057b3e63cc2abeb4ce1 Mon Sep 17 00:00:00 2001 From: Lewin Kelly Date: Tue, 13 Feb 2024 00:56:11 +0000 Subject: [PATCH 8/8] Formatting improvements and other fixes --- luau/157877000.luau | 7 +- luau/38037565.luau | 5 +- luau/53878057.luau | 87 ++++----- luau/60595411.luau | 463 +------------------------------------------- luau/73157242.luau | 50 +++-- luau/89449093.luau | 10 +- 6 files changed, 89 insertions(+), 533 deletions(-) diff --git a/luau/157877000.luau b/luau/157877000.luau index 1cbf0fd..9f2026c 100644 --- a/luau/157877000.luau +++ b/luau/157877000.luau @@ -1,9 +1,6 @@ -- Unused by Mercury print "[Mercury]: Loaded corescript 157877000" -for _ = 1, 4 do - pcall(function() - warn "IF YOU SEE THIS MESSAGE, PLEASE REPORT IT TO THE MERCURY DEVELOPERS" - end) +for _ = 1, 8 do print "IF YOU SEE THIS MESSAGE, PLEASE REPORT IT TO THE MERCURY DEVELOPERS" end @@ -13,7 +10,7 @@ local Create: (instance: string) -> ({ [string]: any }) -> Instance = -- A Few Script Globals local gui -if script.Parent:FindFirstChild "ControlFrame" then +if script.Parent and script.Parent:FindFirstChild "ControlFrame" then gui = script.Parent:FindFirstChild "ControlFrame" else gui = script.Parent diff --git a/luau/38037565.luau b/luau/38037565.luau index 4c78e97..81daaf3 100644 --- a/luau/38037565.luau +++ b/luau/38037565.luau @@ -1,9 +1,6 @@ -- Unused by Mercury print "[Mercury]: Loaded corescript 38037565" -for _ = 1, 4 do - pcall(function() - warn "IF YOU SEE THIS MESSAGE, PLEASE REPORT IT TO THE MERCURY DEVELOPERS" - end) +for _ = 1, 8 do print "IF YOU SEE THIS MESSAGE, PLEASE REPORT IT TO THE MERCURY DEVELOPERS" end diff --git a/luau/53878057.luau b/luau/53878057.luau index b5ff238..a53fec5 100644 --- a/luau/53878057.luau +++ b/luau/53878057.luau @@ -1,6 +1,10 @@ -- CoreGui.RobloxGui.CurrentLoadout.CoreScripts/BackpackScript print "[Mercury]: Loaded corescript 53878057" +local CoreGui = game:GetService "CoreGui" +local GuiService = game:GetService "GuiService" +local UserInputService = Game:GetService "UserInputService" + -- A couple of necessary functions local function waitForChild(instance, name) while not instance:FindFirstChild(name) do @@ -18,9 +22,8 @@ local currentLoadout = script.Parent local StaticTabName = "gear" local backpackEnabled = true -local robloxGui = game:GetService("CoreGui"):FindFirstChild "RobloxGui" +local robloxGui = CoreGui:FindFirstChild "RobloxGui" assert(robloxGui) -local GuiService = game:GetService "GuiService" local controlFrame = waitForChild(robloxGui, "ControlFrame") local backpackButton = waitForChild(controlFrame, "BackpackButton") @@ -35,7 +38,7 @@ local clBackground = currentLoadout.Background local function IsTouchDevice() local touchEnabled = false pcall(function() - touchEnabled = Game:GetService("UserInputService").TouchEnabled + touchEnabled = UserInputService.TouchEnabled end) return touchEnabled end @@ -124,6 +127,8 @@ local function backpackIsOpen() return false end +local reorganizeLoadout + local function kill(prop, con, gear) if con then con:disconnect() @@ -282,7 +287,7 @@ local function insertGear(gear, addToSlot) end) end -local function reorganizeLoadout(gear, inserting, _, addToSlot) +reorganizeLoadout = function(gear, inserting, _, addToSlot) if inserting then -- add in gear insertGear(gear, addToSlot) else @@ -294,33 +299,36 @@ local function reorganizeLoadout(gear, inserting, _, addToSlot) end local function checkToolAncestry(child, parent) - if child:FindFirstChild "RobloxBuildTool" then + if + child:FindFirstChild "RobloxBuildTool" -- don't show roblox build tools + or not (child:IsA "Tool" or child:IsA "HopperBin") + then return - end -- don't show roblox build tools - if child:IsA "Tool" or child:IsA "HopperBin" then - for i = 1, #gearSlots do - if - gearSlots[i] ~= "empty" - and gearSlots[i].GearReference.Value == child - then - if parent == nil then - gearSlots[i].Kill.Value = true - return false - elseif child.Parent == player.Character then - gearSlots[i].Selected = true - return true - elseif child.Parent == player.Backpack then - if child:IsA "Tool" or child:IsA "HopperBin" then - gearSlots[i].Selected = false - end - return true - end + end + for i = 1, #gearSlots do + if + gearSlots[i] ~= "empty" + and gearSlots[i].GearReference.Value == child + then + if parent == nil then gearSlots[i].Kill.Value = true return false + elseif child.Parent == player.Character then + gearSlots[i].Selected = true + return true + elseif child.Parent == player.Backpack then + if child:IsA "Tool" or child:IsA "HopperBin" then + gearSlots[i].Selected = false + end + return true end + + gearSlots[i].Kill.Value = true + return false end end + return end local function removeAllEquippedGear(physGear) @@ -384,21 +392,14 @@ local function normaliseButton(button, speed) end local function enlargeButton(button) - if button.Size.Y.Scale > 1 then - return - end - if not button.Parent then - return - end - if not button.Selected then + if button.Size.Y.Scale > 1 or not button.Parent or not button.Selected then return end for i = 1, #gearSlots do if gearSlots[i] == "empty" then break - end - if gearSlots[i] ~= button then + elseif gearSlots[i] ~= button then normaliseButton(gearSlots[i]) end end @@ -720,11 +721,9 @@ local function addingPlayerChild( debounce = false return end -- don't show roblox build tools - if not child:IsA "Tool" then - if not child:IsA "HopperBin" then - debounce = false - return -- we don't care about anything besides tools (sigh...) - end + if not (child:IsA "Tool" or child:IsA "HopperBin") then + debounce = false + return -- we don't care about anything besides tools (sigh...) end if not addToSlot then @@ -812,10 +811,8 @@ local function addingPlayerChild( if gearClone.Parent == nil then debounce = false - return - end -- couldn't fit in (hopper is full!) - - if equipped then + return -- couldn't fit in (hopper is full!) + elseif equipped then gearClone.Selected = true unequipAllItems(child) delay( @@ -843,10 +840,8 @@ local function addingPlayerChild( local clickCon, buttonDeleteCon, mouseEnterCon, mouseLeaveCon, dragStop, dragBegin clickCon = gearClone.MouseButton1Click:connect(function() - if characterInWorkspace() then - if not gearClone.Draggable then - activateGear(gearClone.SlotNumber.Text) - end + if characterInWorkspace() and not gearClone.Draggable then + activateGear(gearClone.SlotNumber.Text) end end) mouseEnterCon = gearClone.MouseEnter:connect(function() diff --git a/luau/60595411.luau b/luau/60595411.luau index 53b8e2b..a3e662f 100644 --- a/luau/60595411.luau +++ b/luau/60595411.luau @@ -1,467 +1,18 @@ -- RbxUtility print "[Mercury]: Loaded corescript 60595411" ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------JSON Functions Begin---------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------- - ---JSON Encoder and Parser for Lua 5.1 --- ---2007 Shaun Brown (http://www.chipmunkav.com) ---All Rights Reserved. - ---Permission is hereby granted, free of charge, to any person ---obtaining a copy of this software to deal in the Software without ---restriction, including without limitation the rights to use, ---copy, modify, merge, publish, distribute, sublicense, and/or ---sell copies of the Software, and to permit persons to whom the ---Software is furnished to do so, subject to the following conditions: - ---The above copyright notice and this permission notice shall be ---included in all copies or substantial portions of the Software. ---If you find this software useful please give www.chipmunkav.com a mention. - ---THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ---EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES ---OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. ---IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ---ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF ---CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN ---CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -local assert = assert - -local StringBuilder = { - buffer = {}, -} - -function StringBuilder:New() - local o = {} - setmetatable(o, self) - self.__index = self - o.buffer = {} - return o -end - -function StringBuilder:Append(s) - self.buffer[#self.buffer + 1] = s -end - -function StringBuilder:ToString() - return table.concat(self.buffer) -end - -local JsonWriter = { - backslashes = { - ["\b"] = "\\b", - ["\t"] = "\\t", - ["\n"] = "\\n", - ["\f"] = "\\f", - ["\r"] = "\\r", - ['"'] = '\\"', - ["\\"] = "\\\\", - ["/"] = "\\/", - }, -} - -function JsonWriter:New() - local o = {} - o.writer = StringBuilder:New() - setmetatable(o, self) - self.__index = self - return o -end - -function JsonWriter:Append(s) - self.writer:Append(s) -end - -function JsonWriter:ToString() - return self.writer:ToString() -end - -function JsonWriter:Write(o) - local t = type(o) - if t == "nil" then - self:WriteNil() - elseif t == "boolean" or t == "number" then - self:WriteString(o) - elseif t == "string" then - self:ParseString(o) - elseif t == "table" then - self:WriteTable(o) - elseif t == "function" then - self:WriteFunction(o) - elseif t == "thread" or t == "userdata" then - self:WriteError(o) - end -end - -function JsonWriter:WriteNil() - self:Append "null" -end - -function JsonWriter:WriteString(o) - self:Append(tostring(o)) -end - -function JsonWriter:ParseString(s) - self:Append '"' - local copy = string.gsub(s, '[%z%c\\"/]', function(n) - return self.backslashes[n] or string.format("\\u%.4X", string.byte(n)) - end) - self:Append(copy) - self:Append '"' -end - -function JsonWriter:IsArray(t) - local count = 0 - local function isindex(k) - return type(k) == "number" and k > 0 and math.floor(k) == k - end - for k, _ in pairs(t) do - if not isindex(k) then - return false, "{", "}" - end - count = math.max(count, k) - end - return true, "[", "]", count -end - -function JsonWriter:WriteTable(t) - local ba, st, et, n = self:IsArray(t) - self:Append(st) - if ba then - for i = 1, n do - self:Write(t[i]) - if i < n then - self:Append "," - end - end - else - local first = true - for k, v in pairs(t) do - if not first then - self:Append "," - end - first = false - self:ParseString(k) - self:Append ":" - self:Write(v) - end - end - self:Append(et) -end - -function JsonWriter:WriteError(o) - error(string.format("Encoding of %s unsupported", tostring(o))) -end - -function JsonWriter:WriteFunction(o) - if o == Null then - self:WriteNil() - else - self:WriteError(o) - end -end - -local StringReader = { - s = "", - i = 0, -} - -function StringReader:New(s) - local o = {} - setmetatable(o, self) - self.__index = self - o.s = s or o.s - return o -end - -function StringReader:Peek() - local i = self.i + 1 - if i <= #self.s then - return string.sub(self.s, i, i) - end - return nil -end - -function StringReader:Next() - self.i += 1 - if self.i <= #self.s then - return string.sub(self.s, self.i, self.i) - end - return nil -end - -function StringReader:All() - return self.s -end - -local JsonReader = { - escapes = { - t = "\t", - n = "\n", - f = "\f", - r = "\r", - b = "\b", - }, -} - -function JsonReader:New(s) - local o = {} - o.reader = StringReader:New(s) - setmetatable(o, self) - self.__index = self - return o -end - -function JsonReader:Read() - self:SkipWhiteSpace() - local peek = self:Peek() - if peek == nil then - error(string.format("Nil string: '%s'", self:All())) - elseif peek == "{" then - return self:ReadObject() - elseif peek == "[" then - return self:ReadArray() - elseif peek == '"' then - return self:ReadString() - elseif string.find(peek, "[%+%-%d]") then - return self:ReadNumber() - elseif peek == "t" then - return self:ReadTrue() - elseif peek == "f" then - return self:ReadFalse() - elseif peek == "n" then - return self:ReadNull() - elseif peek == "/" then - self:ReadComment() - return self:Read() - end - return nil -end - -function JsonReader:ReadTrue() - self:TestReservedWord { "t", "r", "u", "e" } - return true -end - -function JsonReader:ReadFalse() - self:TestReservedWord { "f", "a", "l", "s", "e" } - return false -end - -function JsonReader:ReadNull() - self:TestReservedWord { "n", "u", "l", "l" } - return nil -end - -function JsonReader:TestReservedWord(t) - for _, v in ipairs(t) do - if self:Next() ~= v then - error( - string.format( - "Error reading '%s': %s", - table.concat(t), - self:All() - ) - ) - end - end -end - -function JsonReader:ReadNumber() - local result = self:Next() - local peek = self:Peek() - while peek ~= nil and string.find(peek, "[%+%-%d%.eE]") do - result ..= self:Next() - peek = self:Peek() - end - result = tonumber(result) - if result == nil then - error(`"Invalid number: '{result}'`) - else - return result - end -end - -function JsonReader:ReadString() - local result = "" - assert(self:Next() == '"') - while self:Peek() ~= '"' do - local ch = self:Next() - if ch == "\\" then - ch = self:Next() - if self.escapes[ch] then - ch = self.escapes[ch] - end - end - result ..= ch - end - assert(self:Next() == '"') - local fromunicode = function(m) - return string.char(tonumber(m, 16)) - end - return string.gsub(result, "u%x%x(%x%x)", fromunicode) -end - -function JsonReader:ReadComment() - assert(self:Next() == "/") - local second = self:Next() - if second == "/" then - self:ReadSingleLineComment() - elseif second == "*" then - self:ReadBlockComment() - else - error(`Invalid comment: {self:All()}`) - end -end - -function JsonReader:ReadBlockComment() - local done = false - while not done do - local ch = self:Next() - if ch == "*" and self:Peek() == "/" then - done = true - end - if not done and ch == "/" and self:Peek() == "*" then - error(`Invalid comment: {self:All()}, '/*' illegal.`) - end - end - self:Next() -end - -function JsonReader:ReadSingleLineComment() - local ch = self:Next() - while ch ~= "\r" and ch ~= "\n" do - ch = self:Next() - end -end - -function JsonReader:ReadArray() - local result = {} - assert(self:Next() == "[") - local done = false - if self:Peek() == "]" then - done = true - end - while not done do - local item = self:Read() - result[#result + 1] = item - self:SkipWhiteSpace() - if self:Peek() == "]" then - done = true - end - if not done then - local ch = self:Next() - if ch ~= "," then - error(`Invalid array: '{self:All()}' due to: '{ch}'`) - end - end - end - assert("]" == self:Next()) - return result -end - -function JsonReader:ReadObject() - local result = {} - assert(self:Next() == "{") - local done = false - if self:Peek() == "}" then - done = true - end - while not done do - local key = self:Read() - if type(key) ~= "string" then - error(`Invalid non-string object key: {key}`) - end - self:SkipWhiteSpace() - local ch = self:Next() - if ch ~= ":" then - error(`Invalid object: '{self:All()}' due to: '{ch}'`) - end - self:SkipWhiteSpace() - local val = self:Read() - result[key] = val - self:SkipWhiteSpace() - if self:Peek() == "}" then - done = true - end - if not done then - ch = self:Next() - if ch ~= "," then - error(`Invalid array: '{self:All()}' near: '{ch}'`) - end - end - end - assert(self:Next() == "}") - return result -end - -function JsonReader:SkipWhiteSpace() - local p = self:Peek() - while p ~= nil and string.find(p, "[%s/]") do - if p == "/" then - self:ReadComment() - else - self:Next() - end - p = self:Peek() - end -end - -function JsonReader:Peek() - return self.reader:Peek() -end - -function JsonReader:Next() - return self.reader:Next() -end - -function JsonReader:All() - return self.reader:All() -end - -function Encode(o) - local writer = JsonWriter:New() - writer:Write(o) - return writer:ToString() -end - -function Decode(s) - local reader = JsonReader:New(s) - return reader:Read() -end - -function Null() - return Null -end --------------------- End JSON Parser ------------------------ +local Terrain = game:GetService "Terrain" local RbxUtility = {} -RbxUtility.DecodeJSON = function(jsonString: string) - pcall(function() - warn "RbxUtility.DecodeJSON is deprecated, please use Game:GetService('HttpService'):JSONDecode() instead." - end) +-- Fuq json - if type(jsonString) == "string" then - return Decode(jsonString) - end - print "RbxUtil.DecodeJSON expects string argument!" - return nil +RbxUtility.DecodeJSON = function() + error 'RbxUtility.DecodeJSON has been removed, please use Game:GetService("HttpService"):JSONDecode() instead.' end -RbxUtility.EncodeJSON = function(jsonTable: { [any]: any }) - pcall(function() - warn "RbxUtility.EncodeJSON is deprecated, please use Game:GetService('HttpService'):JSONEncode() instead." - end) - return Encode(jsonTable) +RbxUtility.EncodeJSON = function() + error 'RbxUtility.EncodeJSON has been removed, please use Game:GetService("HttpService"):JSONEncode() instead.' end ------------------------------------------------------------------------------------------------------------------------ @@ -475,7 +26,7 @@ end --sets cell x, y, z to default material if parameter is provided, if not sets cell x, y, z to be whatever material it previously w --returns true if made a wedge, false if the cell remains a block RbxUtility.MakeWedge = function(x, y, z, _) - return game:GetService("Terrain"):AutoWedgeCell(x, y, z) + return Terrain:AutoWedgeCell(x, y, z) end RbxUtility.SelectTerrainRegion = function( diff --git a/luau/73157242.luau b/luau/73157242.luau index aecfb50..fa449da 100644 --- a/luau/73157242.luau +++ b/luau/73157242.luau @@ -486,14 +486,14 @@ local function findConfigAtMouseTarget(Mouse: Mouse, stampData: Instance) -- This can happen sometimes, return if so if not Mouse then - return nil + return end if not stampData then error "findConfigAtMouseTarget: stampData is nil" - return nil + return end if not stampData.CurrentParts then - return nil + return end local grid = 4.0 @@ -587,15 +587,27 @@ local function findConfigAtMouseTarget(Mouse: Mouse, stampData: Instance) targetVectorInWorld = targetPart.CFrame:vectorToWorldSpace( Vector3.FromNormalId(Mouse.TargetSurface) ) -- better, but model cframe would be best - --[[if targetPart.Parent:IsA("Model") then - local hitFace = modelTargetSurface(targetPart.Parent, Mouse.Hit.p, game.Workspace.CurrentCamera.CoordinateFrame.p) -- best, if you get it right - local WORLD_AXES = {Vector3.new(1, 0, 0), Vector3.new(0, 1, 0), Vector3.new(0, 0, 1)} - if hitFace > 0 then - targetVectorInWorld = targetCFrame:vectorToWorldSpace(WORLD_AXES[hitFace]) - elseif hitFace < 0 then - targetVectorInWorld = targetCFrame:vectorToWorldSpace(-WORLD_AXES[-hitFace]) + --[[ + if targetPart.Parent:IsA "Model" then + local hitFace = modelTargetSurface( + targetPart.Parent, + Mouse.Hit.p, + game.Workspace.CurrentCamera.CoordinateFrame.p + ) -- best, if you get it right + local WORLD_AXES = { + Vector3.new(1, 0, 0), + Vector3.new(0, 1, 0), + Vector3.new(0, 0, 1), + } + if hitFace > 0 then + targetVectorInWorld = + targetCFrame:vectorToWorldSpace(WORLD_AXES[hitFace]) + elseif hitFace < 0 then + targetVectorInWorld = + targetCFrame:vectorToWorldSpace(-WORLD_AXES[-hitFace]) + end end - end]] + ]] end local targetRefPointInTarget, insertRefPointInInsert @@ -703,10 +715,9 @@ local function truncateToCircleEighth(bigValue, littleValue) -- between 22.5 and 45 degrees, so truncate to 45-degree tilt return 0.707106781 * hypotenuse * bigSign, 0.707106781 * hypotenuse * littleSign - else - -- between 0 and 22.5 degrees, so truncate to 0-degree tilt - return hypotenuse * bigSign, 0 end + -- between 0 and 22.5 degrees, so truncate to 0-degree tilt + return hypotenuse * bigSign, 0 end local function saveTheWelds(object, manualWeldTable, manualWeldParentTable) @@ -740,9 +751,7 @@ RbxStamper.CanEditRegion = function(partOrModel, EditRegion) -- todo: use model or minBB.Z < EditRegion.CFrame.p.Z - EditRegion.Size.Z / 2 then return false, false - end - - if + elseif maxBB.X > EditRegion.CFrame.p.X + EditRegion.Size.X / 2 or maxBB.Y > EditRegion.CFrame.p.Y + EditRegion.Size.Y / 2 or maxBB.Z > EditRegion.CFrame.p.Z + EditRegion.Size.Z / 2 @@ -1017,6 +1026,13 @@ RbxStamper.SetupStamperDragger = function( local gInitial90DegreeRotations = 0 local stampData: { DisabledScripts: { LuaSourceContainer }?, + TransparencyTable: {}?, + MaterialTable: {}?, + CanCollideTable: {}?, + AnchoredTable: {}?, + ArchivableTable: {}?, + DecalTransparencyTable: {}?, + SurfaceTypeTable: {}?, } local mouseTarget diff --git a/luau/89449093.luau b/luau/89449093.luau index 5d2187b..1837d18 100644 --- a/luau/89449093.luau +++ b/luau/89449093.luau @@ -67,7 +67,7 @@ local guiTweenSpeed = 0.25 -- how quickly we open/close the backpack local searchDefaultText = "Search..." local tilde = "~" -local backquote = "`" +local backtick = "`" local backpackSize = UDim2.new(0, 600, 0, 400) @@ -391,11 +391,11 @@ function coreGuiChanged(coreGuiType, enabled) if disabledByDeveloper then pcall(function() GuiService:RemoveKey(tilde) - GuiService:RemoveKey(backquote) + GuiService:RemoveKey(backtick) end) else GuiService:AddKey(tilde) - GuiService:AddKey(backquote) + GuiService:AddKey(backtick) end resetSearch() @@ -444,12 +444,12 @@ end) -- GuiService key setup GuiService:AddKey(tilde) -GuiService:AddKey(backquote) +GuiService:AddKey(backtick) GuiService.KeyPressed:connect(function(key) if not active or disabledByDeveloper then return end - if key == tilde or key == backquote then + if key == tilde or key == backtick then toggleBackpack() end end)