From f57bcf964f71397a9c19fc1226ae744f0e2f7ac4 Mon Sep 17 00:00:00 2001 From: brinkokevin Date: Thu, 4 Mar 2021 19:40:09 +0100 Subject: [PATCH 01/12] dictionary support for StreamableUtil.Compound --- src/Knit/Util/StreamableUtil.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Knit/Util/StreamableUtil.lua b/src/Knit/Util/StreamableUtil.lua index 7d6a4662..ed1a6c78 100644 --- a/src/Knit/Util/StreamableUtil.lua +++ b/src/Knit/Util/StreamableUtil.lua @@ -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() From 561c532c8713dfbc80510442ff7d8c5387592767 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Mon, 15 Mar 2021 01:05:56 -0400 Subject: [PATCH 02/12] Fix leak --- src/Knit/Util/Signal.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Knit/Util/Signal.lua b/src/Knit/Util/Signal.lua index 3740acee..671b7240 100644 --- a/src/Knit/Util/Signal.lua +++ b/src/Knit/Util/Signal.lua @@ -86,9 +86,11 @@ 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 From 2594a6f3aa06a5890043f964837aea17b7bc962f Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Mon, 15 Mar 2021 01:13:45 -0400 Subject: [PATCH 03/12] Change _instance to Instance for component field --- docs/util.md | 2 +- src/Knit/Util/Component.lua | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/util.md b/docs/util.md index 992d4c4a..7540ba85 100644 --- a/docs/util.md +++ b/docs/util.md @@ -238,7 +238,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() diff --git a/src/Knit/Util/Component.lua b/src/Knit/Util/Component.lua index d56ba82c..802a9619 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 @@ -270,7 +273,7 @@ function Component:_instanceAdded(instance) idStr.Parent = instance 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,7 +291,7 @@ 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 @@ -336,7 +339,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 From 9203a459ba53dbb28af2f9420aeba06b98854865 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Mon, 15 Mar 2021 01:18:20 -0400 Subject: [PATCH 04/12] Use attributes for component server ID --- src/Knit/Util/Component.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Knit/Util/Component.lua b/src/Knit/Util/Component.lua index 802a9619..4a14e7a0 100644 --- a/src/Knit/Util/Component.lua +++ b/src/Knit/Util/Component.lua @@ -87,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} @@ -267,10 +268,7 @@ 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 @@ -295,8 +293,8 @@ function Component:_instanceRemoved(instance) 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() From 6251db6c1093e7d9d31ca53fec61787b2bf34d7d Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Mon, 15 Mar 2021 01:28:56 -0400 Subject: [PATCH 05/12] Signal proxy --- src/Knit/Util/Signal.lua | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Knit/Util/Signal.lua b/src/Knit/Util/Signal.lua index 671b7240..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,11 +83,36 @@ 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 @@ -142,6 +170,7 @@ end function Signal:Destroy() self:DisconnectAll() + self:_clearProxy() self._bindable:Destroy() end From 5e7e3c05b74c8c334e7e6060c5224bd7263a3ec7 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Mon, 15 Mar 2021 01:32:52 -0400 Subject: [PATCH 06/12] Signal proxy docs --- docs/util.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/util.md b/docs/util.md index 7540ba85..f37dded3 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) From 16a25d6074ae96f1fc482f4557cf2a3c897b02fa Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Mon, 15 Mar 2021 01:47:16 -0400 Subject: [PATCH 07/12] Add GivePromise --- src/Knit/Util/Maid.lua | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/Knit/Util/Maid.lua b/src/Knit/Util/Maid.lua index b9ce1c6d..3d7a10d7 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() From 825ab4deb7a9e02af1cf7934380dd74485348fb8 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Mon, 15 Mar 2021 01:49:09 -0400 Subject: [PATCH 08/12] Docs for maid GivePromise --- docs/util.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/util.md b/docs/util.md index f37dded3..e78e61b4 100644 --- a/docs/util.md +++ b/docs/util.md @@ -108,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() From 26ff47a57b87e16636bc52b3f9835deb5eae44fd Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Mon, 15 Mar 2021 09:11:43 -0400 Subject: [PATCH 09/12] Update StreamableUtil docs --- docs/util.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/util.md b/docs/util.md index e78e61b4..20819b90 100644 --- a/docs/util.md +++ b/docs/util.md @@ -325,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) From 5c2063237aa74b543032473401b1db6afd6b3998 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Mon, 15 Mar 2021 09:29:32 -0400 Subject: [PATCH 10/12] Fix GivePromise --- src/Knit/Util/Maid.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Knit/Util/Maid.lua b/src/Knit/Util/Maid.lua index 3d7a10d7..c66159fc 100644 --- a/src/Knit/Util/Maid.lua +++ b/src/Knit/Util/Maid.lua @@ -133,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 From fe0ad4c38d51f6779ba1ef0f88ce13536e402b0a Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Mon, 15 Mar 2021 09:39:47 -0400 Subject: [PATCH 11/12] Docs --- src/Knit/Util/StreamableUtil.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Knit/Util/StreamableUtil.lua b/src/Knit/Util/StreamableUtil.lua index ed1a6c78..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) From 5f329efe09316531e7d724b15a5534b56fbafc27 Mon Sep 17 00:00:00 2001 From: Stephen Leitnick Date: Mon, 15 Mar 2021 09:40:39 -0400 Subject: [PATCH 12/12] Bump version --- src/Knit/Version.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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