diff --git a/docs/util.md b/docs/util.md index 992d4c4a..20819b90 100644 --- a/docs/util.md +++ b/docs/util.md @@ -25,6 +25,12 @@ connection:Disconnect() The Connection object internal to the Signal module also has a Destroy method associated with it, so it will still play nicely with the Maid module. +It is possible to wrap an existing RBXScriptSignal (e.g. `BasePart.Touched`) using `Signal.Proxy`, which is useful when creating abstractions that utilize existing built-in signals: + +```lua +local touchTap = Signal.Proxy(UserInputService.TouchTap) +``` + -------------------- ## [Thread](https://github.com/Sleitnick/Knit/blob/main/src/Knit/Util/Thread.lua) @@ -102,6 +108,9 @@ maid:GiveTask(somePart) maid:GiveTask(something.SomeEvent:Connect(function() end)) maid:GiveTask(function() end) +-- Give promises, which will have 'Cancel' called if the maid is cleaned up: +maid:GivePromise(somePromise) + -- Both Destroy and DoCleaning do the same thing: maid:Destroy() maid:DoCleaning() @@ -238,7 +247,7 @@ end function DanceFloor:HeartbeatUpdate(dt) if (time() > self._nextUpdate) then -- Set the assigned instance to a random color: - self._instance.Color = Color3.new( + self.Instance.Color = Color3.new( math.random(), math.random(), math.random() @@ -316,9 +325,9 @@ Extra functionality for Streamables. For instance, `StreamableUtil.Compound` can local s1 = Streamable.new(someModel, "SomeChild") local s2 = Streamable.new(anotherModel, "AnotherChild") -StreamableUtil.Compound({s1, s2}, function(streamables, maid) - local someChild = streamables[1].Instance - local anotherChild = streamables[2].Instance +StreamableUtil.Compound({Stream1 = s1, Stream2 = s2}, function(streamables, maid) + local someChild = streamables.Stream1.Instance + local anotherChild = streamables.Stream2.Instance maid:GiveTask(function() -- Cleanup (will be called if ANY streamables are cleaned up) end) diff --git a/src/Knit/Util/Component.lua b/src/Knit/Util/Component.lua index d56ba82c..4a14e7a0 100644 --- a/src/Knit/Util/Component.lua +++ b/src/Knit/Util/Component.lua @@ -39,6 +39,9 @@ return self end + -- FIELDS AFTER CONSTRUCTOR COMPLETES + MyComponent.Instance: Instance + -- OPTIONAL LIFECYCLE HOOKS function MyComponent:Init() end -> Called right after constructor function MyComponent:Deinit() end -> Called right before deconstructor @@ -84,6 +87,7 @@ local Players = game:GetService("Players") local IS_SERVER = RunService:IsServer() local DEFAULT_WAIT_FOR_TIMEOUT = 60 +local ATTRIBUTE_ID_NAME = "ComponentServerId" -- Components will only work on instances parented under these descendants: local DESCENDANT_WHITELIST = {workspace, Players} @@ -264,13 +268,10 @@ function Component:_instanceAdded(instance) self._nextId = (self._nextId + 1) local id = (self._tag .. tostring(self._nextId)) if (IS_SERVER) then - local idStr = Instance.new("StringValue") - idStr.Name = "ServerID" - idStr.Value = id - idStr.Parent = instance + instance:SetAttribute(ATTRIBUTE_ID_NAME, id) end local obj = self._class.new(instance) - obj._instance = instance + obj.Instance = instance obj._id = id self._instancesToObjects[instance] = obj table.insert(self._objects, obj) @@ -288,12 +289,12 @@ end function Component:_instanceRemoved(instance) self._instancesToObjects[instance] = nil for i,obj in ipairs(self._objects) do - if (obj._instance == instance) then + if (obj.Instance == instance) then if (self._hasDeinit) then obj:Deinit() end - if (IS_SERVER and instance:FindFirstChild("ServerID")) then - instance.ServerID:Destroy() + if (IS_SERVER and instance.Parent and instance:GetAttribute(ATTRIBUTE_ID_NAME) ~= nil) then + instance:SetAttribute(ATTRIBUTE_ID_NAME, nil) end self.Removed:Fire(obj) obj:Destroy() @@ -336,7 +337,7 @@ end function Component:WaitFor(instance, timeout) local isName = (type(instance) == "string") local function IsInstanceValid(obj) - return ((isName and obj._instance.Name == instance) or ((not isName) and obj._instance == instance)) + return ((isName and obj.Instance.Name == instance) or ((not isName) and obj.Instance == instance)) end for _,obj in ipairs(self._objects) do if (IsInstanceValid(obj)) then diff --git a/src/Knit/Util/Maid.lua b/src/Knit/Util/Maid.lua index b9ce1c6d..c66159fc 100644 --- a/src/Knit/Util/Maid.lua +++ b/src/Knit/Util/Maid.lua @@ -10,6 +10,9 @@ maid:GiveTask(task) > task is an event connection, function, or instance/table with a 'Destroy' method + + maid:GivePromise(promise) + > Give the maid a promise as a task, which will call 'promise:Cancel()' on cleanup maid:DoCleaning() > Alias for Destroy @@ -19,18 +22,13 @@ --]] ---- Manages the cleaning of events and other things. --- Useful for encapsulating state and make deconstructors easy --- @classmod Maid --- @see Signal +local Promise = require(script.Parent.Promise) + local Maid = {} Maid.ClassName = "Maid" ---- Returns a new Maid object --- @constructor Maid.new() --- @treturn Maid function Maid.new() local self = setmetatable({ _tasks = {}; @@ -74,6 +72,8 @@ function Maid:__newindex(index, newTask) oldTask:Disconnect() elseif (oldTask.Destroy) then oldTask:Destroy() + elseif (Promise.Is(oldTask)) then + oldTask:Cancel() end end end @@ -88,7 +88,7 @@ function Maid:GiveTask(task) local taskId = (#self._tasks + 1) self[taskId] = task - if (type(task) == "table" and (not task.Destroy)) then + if (type(task) == "table" and (not task.Destroy) and (not Promise.Is(task))) then warn("[Maid.GiveTask] - Gave table task without .Destroy\n\n" .. debug.traceback()) end @@ -96,6 +96,20 @@ function Maid:GiveTask(task) end +function Maid:GivePromise(promise) + assert(Promise.Is(promise), "Expected promise") + if (promise:GetStatus() ~= Promise.Status.Started) then + return promise + end + local newPromise = Promise.Resolve(promise) + local id = self:GiveTask(newPromise) + newPromise:Finally(function() + self[id] = nil + end) + return newPromise +end + + --- Cleans up all tasks. -- @alias Destroy function Maid:DoCleaning() @@ -119,6 +133,8 @@ function Maid:DoCleaning() task:Disconnect() elseif (task.Destroy) then task:Destroy() + elseif (Promise.Is(task)) then + task:Cancel() end index, task = next(tasks) end diff --git a/src/Knit/Util/Signal.lua b/src/Knit/Util/Signal.lua index 3740acee..b9815aa3 100644 --- a/src/Knit/Util/Signal.lua +++ b/src/Knit/Util/Signal.lua @@ -4,7 +4,10 @@ --[[ - signal = Signal.new([maid]) + signal = Signal.new([maid: Maid]) + signal = Signal.Proxy(rbxSignal: RBXScriptSignal [, maid: Maid]) + + Signal.Is(object: any): boolean signal:Fire(...) signal:Wait() @@ -12,7 +15,7 @@ signal:Destroy() signal:DisconnectAll() - connection = signal:Connect(functionHandler) + connection = signal:Connect((...) -> void) connection:Disconnect() connection:IsConnected() @@ -80,15 +83,42 @@ function Signal.new(maid) end +function Signal.Proxy(rbxScriptSignal, maid) + assert(typeof(rbxScriptSignal) == "RBXScriptSignal", "Argument #1 must be of type RBXScriptSignal") + local signal = Signal.new(maid) + signal:_setProxy(rbxScriptSignal) + return signal +end + + function Signal.Is(obj) return (type(obj) == "table" and getmetatable(obj) == Signal) end +function Signal:_setProxy(rbxScriptSignal) + assert(typeof(rbxScriptSignal) == "RBXScriptSignal", "Argument #1 must be of type RBXScriptSignal") + self:_clearProxy() + self._proxyHandle = rbxScriptSignal:Connect(function(...) + self:Fire(...) + end) +end + + +function Signal:_clearProxy() + if (self._proxyHandle) then + self._proxyHandle:Disconnect() + self._proxyHandle = nil + end +end + + function Signal:Fire(...) + local totalListeners = (#self._connections + self._threads) + if (totalListeners == 0) then return end local id = self._id self._id += 1 - self._args[id] = {#self._connections + self._threads, {n = select("#", ...), ...}} + self._args[id] = {totalListeners, {n = select("#", ...), ...}} self._threads = 0 self._bindable:Fire(id) end @@ -140,6 +170,7 @@ end function Signal:Destroy() self:DisconnectAll() + self:_clearProxy() self._bindable:Destroy() end diff --git a/src/Knit/Util/StreamableUtil.lua b/src/Knit/Util/StreamableUtil.lua index 7d6a4662..5ddf1080 100644 --- a/src/Knit/Util/StreamableUtil.lua +++ b/src/Knit/Util/StreamableUtil.lua @@ -11,9 +11,9 @@ local streamable1 = Streamable.new(someModel, "SomeChild") local streamable2 = Streamable.new(anotherModel, "AnotherChild") - StreamableUtil.Compound({streamable1, streamable2}, function(streamables, maid) - local someChild = streamables[1].Instance - local anotherChild = streamables[2].Instance + StreamableUtil.Compound({S1 = streamable1, S2 = streamable2}, function(streamables, maid) + local someChild = streamables.S1.Instance + local anotherChild = streamables.S2.Instance maid:GiveTask(function() -- Cleanup end) @@ -34,7 +34,7 @@ function StreamableUtil.Compound(streamables, handler) local allAvailable = false local function Check() if (allAvailable) then return end - for _,streamable in ipairs(streamables) do + for _,streamable in pairs(streamables) do if (not streamable.Instance) then return end @@ -47,7 +47,7 @@ function StreamableUtil.Compound(streamables, handler) allAvailable = false observeAllMaid:DoCleaning() end - for _,streamable in ipairs(streamables) do + for _,streamable in pairs(streamables) do compoundMaid:GiveTask(streamable:Observe(function(_child, maid) Check() maid:GiveTask(function() diff --git a/src/Knit/Version.txt b/src/Knit/Version.txt index d1aea9de..c61406f6 100644 --- a/src/Knit/Version.txt +++ b/src/Knit/Version.txt @@ -1 +1 @@ -0.0.13-alpha \ No newline at end of file +0.0.14-alpha \ No newline at end of file