Ok, here’s the code implementing this solution. Note: it turns out that you don’t even need the coroutines I was talking about.
Usage:
Runs the module:run()
method on all module scripts tagged as “LocalScript
” who are descendants of game.Workspace
or game.Players
.
StarterPlayer/StarterPlayerScripts/WorkspaceClientScripts
--[[
WorkspaceClientScripts
Author: radbuglet
--]]
-- /// Services ///
local CollectionService = game:GetService("CollectionService")
-- /// Constants ///
local LOCAL_SCRIPT_TAG = "LocalScript"
local APPLICABLE_ANCESTORS = { workspace, game.Players }
local MIRROR_SCRIPTS_CONTAINER_FOLDER = script
local MIRROR_SCRIPT_TEMPLATE = script.MirrorScriptTemplate
-- /// Helpers ///
local function run_for_each_tagged_instance(tag, callback)
local instances_detected = {} -- @WARN there's a possible memory leak here because deleted instances are never cleaned up
local function wrapped_callback(object)
-- Temporary patch for double firing error detailed [here](https://devforum.roblox.com/t/double-firing-of-collectionservice-getinstanceaddedsignal-when-applying-tag-in-same-frame-that-object-is-added-to-datamodel/244235?u=radbuglet)
if instances_detected[object] == true then return end
instances_detected[object] = true
-- Check if is descendant of applicable ancestor
for _, ancestor in pairs(APPLICABLE_ANCESTORS) do
if ancestor:IsAncestorOf(object) then
callback(object)
break
end
end
end
CollectionService:GetInstanceAddedSignal(tag):Connect(wrapped_callback)
for _, object in pairs(CollectionService:GetTagged(tag)) do
wrapped_callback(object)
end
end
-- /// Setup ///
run_for_each_tagged_instance(LOCAL_SCRIPT_TAG, function(object)
if not object:IsA("ModuleScript") then
error("Objects tagged with " .. LOCAL_SCRIPT_TAG .. " must be module scripts!")
return
end
local mirror_script = MIRROR_SCRIPT_TEMPLATE:Clone()
mirror_script.Name = object:GetFullName()
mirror_script.Disabled = false
local master_script_value = Instance.new("ObjectValue")
master_script_value.Name = "MasterScript"
master_script_value.Value = object
master_script_value.Parent = mirror_script
mirror_script.Parent = MIRROR_SCRIPTS_CONTAINER_FOLDER
end)
StarterPlayer/StarterPlayerScripts/WorkspaceClientScripts/MirrorScriptTemplate
(Must be Disabled
!)
--[[
WorkspaceClientScripts/MirrorScriptTemplate
Author: radbuglet
--]]
local master_script = script.MasterScript.Value
master_script.AncestryChanged:Connect(function()
if not game:IsAncestorOf(master_script) then
script:Destroy()
end
end)
require(master_script):run()
Please tell me if there are any bugs I need to fix or an easier way to fix my problem.
Thanks,
rad