diff --git a/modules/input/Keyboard.lua b/modules/input/Keyboard.lua index d27bb5e1..d4e83026 100644 --- a/modules/input/Keyboard.lua +++ b/modules/input/Keyboard.lua @@ -60,6 +60,7 @@ function Keyboard.new() self._trove = Trove.new() self.KeyDown = self._trove:Construct(Signal) self.KeyUp = self._trove:Construct(Signal) + self.State = {} self:_setup() return self end @@ -74,7 +75,7 @@ end ``` ]=] function Keyboard:IsKeyDown(keyCode: Enum.KeyCode): boolean - return UserInputService:IsKeyDown(keyCode) + return self.State[keyCode] == true end @@ -112,11 +113,13 @@ function Keyboard:_setup() self._trove:Connect(UserInputService.InputBegan, function(input, processed) if processed then return end if input.UserInputType == Enum.UserInputType.Keyboard then + self.State[input.KeyCode] = true self.KeyDown:Fire(input.KeyCode) end end) self._trove:Connect(UserInputService.InputEnded, function(input, processed) + self.State[input.KeyCode] = nil if processed then return end if input.UserInputType == Enum.UserInputType.Keyboard then self.KeyUp:Fire(input.KeyCode) diff --git a/modules/trove/init.lua b/modules/trove/init.lua index 9abb56aa..4ef8e244 100644 --- a/modules/trove/init.lua +++ b/modules/trove/init.lua @@ -2,13 +2,11 @@ -- Stephen Leitnick -- October 16, 2021 - local FN_MARKER = newproxy() local THREAD_MARKER = newproxy() local RunService = game:GetService("RunService") - local function GetObjectCleanupFunction(object, cleanupMethod) local t = typeof(object) if t == "function" then @@ -33,14 +31,17 @@ local function GetObjectCleanupFunction(object, cleanupMethod) error("Failed to get cleanup function for object " .. t .. ": " .. tostring(object), 3) end - local function AssertPromiseLike(object) - if type(object) ~= "table" or type(object.getStatus) ~= "function" or type(object.finally) ~= "function" or type(object.cancel) ~= "function" then + if + type(object) ~= "table" + or type(object.getStatus) ~= "function" + or type(object.finally) ~= "function" + or type(object.cancel) ~= "function" + then error("Did not receive a Promise as an argument", 3) end end - --[=[ @class Trove A Trove is helpful for tracking any sort of object during @@ -49,7 +50,6 @@ end local Trove = {} Trove.__index = Trove - --[=[ @return Trove Constructs a Trove object. @@ -57,10 +57,10 @@ Trove.__index = Trove function Trove.new() local self = setmetatable({}, Trove) self._objects = {} + self._cleaning = false return self end - --[=[ @return Trove Creates and adds another trove to itself. This is just shorthand @@ -81,19 +81,23 @@ end ``` ]=] function Trove:Extend() + if self._cleaning then + error("Cannot call trove:Extend() while cleaning", 2) + end return self:Construct(Trove) end - --[=[ Clones the given instance and adds it to the trove. Shorthand for `trove:Add(instance:Clone())`. ]=] function Trove:Clone(instance: Instance): Instance + if self._cleaning then + error("Cannot call trove:Clone() while cleaning", 2) + end return self:Add(instance:Clone()) end - --[=[ @param class table | (...any) -> any @param ... any @@ -128,6 +132,9 @@ end ``` ]=] function Trove:Construct(class, ...) + if self._cleaning then + error("Cannot call trove:Construct() while cleaning", 2) + end local object = nil local t = type(class) if t == "table" then @@ -138,7 +145,6 @@ function Trove:Construct(class, ...) return self:Add(object) end - --[=[ @param signal RBXScriptSignal @param fn (...: any) -> () @@ -155,9 +161,33 @@ end ``` ]=] function Trove:Connect(signal, fn) + if self._cleaning then + error("Cannot call trove:Connect() while cleaning", 2) + end return self:Add(signal:Connect(fn)) end +--[=[ + @param signal RBXScriptSignal + @param fn (...: any) -> () + @return RBXScriptConnection + Connects the function to the signal, adds the connection + to the trove, and then returns the connection. + + This is shorthand for `trove:Add(signal:Once(fn))`. + + ```lua + trove:ConnectOnce(workspace.ChildAdded, function(instance) + print(instance.Name .. " added to workspace") + end) + ``` +]=] +function Trove:ConnectOnce(signal, fn) + if self._cleaning then + error("Cannot call trove:Connect() while cleaning", 2) + end + return self:Add(signal:Once(fn)) +end --[=[ @param name string @@ -173,13 +203,15 @@ end ``` ]=] function Trove:BindToRenderStep(name: string, priority: number, fn: (dt: number) -> ()) + if self._cleaning then + error("Cannot call trove:BindToRenderStep() while cleaning", 2) + end RunService:BindToRenderStep(name, priority, fn) self:Add(function() RunService:UnbindFromRenderStep(name) end) end - --[=[ @param promise Promise @return Promise @@ -204,17 +236,22 @@ end ::: ]=] function Trove:AddPromise(promise) + if self._cleaning then + error("Cannot call trove:AddPromise() while cleaning", 2) + end AssertPromiseLike(promise) if promise:getStatus() == "Started" then promise:finally(function() - return self:_findAndRemoveFromObjects(promise, false) + if self._cleaning then + return + end + self:_findAndRemoveFromObjects(promise, false) end) self:Add(promise, "cancel") end return promise end - --[=[ @param object any -- Object to track @param cleanupMethod string? -- Optional cleanup name override @@ -264,12 +301,14 @@ end ``` ]=] function Trove:Add(object: any, cleanupMethod: string?): any + if self._cleaning then + error("Cannot call trove:Add() while cleaning", 2) + end local cleanup = GetObjectCleanupFunction(object, cleanupMethod) - table.insert(self._objects, {object, cleanup}) + table.insert(self._objects, { object, cleanup }) return object end - --[=[ @param object any -- Object to remove Removes the object from the Trove and cleans it up. @@ -281,26 +320,33 @@ end ``` ]=] function Trove:Remove(object: any): boolean + if self._cleaning then + error("Cannot call trove:Remove() while cleaning", 2) + end return self:_findAndRemoveFromObjects(object, true) end - --[=[ Cleans up all objects in the trove. This is similar to calling `Remove` on each object - within the trove. + within the trove. The ordering of the objects + removed is _not_ guaranteed. ]=] function Trove:Clean() - for _,obj in ipairs(self._objects) do + if self._cleaning then + return + end + self._cleaning = true + for _, obj in self._objects do self:_cleanupObject(obj[1], obj[2]) end table.clear(self._objects) + self._cleaning = false end - function Trove:_findAndRemoveFromObjects(object: any, cleanup: boolean): boolean local objects = self._objects - for i,obj in ipairs(objects) do + for i, obj in ipairs(objects) do if obj[1] == object then local n = #objects objects[i] = objects[n] @@ -314,7 +360,6 @@ function Trove:_findAndRemoveFromObjects(object: any, cleanup: boolean): boolean return false end - function Trove:_cleanupObject(object, cleanupMethod) if cleanupMethod == FN_MARKER then object() @@ -325,7 +370,6 @@ function Trove:_cleanupObject(object, cleanupMethod) end end - --[=[ @param instance Instance @return RBXScriptConnection @@ -340,19 +384,19 @@ end ::: ]=] function Trove:AttachToInstance(instance: Instance) - assert(instance:IsDescendantOf(game), "Instance is not a descendant of the game hierarchy") - return self:Connect(instance.Destroying, function() + if self._cleaning then + error("Cannot call trove:AttachToInstance() while cleaning", 2) + end + return self:ConnectOnce(instance.Destroying, function() self:Destroy() end) end - --[=[ - Destroys the Trove object. Forces `Clean` to run. + Alias for `trove:Clean()`. ]=] function Trove:Destroy() self:Clean() end - return Trove