Skip to content

Commit

Permalink
Full rewrite + native client compile and run
Browse files Browse the repository at this point in the history
This commit introduces:
- A full rewrite
- Natively compiling and interpreting on both server and client without the usage of loadstring
- Execute something on the server context while on the client context and the other way around
  • Loading branch information
RealEthanPlayzDev committed Nov 9, 2022
1 parent 32c46d7 commit f47207b
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 21 deletions.
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -2,11 +2,11 @@
This is a simple Roblox Studio plugin for running LuaSourceContainer(s) (excluding CoreScript instances), I wrote this because I could not find a feature where you can select multiple scripts and click a button to simulate running them.

## Limitations
- If loadstring is not available, then it will fall back to using Yueliang + FiOne for code execution, otherwise it will use Roblox’s provided loadstring. (NOTE November 8th 2022: Alternative execution way for the client has been found that uses the native compiler and loadstring, this will be soon implemented)
- ~~If loadstring is not available, then it will fall back to using Yueliang + FiOne for code execution, otherwise it will use Roblox’s provided loadstring.~~ No longer applicable if using any version newer than 1.2

## TODOs
- [ ] Implement a way to load scripts and run them on the client natively (already found a way, just needs implementation)
- [ ] Ability to run a script on the server while being on the client context and the way around
- [x] Implement a way to load scripts and run them on the client natively (already found a way, just needs implementation)
- [x] Ability to run a script on the server while being on the client context and the way around
- [ ] Settings manager and ui
- [ ] Built-in script executor for replacing command bar?

Expand Down
238 changes: 220 additions & 18 deletions src/RunLSC_Plugin.lua
Expand Up @@ -6,36 +6,238 @@ Created at: May 6, 2022
The core RunLSC code for setting up the plugin toolbars, buttons, etc
--]]
--// Static configuration
local SHARED_INTERNAL_FOLDER_LOCATION = game:GetService("ReplicatedStorage")
local SHARED_INTERNAL_FOLDER_NAME = "RunLSC_InternalStorage"
local SHARED_INTERNAL_FOLDER_CONNECTOR_NAME = "RunLSC_DataModelConnector"
local SHARED_INTERNAL_FOLDER_MSCACHE_NAME = "RunLSC_ModuleScriptCache"
local EMPTY_CLOSURE = function() return end
--// Services
local serv = {
Selection = game:GetService("Selection");
ChangeHistoryService = game:GetService("ChangeHistoryService");
RunService = game:GetService("RunService");
Debris = game:GetService("Debris");
Players = game:GetService("Players");
}
--// Libraries
local loadstring = require(script.Parent.lib.LoadstringHelper)
--// Replace stdout functions with a wrapped one (except error)
local function _print(...)
return getfenv()["print"]("[RunLSC]: ", ...)
end
local function warn(...)
return getfenv()["warn"]("[RunLSC]: ", ...)
end
--// Functions
local function AddToDebris(item: any, lifetime: number?)
return serv.Debris:AddItem(item, if lifetime == nil then 0 else lifetime)
end
local function SetupInternalSharedStorage()
if serv.RunService:IsServer() then
AddToDebris(SHARED_INTERNAL_FOLDER_LOCATION:FindFirstChild(SHARED_INTERNAL_FOLDER_NAME))
end
local RootSharedStorage = Instance.new("Folder")
RootSharedStorage.Archivable = false
RootSharedStorage.Name = SHARED_INTERNAL_FOLDER_NAME
local DataModelConnector = Instance.new("RemoteEvent")
DataModelConnector.Name = SHARED_INTERNAL_FOLDER_CONNECTOR_NAME
DataModelConnector.Parent = RootSharedStorage
local ModuleScriptCache = Instance.new("Folder")
ModuleScriptCache.Name = SHARED_INTERNAL_FOLDER_MSCACHE_NAME
ModuleScriptCache.Parent = RootSharedStorage
RootSharedStorage.Parent = SHARED_INTERNAL_FOLDER_LOCATION
return RootSharedStorage, DataModelConnector, ModuleScriptCache
end
local function CompileAndRun(lsc: Script | LocalScript | ModuleScript, ... : any)
local Environment = getfenv(EMPTY_CLOSURE)
table.clear(Environment)
Environment["script"] = lsc
local Closure = loadstring(lsc.Source, lsc:GetFullName(), Environment)
local Ret = {pcall(Closure, ...)}
local ClosureRunSuccess = table.remove(Ret, 1)
if not ClosureRunSuccess then
task.spawn(error, lsc:GetFullName()..": "..Ret[1])
end
return ClosureRunSuccess, table.unpack(Ret)
end
local function CreateModuleScriptWrappedLSC(script: Script | LocalScript | ModuleScript)
local ParsedScriptPath = "game" do
for _, v in ipairs(string.split(script:GetFullName(), ".")) do
ParsedScriptPath ..= "[\""..v.."\"]"
end
end
local MS = Instance.new("ModuleScript")
MS.Name = script.Name
MS.Source = string.format([[return {
ScriptInstance = %s;
Closure = function()
%s
end;
}]], ParsedScriptPath, script.Source)

return MS
end

local function RunModuleScriptWrappedLSC(modulescript: ModuleScript)
local RequireSuccess, Result = pcall(require, modulescript)
if not RequireSuccess then
return warn("Script run failure (module require failure):\n"..Result)
end

local Environment = getfenv(EMPTY_CLOSURE)
table.clear(Environment)
Environment["script"] = Result.ScriptInstance
setfenv(Result.Closure, Environment)

local Ret = {pcall(Result.Closure)}
local ClosureRunSuccess = table.remove(Ret, 1)
if not ClosureRunSuccess then
task.spawn(error, Result.ScriptInstance:GetFullName()..": "..Ret[1])
end
return ClosureRunSuccess, table.unpack(Ret)
end

return function(plugin: Plugin)
--// Toolbar and buttons
--// Remove existing internal shared storage
if serv.RunService:IsServer() then
AddToDebris(SHARED_INTERNAL_FOLDER_LOCATION:FindFirstChild(SHARED_INTERNAL_FOLDER_NAME))
end

--// Toolbar creation
local Toolbar = plugin:CreateToolbar("RunLSC")
local RunBtn = Toolbar:CreateButton(if serv.RunService:IsEdit() then "Run" else if serv.RunService:IsServer() then "Run (server)" else "Run (client)", "Run the selected LuaSourceContainer(s)", "rbxassetid://4458901886")
RunBtn.ClickableWhenViewportHidden = true
--// Run button
RunBtn.Click:Connect(function()
for _, selection in pairs(serv.Selection:Get()) do
if not selection:IsA("LuaSourceContainer") or selection:IsA("CoreScript") then continue end
task.spawn(function()
local Environment = getfenv(1)
Environment["script"] = selection
local Success, Result = pcall(loadstring(selection.Source, selection:GetFullName(), Environment))
if not Success then
task.spawn(error, string.format("%s:%s", selection:GetFullName(), Result))

if serv.RunService:IsEdit() then
--// Run button setup
local RunBtn = Toolbar:CreateButton("Run", "Run the selected LuaSourceContainer(s)", "rbxassetid://4458901886")
RunBtn.ClickableWhenViewportHidden = true
RunBtn.Click:Connect(function()
for _, lsc in ipairs(serv.Selection:Get()) do
if (not lsc:IsA("Script")) and (not lsc:IsA("LocalScript")) and (not lsc:IsA("ModuleScript")) then
continue
end
task.spawn(CompileAndRun, lsc)
end
RunBtn.Enabled = false
RunBtn.Enabled = true
end)
else
--// Shared internal storage setup
local InternalSharedStorage, DataModelConnector, ModuleScriptCache do
if serv.RunService:IsServer() then
InternalSharedStorage, DataModelConnector, ModuleScriptCache = SetupInternalSharedStorage()
else
InternalSharedStorage = SHARED_INTERNAL_FOLDER_LOCATION:WaitForChild(SHARED_INTERNAL_FOLDER_NAME)
DataModelConnector = InternalSharedStorage:WaitForChild(SHARED_INTERNAL_FOLDER_CONNECTOR_NAME)
ModuleScriptCache = InternalSharedStorage:WaitForChild(SHARED_INTERNAL_FOLDER_MSCACHE_NAME)
end
end

--// DataModel connector setup
--// Mostly everything here should be self-explainable
if serv.RunService:IsServer() then
local Actions = {
["RequestScriptRunOnClient"] = function(plr: Player, targets: {Script | LocalScript | ModuleScript})
local MSTargets = {}
for _, lsc in ipairs(targets) do
if (not lsc:IsA("Script")) and (not lsc:IsA("LocalScript")) and (not lsc:IsA("ModuleScript")) then
continue
end
local MS = CreateModuleScriptWrappedLSC(lsc)
MS.Parent = ModuleScriptCache
table.insert(MSTargets, MS)
end
return DataModelConnector:FireClient(plr, MSTargets)
end,
["RequestScriptRunOnServer"] = function(_, targets: {Script | LocalScript | ModuleScript})
for _, lsc in ipairs(targets) do
if (not lsc:IsA("Script")) and (not lsc:IsA("LocalScript")) and (not lsc:IsA("ModuleScript")) then
continue
end
--// task.spawn(CompileAndRun, lsc)
local MS = CreateModuleScriptWrappedLSC(lsc)
task.spawn(RunModuleScriptWrappedLSC, MS)
end
return
end,
["RequestDeleteMSCacheTargets"] = function(_, targets: {ModuleScript})
for _, ms in ipairs(targets) do
if (not ms:IsA("ModuleScript")) or (not ms:IsDescendantOf(ModuleScriptCache)) then
continue
end
AddToDebris(ms)
end
return
end
}
DataModelConnector.OnServerEvent:Connect(function(plr: Player, action: string, ... : any)
if typeof(Actions[action]) == "function" then
return Actions[action](plr, ...)
end
return
end)
else
DataModelConnector.OnClientEvent:Connect(function(targets: {ModuleScript})
for _, ms in ipairs(targets) do
if (not ms:IsA("ModuleScript")) or (not ms:IsDescendantOf(ModuleScriptCache)) then
continue
end
task.spawn(RunModuleScriptWrappedLSC, ms)
end
DataModelConnector:FireServer("RequestDeleteMSCacheTargets", targets)
return
end)
end
RunBtn.Enabled = false
RunBtn.Enabled = true
end)

--// Run buttons setup
local RunServerBtn = Toolbar:CreateButton("Run (server)", "Run the selected LuaSourceContainer(s) at the server", "rbxassetid://4458901886")
RunServerBtn.Click:Connect(function()
if serv.RunService:IsServer() then
for _, lsc in ipairs(serv.Selection:Get()) do
if (not lsc:IsA("Script")) and (not lsc:IsA("LocalScript")) and (not lsc:IsA("ModuleScript")) then
continue
end
task.spawn(CompileAndRun, lsc)
end
else
DataModelConnector:FireServer("RequestScriptRunOnServer", serv.Selection:Get())
end
RunServerBtn.Enabled = false
RunServerBtn.Enabled = true
return
end)

local RunClientBtn = Toolbar:CreateButton("Run (client)", "Run the selected LuaSourceContainer(s) at the client", "rbxassetid://4458901886")
RunClientBtn.Click:Connect(function()
if serv.RunService:IsServer() then
local MSTargets = {}
for _, lsc in ipairs(serv.Selection:Get()) do
if (not lsc:IsA("Script")) and (not lsc:IsA("LocalScript")) and (not lsc:IsA("ModuleScript")) then
continue
end
local MS = CreateModuleScriptWrappedLSC(lsc)
MS.Parent = ModuleScriptCache
table.insert(MSTargets, MS)
end
DataModelConnector:FireAllClients(MSTargets)
else
DataModelConnector:FireServer("RequestScriptRunOnClient", serv.Selection:Get())
end
RunClientBtn.Enabled = false
RunClientBtn.Enabled = true
return
end)
end
end

0 comments on commit f47207b

Please sign in to comment.