Ok, so thanks to that i was able to figure something out. Like you said i detect the while true loop and convert it into a function.
Original Example Script:
Modified Script:
Using the returned function i spawn a thread that recreates the loop, storing the thread.

then simply calling task.cancel on the thread to stop it from running.

Result:
I turned this into a module script incase anyone else needs this for their plugins, i have also added tracking for instances created in the script with either Instance.New() or Instance:Clone(). They get destroyed upon cleanup.
local ScriptRuntime = {}
ScriptRuntime.__index = ScriptRuntime
local RuntimeScript = {}
RuntimeScript.__index = RuntimeScript
local function ParseScript(scriptSource)
-- Create a new script with the tracking tables and loop function at the top
local newSource = "local SCRIPTCONNECTIONS = {}\n"
newSource = newSource .. "local SCRIPTINSTANCES = {}\n"
newSource = newSource .. "local LOOP_FUNCTION = nil\n\n"
newSource = newSource .. "local success, errorMessage = pcall(function()\n"
-- Split the script into lines
local lines = {}
local connectionVars = {}
local instanceVars = {}
local insideLoop = false
local loopStart = 0
local loopEnd = 0
local indentLevel = ""
for line in scriptSource:gmatch("([^\n]*)\n?") do
table.insert(lines, line)
end
-- First pass to find while true loop
for i, line in ipairs(lines) do
if line:match("%s*while%s+true%s+do%s*") and not insideLoop then
insideLoop = true
loopStart = i
indentLevel = line:match("^(%s*)")
elseif insideLoop and line:match("^" .. indentLevel .. "end%s*") then
insideLoop = false
loopEnd = i
break -- Only process the first while true loop
end
end
-- Helper function to process connections and instances
local function processLine(line, lineIndex)
local processedLine = line
-- Look for connections
if line:find(":[Cc]onnect%(") then
if line:match("^%s*local%s+([%w_]+)%s*=") then
local varName = line:match("^%s*local%s+([%w_]+)%s*=")
processedLine = line:gsub("^%s*local%s+", "")
table.insert(connectionVars, varName)
elseif line:match("^%s*([%w_]+)%s*=") then
local varName = line:match("^%s*([%w_]+)%s*=")
table.insert(connectionVars, varName)
else
local varName = "connection_" .. lineIndex
local indent = line:match("^(%s*)")
processedLine = indent .. varName .. " = " .. line:gsub("^%s*", "")
table.insert(connectionVars, varName)
end
-- Look for Instance.new calls OR Clone() calls
elseif line:find("Instance%.new%(") or line:find(":[Cc]lone%(") then
if line:match("^%s*local%s+([%w_]+)%s*=") then
local varName = line:match("^%s*local%s+([%w_]+)%s*=")
processedLine = line:gsub("^%s*local%s+", "")
table.insert(instanceVars, varName)
elseif line:match("^%s*([%w_]+)%s*=") then
local varName = line:match("^%s*([%w_]+)%s*=")
table.insert(instanceVars, varName)
else
local varName = "instance_" .. lineIndex
local indent = line:match("^(%s*)")
processedLine = indent .. varName .. " = " .. line:gsub("^%s*", "")
table.insert(instanceVars, varName)
end
end
return processedLine
end
-- Process each line
for i, line in ipairs(lines) do
-- Special handling for loop
if i == loopStart and loopStart > 0 then
-- Convert while true to LOOP_FUNCTION definition
newSource = newSource .. indentLevel .. "LOOP_FUNCTION = function()\n"
elseif i == loopEnd and loopEnd > 0 then
-- Close the function definition
newSource = newSource .. indentLevel .. "end\n"
elseif i > loopStart and i < loopEnd and loopStart > 0 then
-- Process lines inside the loop
local processedLine = processLine(line, i)
newSource = newSource .. processedLine .. "\n"
else
-- Process lines outside the loop
local processedLine = processLine(line, i)
newSource = newSource .. processedLine .. "\n"
end
end
-- Add code to collect connections at the end (still inside pcall)
newSource = newSource .. "\n-- Collect all connections\n"
for _, varName in ipairs(connectionVars) do
newSource = newSource .. "table.insert(SCRIPTCONNECTIONS, " .. varName .. ")\n"
end
-- Add code to collect instances at the end (still inside pcall)
newSource = newSource .. "\n-- Collect all instances\n"
for _, varName in ipairs(instanceVars) do
newSource = newSource .. "table.insert(SCRIPTINSTANCES, " .. varName .. ")\n"
end
-- Close the pcall function
newSource = newSource .. "end)\n\n"
-- Add return statement (outside pcall)
newSource = newSource .. "return {Connections = SCRIPTCONNECTIONS, Instances = SCRIPTINSTANCES, LoopFunction = LOOP_FUNCTION, ErrorMsg = errorMessage}"
-- Create the module script
local moduleScript = Instance.new("ModuleScript")
moduleScript.Name = "ConnectionsModule"
moduleScript.Source = newSource
return moduleScript
end
function ScriptRuntime.NewScript(originalScript)
local self = setmetatable({}, RuntimeScript)
--Parse Script
local success, processedScript = pcall(function()
return ParseScript(originalScript.Source)
end)
if not success then
warn("Failed to process the script: " .. tostring(processedScript))
return
end
processedScript.Name = originalScript.Name .. "_RUNTIME"
processedScript.Parent = originalScript.Parent
self.Script = processedScript
return self
end
function RuntimeScript:Execute()
-- Try to run the script
local runSuccess, result = pcall(function()
return require(self.Script)
end)
if not runSuccess then
warn("Error running the processed script: " .. tostring(result))
else
if result then
self.Connections = result.Connections or {}
self.Instances = result.Instances or {}
self.LoopFunction = result.LoopFunction
if result.ErrorMsg then
warn("Script reported an error: " .. tostring(result.ErrorMsg))
self:Terminate()
end
if self.LoopFunction then
self.LoopThread = task.spawn(function()
while true do
self.LoopFunction()
end
end)
end
else
warn("Script didn't return an expected result")
end
end
end
function RuntimeScript:Terminate()
if not self.Connections and not self.Instances and not self.LoopFunction then
return
end
for _, connection in pairs(self.Connections) do
connection:Disconnect()
connection = nil
end
table.clear(self.Connections)
for _, instance in pairs(self.Instances) do
instance:Destroy()
instance = nil
end
if self.LoopThread then
task.cancel(self.LoopThread)
self.LoopThread = nil
end
table.clear(self.Instances)
end
function RuntimeScript:Destroy()
self:Terminate()
self.Script:Destroy()
self.Script = nil
end
return ScriptRuntime