mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-22 21:55:15 +00:00
Lint plugin src (#846)
This commit is contained in:
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -72,7 +72,7 @@ jobs:
|
|||||||
run: cargo build --locked --verbose
|
run: cargo build --locked --verbose
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
name: Rustfmt, Clippy, & Stylua
|
name: Rustfmt, Clippy, Stylua, & Selene
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -98,6 +98,9 @@ jobs:
|
|||||||
- name: Stylua
|
- name: Stylua
|
||||||
run: stylua --check plugin/src
|
run: stylua --check plugin/src
|
||||||
|
|
||||||
|
- name: Selene
|
||||||
|
run: selene plugin/src
|
||||||
|
|
||||||
- name: Rustfmt
|
- name: Rustfmt
|
||||||
run: cargo fmt -- --check
|
run: cargo fmt -- --check
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
[tools]
|
[tools]
|
||||||
rojo = "rojo-rbx/rojo@7.3.0"
|
rojo = "rojo-rbx/rojo@7.3.0"
|
||||||
selene = "Kampfkarren/selene@0.25.0"
|
selene = "Kampfkarren/selene@0.26.1"
|
||||||
stylua = "JohnnyMorganz/stylua@0.18.2"
|
stylua = "JohnnyMorganz/stylua@0.18.2"
|
||||||
run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0"
|
run-in-roblox = "rojo-rbx/run-in-roblox@0.3.0"
|
||||||
|
|||||||
@@ -185,10 +185,10 @@ function ApiContext:write(patch)
|
|||||||
|
|
||||||
body = Http.jsonEncode(body)
|
body = Http.jsonEncode(body)
|
||||||
|
|
||||||
return Http.post(url, body):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(body)
|
return Http.post(url, body):andThen(rejectFailedRequests):andThen(Http.Response.json):andThen(function(responseBody)
|
||||||
Log.info("Write response: {:?}", body)
|
Log.info("Write response: {:?}", responseBody)
|
||||||
|
|
||||||
return body
|
return responseBody
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ function Dropdown:render()
|
|||||||
self.setContentSize(object.AbsoluteContentSize)
|
self.setContentSize(object.AbsoluteContentSize)
|
||||||
end,
|
end,
|
||||||
}),
|
}),
|
||||||
Roact.createFragment(optionButtons),
|
Options = Roact.createFragment(optionButtons),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
else nil,
|
else nil,
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ local function DisplayValue(props)
|
|||||||
-- We don't need to support mixed tables, so checking the first key is enough
|
-- We don't need to support mixed tables, so checking the first key is enough
|
||||||
-- to determine if it's a simple array
|
-- to determine if it's a simple array
|
||||||
local out, i = table.create(#props.value), 0
|
local out, i = table.create(#props.value), 0
|
||||||
for k, v in props.value do
|
for _, v in props.value do
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
-- Wrap strings in quotes
|
-- Wrap strings in quotes
|
||||||
|
|||||||
@@ -98,21 +98,16 @@ function DomLabel:render()
|
|||||||
-- Line guides help indent depth remain readable
|
-- Line guides help indent depth remain readable
|
||||||
local lineGuides = {}
|
local lineGuides = {}
|
||||||
for i = 1, props.depth or 0 do
|
for i = 1, props.depth or 0 do
|
||||||
table.insert(
|
lineGuides["Line_" .. i] = e("Frame", {
|
||||||
lineGuides,
|
Size = UDim2.new(0, 2, 1, 2),
|
||||||
e("Frame", {
|
Position = UDim2.new(0, (20 * i) + 15, 0, -1),
|
||||||
Name = "Line_" .. i,
|
BorderSizePixel = 0,
|
||||||
Size = UDim2.new(0, 2, 1, 2),
|
BackgroundTransparency = props.transparency,
|
||||||
Position = UDim2.new(0, (20 * i) + 15, 0, -1),
|
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
||||||
BorderSizePixel = 0,
|
})
|
||||||
BackgroundTransparency = props.transparency,
|
|
||||||
BackgroundColor3 = theme.BorderedContainer.BorderColor,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return e("Frame", {
|
return e("Frame", {
|
||||||
Name = "Change",
|
|
||||||
ClipsDescendants = true,
|
ClipsDescendants = true,
|
||||||
BackgroundColor3 = if props.patchType then theme.Diff[props.patchType] else nil,
|
BackgroundColor3 = if props.patchType then theme.Diff[props.patchType] else nil,
|
||||||
BorderSizePixel = 0,
|
BorderSizePixel = 0,
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ end
|
|||||||
function TextButton:render()
|
function TextButton:render()
|
||||||
return Theme.with(function(theme)
|
return Theme.with(function(theme)
|
||||||
local textSize =
|
local textSize =
|
||||||
TextService:GetTextSize(self.props.text, 18, Enum.Font.GothamSemibold, Vector2.new(math.huge, math.huge))
|
TextService:GetTextSize(self.props.text, 18, Enum.Font.GothamMedium, Vector2.new(math.huge, math.huge))
|
||||||
|
|
||||||
local style = self.props.style
|
local style = self.props.style
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ function TextButton:render()
|
|||||||
|
|
||||||
Text = e("TextLabel", {
|
Text = e("TextLabel", {
|
||||||
Text = self.props.text,
|
Text = self.props.text,
|
||||||
Font = Enum.Font.GothamSemibold,
|
Font = Enum.Font.GothamMedium,
|
||||||
TextSize = 18,
|
TextSize = 18,
|
||||||
TextColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.TextColor, theme.Disabled.TextColor),
|
TextColor3 = bindingUtil.mapLerp(bindingEnabled, theme.Enabled.TextColor, theme.Disabled.TextColor),
|
||||||
TextTransparency = self.props.transparency,
|
TextTransparency = self.props.transparency,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
local TextService = game:GetService("TextService")
|
|
||||||
|
|
||||||
local Rojo = script:FindFirstAncestor("Rojo")
|
local Rojo = script:FindFirstAncestor("Rojo")
|
||||||
local Plugin = Rojo.Plugin
|
local Plugin = Rojo.Plugin
|
||||||
local Packages = Rojo.Packages
|
local Packages = Rojo.Packages
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ local Rojo = script:FindFirstAncestor("Rojo")
|
|||||||
local Packages = Rojo.Packages
|
local Packages = Rojo.Packages
|
||||||
|
|
||||||
local Roact = require(Packages.Roact)
|
local Roact = require(Packages.Roact)
|
||||||
local Log = require(Packages.Log)
|
|
||||||
|
|
||||||
local strict = require(script.Parent.Parent.strict)
|
local strict = require(script.Parent.Parent.strict)
|
||||||
|
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ local function applyPatch(instanceMap, patch)
|
|||||||
local unappliedPatch = PatchSet.newEmpty()
|
local unappliedPatch = PatchSet.newEmpty()
|
||||||
|
|
||||||
for _, removedIdOrInstance in ipairs(patch.removed) do
|
for _, removedIdOrInstance in ipairs(patch.removed) do
|
||||||
local ok = pcall(function()
|
local removeInstanceSuccess = pcall(function()
|
||||||
if Types.RbxId(removedIdOrInstance) then
|
if Types.RbxId(removedIdOrInstance) then
|
||||||
instanceMap:destroyId(removedIdOrInstance)
|
instanceMap:destroyId(removedIdOrInstance)
|
||||||
else
|
else
|
||||||
instanceMap:destroyInstance(removedIdOrInstance)
|
instanceMap:destroyInstance(removedIdOrInstance)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
if not ok then
|
if not removeInstanceSuccess then
|
||||||
table.insert(unappliedPatch.removed, removedIdOrInstance)
|
table.insert(unappliedPatch.removed, removedIdOrInstance)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -175,10 +175,10 @@ local function applyPatch(instanceMap, patch)
|
|||||||
end
|
end
|
||||||
|
|
||||||
if update.changedName ~= nil then
|
if update.changedName ~= nil then
|
||||||
local ok = pcall(function()
|
local setNameSuccess = pcall(function()
|
||||||
instance.Name = update.changedName
|
instance.Name = update.changedName
|
||||||
end)
|
end)
|
||||||
if not ok then
|
if not setNameSuccess then
|
||||||
unappliedUpdate.changedName = update.changedName
|
unappliedUpdate.changedName = update.changedName
|
||||||
partiallyApplied = true
|
partiallyApplied = true
|
||||||
end
|
end
|
||||||
@@ -194,15 +194,15 @@ local function applyPatch(instanceMap, patch)
|
|||||||
|
|
||||||
if update.changedProperties ~= nil then
|
if update.changedProperties ~= nil then
|
||||||
for propertyName, propertyValue in pairs(update.changedProperties) do
|
for propertyName, propertyValue in pairs(update.changedProperties) do
|
||||||
local ok, decodedValue = decodeValue(propertyValue, instanceMap)
|
local decodeSuccess, decodedValue = decodeValue(propertyValue, instanceMap)
|
||||||
if not ok then
|
if not decodeSuccess then
|
||||||
unappliedUpdate.changedProperties[propertyName] = propertyValue
|
unappliedUpdate.changedProperties[propertyName] = propertyValue
|
||||||
partiallyApplied = true
|
partiallyApplied = true
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok = setProperty(instance, propertyName, decodedValue)
|
local setPropertySuccess = setProperty(instance, propertyName, decodedValue)
|
||||||
if not ok then
|
if not setPropertySuccess then
|
||||||
unappliedUpdate.changedProperties[propertyName] = propertyValue
|
unappliedUpdate.changedProperties[propertyName] = propertyValue
|
||||||
partiallyApplied = true
|
partiallyApplied = true
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ local function decodeValue(encodedValue, instanceMap)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok, decodedValue = RbxDom.EncodedValue.decode(encodedValue)
|
local decodeSuccess, decodedValue = RbxDom.EncodedValue.decode(encodedValue)
|
||||||
|
|
||||||
if not ok then
|
if not decodeSuccess then
|
||||||
return false,
|
return false,
|
||||||
Error.new(Error.CannotDecodeValue, {
|
Error.new(Error.CannotDecodeValue, {
|
||||||
encodedValue = encodedValue,
|
encodedValue = encodedValue,
|
||||||
|
|||||||
@@ -147,13 +147,13 @@ local function diff(instanceMap, virtualInstances, rootId)
|
|||||||
|
|
||||||
local changedProperties = {}
|
local changedProperties = {}
|
||||||
for propertyName, virtualValue in pairs(virtualInstance.Properties) do
|
for propertyName, virtualValue in pairs(virtualInstance.Properties) do
|
||||||
local ok, existingValueOrErr = getProperty(instance, propertyName)
|
local getProperySuccess, existingValueOrErr = getProperty(instance, propertyName)
|
||||||
|
|
||||||
if ok then
|
if getProperySuccess then
|
||||||
local existingValue = existingValueOrErr
|
local existingValue = existingValueOrErr
|
||||||
local ok, decodedValue = decodeValue(virtualValue, instanceMap)
|
local decodeSuccess, decodedValue = decodeValue(virtualValue, instanceMap)
|
||||||
|
|
||||||
if ok then
|
if decodeSuccess then
|
||||||
if not trueEquals(existingValue, decodedValue) then
|
if not trueEquals(existingValue, decodedValue) then
|
||||||
Log.debug(
|
Log.debug(
|
||||||
"{}.{} changed from '{}' to '{}'",
|
"{}.{} changed from '{}' to '{}'",
|
||||||
@@ -165,7 +165,6 @@ local function diff(instanceMap, virtualInstances, rootId)
|
|||||||
changedProperties[propertyName] = virtualValue
|
changedProperties[propertyName] = virtualValue
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local propertyType = next(virtualValue)
|
|
||||||
Log.warn(
|
Log.warn(
|
||||||
"Failed to decode property {}.{}. Encoded property was: {:#?}",
|
"Failed to decode property {}.{}. Encoded property was: {:#?}",
|
||||||
virtualInstance.ClassName,
|
virtualInstance.ClassName,
|
||||||
@@ -220,9 +219,9 @@ local function diff(instanceMap, virtualInstances, rootId)
|
|||||||
table.insert(patch.removed, childInstance)
|
table.insert(patch.removed, childInstance)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
local ok, err = diffInternal(childId)
|
local diffSuccess, err = diffInternal(childId)
|
||||||
|
|
||||||
if not ok then
|
if not diffSuccess then
|
||||||
return false, err
|
return false, err
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -243,9 +242,9 @@ local function diff(instanceMap, virtualInstances, rootId)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok, err = diffInternal(rootId)
|
local diffSuccess, err = diffInternal(rootId)
|
||||||
|
|
||||||
if not ok then
|
if not diffSuccess then
|
||||||
return false, err
|
return false, err
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -31,13 +31,13 @@ local function hydrate(instanceMap, virtualInstances, rootId, rootInstance)
|
|||||||
-- We guard accessing Name and ClassName in order to avoid
|
-- We guard accessing Name and ClassName in order to avoid
|
||||||
-- tripping over children of DataModel that Rojo won't have
|
-- tripping over children of DataModel that Rojo won't have
|
||||||
-- permissions to access at all.
|
-- permissions to access at all.
|
||||||
local ok, name, className = pcall(function()
|
local accessSuccess, name, className = pcall(function()
|
||||||
return childInstance.Name, childInstance.ClassName
|
return childInstance.Name, childInstance.ClassName
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- This rule is very conservative and could be loosened in the
|
-- This rule is very conservative and could be loosened in the
|
||||||
-- future, or more heuristics could be introduced.
|
-- future, or more heuristics could be introduced.
|
||||||
if ok and name == virtualChild.Name and className == virtualChild.ClassName then
|
if accessSuccess and name == virtualChild.Name and className == virtualChild.ClassName then
|
||||||
isExistingChildVisited[childIndex] = true
|
isExistingChildVisited[childIndex] = true
|
||||||
hydrate(instanceMap, virtualInstances, childId, childInstance)
|
hydrate(instanceMap, virtualInstances, childId, childInstance)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -53,9 +53,9 @@ function reifyInner(instanceMap, virtualInstances, id, parentInstance, unapplied
|
|||||||
-- Instance.new can fail if we're passing in something that can't be
|
-- Instance.new can fail if we're passing in something that can't be
|
||||||
-- created, like a service, something enabled with a feature flag, or
|
-- created, like a service, something enabled with a feature flag, or
|
||||||
-- something that requires higher security than we have.
|
-- something that requires higher security than we have.
|
||||||
local ok, instance = pcall(Instance.new, virtualInstance.ClassName)
|
local createSuccess, instance = pcall(Instance.new, virtualInstance.ClassName)
|
||||||
|
|
||||||
if not ok then
|
if not createSuccess then
|
||||||
addAllToPatch(unappliedPatch, virtualInstances, id)
|
addAllToPatch(unappliedPatch, virtualInstances, id)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -80,14 +80,14 @@ function reifyInner(instanceMap, virtualInstances, id, parentInstance, unapplied
|
|||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok, value = decodeValue(virtualValue, instanceMap)
|
local decodeSuccess, value = decodeValue(virtualValue, instanceMap)
|
||||||
if not ok then
|
if not decodeSuccess then
|
||||||
unappliedProperties[propertyName] = virtualValue
|
unappliedProperties[propertyName] = virtualValue
|
||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok = setProperty(instance, propertyName, value)
|
local setPropertySuccess = setProperty(instance, propertyName, value)
|
||||||
if not ok then
|
if not setPropertySuccess then
|
||||||
unappliedProperties[propertyName] = virtualValue
|
unappliedProperties[propertyName] = virtualValue
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -148,8 +148,8 @@ function applyDeferredRefs(instanceMap, deferredRefs, unappliedPatch)
|
|||||||
continue
|
continue
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok = setProperty(entry.instance, entry.propertyName, targetInstance)
|
local setPropertySuccess = setProperty(entry.instance, entry.propertyName, targetInstance)
|
||||||
if not ok then
|
if not setPropertySuccess then
|
||||||
markFailed(entry.id, entry.propertyName, entry.virtualValue)
|
markFailed(entry.id, entry.propertyName, entry.virtualValue)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ return function()
|
|||||||
|
|
||||||
local PatchSet = require(script.Parent.Parent.PatchSet)
|
local PatchSet = require(script.Parent.Parent.PatchSet)
|
||||||
local InstanceMap = require(script.Parent.Parent.InstanceMap)
|
local InstanceMap = require(script.Parent.Parent.InstanceMap)
|
||||||
local Error = require(script.Parent.Error)
|
|
||||||
|
|
||||||
local function isEmpty(table)
|
local function isEmpty(table)
|
||||||
return next(table) == nil, "Table was not empty"
|
return next(table) == nil, "Table was not empty"
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ local function setProperty(instance, propertyName, value)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
local ok, err = descriptor:write(instance, value)
|
local writeSuccess, err = descriptor:write(instance, value)
|
||||||
|
|
||||||
if not ok then
|
if not writeSuccess then
|
||||||
if err.kind == RbxDom.Error.Kind.Roblox and err.extra:find("lacking permission") then
|
if err.kind == RbxDom.Error.Kind.Roblox and err.extra:find("lacking permission") then
|
||||||
return false,
|
return false,
|
||||||
Error.new(Error.LackingPropertyPermissions, {
|
Error.new(Error.LackingPropertyPermissions, {
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ local Status = strict("Session.Status", {
|
|||||||
Disconnected = "Disconnected",
|
Disconnected = "Disconnected",
|
||||||
})
|
})
|
||||||
|
|
||||||
local function debugPatch(patch)
|
local function debugPatch(object)
|
||||||
return Fmt.debugify(patch, function(patch, output)
|
return Fmt.debugify(object, function(patch, output)
|
||||||
output:writeLine("Patch {{")
|
output:writeLine("Patch {{")
|
||||||
output:indent()
|
output:indent()
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ function ServeSession:__onActiveScriptChanged(activeScript)
|
|||||||
local existingParent = activeScript.Parent
|
local existingParent = activeScript.Parent
|
||||||
activeScript.Parent = nil
|
activeScript.Parent = nil
|
||||||
|
|
||||||
for i = 1, 3 do
|
for _ = 1, 3 do
|
||||||
RunService.Heartbeat:Wait()
|
RunService.Heartbeat:Wait()
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -251,7 +251,10 @@ function ServeSession:__initialSync(serverInfo)
|
|||||||
|
|
||||||
if userDecision == "Abort" then
|
if userDecision == "Abort" then
|
||||||
return Promise.reject("Aborted Rojo sync operation")
|
return Promise.reject("Aborted Rojo sync operation")
|
||||||
elseif userDecision == "Reject" and self.__twoWaySync then
|
elseif userDecision == "Reject" then
|
||||||
|
if not self.__twoWaySync then
|
||||||
|
return Promise.reject("Cannot reject sync operation without two-way sync enabled")
|
||||||
|
end
|
||||||
-- The user wants their studio DOM to write back to their Rojo DOM
|
-- The user wants their studio DOM to write back to their Rojo DOM
|
||||||
-- so we will reverse the patch and send it back
|
-- so we will reverse the patch and send it back
|
||||||
|
|
||||||
@@ -268,7 +271,7 @@ function ServeSession:__initialSync(serverInfo)
|
|||||||
table.insert(inversePatch.updated, update)
|
table.insert(inversePatch.updated, update)
|
||||||
end
|
end
|
||||||
-- Add the removed instances back to Rojo
|
-- Add the removed instances back to Rojo
|
||||||
-- selene:allow(empty_if, unused_variable)
|
-- selene:allow(empty_if, unused_variable, empty_loop)
|
||||||
for _, instance in catchUpPatch.removed do
|
for _, instance in catchUpPatch.removed do
|
||||||
-- TODO: Generate ID for our instance and add it to inversePatch.added
|
-- TODO: Generate ID for our instance and add it to inversePatch.added
|
||||||
end
|
end
|
||||||
@@ -277,7 +280,7 @@ function ServeSession:__initialSync(serverInfo)
|
|||||||
table.insert(inversePatch.removed, id)
|
table.insert(inversePatch.removed, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.__apiContext:write(inversePatch)
|
return self.__apiContext:write(inversePatch)
|
||||||
elseif userDecision == "Accept" then
|
elseif userDecision == "Accept" then
|
||||||
local unappliedPatch = self.__reconciler:applyPatch(catchUpPatch)
|
local unappliedPatch = self.__reconciler:applyPatch(catchUpPatch)
|
||||||
|
|
||||||
@@ -287,6 +290,10 @@ function ServeSession:__initialSync(serverInfo)
|
|||||||
PatchSet.humanSummary(self.__instanceMap, unappliedPatch)
|
PatchSet.humanSummary(self.__instanceMap, unappliedPatch)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return Promise.resolve()
|
||||||
|
else
|
||||||
|
return Promise.reject("Invalid user decision: " .. userDecision)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
--[[
|
|
||||||
Create a new signal that can be connected to, disconnected from, and fired.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
local signal = createSignal()
|
|
||||||
local disconnect = signal:connect(function(...)
|
|
||||||
print("fired:", ...)
|
|
||||||
end)
|
|
||||||
|
|
||||||
signal:fire("a", "b", "c")
|
|
||||||
disconnect()
|
|
||||||
|
|
||||||
Avoids mutating listeners list directly to prevent iterator invalidation if
|
|
||||||
a listener is disconnected while the signal is firing.
|
|
||||||
]]
|
|
||||||
local function createSignal()
|
|
||||||
local listeners = {}
|
|
||||||
|
|
||||||
local function connect(newListener)
|
|
||||||
local nextListeners = {}
|
|
||||||
for listener in pairs(listeners) do
|
|
||||||
nextListeners[listener] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
nextListeners[newListener] = true
|
|
||||||
listeners = nextListeners
|
|
||||||
|
|
||||||
return function()
|
|
||||||
local nextListeners = {}
|
|
||||||
for listener in pairs(listeners) do
|
|
||||||
if listener ~= newListener then
|
|
||||||
nextListeners[listener] = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
listeners = nextListeners
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function fire(...)
|
|
||||||
for listener in pairs(listeners) do
|
|
||||||
listener(...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
connect = connect,
|
|
||||||
fire = fire,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return createSignal
|
|
||||||
Reference in New Issue
Block a user