|
| 1 | +--!nonstrict |
1 | 2 | --// Initialization |
2 | 3 |
|
3 | 4 | local RunService = game:GetService("RunService") |
4 | 5 | local PlayerService = game:GetService("Players") |
5 | 6 | local StarterPlayer = game:GetService("StarterPlayer") |
6 | | -local CollectionService = game:GetService("CollectionService") |
7 | 7 | local ReplicatedStorage = game:GetService("ReplicatedStorage") |
8 | | -local ServerScriptService = game:GetService("ServerScriptService") |
9 | | - |
10 | | -local IsClient = RunService:IsClient() |
11 | | -local IsServer = RunService:IsServer() |
| 8 | +local CollectionService = game:GetService("CollectionService") |
| 9 | +local StarterCharacterScripts = StarterPlayer:WaitForChild("StarterCharacterScripts"):: Folder |
12 | 10 |
|
13 | 11 | local Module = {} |
14 | | -local CollectionMetatable = {} |
| 12 | +local LibraryThreadCache: {[thread]: string} = {} |
| 13 | +local Libraries: {[string]: ModuleScript} = {} |
15 | 14 |
|
16 | | ---// Variables |
| 15 | +--// Functions |
17 | 16 |
|
18 | | -local DebugPrint = false |
| 17 | +local function Retrieve(InstanceName: string, InstanceClass: string, InstanceParent: Instance, ForceWait: boolean?): Instance |
| 18 | + if ForceWait then |
| 19 | + return InstanceParent:WaitForChild(InstanceName) |
| 20 | + end |
19 | 21 |
|
20 | | -local RetrievalSets = { |
21 | | - ["RemoteEvent"] = "RemoteEvent", |
22 | | - ["RemoteFunction"] = "RemoteFunction", |
23 | | - ["BindableEvent"] = "BindableEvent", |
24 | | - ["BindableFunction"] = "BindableFunction", |
25 | | -} |
| 22 | + local SearchInstance = InstanceParent:FindFirstChild(InstanceName) |
26 | 23 |
|
27 | | ---// Functions |
| 24 | + if not SearchInstance then |
| 25 | + SearchInstance = Instance.new(InstanceClass) |
| 26 | + SearchInstance.Name = InstanceName |
| 27 | + SearchInstance.Parent = InstanceParent |
| 28 | + end |
| 29 | + |
| 30 | + return SearchInstance |
| 31 | +end |
28 | 32 |
|
29 | | -local function printd(...) |
30 | | - if DebugPrint then |
31 | | - return print(...) |
| 33 | +function Module._BindFunction(Function: (Instance) -> (), Event: RBXScriptSignal, Existing: {Instance}): RBXScriptConnection |
| 34 | + if Existing then |
| 35 | + for _, Value in next, Existing do |
| 36 | + task.spawn(Function, Value) |
| 37 | + end |
32 | 38 | end |
| 39 | + |
| 40 | + return Event:Connect(Function) |
| 41 | +end |
| 42 | + |
| 43 | +function Module._BindToTag(Tag: string, Function: (Instance) -> ()) |
| 44 | + return Module._BindFunction(Function, CollectionService:GetInstanceAddedSignal(Tag), CollectionService:GetTagged(Tag)) |
33 | 45 | end |
34 | 46 |
|
35 | | -local function Retrieve(InstanceName, InstanceClass, InstanceParent) |
36 | | - --/ Finds an Instance by name and creates a new one if it doesen't exist |
37 | | - |
38 | | - local SearchInstance = nil |
39 | | - local InstanceCreated = false |
40 | | - |
41 | | - if InstanceParent:FindFirstChild(InstanceName) then |
42 | | - SearchInstance = InstanceParent[InstanceName] |
| 47 | +function Module:LoadLibrary(Index: string) |
| 48 | + if Libraries[Index] then |
| 49 | + return require(Libraries[Index]) |
43 | 50 | else |
44 | | - InstanceCreated = true |
45 | | - SearchInstance = Instance.new(InstanceClass) |
46 | | - SearchInstance.Name = InstanceName |
47 | | - SearchInstance.Parent = InstanceParent |
| 51 | + assert(not RunService:IsServer(), "The library \"" .. Index .. "\" does not exist!") |
| 52 | + |
| 53 | + LibraryThreadCache[coroutine.running()] = Index |
| 54 | + return require(coroutine.yield()) |
48 | 55 | end |
49 | | - |
50 | | - return SearchInstance, InstanceCreated |
51 | 56 | end |
52 | 57 |
|
53 | | -local function BindToTag(Tag, Callback) |
54 | | - CollectionService:GetInstanceAddedSignal(Tag):Connect(Callback) |
55 | | - |
56 | | - for _, TaggedItem in next, CollectionService:GetTagged(Tag) do |
57 | | - Callback(TaggedItem) |
| 58 | +function Module:LoadLibraryOnClient(...) |
| 59 | + if RunService:IsClient() then |
| 60 | + return self:LoadLibrary(...) |
58 | 61 | end |
59 | 62 | end |
60 | 63 |
|
61 | | -function CollectionMetatable:__newindex(Index, Value) |
62 | | - rawset(self, Index, Value) |
63 | | - if Index:sub(1, 1) == "_" then return end |
64 | | - |
65 | | - for BindableEvent, ExpectedIndex in next, self._WaitCache do |
66 | | - if Index == ExpectedIndex then |
67 | | - spawn(function() |
68 | | - BindableEvent:Fire(Value) |
69 | | - BindableEvent:Destroy() |
70 | | - end) |
71 | | - end |
| 64 | +function Module:LoadLibraryOnServer(...) |
| 65 | + if RunService:IsServer() then |
| 66 | + return self:LoadLibrary(...) |
72 | 67 | end |
73 | 68 | end |
74 | 69 |
|
75 | | -do Module.Libraries = setmetatable({}, CollectionMetatable) |
76 | | - Module.Libraries._Folder = Retrieve("Libraries", "Folder", ReplicatedStorage) |
77 | | - Module.Libraries._WaitCache = {} |
78 | | - |
79 | | - BindToTag("oLibrary", function(Object) |
80 | | - Module.Libraries[Object.Name] = Object |
81 | | - |
82 | | - if CollectionService:HasTag(Object, "ForceReplicate") then |
83 | | - Object.Parent = Module.Libraries._Folder |
84 | | - end |
85 | | - end) |
86 | | - |
87 | | - function Module:LoadLibrary(Index) |
88 | | - if self.Libraries[Index] then |
89 | | - return require(self.Libraries[Index]) |
90 | | - else |
91 | | - assert(IsClient, "The library \"" .. Index .. "\" does not exist!") |
92 | | - printd("The client is yielding for the library \"" .. Index .. "\".") |
93 | | - |
94 | | - --/ Coroutine yielding has been temporarily replaced with BindableEvents due to Roblox issues. |
95 | | - |
96 | | - local BindableEvent = Instance.new("BindableEvent") |
97 | | - BindableEvent.Parent = script |
98 | | - |
99 | | - self.Libraries._WaitCache[BindableEvent] = Index |
100 | | - return require(BindableEvent.Event:Wait()) |
101 | | - end |
| 70 | +function Module:GetLocal(InstanceClass: string, InstanceName: string): Instance |
| 71 | + return Retrieve(InstanceName, InstanceClass, (Retrieve("Local" .. InstanceClass, "Folder", script))) |
| 72 | +end |
| 73 | + |
| 74 | +function Module:WaitFor(InstanceClass: string, InstanceName: string): Instance |
| 75 | + return Retrieve(InstanceClass, "Folder", script, RunService:IsClient()):WaitForChild(InstanceName, math.huge) |
| 76 | +end |
| 77 | + |
| 78 | +function Module:Get(InstanceClass: string, InstanceName: string): Instance |
| 79 | + local SetFolder = Retrieve(InstanceClass, "Folder", script, RunService:IsClient()) |
| 80 | + local Item = SetFolder:FindFirstChild(InstanceName) |
| 81 | + |
| 82 | + if Item then |
| 83 | + return Item |
| 84 | + elseif RunService:IsClient() then |
| 85 | + return SetFolder:WaitForChild(InstanceName) |
| 86 | + else |
| 87 | + return Retrieve(InstanceName, InstanceClass, SetFolder) |
102 | 88 | end |
103 | | - |
104 | | - function Module:LoadLibraryOnClient(...) |
105 | | - if IsClient then |
106 | | - return self:LoadLibrary(...) |
| 89 | +end |
| 90 | + |
| 91 | +Module._BindToTag("oLibrary", function(Object) |
| 92 | + Libraries[Object.Name] = Object |
| 93 | + |
| 94 | + for Thread, WantedName in next, LibraryThreadCache do |
| 95 | + if Object.Name == WantedName then |
| 96 | + LibraryThreadCache[Thread] = nil |
| 97 | + task.spawn(Thread, Object) |
107 | 98 | end |
108 | 99 | end |
109 | | - |
110 | | - function Module:LoadLibraryOnServer(...) |
111 | | - if IsServer then |
112 | | - return self:LoadLibrary(...) |
| 100 | +end) |
| 101 | + |
| 102 | +local function Initialize() |
| 103 | + if RunService:IsServer() then |
| 104 | + if script:GetAttribute("ServerHandled") then |
| 105 | + return |
113 | 106 | end |
| 107 | + |
| 108 | + script:SetAttribute("ServerHandled", true) |
| 109 | + |
| 110 | + local oStarterPlayerScripts = Retrieve("StarterPlayerScripts", "Folder", script) |
| 111 | + local oStarterCharacterScripts = Retrieve("StarterCharacterScripts", "Folder", script) |
| 112 | + local function Reparent(Child, NewParent) |
| 113 | + if Child:IsA("LocalScript") and not Child.Disabled then |
| 114 | + Child:SetAttribute("EnableOnceReady", true) |
| 115 | + Child.Disabled = true |
| 116 | + end |
| 117 | + |
| 118 | + Child.Parent = Retrieve(NewParent, "Folder", script) |
| 119 | + end |
| 120 | + |
| 121 | + Module._BindToTag("oHandled", function(LuaSourceContainer: Instance) |
| 122 | + local RunsOn = LuaSourceContainer:GetAttribute("RunsOn") or "Empty" |
| 123 | + |
| 124 | + if LuaSourceContainer:IsA("LocalScript") then |
| 125 | + if RunsOn == "Player" then |
| 126 | + task.defer(Reparent, LuaSourceContainer, "StarterPlayerScripts") |
| 127 | + elseif RunsOn == "Character" then |
| 128 | + task.defer(Reparent, LuaSourceContainer, "StarterCharacterScripts") |
| 129 | + else |
| 130 | + warn(string.format([[Unknown RunsOn type "%s" on %s]], RunsOn, LuaSourceContainer:GetFullName())) |
| 131 | + end |
| 132 | + elseif LuaSourceContainer:IsA("Script") then |
| 133 | + if RunsOn == "Player" then |
| 134 | + warn(string.format([[Invalid RunsOn type "%s" on %s]], RunsOn, LuaSourceContainer:GetFullName())) |
| 135 | + elseif RunsOn == "Character" then |
| 136 | + LuaSourceContainer.Parent = StarterCharacterScripts |
| 137 | + |
| 138 | + for _, Player in next, PlayerService:GetPlayers() do |
| 139 | + if Player.Character then |
| 140 | + LuaSourceContainer:Clone().Parent = Player.Character |
| 141 | + end |
| 142 | + end |
| 143 | + else |
| 144 | + warn(string.format([[Unknown RunsOn type "%s" on %s]], RunsOn, LuaSourceContainer:GetFullName())) |
| 145 | + end |
| 146 | + elseif LuaSourceContainer:IsA("ModuleScript") then |
| 147 | + warn(string.format([[Invalid tag "oHandled" applied to %s]], LuaSourceContainer:GetFullName())) |
| 148 | + end |
| 149 | + end) |
| 150 | + |
| 151 | + Module._BindToTag("oLibrary", function(LuaSourceContainer: Instance) |
| 152 | + if LuaSourceContainer:GetAttribute("ForceReplicate") then |
| 153 | + LuaSourceContainer.Parent = Retrieve("Libraries", "Folder", script) |
| 154 | + end |
| 155 | + end) |
| 156 | + |
| 157 | + Module._BindToTag("ForceReplicate", function(LuaSourceContainer: Instance) |
| 158 | + LuaSourceContainer.Parent = Retrieve("Libraries", "Folder", script) |
| 159 | + end) |
| 160 | + |
| 161 | + Module._BindToTag("StarterPlayerScripts", function(LuaSourceContainer: Instance) |
| 162 | + if LuaSourceContainer:IsA("LocalScript") then |
| 163 | + task.defer(Reparent, LuaSourceContainer, "StarterPlayerScripts") |
| 164 | + else |
| 165 | + warn(string.format([[Invalid tag "StarterPlayerScripts" applied to %s]], LuaSourceContainer:GetFullName())) |
| 166 | + end |
| 167 | + end) |
| 168 | + |
| 169 | + Module._BindToTag("StarterCharacterScripts", function(LuaSourceContainer: Instance) |
| 170 | + if LuaSourceContainer:IsA("LocalScript") then |
| 171 | + task.defer(Reparent, LuaSourceContainer, "StarterCharacterScripts") |
| 172 | + elseif LuaSourceContainer:IsA("Script") then |
| 173 | + LuaSourceContainer.Parent = StarterCharacterScripts |
| 174 | + |
| 175 | + for _, Player in next, PlayerService:GetPlayers() do |
| 176 | + if Player.Character then |
| 177 | + LuaSourceContainer:Clone().Parent = Player.Character |
| 178 | + end |
| 179 | + end |
| 180 | + else |
| 181 | + warn(string.format([[Invalid tag "StarterCharacterScripts" applied to %s]], LuaSourceContainer:GetFullName())) |
| 182 | + end |
| 183 | + end) |
| 184 | + elseif RunService:IsClient() then |
| 185 | + if script:GetAttribute("ClientHandled") then |
| 186 | + return |
| 187 | + end |
| 188 | + |
| 189 | + script:SetAttribute("ClientHandled", true) |
| 190 | + |
| 191 | + local Player = PlayerService.LocalPlayer |
| 192 | + local PlayerScripts = Player:WaitForChild("PlayerScripts") |
| 193 | + local oStarterPlayerScripts = script:WaitForChild("StarterPlayerScripts") |
| 194 | + local oStarterCharacterScripts = script:WaitForChild("StarterCharacterScripts") |
| 195 | + local function Reparent(Child, NewParent) |
| 196 | + Child.Parent = NewParent |
| 197 | + |
| 198 | + if Child:IsA("LocalScript") and Child:GetAttribute("EnableOnceReady") then |
| 199 | + Child.Disabled = false |
| 200 | + end |
| 201 | + end |
| 202 | + |
| 203 | + if script:FindFirstAncestorWhichIsA("PlayerGui") then |
| 204 | + task.defer(Reparent, script:Clone(), PlayerScripts) |
| 205 | + task.wait() |
| 206 | + |
| 207 | + script.Disabled = true |
| 208 | + script:Destroy() |
| 209 | + |
| 210 | + return |
| 211 | + end |
| 212 | + |
| 213 | + Module._BindFunction(function(Child: Instance) |
| 214 | + task.defer(Reparent, Child, PlayerScripts) |
| 215 | + end, oStarterPlayerScripts.ChildAdded, oStarterPlayerScripts:GetChildren()) |
| 216 | + |
| 217 | + Module._BindFunction(function(Child: Instance) |
| 218 | + if Player.Character then |
| 219 | + task.defer(Reparent, Child:Clone(), Player.Character) |
| 220 | + end |
| 221 | + end, oStarterCharacterScripts.ChildAdded, oStarterCharacterScripts:GetChildren()) |
| 222 | + |
| 223 | + Module._BindFunction(function(Character: Instance) |
| 224 | + for _, Child in next, oStarterCharacterScripts:GetChildren() do |
| 225 | + task.spawn(Reparent, Child:Clone(), Character) |
| 226 | + end |
| 227 | + end, Player.CharacterAdded, {Player.Character}) |
114 | 228 | end |
115 | 229 | end |
116 | 230 |
|
117 | | -for SetName, SetClass in next, RetrievalSets do |
118 | | - local SetFolder = Retrieve(SetName, "Folder", ReplicatedStorage) |
119 | | - |
120 | | - Module["GetLocal" .. SetName] = function(self, ItemName) |
121 | | - return Retrieve(ItemName, SetClass, SetFolder) |
122 | | - end |
123 | | - |
124 | | - Module["WaitFor" .. SetName] = function(self, ItemName) |
125 | | - return SetFolder:WaitForChild(ItemName, math.huge) |
126 | | - end |
127 | | - |
128 | | - Module["Get" .. SetName] = function(self, ItemName) |
129 | | - local Item = SetFolder:FindFirstChild(ItemName) |
130 | | - if Item then return Item end |
131 | | - |
132 | | - if IsClient then |
133 | | - return SetFolder:WaitForChild(ItemName) |
134 | | - else |
135 | | - return self["GetLocal" .. SetName](self, ItemName) |
| 231 | +do --/ LEGACY SUPPORT |
| 232 | + for _, SetClass in next, {"RemoteEvent", "RemoteFunction", "BindableEvent", "BindableFunction"} do |
| 233 | + local SetFolder = Retrieve(SetClass, "Folder", ReplicatedStorage) |
| 234 | + |
| 235 | + Module["GetLocal" .. SetClass] = function(self, ItemName) |
| 236 | + return Retrieve(ItemName, SetClass, SetFolder) |
| 237 | + end |
| 238 | + |
| 239 | + Module["WaitFor" .. SetClass] = function(self, ItemName) |
| 240 | + return SetFolder:WaitForChild(ItemName, math.huge) |
| 241 | + end |
| 242 | + |
| 243 | + Module["Get" .. SetClass] = function(self, ItemName) |
| 244 | + local Item = SetFolder:FindFirstChild(ItemName) |
| 245 | + if Item then return Item end |
| 246 | + |
| 247 | + if RunService:IsClient() then |
| 248 | + return SetFolder:WaitForChild(ItemName) |
| 249 | + else |
| 250 | + return self["GetLocal" .. SetClass](self, ItemName) |
| 251 | + end |
136 | 252 | end |
137 | 253 | end |
138 | 254 | end |
139 | 255 |
|
140 | | -if not IsClient then |
141 | | - BindToTag("StarterCharacterScripts", function(Object) |
142 | | - Object.Parent = StarterPlayer.StarterCharacterScripts |
143 | | - CollectionService:RemoveTag(Object, "StarterCharacterScripts") |
144 | | - end) |
145 | | - |
146 | | - BindToTag("StarterPlayerScripts", function(Object) |
147 | | - Object.Parent = StarterPlayer.StarterPlayerScripts |
148 | | - CollectionService:RemoveTag(Object, "StarterPlayerScripts") |
149 | | - end) |
150 | | -end |
| 256 | +--// Triggers |
| 257 | + |
| 258 | +task.defer(Initialize) |
151 | 259 |
|
152 | 260 | return Module |
0 commit comments