@quenty/loader 10.0.0 → 10.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [10.1.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/loader@10.0.0...@quenty/loader@10.1.0) (2024-03-09)
7
+
8
+ **Note:** Version bump only for package @quenty/loader
9
+
10
+
11
+
12
+
13
+
6
14
  # [10.0.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/loader@9.0.0...@quenty/loader@10.0.0) (2024-02-14)
7
15
 
8
16
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loader",
3
3
  "tree": {
4
- "$path": "src2"
4
+ "$path": "src"
5
5
  }
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/loader",
3
- "version": "10.0.0",
3
+ "version": "10.1.0",
4
4
  "description": "A simple module loader for Roblox",
5
5
  "keywords": [
6
6
  "Roblox",
@@ -26,5 +26,5 @@
26
26
  "publishConfig": {
27
27
  "access": "public"
28
28
  },
29
- "gitHead": "63f949a67b77d3edc98b358cace163da8c789e9f"
29
+ "gitHead": "e0148dde5ca3864389a0faa2da66153a776acc1e"
30
30
  }
@@ -1,236 +1,3 @@
1
- --[=[
2
- @private
3
- @class LoaderUtils
4
- ]=]
1
+ -- Literally here just so old loading code works
5
2
 
6
- local loader = script.Parent
7
- local BounceTemplateUtils = require(script.Parent.BounceTemplateUtils)
8
- local GroupInfoUtils = require(script.Parent.GroupInfoUtils)
9
- local PackageInfoUtils = require(script.Parent.PackageInfoUtils)
10
- local ScriptInfoUtils = require(script.Parent.ScriptInfoUtils)
11
- local Utils = require(script.Parent.Utils)
12
-
13
- local LoaderUtils = {}
14
- LoaderUtils.Utils = Utils -- TODO: Remove this
15
-
16
- LoaderUtils.ContextTypes = Utils.readonly({
17
- CLIENT = "client";
18
- SERVER = "server";
19
- })
20
- LoaderUtils.IncludeBehavior = Utils.readonly({
21
- NO_INCLUDE = "noInclude";
22
- INCLUDE_ONLY = "includeOnly";
23
- INCLUDE_SHARED = "includeShared";
24
- })
25
-
26
- function LoaderUtils.toWallyFormat(instance, isPlugin)
27
- assert(typeof(instance) == "Instance", "Bad instance")
28
- assert(type(isPlugin) == "boolean", "Bad isPlugin")
29
-
30
- local topLevelPackages = {}
31
- LoaderUtils.discoverTopLevelPackages(topLevelPackages, instance)
32
- LoaderUtils.injectLoader(topLevelPackages)
33
-
34
- local packageInfoList = {}
35
- local packageInfoMap = {}
36
- local defaultReplicationType = isPlugin
37
- and ScriptInfoUtils.ModuleReplicationTypes.PLUGIN
38
- or ScriptInfoUtils.ModuleReplicationTypes.SHARED
39
-
40
- for _, folder in pairs(topLevelPackages) do
41
- local packageInfo = PackageInfoUtils.getOrCreatePackageInfo(folder, packageInfoMap, "", defaultReplicationType)
42
- table.insert(packageInfoList, packageInfo)
43
- end
44
-
45
- PackageInfoUtils.fillDependencySet(packageInfoList)
46
-
47
- if isPlugin then
48
- local pluginGroup = GroupInfoUtils.groupPackageInfos(packageInfoList, ScriptInfoUtils.ModuleReplicationTypes.PLUGIN)
49
- local publishSet = LoaderUtils.getPublishPackageInfoSet(packageInfoList)
50
-
51
- local pluginFolder = Instance.new("Folder")
52
- pluginFolder.Name = "PluginPackages"
53
-
54
- LoaderUtils.reifyGroupList(pluginGroup, publishSet, pluginFolder, ScriptInfoUtils.ModuleReplicationTypes.PLUGIN)
55
-
56
- return pluginFolder
57
- else
58
- local clientGroupList = GroupInfoUtils.groupPackageInfos(packageInfoList, ScriptInfoUtils.ModuleReplicationTypes.CLIENT)
59
- local serverGroupList = GroupInfoUtils.groupPackageInfos(packageInfoList, ScriptInfoUtils.ModuleReplicationTypes.SERVER)
60
- local sharedGroupList = GroupInfoUtils.groupPackageInfos(packageInfoList, ScriptInfoUtils.ModuleReplicationTypes.SHARED)
61
- local publishSet = LoaderUtils.getPublishPackageInfoSet(packageInfoList)
62
-
63
- local clientFolder = Instance.new("Folder")
64
- clientFolder.Name = "Packages"
65
-
66
- local sharedFolder = Instance.new("Folder")
67
- sharedFolder.Name = "SharedPackages"
68
-
69
- local serverFolder = Instance.new("Folder")
70
- serverFolder.Name = "Packages"
71
-
72
- LoaderUtils.reifyGroupList(clientGroupList, publishSet, clientFolder, ScriptInfoUtils.ModuleReplicationTypes.CLIENT)
73
- LoaderUtils.reifyGroupList(serverGroupList, publishSet, serverFolder, ScriptInfoUtils.ModuleReplicationTypes.SERVER)
74
- LoaderUtils.reifyGroupList(sharedGroupList, publishSet, sharedFolder, ScriptInfoUtils.ModuleReplicationTypes.SHARED)
75
-
76
- return clientFolder, serverFolder, sharedFolder
77
- end
78
- end
79
-
80
- function LoaderUtils.isPackage(folder)
81
- assert(typeof(folder) == "Instance", "Bad instance")
82
-
83
- for _, item in pairs(folder:GetChildren()) do
84
- if item:IsA("Folder") or item:IsA("Camera") then
85
- if item.Name == "Server"
86
- or item.Name == "Client"
87
- or item.Name == "Shared"
88
- or item.Name == ScriptInfoUtils.DEPENDENCY_FOLDER_NAME then
89
- return true
90
- end
91
- end
92
- end
93
-
94
- return false
95
- end
96
-
97
- function LoaderUtils.injectLoader(topLevelPackages)
98
- for _, item in pairs(topLevelPackages) do
99
- -- If we're underneath the hierachy or if we're in the actual item...
100
- if item == loader or loader:IsDescendantOf(item) then
101
- return
102
- end
103
- end
104
-
105
- -- We need the loader injected!
106
- table.insert(topLevelPackages, loader)
107
- end
108
-
109
- function LoaderUtils.discoverTopLevelPackages(packages, instance)
110
- assert(type(packages) == "table", "Bad packages")
111
- assert(typeof(instance) == "Instance", "Bad instance")
112
-
113
- if LoaderUtils.isPackage(instance) then
114
- table.insert(packages, instance)
115
- elseif instance:IsA("ObjectValue") then
116
- local linkedValue = instance.Value
117
- if linkedValue and LoaderUtils.isPackage(linkedValue) then
118
- table.insert(packages, linkedValue)
119
- end
120
- else
121
- -- Loop through all folders
122
- for _, item in pairs(instance:GetChildren()) do
123
- if item:IsA("Folder") or item:IsA("Camera") then
124
- LoaderUtils.discoverTopLevelPackages(packages, item)
125
- elseif item:IsA("ObjectValue") then
126
- local linkedValue = item.Value
127
- if linkedValue and LoaderUtils.isPackage(linkedValue) then
128
- table.insert(packages, linkedValue)
129
- end
130
- elseif item:IsA("ModuleScript") then
131
- table.insert(packages, item)
132
- end
133
- end
134
- end
135
- end
136
-
137
- function LoaderUtils.reifyGroupList(groupInfoList, publishSet, parent, replicationMode)
138
- assert(type(groupInfoList) == "table", "Bad groupInfoList")
139
- assert(type(publishSet) == "table", "Bad publishSet")
140
- assert(typeof(parent) == "Instance", "Bad parent")
141
- assert(type(replicationMode) == "string", "Bad replicationMode")
142
-
143
- local folder = Instance.new("Folder")
144
- folder.Name = "_Index"
145
-
146
- for _, groupInfo in pairs(groupInfoList) do
147
- if LoaderUtils.needToReify(groupInfo, replicationMode) then
148
- LoaderUtils.reifyGroup(groupInfo, folder, replicationMode)
149
- end
150
- end
151
-
152
- -- Publish
153
- for packageInfo, _ in pairs(publishSet) do
154
- for scriptName, scriptInfo in pairs(packageInfo.scriptInfoLookup[replicationMode]) do
155
- local link = BounceTemplateUtils.create(scriptInfo.instance, scriptName)
156
- link.Parent = parent
157
- end
158
- end
159
-
160
- folder.Parent = parent
161
- end
162
-
163
- function LoaderUtils.getPublishPackageInfoSet(packageInfoList)
164
- local packageInfoSet = {}
165
- for _, packageInfo in pairs(packageInfoList) do
166
- packageInfoSet[packageInfo] = true
167
- -- First level declared dependencies too (assuming we're importing just one item)
168
- for dependentPackageInfo, _ in pairs(packageInfo.explicitDependencySet) do
169
- packageInfoSet[dependentPackageInfo] = true
170
- end
171
- end
172
- return packageInfoSet
173
- end
174
-
175
- function LoaderUtils.needToReify(groupInfo, replicationMode)
176
- for _, scriptInfo in pairs(groupInfo.packageScriptInfoMap) do
177
- if scriptInfo.replicationMode == replicationMode then
178
- return true
179
- end
180
- end
181
-
182
- return false
183
- end
184
-
185
- function LoaderUtils.reifyGroup(groupInfo, parent, replicationMode)
186
- assert(type(groupInfo) == "table", "Bad groupInfo")
187
- assert(typeof(parent) == "Instance", "Bad parent")
188
- assert(type(replicationMode) == "string", "Bad replicationMode")
189
-
190
- local folder = Instance.new("Folder")
191
- folder.Name = assert(next(groupInfo.packageSet).fullName, "Bad package fullName")
192
-
193
- for scriptName, scriptInfo in pairs(groupInfo.packageScriptInfoMap) do
194
- assert(scriptInfo.name == scriptName, "Bad scriptInfo.name")
195
-
196
- if scriptInfo.replicationMode == replicationMode then
197
- if scriptInfo.instance == loader and loader.Parent == game:GetService("ReplicatedStorage") then
198
- -- Hack to prevent reparenting of loader in legacy mode
199
- local link = BounceTemplateUtils.create(scriptInfo.instance, scriptName)
200
- link.Parent = folder
201
- else
202
- scriptInfo.instance.Name = scriptName
203
- scriptInfo.instance.Parent = folder
204
- end
205
- else
206
- if scriptInfo.instance == loader then
207
- local link = BounceTemplateUtils.create(scriptInfo.instance, scriptName)
208
- link.Parent = folder
209
- else
210
- -- Turns out creating these links are a LOT faster than cloning a module script
211
- local link = BounceTemplateUtils.createLink(scriptInfo.instance, scriptName)
212
- link.Parent = folder
213
- end
214
- end
215
- end
216
-
217
- -- Link all of the other dependencies
218
- for scriptName, scriptInfo in pairs(groupInfo.scriptInfoMap) do
219
- assert(scriptInfo.name == scriptName, "Bad scriptInfo.name")
220
-
221
- if not groupInfo.packageScriptInfoMap[scriptName] then
222
- if scriptInfo.instance == loader then
223
- local link = BounceTemplateUtils.create(scriptInfo.instance, scriptName)
224
- link.Parent = folder
225
- else
226
- -- Turns out creating these links are a LOT faster than cloning a module script
227
- local link = BounceTemplateUtils.createLink(scriptInfo.instance, scriptName)
228
- link.Parent = folder
229
- end
230
- end
231
- end
232
-
233
- folder.Parent = parent
234
- end
235
-
236
- return LoaderUtils
3
+ error("Not implemented! Please require LoaderUtils.Parent. This file acts as a marker for old loader versions.")
package/src/Utils.lua CHANGED
@@ -6,7 +6,7 @@
6
6
  local Utils = {}
7
7
 
8
8
  local function errorOnIndex(_, index)
9
- error(("Bad index %q"):format(tostring(index)), 2)
9
+ error(string.format("Bad index %q", tostring(index)), 2)
10
10
  end
11
11
 
12
12
  local READ_ONLY_METATABLE = {
package/src/init.lua CHANGED
@@ -1,137 +1,188 @@
1
1
  --[=[
2
- Loads Nevermore and handles loading!
2
+ Primary loader which handles bootstrapping different scenarios quickly
3
3
 
4
- This is a centralized loader that handles the following scenarios:
5
-
6
- * Specific layouts for npm node_modules
7
- * Layouts for node_modules being symlinked
8
- * Multiple versions of modules being used in conjunction with each other
9
- * Relative path requires
10
- * Require by name
11
- * Replication to client and server
12
-
13
- @class Loader
4
+ @class loader
14
5
  ]=]
15
6
 
16
7
  local ReplicatedStorage = game:GetService("ReplicatedStorage")
17
- local ServerScriptService = game:GetService("ServerScriptService")
18
8
  local RunService = game:GetService("RunService")
19
9
 
20
- local LegacyLoader = require(script.LegacyLoader)
21
- local StaticLegacyLoader = require(script.StaticLegacyLoader)
22
- local LoaderUtils = require(script.LoaderUtils)
23
-
24
- local loader, metatable
25
- if RunService:IsRunning() then
26
- loader = LegacyLoader.new(script)
27
- metatable = {
28
- __call = function(_self, value)
29
- return loader:Require(value)
30
- end;
31
- __index = function(_self, key)
32
- return loader:Require(key)
33
- end;
34
- }
35
- else
36
- loader = StaticLegacyLoader.new()
37
- metatable = {
38
- __call = function(_self, value)
39
- local env = getfenv(2)
40
- return loader:Require(env.script, value)
41
- end;
42
- __index = function(_self, key)
43
- local env = getfenv(2)
44
- return loader:Require(env.script, key)
45
- end;
46
- }
10
+ local DependencyUtils = require(script.Dependencies.DependencyUtils)
11
+ local LoaderLinkCreator = require(script.LoaderLink.LoaderLinkCreator)
12
+ local LoaderLinkUtils = require(script.LoaderLink.LoaderLinkUtils)
13
+ local Maid = require(script.Maid)
14
+ local PackageTrackerProvider = require(script.Dependencies.PackageTrackerProvider)
15
+ local ReplicationType = require(script.Replication.ReplicationType)
16
+ local ReplicationTypeUtils = require(script.Replication.ReplicationTypeUtils)
17
+ local Replicator = require(script.Replication.Replicator)
18
+ local ReplicatorReferences = require(script.Replication.ReplicatorReferences)
19
+
20
+ local GLOBAL_PACKAGE_TRACKER = PackageTrackerProvider.new()
21
+
22
+ local Loader = {}
23
+ Loader.__index = Loader
24
+ Loader.ClassName = "Loader"
25
+
26
+ function Loader.new(packages, replicationType)
27
+ assert(typeof(packages) == "Instance", "Bad packages")
28
+ assert(ReplicationTypeUtils.isReplicationType(replicationType), "Bad replicationType")
29
+
30
+ local self = setmetatable({}, Loader)
31
+
32
+ self._maid = Maid.new()
33
+
34
+ self._replicationType = assert(replicationType, "No replicationType")
35
+ self._packages = assert(packages, "No packages")
36
+
37
+ return self
47
38
  end
48
39
 
49
- --[=[
50
- Bootstraps the game by replicating packages to server, client, and
51
- shared.
40
+ function Loader.bootstrapGame(packages)
41
+ assert(typeof(packages) == "Instance", "Bad packages")
52
42
 
53
- ```lua
54
- local ServerScriptService = game:GetService("ServerScriptService")
43
+ local self = Loader.new(packages, ReplicationTypeUtils.inferReplicationType())
55
44
 
56
- local loader = ServerScriptService:FindFirstChild("LoaderUtils", true).Parent
57
- local require = require(loader).bootstrapGame(ServerScriptService.ik)
58
- ```
45
+ if self._replicationType == ReplicationType.SERVER then
46
+ self:_setupLoaderPopulation()
59
47
 
60
- :::info
61
- The game must be running to do this bootstrapping operation.
62
- :::
48
+ -- Trade off security for performance
49
+ if RunService:IsStudio() then
50
+ packages.Parent = ReplicatedStorage
51
+ else
52
+ self:_setupClientReplication()
53
+ end
54
+ end
63
55
 
64
- @server
65
- @function bootstrapGame
66
- @param packageFolder Instance
67
- @return Folder -- serverFolder
68
- @within Loader
69
- ]=]
70
- local function bootstrapGame(packageFolder)
71
- assert(typeof(packageFolder) == "Instance", "Bad instance")
72
- assert(RunService:IsRunning(), "Game must be running")
56
+ GLOBAL_PACKAGE_TRACKER:AddPackageRoot(packages)
57
+
58
+ -- We need to wait here once we "populate" the loader so the Ancestry/query events can
59
+ -- fire in Signal deferred mode.
60
+ self:_waitForNextDeferFrame()
61
+
62
+ return self
63
+ end
64
+
65
+ function Loader.bootstrapPlugin(packages)
66
+ assert(typeof(packages) == "Instance", "Bad packages")
67
+
68
+ local self = Loader.new(packages, ReplicationType.PLUGIN)
69
+
70
+ self:_setupLoaderPopulation()
71
+
72
+ GLOBAL_PACKAGE_TRACKER:AddPackageRoot(packages)
73
+
74
+ -- We need to wait here once we "populate" the loader so the Ancestry/query events can
75
+ -- fire in Signal deferred mode.
76
+ self:_waitForNextDeferFrame()
77
+
78
+ return self
79
+ end
80
+
81
+ function Loader.load(packagesOrModuleScript)
82
+ assert(typeof(packagesOrModuleScript) == "Instance", "Bad packagesOrModuleScript")
73
83
 
74
- loader:Lock()
84
+ local self = Loader.new(packagesOrModuleScript, ReplicationTypeUtils.inferReplicationType())
75
85
 
76
- local clientFolder, serverFolder, sharedFolder = LoaderUtils.toWallyFormat(packageFolder, false)
86
+ return self
87
+ end
77
88
 
78
- clientFolder.Parent = ReplicatedStorage
79
- sharedFolder.Parent = ReplicatedStorage
80
- serverFolder.Parent = ServerScriptService
89
+ function Loader:__index(request)
90
+ if Loader[request] then
91
+ return Loader[request]
92
+ end
81
93
 
82
- return serverFolder, clientFolder, sharedFolder
94
+ return self:_findDependency(request)
83
95
  end
84
96
 
85
- local function bootstrapPlugin(packageFolder)
86
- assert(typeof(packageFolder) == "Instance", "Bad instance")
87
- loader = LegacyLoader.new(script)
88
- loader:Lock()
97
+ function Loader:__call(request)
98
+ if type(request) == "string" then
99
+ local module = self:_findDependency(request)
100
+ return require(module)
101
+ else
102
+ return require(request)
103
+ end
104
+ end
89
105
 
90
- local pluginFolder = LoaderUtils.toWallyFormat(packageFolder, true)
91
- pluginFolder.Parent = packageFolder
106
+ function Loader:_waitForNextDeferFrame()
107
+ local signal = Instance.new("BindableEvent")
108
+ task.defer(function()
109
+ signal:Fire()
110
+ end)
111
+ signal.Event:Wait()
112
+ signal:Destroy()
113
+ end
114
+
115
+ function Loader:_findDependency(request)
116
+ assert(type(request) == "string", "Bad request")
92
117
 
93
- return function(value)
94
- if type(value) == "string" then
95
- if pluginFolder:FindFirstChild(value) then
96
- return require(pluginFolder:FindFirstChild(value))
118
+ local packageTracker = GLOBAL_PACKAGE_TRACKER:FindPackageTracker(self._packages)
119
+ if packageTracker then
120
+ local foundDependency = packageTracker:ResolveDependency(request, self._replicationType)
121
+ if foundDependency then
122
+ return foundDependency
123
+ end
124
+
125
+ -- Otherwise let's fail with an error acknowledging that the module exists
126
+ if self._replicationType == ReplicationType.SERVER or self._replicationType == ReplicationType.SHARED then
127
+ local foundClientDependency = packageTracker:ResolveDependency(request, ReplicationType.CLIENT)
128
+ if foundClientDependency then
129
+ error(string.format("[Loader] - %q is only available on the client", foundClientDependency.Name))
97
130
  end
131
+ end
98
132
 
99
- error(string.format("Unknown module %q", tostring(value)))
100
- else
101
- return require(value)
133
+ if self._replicationType == ReplicationType.CLIENT or self._replicationType == ReplicationType.SHARED then
134
+ local foundServerDependency = packageTracker:ResolveDependency(request, ReplicationType.SERVER)
135
+ if foundServerDependency then
136
+ error(string.format("[Loader] - %q is only available on the server", foundServerDependency.Name))
137
+ end
102
138
  end
103
139
  end
140
+
141
+ -- Just standard dependency search
142
+ local foundBackup = DependencyUtils.findDependency(self._packages, request, self._replicationType)
143
+ if foundBackup then
144
+ if RunService:IsRunning() then
145
+ warn(string.format("[Loader] - Failed to find package %q in package tracker\n%s", request, debug.traceback()))
146
+ end
147
+
148
+ -- Ensure hoarcekat story has a link to use
149
+ -- TODO: Maybe add to global package cache instead...
150
+ local parent = foundBackup.Parent
151
+ if parent and not parent:FindFirstChild("loader") then
152
+ local link = LoaderLinkUtils.create(script, "loader")
153
+ link.Parent = parent
154
+ end
155
+
156
+ return foundBackup
157
+ end
158
+
159
+ -- TODO: Track location and provider install command
160
+ error(string.format("[Loader] - %q is not available. Please make this module or install it to the package requiring it.", request))
161
+ return nil
104
162
  end
105
163
 
106
- --[=[
107
- A type that can be loaded into a module
108
- @type ModuleReference ModuleScript | number | string
109
- @within Loader
110
- ]=]
164
+ function Loader:_setupClientReplication()
165
+ local copy = self._maid:Add(Instance.new("Folder"))
166
+ copy.Name = self._packages.Name
111
167
 
112
- --[=[
113
- Returns a function that can be used to load modules relative
114
- to the script specified.
168
+ local references = ReplicatorReferences.new()
115
169
 
116
- ```lua
117
- local require = require(script.Parent.loader).load(script)
170
+ local replicator = self._maid:Add(Replicator.new(references))
171
+ replicator:SetTarget(copy)
172
+ replicator:ReplicateFrom(self._packages)
118
173
 
119
- local maid = require("Maid")
120
- ```
174
+ self._maid:Add(LoaderLinkCreator.new(copy, references, true))
121
175
 
122
- @function load
123
- @param script Script -- The script to load dependencies for.
124
- @return (moduleReference: ModuleReference) -> any
125
- @within Loader
126
- ]=]
127
- local function handleLoad(moduleScript)
128
- assert(typeof(moduleScript) == "Instance", "Bad moduleScript")
176
+ copy.Parent = ReplicatedStorage
177
+ end
178
+
179
+ function Loader:_setupLoaderPopulation()
180
+ self._maid:Add(LoaderLinkCreator.new(self._packages, nil, true))
181
+ end
129
182
 
130
- return loader:GetLoader(moduleScript)
183
+ function Loader:Destroy()
184
+ self._maid:DoCleaning()
185
+ setmetatable(self, nil)
131
186
  end
132
187
 
133
- return setmetatable({
134
- load = handleLoad;
135
- bootstrapGame = bootstrapGame;
136
- bootstrapPlugin = bootstrapPlugin;
137
- }, metatable)
188
+ return Loader
@@ -1,17 +0,0 @@
1
- --[=[
2
- Bounces the current named script to the expected version of this module
3
-
4
- @private
5
- @class BounceTemplate
6
- ]=]
7
-
8
- local function waitForValue(objectValue)
9
- local value = objectValue.Value
10
- if value then
11
- return value
12
- end
13
-
14
- return objectValue.Changed:Wait()
15
- end
16
-
17
- return require(waitForValue(script:WaitForChild("BounceTarget")))
@@ -1,66 +0,0 @@
1
- --[=[
2
- @class BounceTemplateUtils
3
- @private
4
- ]=]
5
-
6
- local BounceTemplate = script.Parent.BounceTemplate
7
-
8
- local CREATE_ONLY_LINK = false
9
-
10
- local BounceTemplateUtils = {}
11
-
12
- function BounceTemplateUtils.isBounceTemplate(instance)
13
- return instance:GetAttribute("IsBounceTemplate", true) == true
14
- end
15
-
16
- function BounceTemplateUtils.getTarget(instance)
17
- if not BounceTemplateUtils.isBounceTemplate(instance) then
18
- return nil
19
- end
20
-
21
- if instance:IsA("ObjectValue") then
22
- return instance.Value
23
- else
24
- local found = instance:FindFirstChild("BounceTarget")
25
- if found then
26
- return found.Value
27
- else
28
- warn("[BounceTemplateUtils.getTarget] - Bounce template without BounceTarget")
29
- return nil
30
- end
31
- end
32
- end
33
-
34
- function BounceTemplateUtils.create(target, linkName)
35
- assert(typeof(target) == "Instance", "Bad target")
36
- assert(type(linkName) == "string", "Bad linkName")
37
-
38
- if CREATE_ONLY_LINK then
39
- return BounceTemplateUtils.createLink(target, linkName)
40
- end
41
-
42
- local copy = BounceTemplate:Clone()
43
- copy:SetAttribute("IsBounceTemplate", true)
44
- copy.Name = linkName
45
-
46
- local objectValue = Instance.new("ObjectValue")
47
- objectValue.Name = "BounceTarget"
48
- objectValue.Value = target
49
- objectValue.Parent = copy
50
-
51
- return copy
52
- end
53
-
54
- function BounceTemplateUtils.createLink(target, linkName)
55
- assert(typeof(target) == "Instance", "Bad target")
56
- assert(type(linkName) == "string", "Bad linkName")
57
-
58
- local objectValue = Instance.new("ObjectValue")
59
- objectValue.Name = linkName
60
- objectValue.Value = target
61
- objectValue:SetAttribute("IsBounceTemplate", true)
62
-
63
- return objectValue
64
- end
65
-
66
- return BounceTemplateUtils