Skip to content

Instantly share code, notes, and snippets.

@howmanysmall
Last active August 14, 2021 17:16
Show Gist options
  • Save howmanysmall/c7d72158ffae16d43960221a327e8cf1 to your computer and use it in GitHub Desktop.
Save howmanysmall/c7d72158ffae16d43960221a327e8cf1 to your computer and use it in GitHub Desktop.
for those of you who hold the weeb card
-- WeebMaid
-- Original by Validark
-- Modifications by pobammer
-- roblox-ts support by OverHash and Validark
-- LinkToInstance fixed by Elttob.
local RunService = game:GetService("RunService")
local Promise = require(script.Parent.Promise)
local Heartbeat = RunService.Heartbeat
local IndicesReference = newproxy(true)
getmetatable(IndicesReference).__tostring = function()
return "IndicesReference"
end
local LinkToInstanceIndex = newproxy(true)
getmetatable(LinkToInstanceIndex).__tostring = function()
return "LinkToInstanceIndex"
end
local NOT_A_PROMISE = "Invalid argument #1 to 'WeebMaid:AddPromise' (Promise expected, got %s (%s))"
local METHOD_NOT_FOUND_ERROR = "Object %s doesn't have method %s, are you sure you want to add it? Traceback: %s"
local WeebMaid = {
ClassName = "WeebMaid";
__index = {
CurrentlyCleaning = true;
[IndicesReference] = nil;
};
}
local TypeDefaults = {
["function"] = true;
RBXScriptConnection = "Disconnect";
}
--[[**
Instantiates a new WeebMaid object.
@returns [t:WeebMaid]
**--]]
function WeebMaid.new()
return setmetatable({
CurrentlyCleaning = false;
[IndicesReference] = nil;
}, WeebMaid)
end
--[[**
Determines if the passed object is a WeebMaid.
@param [t:any] Object The object you are checking.
@returns [t:boolean] Whether or not the object is a WeebMaid.
**--]]
function WeebMaid.Is(Object)
return type(Object) == "table" and getmetatable(Object) == WeebMaid
end
--[[**
Adds an `Object` to WeebMaid for later cleanup, where `MethodName` is the key of the method within `Object` which should be called at cleanup time. If the `MethodName` is `true` the `Object` itself will be called instead. If passed an index it will occupy a namespace which can be `Remove()`d or overwritten. Returns the `Object`.
@param [t:any] Object The object you want to clean up.
@param [t:string|true?] MethodName The name of the method that will be used to clean up. If not passed, it will first check if the object's type exists in TypeDefaults, and if that doesn't exist, it assumes `Destroy`.
@param [t:any?] Index The index that can be used to clean up the object manually.
@returns [t:any] The object that was passed.
**--]]
function WeebMaid.__index:Add(Object, MethodName, Index)
if Index then
self:Remove(Index)
local This = self[IndicesReference]
if not This then
This = {}
self[IndicesReference] = This
end
This[Index] = Object
end
MethodName = MethodName or TypeDefaults[typeof(Object)] or "Destroy"
if type(Object) ~= "function" and not Object[MethodName] then
warn(string.format(METHOD_NOT_FOUND_ERROR, tostring(Object), tostring(MethodName), debug.traceback(nil, 2)))
end
self[Object] = MethodName
return Object
end
WeebMaid.__index.GiveTask = WeebMaid.__index.Add
-- My version of Promise has PascalCase, but I converted it to use lowerCamelCase for this release since obviously that's important to do.
--[[**
Adds a promise to the WeebMaid. If the WeebMaid is cleaned up and the promise is not completed, the promise will be cancelled.
@param [t:Promise] PromiseObject The promise you want to add to the WeebMaid.
@returns [t:Promise]
**--]]
function WeebMaid.__index:AddPromise(PromiseObject)
if not Promise.is(PromiseObject) then
error(string.format(NOT_A_PROMISE, typeof(PromiseObject), tostring(PromiseObject)))
end
if PromiseObject:getStatus() == Promise.Status.Started then
local Id = newproxy(false)
local NewPromise = self:Add(Promise.resolve(PromiseObject), "cancel", Id)
NewPromise:finallyCall(self.Remove, self, Id)
return NewPromise
else
return PromiseObject
end
end
WeebMaid.__index.GivePromise = WeebMaid.__index.AddPromise
--[[**
Cleans up whatever `Object` was set to this namespace by the 3rd parameter of `:Add()`.
@param [t:any] Index The index you want to remove.
@returns [t:WeebMaid] The same WeebMaid, for chaining reasons.
**--]]
function WeebMaid.__index:Remove(Index)
local This = self[IndicesReference]
if This then
local Object = This[Index]
if Object then
local MethodName = self[Object]
if MethodName then
if MethodName == true then
Object()
else
local ObjectMethod = Object[MethodName]
if ObjectMethod then
ObjectMethod(Object)
end
end
self[Object] = nil
end
This[Index] = nil
end
end
return self
end
--[[**
Gets whatever object is stored with the given index, if it exists. This was added since Maid allows getting the task using `__index`.
@param [t:any] Index The index that the object is stored under.
@returns [t:any?] This will return the object if it is found, but it won't return anything if it doesn't exist.
**--]]
function WeebMaid.__index:Get(Index)
local This = self[IndicesReference]
if This then
return This[Index]
end
end
WeebMaid.__index.Request = WeebMaid.__index.Get
--[[**
Calls each Object's `MethodName` (or calls the Object if `MethodName == true`) and removes them from the WeebMaid. Also clears the namespace. This function is also called when you call a WeebMaid Object (so it can be used as a destructor callback).
@returns [t:void]
**--]]
function WeebMaid.__index:Cleanup()
if not self.CurrentlyCleaning then
self.CurrentlyCleaning = nil
for Object, MethodName in next, self do
if Object == IndicesReference then
continue
end
if MethodName == true then
Object()
else
local ObjectMethod = Object[MethodName]
if ObjectMethod then
ObjectMethod(Object)
end
end
self[Object] = nil
end
local This = self[IndicesReference]
if This then
for Index in next, This do
This[Index] = nil
end
self[IndicesReference] = {}
end
self.CurrentlyCleaning = false
end
end
--[[**
Calls `:Cleanup()` and renders the WeebMaid unusable.
@returns [t:void]
**--]]
function WeebMaid.__index:Destroy()
self:Cleanup()
table.clear(self)
setmetatable(self, nil)
end
WeebMaid.__call = WeebMaid.__index.Cleanup
--- Makes the WeebMaid clean up when the instance is destroyed
-- @param Instance Instance The Instance the WeebMaid will wait for to be Destroyed
-- @returns Disconnectable table to stop WeebMaid from being cleaned up upon Instance Destroy (automatically cleaned up by WeebMaid, btw)
-- @author Corecii
local Disconnect = {Connected = true}
Disconnect.__index = Disconnect
function Disconnect:Disconnect()
if self.Connected then
self.Connected = false
self.Connection:Disconnect()
end
end
function Disconnect:__tostring()
return "Disconnect<" .. tostring(self.Connected) .. ">"
end
--[[**
"Links" this WeebMaid to an Instance, such that the WeebMaid will `Cleanup` when the Instance is `Destroyed()` and garbage collected. A WeebMaid may only be linked to one instance at a time, unless `AllowMultiple` is true. When called with a truthy `AllowMultiple` parameter, the WeebMaid will "link" the Instance without overwriting any previous links, and will also not be overwritable. When called with a falsy `AllowMultiple` parameter, the WeebMaid will overwrite the previous link which was also called with a falsy `AllowMultiple` parameter, if applicable.
@param [t:Instance] Object The instance you want to link the WeebMaid to.
@param [t:boolean?] AllowMultiple Whether or not to allow multiple links on the same WeebMaid.
@returns [t:RbxScriptConnection] A pseudo RBXScriptConnection that can be disconnected.
**--]]
function WeebMaid.__index:LinkToInstance(Object, AllowMultiple)
local Connection
local IndexToUse = AllowMultiple and newproxy(false) or LinkToInstanceIndex
local IsNilParented = Object.Parent == nil
local ManualDisconnect = setmetatable({}, Disconnect)
local function ChangedFunction(_DoNotUse, NewParent)
if ManualDisconnect.Connected then
_DoNotUse = nil
IsNilParented = NewParent == nil
if IsNilParented then
coroutine.wrap(function()
Heartbeat:Wait()
if not ManualDisconnect.Connected then
return
elseif not Connection.Connected then
self:Cleanup()
else
while IsNilParented and Connection.Connected and ManualDisconnect.Connected do
Heartbeat:Wait()
end
if ManualDisconnect.Connected and IsNilParented then
self:Cleanup()
end
end
end)()
end
end
end
Connection = Object.AncestryChanged:Connect(ChangedFunction)
ManualDisconnect.Connection = Connection
if IsNilParented then
ChangedFunction(nil, Object.Parent)
end
Object = nil
return self:Add(ManualDisconnect, "Disconnect", IndexToUse)
end
--[[**
Links several instances to a WeebMaid, which is then returned.
@param [t:...Instance] ... All the instances you want linked.
@returns [t:WeebMaid] A WeebMaid that can be used to manually disconnect all LinkToInstances.
**--]]
function WeebMaid.__index:LinkToInstances(...)
local ManualCleanup = WeebMaid.new()
for _, Object in ipairs({...}) do
ManualCleanup:Add(self:LinkToInstance(Object, true), "Disconnect")
end
return ManualCleanup
end
return WeebMaid
@SigmaThetaTech
Copy link

So now WeebMaid > Janitor?

@SigmaThetaTech
Copy link

Oh wow it works with promises

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment