Free types leaked into this module's public interface

Studio told me to report this.

script:


local module = {}

local runService = game:GetService('RunService')
local Signal = require(game.ReplicatedStorage.Signal)

export type ValueFunction<T> = (T, T, number, {[any]: any}?)->T
export type StyleFunction = (number, {[any]: any}?)->number
export type DirectionFunction = {
	a: (number, {[any]: any}?)->number,
	b: (number, number, {[any]: any}?)->number
}

export type TweenResult<K, V> = {
	tween: Tween<K, V>,
	value: V,
	didFinish: boolean,
	elapsedTime: number,
	elapsedAlpha: number,
	transformedAlpha: number,
}

export type Tween<K, V> = {
	lastStep: number,
	
	Connect: ( Tween<K, V>, (TweenResult<K, V>)->() )->Signal.Connection<TweenResult<K, V>>,
	Wait: (Tween<K, V>)->TweenResult<K, V>,
	Step: (Tween<K, V>, number)->(),
	Stop: (Tween<K, V>, boolean)->TweenResult<K, V>,
	GetRawAlpha: (Tween<K, V>)->(number),
	GetTransformedAlpha: (Tween<K, V>)->(number),
	GetCurrentValue: (Tween<K, V>)->(V, number),
	SetSteppingSignal: (Tween<K, V>, string | RBXScriptSignal)->()
}

export type TweenParams<K, V> = {
	object: any?,
	key: K?,
	set_f: ((K, V, number)->())?,
	start: V?,
	start_f: ((K)->V)?,
	finish: V?,
	finish_f: ((K, V)->V)?,
	length: number?,
	length_f: (()->number)?,
	style: string?,
	style_f: StyleFunction?,
	style_args: {[any]: any}?,
	direction: string?,
	direction_f: DirectionFunction?,
	direction_args: {[any]: any}?,
	as: string?,
	value_f: ValueFunction<V>?,
	value_args: {[any]: any}?,
}




local styleTransforms: {[string]: StyleFunction} = {
	Linear = function(a: number) : number
		return a
	end,
	Sine = function(a: number) : number
		return math.sin(a * math.pi/2)
	end,
}

local directionTransforms: {[string]: DirectionFunction} = {
	In = {
		a = function(raw: number) : number
			return raw
		end,
		b = function(raw: number, styled: number) : number
			return styled
		end
	},
	Out = {
		a = function(raw: number) : number
			return 1-raw
		end,
		b = function(raw: number, styled: number) : number
			return styled
		end
	},
	InOut = {
		a = function(raw: number) : number
			if raw > 0.5 then
				return 1-(raw-0.5)*2
			else
				return raw*2
			end
		end,
		b = function(raw: number, styled: number) : number
			if raw > 0.5 then
				return 0.5 + styled/2
			else
				return styled/2
			end
		end
	},
	OutIn = {
		a = function(raw: number) : number
			if raw < 0.5 then
				return 1-raw*2
			else
				return (raw-0.5)*2
			end
		end,
		b = function(raw: number, styled: number) : number
			if raw < 0.5 then
				return styled/2
			else
				return 0.5 + styled/2
			end
		end
	}
}

local function makeFlipFunction(value)
	type T = typeof(value)
	local flipFunction: ValueFunction<T> = function(a: T, b: T, alpha: number) : T
		if alpha > 0.5 then
			return b
		end
		return a
	end
	return flipFunction
end
local function makeMathFunction(value)
	type T = typeof(value)
	local mathFunction: ValueFunction<T> = function(a: T, b: T, alpha: number) : T
		return (b::number - a::number)*alpha + a
	end
	return mathFunction
end

local valueFunctions: { [string] : ValueFunction<any> }
valueFunctions = {
	number = makeMathFunction(1),
	boolean = makeFlipFunction(true),
	string = makeFlipFunction('a'),
	['nil'] = makeFlipFunction(nil),
	
	table = makeFlipFunction({}),

	Axes = function(a: Axes, b: Axes, alpha: number) : Axes
		local t: {Enum.Axis} = {}
		if valueFunctions.boolean(a.X, b.X, alpha) then
			table.insert(t, Enum.Axis.X)
		end
		if valueFunctions.boolean(a.Y, b.Y, alpha) then
			table.insert(t, Enum.Axis.Y)
		end
		if valueFunctions.boolean(a.Z, b.Z, alpha) then
			table.insert(t, Enum.Axis.Z)
		end
		return Axes.new(unpack(t))
	end,
	BrickColor = function(a: BrickColor, b: BrickColor, alpha: number) : BrickColor
		return BrickColor.new( valueFunctions.Color3(a.Color, b.Color, alpha) )
	end,
	CFrame = function(a: CFrame, b: CFrame, alpha: number) : CFrame
		return a:Lerp(b, alpha)
	end,
	CFrameRaw = function(a: CFrame, b: CFrame, alpha: number) : CFrame
		-- CFrame.Lerp performs a quarternion interpolation which is not always ideal, especially for camera animations
		local ax, ay, az = a:ToOrientation()
		local bx, by, bz = b:ToOrientation()
		return CFrame.new(a.Position:Lerp(b.Position, alpha)) * CFrame.fromOrientation(
			(bx-ax)*alpha + ax,
			(by-ay)*alpha + ay,
			(bz-az)*alpha + az
		)
	end,
	CFrameSlerp = function(a: CFrame, b: CFrame, alpha: number, args: any) : CFrame
		return CFrame.new(valueFunctions.Vector3Slerp(a, b, alpha, args), args.focus)
	end,
	CatalogSearchParams = function(a: CatalogSearchParams, b: CatalogSearchParams, alpha: number) : CatalogSearchParams
		local new = CatalogSearchParams.new()
		new.SearchKeyword = valueFunctions.string(a.SearchKeyword, b.SearchKeyword, alpha)
		new.MinPrice = valueFunctions.number(a.MinPrice, b.MinPrice, alpha)
		new.MaxPrice = valueFunctions.number(a.MaxPrice, b.MaxPrice, alpha)
		new.SortType = valueFunctions.EnumItem(a.SortType, b.SortType, alpha)
		new.CategoryFilter = valueFunctions.EnumItem(a.CategoryFilter, b.CategoryFilter, alpha)
		new.BundleTypes = valueFunctions.table(a.BundleTypes, b.BundleTypes, alpha)
		new.AssetTypes = valueFunctions.table(a.AssetTypes, b.AssetTypes, alpha)
		
		return new
	end,
	Color3 = function(a: Color3, b: Color3, alpha: number) : Color3
		return a:Lerp(b, alpha)
	end,
	Color3HSV = function(a: Color3, b: Color3, alpha: number) : Color3
		local ha, sa, va = Color3.toHSV(a)
		local hb, sb, vb = Color3.toHSV(b)

		if hb - 0.5 > ha then
			ha = ha + 1
		elseif hb + 0.5 < ha then
			ha = ha - 1
		end

		return Color3.fromHSV(
			((hb - ha)*alpha + ha)%1.000001,
			(sb - sa)*alpha + sa,
			(vb - va)*alpha + va
		)
	end,
	ColorSequence = function(a: ColorSequence, b: ColorSequence, alpha: number) : ColorSequence
		local times: {number} = {}
		for i, sequence: ColorSequence in next, {a, b} do
			for i, keypoint: ColorSequenceKeypoint in next, sequence.Keypoints do
				if not table.find(times, keypoint.Time) then
					table.insert(times, keypoint.Time)
				end
			end
		end

		local keys: {[ColorSequence]: {[number]: Color3}} = {}
		for i, sequence: ColorSequence in next, {a, b} do
			keys[sequence] = {}
			for i, time: number in next, times do
				local early: ColorSequenceKeypoint = nil
				local late: ColorSequenceKeypoint = nil
				for i, keypoint: ColorSequenceKeypoint in next, sequence.Keypoints do
					if not early or keypoint.Time < time then
						early = keypoint
					elseif keypoint.Time == time then
						early = keypoint
						break
					else
						late = keypoint
						break
					end
				end
				if early == late or not late then
					keys[sequence][time] = early.Value
				else
					keys[sequence][time] = early.Value:Lerp(late.Value, (time - early.Time) / (late.Time - early.Time))
				end
			end
		end
		
		local keypoints: {ColorSequenceKeypoint} = {}
		for i, time: number in next, times do
			table.insert(keypoints, ColorSequenceKeypoint.new(time, keys[a][time]:Lerp(keys[b][time], alpha)))
		end
		
		return ColorSequence.new(keypoints)
	end,
	ColorSequenceKeypoint = function(a: ColorSequenceKeypoint, b: ColorSequenceKeypoint, alpha: number) : ColorSequenceKeypoint
		return ColorSequenceKeypoint.new(
			valueFunctions.number(a.Time, b.Time, alpha),
			valueFunctions.Color3(a.Value, b.Value, alpha)
		)
	end,
	DateTime = function(a: DateTime, b: DateTime, alpha: number) : DateTime
		return DateTime.fromUnixTimestamp(
			valueFunctions.number(a.UnixTimestamp, b.UnixTimestamp, alpha)
		)
	end,
	DockWidgetPluginGuiInfo = function(a: DockWidgetPluginGuiInfo, b: DockWidgetPluginGuiInfo, alpha: number) : DockWidgetPluginGuiInfo
		return DockWidgetPluginGuiInfo.new(
			valueFunctions.EnumItem((a::any).InitialDockState, (b::any).InitialDockState, alpha),
			valueFunctions.boolean(a.InitialEnabled, b.InitialEnabled, alpha),
			valueFunctions.boolean(a.InitialEnabledShouldOverrideRestore, b.InitialEnabledShouldOverrideRestore, alpha),
			valueFunctions.number(a.FloatingXSize, b.FloatingXSize, alpha),
			valueFunctions.number(a.FloatingYSize, b.FloatingYSize, alpha),
			valueFunctions.number(a.MinWidth, b.MinWidth, alpha),
			valueFunctions.number(a.MinHeight, b.MinHeight, alpha)
		)
	end,
	EnumItem = function(a: EnumItem, b: EnumItem, alpha: number) : EnumItem
		local possible: {EnumItem} = (b.EnumType::any):GetEnumItems()
		table.sort(possible, function(a: EnumItem, b: EnumItem)
			return a.Value < b.Value
		end)
		
		local current = 1
		local current_at = table.find(possible, a)
		if current_at then
			current = current_at
		end
		local target = #possible
		local target_at = table.find(possible, a)
		if target_at then
			target = target_at
		end
		
		return possible[math.floor((target - current) * alpha + current)]
	end,
	Enum = makeFlipFunction(Enum.NormalId),
	Enums = makeFlipFunction(Enum),
	Faces = function(a: Faces, b: Faces, alpha: number) : Faces
		local t: {Enum.NormalId} = {}
		for i, normalId in next, Enum.NormalId:GetEnumItems() do
			local normalId = normalId::Enum.NormalId
			if valueFunctions.boolean((a::any)[normalId.Name], (b::any)[normalId.Name], alpha) then
				table.insert(t, normalId)
			end
		end
		return Faces.new(unpack(t))
	end,
	Instance = makeFlipFunction(workspace),
	NumberRange = function(a: NumberRange, b: NumberRange, alpha: number) : NumberRange
		return NumberRange.new(
			valueFunctions.number(a.Min, b.Min, alpha),
			valueFunctions.number(a.Max, b.Max, alpha)
		)
	end,
	NumberSequence = function(a: NumberSequence, b: NumberSequence, alpha: number) : NumberSequence
		local times: {number} = {}
		for i, sequence: NumberSequence in next, {a, b} do
			for i, keypoint: NumberSequenceKeypoint in next, sequence.Keypoints do
				if not table.find(times, keypoint.Time) then
					table.insert(times, keypoint.Time)
				end
			end
		end

		local keys: {
			[NumberSequence]: {
				[number]: {
					value: number,
					envelope: number
				}
			}
		} = {}
		
		for i, sequence: NumberSequence in next, {a, b} do
			keys[sequence] = {}
			for i, time: number in next, times do
				local early: NumberSequenceKeypoint = nil
				local late: NumberSequenceKeypoint = nil
				for i, keypoint: NumberSequenceKeypoint in next, sequence.Keypoints do
					if not early or keypoint.Time < time then
						early = keypoint
					elseif keypoint.Time == time then
						early = keypoint
						break
					else
						late = keypoint
						break
					end
				end
				if early == late or not late then
					keys[sequence][time] = {
						value = early.Value,
						envelope = early.Envelope
					}
				else
					keys[sequence][time] = {
						value = (late.Value - early.Value) * (time - early.Time) / (late.Time - early.Time) + early.Value,
						envelope = (late.Envelope - early.Envelope) * (time - early.Time) / (late.Time - early.Time) + early.Envelope
					}
				end
			end
		end

		local keypoints: {NumberSequenceKeypoint} = {}
		for i, time: number in next, times do
			table.insert(keypoints, NumberSequenceKeypoint.new(
				time,
				(keys[b][time].value - keys[a][time].value) * alpha + keys[a][time].value,
				(keys[b][time].envelope - keys[a][time].envelope) * alpha + keys[a][time].envelope
			))
		end

		return NumberSequence.new(keypoints)
	end,
	NumberSequenceKeypoint = function(a: NumberSequenceKeypoint, b: NumberSequenceKeypoint, alpha: number) : NumberSequenceKeypoint
		return NumberSequenceKeypoint.new(
			valueFunctions.number(a.Time, b.Time, alpha),
			valueFunctions.number(a.Value, b.Value, alpha),
			valueFunctions.number(a.Envelope, b.Envelope, alpha)
		)
	end,
	OverlapParams = function(a: OverlapParams, b: OverlapParams, alpha: number) : OverlapParams
		local params = OverlapParams.new()
		params.MaxParts = valueFunctions.boolean(a.MaxParts, b.MaxParts, alpha)
		params.CollisionGroup = alpha > 0.5 and b.CollisionGroup or a.CollisionGroup
		params.FilterDescendantsInstances = valueFunctions.table(a.FilterDescendantsInstances, b.FilterDescendantsInstances, alpha)
		
		return params
	end,
	PathWaypoint = function(a: PathWaypoint, b: PathWaypoint, alpha: number) : PathWaypoint
		return PathWaypoint.new(
			valueFunctions.Vector3(a.Position, b.Position, alpha),
			valueFunctions.EnumItem(a.Action, b.Action, alpha)
		)
	end,
	PhysicalProperties = function(a: PhysicalProperties, b: PhysicalProperties, alpha: number) : PhysicalProperties
		return PhysicalProperties.new(
			valueFunctions.number(a.Density, b.Density, alpha),
			valueFunctions.number(a.Friction, b.Friction, alpha),
			valueFunctions.number(a.Elasticity, b.Elasticity, alpha),
			valueFunctions.number(a.FrictionWeight, b.FrictionWeight, alpha),
			valueFunctions.number(a.ElasticityWeight, b.ElasticityWeight, alpha)
		)
	end,
	Random = makeFlipFunction(Random.new()),
	Ray = function(a: Ray, b: Ray, alpha: number) : Ray
		return Ray.new(
			valueFunctions.Vector3(a.Origin, b.Origin, alpha),
			valueFunctions.Vector3(a.Direction, b.Direction, alpha)
		)
	end,
	RaycastParams = function(a: RaycastParams, b: RaycastParams, alpha: number) : RaycastParams
		local params = RaycastParams.new()
		params.FilterDescendantsInstances = valueFunctions.table(a.FilterDescendantsInstances, b.FilterDescendantsInstances, alpha)
		params.FilterType = valueFunctions.EnumItem(a.FilterType, b.FilterType, alpha)
		params.IgnoreWater = valueFunctions.boolean(a.IgnoreWater, b.IgnoreWater, alpha)
		params.CollisionGroup = alpha > 0.5 and b.CollisionGroup or a.CollisionGroup
		return params
	end,
	Rect = function(a: Rect, b: Rect, alpha: number) : Rect
		return Rect.new(
			valueFunctions.number(a.Min.X, b.Min.X, alpha),
			valueFunctions.number(a.Min.Y, b.Min.Y, alpha),
			valueFunctions.number(a.Max.X, b.Max.X, alpha),
			valueFunctions.number(a.Max.Y, b.Max.Y, alpha)
		)
	end,
	Region3 = function(a: Region3, b: Region3, alpha: number) : Region3
		return Region3.new(
			valueFunctions.Vector3(a.CFrame * (-a.Size/2), b.CFrame * (-b.Size/2), alpha),
			valueFunctions.Vector3(a.CFrame * (a.Size/2), b.CFrame * (b.Size/2), alpha)
		)
	end,
	Region3int16 = function(a: Region3int16, b: Region3int16, alpha: number) : Region3int16
		return Region3int16.new(
			valueFunctions.Vector3int16(a.Min, b.Min, alpha),
			valueFunctions.Vector3int16(a.Max, b.Max, alpha)
		)
	end,
	TweenInfo = function(a: TweenInfo, b: TweenInfo, alpha: number) : TweenInfo
		return TweenInfo.new(
			valueFunctions.number(a.Time, b.Time, alpha),
			valueFunctions.EnumItem(a.EasingStyle, b.EasingStyle, alpha),
			valueFunctions.EnumItem(a.EasingDirection, b.EasingDirection, alpha),
			valueFunctions.number(a.RepeatCount, b.RepeatCount, alpha),
			valueFunctions.boolean(a.Reverses, b.Reverses, alpha),
			valueFunctions.number(a.DelayTime, b.DelayTime, alpha)
		)
	end,
	UDim = function(a: UDim, b: UDim, alpha: number) : UDim
		return UDim.new(
			valueFunctions.number(a.Scale, b.Scale, alpha),
			valueFunctions.number(a.Offset, b.Offset, alpha)
		)
	end,
	UDim2 = function(a: UDim2, b: UDim2, alpha: number) : UDim2
		return UDim2.new(
			valueFunctions.number(a.X.Scale, b.X.Scale, alpha),
			valueFunctions.number(a.X.Offset, b.X.Offset, alpha),
			valueFunctions.number(a.Y.Scale, b.Y.Scale, alpha),
			valueFunctions.number(a.Y.Offset, b.Y.Offset, alpha)
		)
	end,
	Vector2 = function(a: Vector2, b: Vector2, alpha: number) : Vector2
		return a:Lerp(b, alpha)
	end,
	Vector2int16 = function(a: Vector2int16, b: Vector2int16, alpha: number) : Vector2int16
		return Vector2int16.new(
			valueFunctions.number(a.X, b.X, alpha),
			valueFunctions.number(a.Y, b.Y, alpha)
		)
	end,
	Vector3 = function(a: Vector3, b: Vector3, alpha: number) : Vector3
		return a:Lerp(b, alpha)
	end,
	Vector3Slerp = function(a: Vector3, b: Vector3, alpha: number, args: any) : Vector3
		local am = (a - args.focus).Magnitude
		local bm = (b - args.focus).Magnitude
		local midpoint = a:Lerp(b, alpha)

		return (midpoint - args.focus).Unit * ((bm-am)*alpha+am)
	end,
	Vector3int16 = function(a: Vector3int16, b: Vector3int16, alpha: number) : Vector3int16
		return Vector3int16.new(
			valueFunctions.number(a.X, b.X, alpha),
			valueFunctions.number(a.Y, b.Y, alpha),
			valueFunctions.number(a.Z, b.Z, alpha)
		)
	end,
}

type ExtraParams = {
	style: string?,
	direction: string?,
}

local namedSteppingSignals: {[string]: RBXScriptSignal} = {
	Heartbeat = runService.Heartbeat,
	PostSimulation = runService.PostSimulation,
	PreAnimation = runService.PreAnimation,
	PreRender = runService.PreRender,
	PreSimulation = runService.PreSimulation,
	RenderStepped = runService.RenderStepped,
	Stepped = runService.Stepped
}

local steppingSignalBindings: {[RBXScriptSignal]: {Tween<any, any>}} = {}
local tweensByObject: {
	[any]: {
		[any]: Tween<any, any>
	}
} = {}

function module:Tween(params: TweenParams<any, any>) : Tween<any, any>
	type K = any
	type V = any
	
	local object = params.object
	local key = params.key
	local set_f = params.set_f
	local start = params.start
	local start_f = params.start_f
	local finish = params.finish
	local finish_f = params.finish_f
	local length = params.length
	local length_f = params.length_f
	local style = params.style
	local style_f = params.style_f
	local style_args = params.style_args
	local direction = params.direction
	local direction_f = params.direction_f
	local direction_args = params.direction_args
	local as = params.as
	local value_f = params.value_f
	local value_args = params.value_args
	
	local function get_start() : V
		if start_f then
			return start_f(key)
		else
			return start
		end
	end
	local function get_finish() : V
		if finish_f then
			return finish_f(key, get_start())
		else
			return finish
		end
	end
	local function get_length() : number
		if length_f then
			return length_f()
		elseif length then
			return length
		else
			return 1
		end
	end
	local function get_style_f() : StyleFunction
		if style then
			return styleTransforms[style]
		elseif style_f then
			return style_f
		else
			return styleTransforms.Sine
		end
	end
	local function get_direction_f() : DirectionFunction
		if direction then
			return directionTransforms[direction]
		elseif direction_f then
			return direction_f
		else
			return directionTransforms.In
		end
	end
	local function get_value_f() : ValueFunction<V>
		if as then
			return valueFunctions[as]
		elseif value_f then
			return value_f
		else
			return valueFunctions[typeof(start)]
		end
	end
	
	local currentTime = 0
	local finishSignal = Signal.new() :: Signal.Signal<TweenResult<K, V>>
	local isStopping = false
	local stopping_signal: Signal.Signal<TweenResult<K, V>>
	local stepping_signal: RBXScriptSignal
	
	local this
	local tween: Tween<K, V> = {
		lastStep = tick(),
		
		Connect = function(self: Tween<K, V>, f: (TweenResult<K, V>)->() ) : Signal.Connection<TweenResult<K, V>>
			return finishSignal:Connect(f)
		end,
		Wait = function(self: Tween<K, V>) : TweenResult<K, V>
			return finishSignal:Wait()
		end,
		Step = function(self: Tween<K, V>, dt: number)
			currentTime += dt
			
			local value, alpha = self:GetCurrentValue()
			
			if set_f then
				set_f(key, value, alpha)
			elseif object then
				object[key] = value
			end
			
			if alpha >= 1 then
				self:Stop(true)
			end
		end,
		Stop = function(self: Tween<K, V>, finished: boolean) : TweenResult<K, V>
			if isStopping then
				if not stopping_signal then
					stopping_signal = Signal.new()
				end
				return stopping_signal:Wait()
			end
			isStopping = true
			
			self:Step(0)
			
			local result: TweenResult<K, V> = {
				tween = self,
				value = self:GetCurrentValue(),
				didFinish = finished,
				elapsedAlpha = math.min(currentTime/get_length()),
				transformedAlpha = self:GetTransformedAlpha(),
				elapsedTime = currentTime,
			}
			
			if stepping_signal then
				local bindings = steppingSignalBindings[stepping_signal]
				local index = table.find(bindings, self)
				if index then
					table.remove(bindings, index)
				end
			end
			
			tweensByObject[object][key] = nil
			if next(tweensByObject[object]) == nil then
				tweensByObject[object] = nil
			end
			
			if stopping_signal then
				stopping_signal:Fire(result)
				stopping_signal:Destroy()
			end
			
			return result
		end,
		GetRawAlpha = function(self: Tween<K, V>) : number
			return math.clamp(currentTime / get_length(), 0, 1)
		end,
		GetTransformedAlpha = function(self: Tween<K, V>) : number
			local raw = self:GetRawAlpha()
			local direction_a = get_direction_f().a(raw, direction_args)
			local styled = get_style_f()(raw, style_args)
			local transformed = get_direction_f().b(raw, styled, direction_args)
			return transformed
		end,
		GetCurrentValue = function(self: Tween<K, V>) : (V, number)
			local a = get_start()
			local b = get_finish()
			local alpha = self:GetTransformedAlpha()
			
			return get_value_f()(a, b, alpha, value_args), alpha
		end,
		SetSteppingSignal = function(self: Tween<K, V>, signal_param: string | RBXScriptSignal | Signal.Signal<any> | nil)
			local signal = runService.RenderStepped
			
			if signal_param == nil then
				signal = namedSteppingSignals.RenderStepped
			elseif typeof(signal_param) == 'string' then
				signal = namedSteppingSignals[signal_param]
			else
				signal = signal_param
			end
			
			if steppingSignalBindings[signal] then
				table.insert(steppingSignalBindings[signal], self)
			else
				steppingSignalBindings[signal] = {self}
				
				local con; con = signal:Connect(function()
					local now = tick()
					for i, tween in next, {unpack(steppingSignalBindings[signal])} do
						local dt = now - tween.lastStep
						tween.lastStep = dt
						
						tween:Step(dt)
					end
					
					if #steppingSignalBindings[signal] == 0 then
						steppingSignalBindings[signal] = nil
						con:Disconnect()
					end
				end)
			end
			
			stepping_signal = signal
		end,
	}
	this = tween

	if object then
		if not tweensByObject[object] then
			tweensByObject[object] = {}
		end

		if key then
			if tweensByObject[object][key] then
				tweensByObject[object][key]:Stop(false)
			end
			
			tweensByObject[object][key] = this
		end
	end
	
	this:SetSteppingSignal()
	this:Step(0)
	
	return tween
end

function module:StopTween(object:any, key:any)
	local byObject = tweensByObject[object]
	if byObject then
		local tween = byObject[key]
		if tween then
			tween:Stop(false)
		end
	end
end

function module:StopAllTweens(object:any)
	local byObject = tweensByObject[object]
	if byObject then
		while next(byObject) do
			next(byObject):Stop(false)
		end
	end
end

setmetatable(module, {
	__call = function(t, params: TweenParams<any, any>) : Tween<any, any>
		return module:Tween(params)
	end,
})

return module

Signal module:

--!strict

--[[
	Signal API
	
	signal
		Signal .new()
	
	Signal
		Connection :Connect(func)
		Variant :wait()
		void :Destroy()
		void :fire(variant)
	
	Connection
		bool .connected
		void :disconnect()
--]]

local private = {}
local public = {}

export type Connection<T> = {
	connected: boolean,
	f: ( (...T)->() )?,
	signal: Signal<T>?,
	c: RBXScriptConnection?,
	traceback: string,
	Disconnect: (Connection<T>)->()
}
export type Signal<T> = {
	connections: {[Connection<T>]: boolean},
	Connect: (Signal<T>, (...T)->())->Connection<T>,
	Wait: (Signal<T>)->...T,
	Fire: (Signal<T>, ...T)->(),
	Destroy: (Signal<T>)->(),
	GetDestroyedSignal: (Signal<T>)->Signal<nil>,
	gestroyedSignal: Signal<nil>?,
	GetEvent: (Signal<T>)->BindableEvent,
	e: BindableEvent?,
	args: {[any]: any},
	connectionCount: number,
	uidIndex: number,
	debugMode: boolean?,
	destroyedSignal: Signal<nil>?
}

--
-- Private
function private.connection_disconnect(self: Connection<any>) : ()
	if self.connected then
		self.connected = false
		self.f = nil
		
		local signal = self.signal
		if signal then
			signal.connections[self] = nil
			signal.connectionCount = signal.connectionCount - 1
			self.signal = nil
		end
		
		local c = self.c
		if c then
			c:Disconnect()
			self.c = nil
		end
	end
end

function private.signal_connect(self: Signal<any>, f: (...any)->()) : Connection<any>
	local connection: Connection<any> = {
		connected = true,
		f = f,
		signal = self,
		Disconnect = private.connection_disconnect,
		c = nil,
		traceback = debug.traceback()
	}
	
	self.connections[connection] = true
	
	local dbSignalFiredAt
	connection.c = self:GetEvent().Event:Connect(function(i)
		if self.debugMode then
			print('CONNECTION TRACEBACK',connection.traceback)
			print('CALL FUNCTION',connection.f,unpack(self.args))
			dbSignalFiredAt = tick()
		end
		
		local f = connection.f
		if f then
			f(unpack(self.args[i]))
		end
		
		if self.debugMode then
			warn('time',tick()-dbSignalFiredAt)
		end
	end)
	
	self.connectionCount += 1
	
	return connection
end

function private.signal_wait(self: Signal<any>) : ...any
	self.connectionCount += 1
	local i = self:GetEvent().Event:Wait()
	self.connectionCount -= 1
	return unpack(self.args[i])
end

function private.signal_fire(self: Signal<any>, ...: any) : ()
	if self.debugMode then
		warn('FIRING SIGNAL IN DEBUG MODE',self,self.connectionCount,debug.traceback(),'////',...)
	end
	
	if self.connectionCount > 0 then
		if self.debugMode then
			print('^ firing')
		end
		self.uidIndex = self.uidIndex + 1
		local i = self.uidIndex
		self.args[i] = {...}
		self:GetEvent():Fire(i)
		self.args[i] = nil
	end
end

function private.signal_destroy(self: Signal<any>) : ()
	local e = self.e
	if e then
		e:Destroy()
		self.e = nil
	end
	for c in next, self.connections do
		c:Disconnect()
	end
	
	local destroyedSignal = self.destroyedSignal
	if destroyedSignal then
		destroyedSignal:Fire()
		destroyedSignal:Destroy()
	end
	self.connectionCount = -1
end

function private.signal_getDestroyedSignal(self: Signal<any>) : Signal<nil>
	local destroyedSignal = self.destroyedSignal
	if destroyedSignal == nil then
		destroyedSignal = private.new()
		self.destroyedSignal = destroyedSignal
	end
	local destroyedSignal = destroyedSignal :: Signal<nil>
	
	return destroyedSignal
end

function private.signal_getEvent(self: Signal<any>) : BindableEvent
	local e = self.e
	if e then
		return e
	end
	
	local e = Instance.new('BindableEvent')
	self.e = e
	
	return e
end

function private.new() : Signal<any>
	local signal: Signal<any> = {
		connections = {},
		Connect = private.signal_connect,
		Wait = private.signal_wait,
		Fire = private.signal_fire,
		Destroy = private.signal_destroy,
		GetDestroyedSignal = private.signal_getDestroyedSignal,
		GetEvent = private.signal_getEvent,
		destroyedSignal = nil,
		e = nil,
		args = {},
		connectionCount = 0,
		uidIndex = 0
	}
	
	return signal
end

function private.load()
	public.new = private.new
	
	return public
end

--
-- Module
return private.load()

Thank you for the report.

I’ve reduced the example to:

--!strict
export type Connection<T> = {
	f: (T)->(),
	signal: Signal<T>
}
export type Signal<T> = {
	signal_f: ()->Connection<T>
}
export type TweenResult<K, V> = {
	result_f: ()->Tween<K, V>
}
export type Tween<K, V> = {
	tween_f: ()->Connection<TweenResult<K, V>>
}
return {}
6 Likes

A fix for this issue was implemented, it will be released in one of the future updates.

6 Likes