Skip to content

Instantly share code, notes, and snippets.

@howmanysmall
Created December 29, 2022 20:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save howmanysmall/05fb4651a1d69a4288aca98ba21b6d1c to your computer and use it in GitHub Desktop.
Save howmanysmall/05fb4651a1d69a4288aca98ba21b6d1c to your computer and use it in GitHub Desktop.
A barebones Janitor for the Maid API.
local Janitor = require("Janitor")
--[=[
Manages the cleaning of events and other things. Useful for
encapsulating state and make deconstructors easy.
See the [Five Powerful Code Patterns talk](https://developer.roblox.com/en-us/videos/5-powerful-code-patterns-behind-top-roblox-games)
for a more in-depth look at Maids in top games.
```lua
local maid = Maid.new()
maid:GiveTask(function()
print("Cleaning up")
end)
maid:GiveTask(workspace.ChildAdded:Connect(print))
-- Disconnects all events, and executes all functions
maid:DoCleaning()
```
@class Maid
]=]
-- luacheck: pop
local Maid = {}
Maid.ClassName = "Maid"
--[=[
Constructs a new Maid object
```lua
local maid = Maid.new()
```
@return Maid
]=]
function Maid.new()
return setmetatable({
_taskId = 0;
_tasks = Janitor.new();
}, Maid)
end
--[=[
Returns true if the class is a maid, and false otherwise.
```lua
print(Maid.isMaid(Maid.new())) --> true
print(Maid.isMaid(nil)) --> false
```
@param value any
@return boolean
]=]
function Maid.isMaid(value)
return type(value) == "table" and value.ClassName == "Maid"
end
--[=[
Returns Maid[key] if not part of Maid metatable
```lua
local maid = Maid.new()
maid._current = Instance.new("Part")
print(maid._current) --> Part
maid._current = nil
print(maid._current) --> nil
```
@param index any
@return MaidTask
]=]
function Maid:__index(index)
if Maid[index] then
return Maid[index]
else
return self._tasks:Get(index)
end
end
--[=[
Add a task to clean up. Tasks given to a maid will be cleaned when
maid[index] is set to a different value.
Task cleanup is such that if the task is an event, it is disconnected.
If it is an object, it is destroyed.
```
Maid[key] = (function) Adds a task to perform
Maid[key] = (event connection) Manages an event connection
Maid[key] = (thread) Manages a thread
Maid[key] = (Maid) Maids can act as an event connection, allowing a Maid to have other maids to clean up.
Maid[key] = (Object) Maids can cleanup objects with a `Destroy` method
Maid[key] = nil Removes a named task.
```
@param index any
@param newTask MaidTask
]=]
function Maid:__newindex(index, newTask)
if Maid[index] ~= nil then
error(("Cannot use '%s' as a Maid key"):format(tostring(index)), 2)
end
local tasks = self._tasks
if tasks:Get(index) == newTask then
return
end
tasks:Add(newTask, false, index)
end
--[=[
Gives a task to the maid for cleanup, but uses an incremented number as a key.
@param task MaidTask -- An item to clean
@return number -- taskId
]=]
function Maid:GiveTask(task)
if not task then
error("Task cannot be false or nil", 2)
end
local taskId = self._taskId + 1
self._tasks:Add(task, false, taskId)
if type(task) == "table" and not task.Destroy then
warn("[Maid.GiveTask] - Gave table task without .Destroy\n\n" .. debug.traceback())
end
return taskId
end
--[=[
Gives a promise to the maid for clean.
@param promise Promise<T>
@return Promise<T>
]=]
function Maid:GivePromise(promise)
if not promise:IsPending() then
return promise
end
local newPromise = promise.resolved(promise)
local id = self:GiveTask(newPromise)
-- Ensure GC
newPromise:Finally(function()
self[id] = nil
end)
return newPromise
end
--[=[
Cleans up all tasks and removes them as entries from the Maid.
:::note
Signals that are already connected are always disconnected first. After that
any signals added during a cleaning phase will be disconnected at random times.
:::
:::tip
DoCleaning() may be recursively invoked. This allows the you to ensure that
tasks or other tasks. Each task will be executed once.
However, adding tasks while cleaning is not generally a good idea, as if you add a
function that adds itself, this will loop indefinitely.
:::
]=]
function Maid:DoCleaning()
self._tasks:Cleanup()
end
--[=[
Alias for [Maid.DoCleaning()](/api/Maid#DoCleaning)
@function Destroy
@within Maid
]=]
Maid.Destroy = Maid.DoCleaning
return Maid
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment