@quenty/templateprovider 11.6.0 → 11.7.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 +12 -0
- package/README.md +6 -22
- package/package.json +13 -6
- package/src/Shared/Replication/Util/TemplateReplicationModes.lua +13 -0
- package/src/Shared/Replication/Util/TemplateReplicationModesUtils.lua +26 -0
- package/src/Shared/TaggedTemplateProvider.lua +7 -45
- package/src/Shared/TemplateProvider.lua +471 -240
- package/src/Shared/TemplateContainerUtils.lua +0 -39
- /package/src/Shared/{ModuleProvider.lua → Modules/ModuleProvider.lua} +0 -0
- /package/src/Shared/{ModuleProviderFakeLoader.lua → Modules/ModuleProviderFakeLoader.lua} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
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
|
+
# [11.7.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/templateprovider@11.6.0...@quenty/templateprovider@11.7.0) (2024-10-06)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* Modernize template provider to new specifications ([7b5df24](https://github.com/Quenty/NevermoreEngine/commit/7b5df24cd0759668d2a5a7d59f1798bca9ddf0f9))
|
|
12
|
+
* Remove TemplateContainerUtils.reparentFromWorkspaceIfNeeded() ([ecf8939](https://github.com/Quenty/NevermoreEngine/commit/ecf893949ba127d97b45c946cf320cf7b987e9eb))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
6
18
|
# [11.6.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/templateprovider@11.5.0...@quenty/templateprovider@11.6.0) (2024-10-04)
|
|
7
19
|
|
|
8
20
|
**Note:** Version bump only for package @quenty/templateprovider
|
package/README.md
CHANGED
|
@@ -20,28 +20,12 @@ Base of a template retrieval system
|
|
|
20
20
|
npm install @quenty/templateprovider --save
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
##
|
|
24
|
-
Usage is designed to be simple.
|
|
23
|
+
## Deferred replication template behavior
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
1. We want to defer replication of templates until client requests the template
|
|
26
|
+
2. Then we want to send the template to the client via PlayerGui methods
|
|
27
|
+
3. We want to decide whether or not to bother doing this
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
### `TemplateProvider:Init()`
|
|
31
|
-
|
|
32
|
-
Initializes the template provider, downloading components and other things needed
|
|
33
|
-
|
|
34
|
-
### `TemplateProvider:Clone(templateName)`
|
|
35
|
-
|
|
36
|
-
### `TemplateProvider:Get(templateName)`
|
|
37
|
-
|
|
38
|
-
### `TemplateProvider:AddContainer(container)`
|
|
39
|
-
|
|
40
|
-
### `TemplateProvider:RemoveContainer(container)`
|
|
41
|
-
|
|
42
|
-
### `TemplateProvider:IsAvailable(templateName)`
|
|
43
|
-
|
|
44
|
-
### `TemplateProvider:GetAll()`
|
|
45
|
-
|
|
46
|
-
### `TemplateProvider:GetContainers()`
|
|
29
|
+
This will prevent memory usage of unused templates on the client, which happens with the cars and a variety of other game-systems.
|
|
47
30
|
|
|
31
|
+
We can't store the stuff initially in cameras or in team-create Roblox won't replicate the stuff. But we can move on run-time and hope...
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/templateprovider",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.7.0",
|
|
4
4
|
"description": "Base of a template retrieval system",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -25,15 +25,22 @@
|
|
|
25
25
|
"Quenty"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@quenty/baseobject": "^10.
|
|
29
|
-
"@quenty/
|
|
30
|
-
"@quenty/
|
|
28
|
+
"@quenty/baseobject": "^10.7.0",
|
|
29
|
+
"@quenty/brio": "^14.9.0",
|
|
30
|
+
"@quenty/collectionserviceutils": "^8.9.0",
|
|
31
|
+
"@quenty/ducktype": "^5.7.0",
|
|
32
|
+
"@quenty/insertserviceutils": "^10.7.0",
|
|
33
|
+
"@quenty/instanceutils": "^13.9.0",
|
|
34
|
+
"@quenty/loader": "^10.7.0",
|
|
31
35
|
"@quenty/maid": "^3.4.0",
|
|
32
|
-
"@quenty/
|
|
36
|
+
"@quenty/observablecollection": "^12.9.0",
|
|
37
|
+
"@quenty/promise": "^10.7.0",
|
|
38
|
+
"@quenty/promisemaid": "^5.7.0",
|
|
39
|
+
"@quenty/rx": "^13.9.0",
|
|
33
40
|
"@quenty/string": "^3.3.0"
|
|
34
41
|
},
|
|
35
42
|
"publishConfig": {
|
|
36
43
|
"access": "public"
|
|
37
44
|
},
|
|
38
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "67c5dbf46f6f45213812f3f117419a5534936a0b"
|
|
39
46
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
--[=[
|
|
2
|
+
@class TemplateReplicationModesUtils
|
|
3
|
+
]=]
|
|
4
|
+
|
|
5
|
+
local require = require(script.Parent.loader).load(script)
|
|
6
|
+
|
|
7
|
+
local RunService = game:GetService("RunService")
|
|
8
|
+
local TemplateReplicationModes = require("TemplateReplicationModes")
|
|
9
|
+
|
|
10
|
+
local TemplateReplicationModesUtils = {}
|
|
11
|
+
|
|
12
|
+
function TemplateReplicationModesUtils.inferReplicationMode()
|
|
13
|
+
if not RunService:IsRunning() then
|
|
14
|
+
return TemplateReplicationModes.SHARED
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
if RunService:IsServer() then
|
|
18
|
+
return TemplateReplicationModes.SERVER
|
|
19
|
+
elseif RunService:IsClient() then
|
|
20
|
+
return TemplateReplicationModes.CLIENT
|
|
21
|
+
else
|
|
22
|
+
return TemplateReplicationModes.SHARED
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
return TemplateReplicationModesUtils
|
|
@@ -1,59 +1,21 @@
|
|
|
1
1
|
--[=[
|
|
2
2
|
Like a template provider, but it also reparents and retrieves tagged objects
|
|
3
|
+
|
|
3
4
|
@class TaggedTemplateProvider
|
|
4
5
|
]=]
|
|
5
6
|
|
|
6
7
|
local require = require(script.Parent.loader).load(script)
|
|
7
8
|
|
|
8
|
-
local CollectionService = game:GetService("CollectionService")
|
|
9
|
-
local RunService = game:GetService("RunService")
|
|
10
|
-
|
|
11
9
|
local TemplateProvider = require("TemplateProvider")
|
|
10
|
+
local RxCollectionServiceUtils = require("RxCollectionServiceUtils")
|
|
12
11
|
|
|
13
|
-
local TaggedTemplateProvider =
|
|
14
|
-
TaggedTemplateProvider.ClassName = "TaggedTemplateProvider"
|
|
15
|
-
TaggedTemplateProvider.__index = TaggedTemplateProvider
|
|
16
|
-
|
|
17
|
-
function TaggedTemplateProvider.new(containerTagName)
|
|
18
|
-
local self = setmetatable(TemplateProvider.new(), TaggedTemplateProvider)
|
|
19
|
-
|
|
20
|
-
assert(type(containerTagName) == "string", "Bad containerTagName")
|
|
21
|
-
|
|
22
|
-
-- We prefer a default tag name so test scripts can still read assets for testing
|
|
23
|
-
self._tagsToInitializeSet = { [containerTagName] = true }
|
|
24
|
-
|
|
25
|
-
return self
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
function TaggedTemplateProvider:Init()
|
|
29
|
-
assert(not self._maid, "Should not have a maid")
|
|
30
|
-
|
|
31
|
-
getmetatable(TaggedTemplateProvider).Init(self)
|
|
32
|
-
|
|
33
|
-
assert(self._maid, "Should have a maid")
|
|
34
|
-
|
|
35
|
-
for tag, _ in pairs(self._tagsToInitializeSet) do
|
|
36
|
-
self:AddContainersFromTag(tag)
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
function TaggedTemplateProvider:AddContainersFromTag(containerTagName)
|
|
41
|
-
assert(self._maid, "Not initialized")
|
|
42
|
-
assert(type(containerTagName) == "string", "Bad containerTagName")
|
|
43
|
-
|
|
44
|
-
if RunService:IsRunning() then
|
|
45
|
-
self._maid:GiveTask(CollectionService:GetInstanceAddedSignal(containerTagName):Connect(function(inst)
|
|
46
|
-
self:AddContainer(inst)
|
|
47
|
-
end))
|
|
12
|
+
local TaggedTemplateProvider = {}
|
|
48
13
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
end
|
|
14
|
+
function TaggedTemplateProvider.new(providerName, tagName)
|
|
15
|
+
assert(type(providerName) == "string", "bad providerName")
|
|
16
|
+
assert(type(tagName) == "string", "Bad tagName")
|
|
53
17
|
|
|
54
|
-
|
|
55
|
-
self:AddContainer(inst)
|
|
56
|
-
end
|
|
18
|
+
return TemplateProvider.new(providerName, RxCollectionServiceUtils.observeTaggedBrio(tagName))
|
|
57
19
|
end
|
|
58
20
|
|
|
59
21
|
return TaggedTemplateProvider
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
--[=[
|
|
2
|
-
Base of a template retrieval system. Templates can be retrieved from Roblox
|
|
3
|
-
|
|
2
|
+
Base of a template retrieval system. Templates can be retrieved from Roblox and then retrieved by name. If a folder is used
|
|
3
|
+
all of their children are also included as templates, which allows for flexible organization by artists.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Additionally, you can provide template overrides as the last added template will always be used.
|
|
6
6
|
|
|
7
7
|
```lua
|
|
8
|
-
-- shared/
|
|
8
|
+
-- shared/CarTemplates.lua
|
|
9
9
|
|
|
10
|
-
return TemplateProvider.new(
|
|
10
|
+
return TemplateProvider.new(script.Name, script) -- Load locally
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
:::tip
|
|
14
|
+
If the TemplateProvider is initialized on the server, the the templates will be hidden from the client until the
|
|
15
|
+
client requests them.
|
|
16
|
+
|
|
17
|
+
This prevents large amounts of templates from being rendered to the client, taking up memory on the client. This especially
|
|
18
|
+
affects meshes, but can also affect sounds and other similar templates.
|
|
19
|
+
:::
|
|
20
|
+
|
|
13
21
|
```lua
|
|
14
22
|
-- Server
|
|
15
23
|
local serviceBag = ServiceBag.new()
|
|
16
|
-
local templates = serviceBag:GetService(require("
|
|
24
|
+
local templates = serviceBag:GetService(require("CarTemplates"))
|
|
17
25
|
serviceBag:Init()
|
|
18
26
|
serviceBag:Start()
|
|
19
27
|
```
|
|
@@ -21,12 +29,12 @@
|
|
|
21
29
|
```lua
|
|
22
30
|
-- Client
|
|
23
31
|
local serviceBag = ServiceBag.new()
|
|
24
|
-
local templates = serviceBag:GetService(require("
|
|
32
|
+
local templates = serviceBag:GetService(require("CarTemplates"))
|
|
25
33
|
serviceBag:Init()
|
|
26
34
|
serviceBag:Start()
|
|
27
35
|
|
|
28
|
-
templates:
|
|
29
|
-
print("Got crate
|
|
36
|
+
templates:PromiseCloneTemplate("CopCar"):Then(function(crate)
|
|
37
|
+
print("Got crate!")
|
|
30
38
|
end)
|
|
31
39
|
```
|
|
32
40
|
|
|
@@ -35,13 +43,27 @@
|
|
|
35
43
|
|
|
36
44
|
local require = require(script.Parent.loader).load(script)
|
|
37
45
|
|
|
38
|
-
local
|
|
39
|
-
local
|
|
46
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
47
|
+
local HttpService = game:GetService("HttpService")
|
|
40
48
|
|
|
49
|
+
local Brio = require("Brio")
|
|
50
|
+
local DuckTypeUtils = require("DuckTypeUtils")
|
|
41
51
|
local Maid = require("Maid")
|
|
42
|
-
local
|
|
43
|
-
local
|
|
52
|
+
local Observable = require("Observable")
|
|
53
|
+
local ObservableCountingMap = require("ObservableCountingMap")
|
|
54
|
+
local ObservableMapList = require("ObservableMapList")
|
|
44
55
|
local Promise = require("Promise")
|
|
56
|
+
local PromiseMaidUtils = require("PromiseMaidUtils")
|
|
57
|
+
local Remoting = require("Remoting")
|
|
58
|
+
local Rx = require("Rx")
|
|
59
|
+
local RxInstanceUtils = require("RxInstanceUtils")
|
|
60
|
+
local String = require("String")
|
|
61
|
+
local TemplateReplicationModes = require("TemplateReplicationModes")
|
|
62
|
+
local TemplateReplicationModesUtils = require("TemplateReplicationModesUtils")
|
|
63
|
+
|
|
64
|
+
local TOMBSTONE_ID_ATTRIBUTE = "UnreplicatedTemplateId"
|
|
65
|
+
local TOMBSTONE_NAME_POSTFIX_UNLOADED = "_Unloaded"
|
|
66
|
+
local TOMBSTONE_NAME_POSTFIX_LOADED = "_Loaded"
|
|
45
67
|
|
|
46
68
|
local TemplateProvider = {}
|
|
47
69
|
TemplateProvider.ClassName = "TemplateProvider"
|
|
@@ -49,144 +71,220 @@ TemplateProvider.ServiceName = "TemplateProvider"
|
|
|
49
71
|
TemplateProvider.__index = TemplateProvider
|
|
50
72
|
|
|
51
73
|
--[=[
|
|
52
|
-
|
|
53
|
-
@
|
|
54
|
-
@param replicationParent Instance? -- Place to replicate instances to.
|
|
74
|
+
@type Template Instance | Observable<Brio<Instance>> | table
|
|
75
|
+
@within TemplateProvider
|
|
55
76
|
]=]
|
|
56
|
-
function TemplateProvider.new(container, replicationParent)
|
|
57
|
-
local self = setmetatable({}, TemplateProvider)
|
|
58
|
-
|
|
59
|
-
self._replicationParent = replicationParent
|
|
60
|
-
self._containersToInitializeSet = {}
|
|
61
|
-
self._containersToInitializeList = {}
|
|
62
|
-
|
|
63
|
-
if typeof(container) == "Instance" or type(container) == "number" then
|
|
64
|
-
self:_registerContainer(container)
|
|
65
|
-
elseif typeof(container) == "table" then
|
|
66
|
-
for _, item in pairs(container) do
|
|
67
|
-
assert(typeof(item) == "Instance" or type(item) == "number", "Bad item in initialization set")
|
|
68
77
|
|
|
69
|
-
|
|
78
|
+
--[=[
|
|
79
|
+
Constructs a new [TemplateProvider].
|
|
70
80
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
81
|
+
@param providerName string
|
|
82
|
+
@param initialTemplates Template
|
|
83
|
+
]=]
|
|
84
|
+
function TemplateProvider.new(providerName, initialTemplates)
|
|
85
|
+
assert(type(providerName) == "string", "Bad providerName")
|
|
86
|
+
local self = setmetatable({}, TemplateProvider)
|
|
76
87
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
end
|
|
80
|
-
end
|
|
88
|
+
self.ServiceName = assert(providerName, "No providerName")
|
|
89
|
+
self._initialTemplates = initialTemplates
|
|
81
90
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
self:_registerContainer(self._replicationParent)
|
|
91
|
+
if not (self:_isValidTemplate(self._initialTemplates) or self._initialTemplates == nil) then
|
|
92
|
+
error(string.format("[TemplateProvider.%s] - Bad initialTemplates of type %s", self.ServiceName, typeof(initialTemplates)))
|
|
85
93
|
end
|
|
86
94
|
|
|
87
95
|
return self
|
|
88
96
|
end
|
|
89
97
|
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
--[=[
|
|
99
|
+
Returns if the value is a template provider
|
|
92
100
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
@param value any
|
|
102
|
+
@return boolean
|
|
103
|
+
]=]
|
|
104
|
+
|
|
105
|
+
function TemplateProvider.isTemplateProvider(value)
|
|
106
|
+
return DuckTypeUtils.isImplementation(TemplateProvider, value)
|
|
97
107
|
end
|
|
98
108
|
|
|
99
109
|
--[=[
|
|
100
110
|
Initializes the container provider. Should be done via [ServiceBag].
|
|
101
|
-
]=]
|
|
102
|
-
function TemplateProvider:Init()
|
|
103
|
-
assert(not self._initialized, "Already initialized")
|
|
104
111
|
|
|
112
|
+
@param serviceBag ServiceBag
|
|
113
|
+
]=]
|
|
114
|
+
function TemplateProvider:Init(serviceBag)
|
|
115
|
+
assert(not self._serviceBag, "Already initialized")
|
|
116
|
+
self._serviceBag = assert(serviceBag, "No serviceBag")
|
|
105
117
|
self._maid = Maid.new()
|
|
106
|
-
self._initialized = true
|
|
107
|
-
self._registry = {} -- [name] = rawTemplate
|
|
108
|
-
self._containersSet = {} -- [parentOrAssetId] = true
|
|
109
118
|
|
|
110
|
-
self.
|
|
119
|
+
self._replicationMode = TemplateReplicationModesUtils.inferReplicationMode()
|
|
111
120
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
121
|
+
-- There can be multiple templates for a given name
|
|
122
|
+
self._templateMapList = self._maid:Add(ObservableMapList.new())
|
|
123
|
+
self._unreplicatedTemplateMapList = self._maid:Add(ObservableMapList.new())
|
|
124
|
+
|
|
125
|
+
self._containerRootCountingMap = self._maid:Add(ObservableCountingMap.new())
|
|
126
|
+
self._pendingTemplatePromises = {} -- [templateName] = Promise
|
|
127
|
+
|
|
128
|
+
self:_setupTemplateCache()
|
|
115
129
|
end
|
|
116
130
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
131
|
+
function TemplateProvider:_setupTemplateCache()
|
|
132
|
+
if self._replicationMode == TemplateReplicationModes.SERVER then
|
|
133
|
+
self._tombstoneLookup = {}
|
|
134
|
+
self._remoting = self._maid:Add(Remoting.Server.new(ReplicatedStorage, self.ServiceName .. "TemplateProvider"))
|
|
135
|
+
|
|
136
|
+
-- TODO: Maybe de-duplicate and use a centralized service
|
|
137
|
+
self._maid:GiveTask(self._remoting.ReplicateTemplate:Bind(function(player, tombstoneId)
|
|
138
|
+
assert(type(tombstoneId) == "string", "Bad tombstoneId")
|
|
139
|
+
assert(self._tombstoneLookup[tombstoneId], "Not a valid tombstone")
|
|
140
|
+
|
|
141
|
+
-- Stuff doesn't replicate in the PlayerGui
|
|
142
|
+
local playerGui = player:FindFirstChildWhichIsA("PlayerGui")
|
|
143
|
+
if not playerGui then
|
|
144
|
+
return Promise.rejected("No playerGui")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
-- Just group stuff to simplify things
|
|
148
|
+
local replicationParent = playerGui:FindFirstChild("TemplateProviderReplication")
|
|
149
|
+
if not replicationParent then
|
|
150
|
+
replicationParent = Instance.new("Folder")
|
|
151
|
+
replicationParent.Name = "TemplateProviderReplication"
|
|
152
|
+
replicationParent.Archivable = false
|
|
153
|
+
replicationParent.Parent = playerGui
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
local copy = self._tombstoneLookup[tombstoneId]:Clone()
|
|
157
|
+
copy.Parent = playerGui
|
|
158
|
+
|
|
159
|
+
task.delay(0.1, function()
|
|
160
|
+
copy:Remove()
|
|
161
|
+
end)
|
|
124
162
|
|
|
125
|
-
|
|
163
|
+
return copy
|
|
164
|
+
end))
|
|
165
|
+
elseif self._replicationMode == TemplateReplicationModes.CLIENT then
|
|
166
|
+
self._pendingTombstoneRequests = {}
|
|
126
167
|
|
|
127
|
-
|
|
128
|
-
if template then
|
|
129
|
-
return Promise.resolved(self:Clone(templateName))
|
|
168
|
+
self._remoting = self._maid:Add(Remoting.Client.new(ReplicatedStorage, self.ServiceName .. "TemplateProvider"))
|
|
130
169
|
end
|
|
131
170
|
|
|
132
|
-
if
|
|
133
|
-
|
|
134
|
-
|
|
171
|
+
if self._initialTemplates then
|
|
172
|
+
self._maid:GiveTask(self:AddTemplates(self._initialTemplates))
|
|
173
|
+
end
|
|
135
174
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
end
|
|
175
|
+
-- Recursively adds roots, but also de-duplicates them as necessary
|
|
176
|
+
self._maid:GiveTask(self._containerRootCountingMap:ObserveKeysBrio():Subscribe(function(containerBrio)
|
|
177
|
+
if containerBrio:IsDead() then
|
|
178
|
+
return
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
local containerMaid, container = containerBrio:ToMaidAndValue()
|
|
182
|
+
self:_handleContainer(containerMaid, container)
|
|
183
|
+
end))
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
function TemplateProvider:_handleContainer(containerMaid, container)
|
|
187
|
+
if self._replicationMode == TemplateReplicationModes.SERVER
|
|
188
|
+
and not container:IsA("Camera")
|
|
189
|
+
and not container:FindFirstAncestorWhichIsA("Camera") then
|
|
190
|
+
-- Prevent replication to client immediately
|
|
141
191
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
192
|
+
local camera = containerMaid:Add(Instance.new("Camera"))
|
|
193
|
+
camera.Name = "PreventReplication"
|
|
194
|
+
camera.Parent = container
|
|
195
|
+
|
|
196
|
+
local function handleChild(child)
|
|
197
|
+
if child == camera then
|
|
198
|
+
return
|
|
145
199
|
end
|
|
146
|
-
|
|
200
|
+
if child:GetAttribute(TOMBSTONE_ID_ATTRIBUTE) then
|
|
201
|
+
return
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
child.Parent = camera
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
containerMaid:GiveTask(container.ChildAdded:Connect(handleChild))
|
|
208
|
+
|
|
209
|
+
for _, child in pairs(container:GetChildren()) do
|
|
210
|
+
handleChild(child)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
self:_replicateTombstones(containerMaid, camera, container)
|
|
214
|
+
|
|
215
|
+
return
|
|
147
216
|
end
|
|
148
217
|
|
|
149
|
-
|
|
150
|
-
:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
218
|
+
containerMaid:GiveTask(RxInstanceUtils.observeChildrenBrio(container):Subscribe(function(brio)
|
|
219
|
+
if brio:IsDead() then
|
|
220
|
+
return
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
local maid, child = brio:ToMaidAndValue()
|
|
224
|
+
self:_addInstanceTemplate(maid, child)
|
|
225
|
+
end))
|
|
154
226
|
end
|
|
155
227
|
|
|
156
|
-
function TemplateProvider:
|
|
157
|
-
|
|
228
|
+
function TemplateProvider:_replicateTombstones(topMaid, unreplicatedParent, replicatedParent)
|
|
229
|
+
assert(self._replicationMode == TemplateReplicationModes.SERVER, "Only should be invoked on server")
|
|
158
230
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
231
|
+
-- Tombstone each child so the client knows what is replicated
|
|
232
|
+
topMaid:GiveTask(RxInstanceUtils.observeChildrenBrio(unreplicatedParent):Subscribe(function(brio)
|
|
233
|
+
if brio:IsDead() then
|
|
234
|
+
return
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
local maid, child = brio:ToMaidAndValue()
|
|
238
|
+
self:_addInstanceTemplate(maid, child)
|
|
239
|
+
|
|
240
|
+
local tombstoneId = HttpService:GenerateGUID(false)
|
|
241
|
+
|
|
242
|
+
-- Tell the client something exists here
|
|
243
|
+
local tombstone = maid:Add(Instance.new("Folder"))
|
|
244
|
+
tombstone.Name = child.Name .. TOMBSTONE_NAME_POSTFIX_UNLOADED
|
|
245
|
+
tombstone:SetAttribute(TOMBSTONE_ID_ATTRIBUTE, tombstoneId)
|
|
246
|
+
|
|
247
|
+
-- Recursively replicate other tombstones
|
|
248
|
+
if self:_shouldAddChildrenAsTemplates(child) then
|
|
249
|
+
self:_replicateTombstones(maid, child, tombstone)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
self._tombstoneLookup[tombstoneId] = child
|
|
253
|
+
|
|
254
|
+
maid:GiveTask(function()
|
|
255
|
+
self._tombstoneLookup[tombstoneId] = nil
|
|
256
|
+
end)
|
|
162
257
|
|
|
163
|
-
|
|
258
|
+
tombstone.Parent = replicatedParent
|
|
259
|
+
end))
|
|
164
260
|
end
|
|
165
261
|
|
|
166
262
|
--[=[
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
:::info
|
|
170
|
-
If the template name has a prefix of "Template" then it will remove it on the cloned instance.
|
|
171
|
-
:::
|
|
263
|
+
Observes the given template by name
|
|
172
264
|
|
|
173
265
|
@param templateName string
|
|
174
|
-
@return Instance
|
|
266
|
+
@return Observable<Instance>
|
|
175
267
|
]=]
|
|
176
|
-
function TemplateProvider:
|
|
268
|
+
function TemplateProvider:ObserveTemplate(templateName)
|
|
177
269
|
assert(type(templateName) == "string", "templateName must be a string")
|
|
178
270
|
|
|
179
|
-
self:
|
|
271
|
+
return self._templateMapList:ObserveList(templateName):Pipe({
|
|
272
|
+
Rx.switchMap(function(list)
|
|
273
|
+
if not list then
|
|
274
|
+
return Rx.of(nil);
|
|
275
|
+
end
|
|
180
276
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
end
|
|
277
|
+
return list:ObserveAtIndex(-1)
|
|
278
|
+
end);
|
|
279
|
+
})
|
|
280
|
+
end
|
|
186
281
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
282
|
+
function TemplateProvider:ObserveTemplateNamesBrio()
|
|
283
|
+
return self._templateMapList:ObserveKeysBrio()
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
function TemplateProvider:ObserveUnreplicatedTemplateNamesBrio()
|
|
287
|
+
return self._unreplicatedTemplateMapList:ObserveKeysBrio()
|
|
190
288
|
end
|
|
191
289
|
|
|
192
290
|
--[=[
|
|
@@ -195,197 +293,330 @@ end
|
|
|
195
293
|
@param templateName string
|
|
196
294
|
@return Instance?
|
|
197
295
|
]=]
|
|
198
|
-
function TemplateProvider:
|
|
296
|
+
function TemplateProvider:GetTemplate(templateName)
|
|
199
297
|
assert(type(templateName) == "string", "templateName must be a string")
|
|
200
|
-
self:_verifyInit()
|
|
201
298
|
|
|
202
|
-
return self.
|
|
299
|
+
return self._templateMapList:GetItemForKeyAtIndex(templateName, -1)
|
|
203
300
|
end
|
|
204
301
|
|
|
205
302
|
--[=[
|
|
206
|
-
|
|
303
|
+
Promises to clone the template as soon as it exists
|
|
207
304
|
|
|
208
|
-
@param
|
|
209
|
-
|
|
210
|
-
function TemplateProvider:AddContainer(container)
|
|
211
|
-
assert(typeof(container) == "Instance" or type(container) == "number", "Bad container")
|
|
212
|
-
self:_verifyInit()
|
|
213
|
-
|
|
214
|
-
if not self._containersSet[container] then
|
|
215
|
-
self._containersSet[container] = true
|
|
216
|
-
if type(container) == "number" then
|
|
217
|
-
self._maid[container] = self:_loadCloudAsset(container)
|
|
218
|
-
elseif typeof(container) == "Instance" then
|
|
219
|
-
self._maid[container] = self:_loadFolder(container)
|
|
220
|
-
else
|
|
221
|
-
error("Unknown container type to load")
|
|
222
|
-
end
|
|
223
|
-
end
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
--[=[
|
|
227
|
-
Removes a container from the provisioning set.
|
|
228
|
-
|
|
229
|
-
@param container Instance | number
|
|
305
|
+
@param templateName string
|
|
306
|
+
@return Promise<Instance>
|
|
230
307
|
]=]
|
|
231
|
-
function TemplateProvider:
|
|
232
|
-
assert(
|
|
233
|
-
self:_verifyInit()
|
|
308
|
+
function TemplateProvider:PromiseCloneTemplate(templateName)
|
|
309
|
+
assert(type(templateName) == "string", "templateName must be a string")
|
|
234
310
|
|
|
235
|
-
self
|
|
236
|
-
|
|
311
|
+
return self:PromiseTemplate(templateName)
|
|
312
|
+
:Then(function(template)
|
|
313
|
+
return self:_cloneTemplate(template)
|
|
314
|
+
end)
|
|
237
315
|
end
|
|
238
316
|
|
|
239
317
|
--[=[
|
|
240
|
-
|
|
318
|
+
Promise to resolve the raw template as soon as it exists
|
|
319
|
+
|
|
241
320
|
@param templateName string
|
|
242
|
-
@return
|
|
321
|
+
@return Promise<Instance>
|
|
243
322
|
]=]
|
|
244
|
-
function TemplateProvider:
|
|
323
|
+
function TemplateProvider:PromiseTemplate(templateName)
|
|
245
324
|
assert(type(templateName) == "string", "templateName must be a string")
|
|
246
|
-
self:_verifyInit()
|
|
247
325
|
|
|
248
|
-
|
|
326
|
+
local foundTemplate = self._templateMapList:GetItemForKeyAtIndex(templateName, -1)
|
|
327
|
+
if foundTemplate then
|
|
328
|
+
return Promise.resolved(foundTemplate)
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
if self._pendingTemplatePromises[templateName] then
|
|
332
|
+
return self._pendingTemplatePromises[templateName]
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
local promiseTemplate = Promise.new()
|
|
336
|
+
|
|
337
|
+
-- Observe thet template
|
|
338
|
+
PromiseMaidUtils.whilePromise(promiseTemplate, function(topMaid)
|
|
339
|
+
topMaid:GiveTask(self:ObserveTemplate(templateName):Subscribe(function(template)
|
|
340
|
+
if template then
|
|
341
|
+
promiseTemplate:Resolve(template)
|
|
342
|
+
end
|
|
343
|
+
end))
|
|
344
|
+
|
|
345
|
+
if self._replicationMode == TemplateReplicationModes.SERVER then
|
|
346
|
+
-- There's a chance an external process will stream in our template
|
|
347
|
+
|
|
348
|
+
topMaid:GiveTask(task.delay(5, function()
|
|
349
|
+
warn(string.format("[TemplateProvider.%s.PromiseTemplate] - Missing template %q", self.ServiceName, templateName))
|
|
350
|
+
end))
|
|
351
|
+
elseif self._replicationMode == TemplateReplicationModes.CLIENT then
|
|
352
|
+
-- Replicate from the unfound area
|
|
353
|
+
topMaid:GiveTask(self._unreplicatedTemplateMapList:ObserveAtListIndexBrio(templateName, -1):Subscribe(function(brio)
|
|
354
|
+
if brio:IsDead() then
|
|
355
|
+
return
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
local maid, templateTombstone = brio:ToMaidAndValue()
|
|
359
|
+
|
|
360
|
+
local originalName = templateTombstone.Name
|
|
361
|
+
|
|
362
|
+
maid:GivePromise(self:_promiseReplicateTemplateFromTombstone(templateTombstone))
|
|
363
|
+
:Then(function(template)
|
|
364
|
+
-- Cache the template here which then loads it into the known templates naturally
|
|
365
|
+
templateTombstone.Name = String.removePostfix(originalName, TOMBSTONE_NAME_POSTFIX_UNLOADED) .. TOMBSTONE_NAME_POSTFIX_LOADED
|
|
366
|
+
template.Parent = templateTombstone
|
|
367
|
+
|
|
368
|
+
promiseTemplate:Resolve(template)
|
|
369
|
+
end)
|
|
370
|
+
end))
|
|
371
|
+
|
|
372
|
+
topMaid:GiveTask(task.delay(5, function()
|
|
373
|
+
if self._unreplicatedTemplateMapList:GetListForKey(templateName) then
|
|
374
|
+
warn(string.format("[TemplateProvider.%s.PromiseTemplate] - Failed to replicate template %q from server to client", self.ServiceName, templateName))
|
|
375
|
+
else
|
|
376
|
+
warn(string.format("[TemplateProvider.%s.PromiseTemplate] - Template %q is not a known template", self.ServiceName, templateName))
|
|
377
|
+
end
|
|
378
|
+
end))
|
|
379
|
+
elseif self._replicationMode == TemplateReplicationModes.SHARED then
|
|
380
|
+
-- There's a chance an external process will stream in our template
|
|
381
|
+
|
|
382
|
+
topMaid:GiveTask(task.delay(5, function()
|
|
383
|
+
warn(string.format("[TemplateProvider.%s.PromiseTemplate] - Missing template %q", self.ServiceName, templateName))
|
|
384
|
+
end))
|
|
385
|
+
else
|
|
386
|
+
error("Bad replicationMode")
|
|
387
|
+
end
|
|
388
|
+
end)
|
|
389
|
+
|
|
390
|
+
self._maid[promiseTemplate] = promiseTemplate
|
|
391
|
+
self._pendingTemplatePromises[templateName] = promiseTemplate
|
|
392
|
+
|
|
393
|
+
promiseTemplate:Finally(function()
|
|
394
|
+
self._maid[promiseTemplate] = nil
|
|
395
|
+
self._pendingTemplatePromises[templateName] = nil
|
|
396
|
+
end)
|
|
397
|
+
|
|
398
|
+
return promiseTemplate
|
|
249
399
|
end
|
|
250
400
|
|
|
251
|
-
|
|
252
|
-
|
|
401
|
+
function TemplateProvider:_promiseReplicateTemplateFromTombstone(templateTombstone)
|
|
402
|
+
assert(self._replicationMode == TemplateReplicationModes.CLIENT, "Bad replicationMode")
|
|
403
|
+
assert(typeof(templateTombstone) == "Instance", "Bad templateTombstone")
|
|
253
404
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
405
|
+
local tombstoneId = templateTombstone:GetAttribute(TOMBSTONE_ID_ATTRIBUTE)
|
|
406
|
+
if type(tombstoneId) ~= "string" then
|
|
407
|
+
return Promise.rejected("tombstoneId must be a string")
|
|
408
|
+
end
|
|
258
409
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
table.insert(list, item)
|
|
410
|
+
if self._pendingTombstoneRequests[tombstoneId] then
|
|
411
|
+
return self._pendingTombstoneRequests[tombstoneId]
|
|
262
412
|
end
|
|
263
413
|
|
|
264
|
-
|
|
414
|
+
local promiseTemplate = Promise.new()
|
|
415
|
+
|
|
416
|
+
PromiseMaidUtils.whilePromise(promiseTemplate, function(topMaid)
|
|
417
|
+
topMaid:GivePromise(self._remoting.ReplicateTemplate:PromiseInvokeServer(tombstoneId))
|
|
418
|
+
:Then(function(tempTemplate)
|
|
419
|
+
if not tempTemplate then
|
|
420
|
+
return Promise.rejected("Failed to get any template")
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
-- This tempTemplate will get destroyed by the server soon to free up server memory
|
|
424
|
+
-- TODO: cache on client
|
|
425
|
+
local copy = tempTemplate:Clone()
|
|
426
|
+
promiseTemplate:Resolve(copy)
|
|
427
|
+
end, function(...)
|
|
428
|
+
promiseTemplate:Reject(...)
|
|
429
|
+
end)
|
|
430
|
+
end)
|
|
431
|
+
|
|
432
|
+
self._maid[promiseTemplate] = promiseTemplate
|
|
433
|
+
self._pendingTombstoneRequests[tombstoneId] = promiseTemplate
|
|
434
|
+
|
|
435
|
+
promiseTemplate:Finally(function()
|
|
436
|
+
self._maid[promiseTemplate] = nil
|
|
437
|
+
self._pendingTombstoneRequests[tombstoneId] = nil
|
|
438
|
+
end)
|
|
439
|
+
|
|
440
|
+
return promiseTemplate
|
|
265
441
|
end
|
|
266
442
|
|
|
267
443
|
--[=[
|
|
268
|
-
|
|
444
|
+
Clones the template.
|
|
445
|
+
|
|
446
|
+
:::info
|
|
447
|
+
If the template name has a prefix of "Template" then it will remove it on the cloned instance.
|
|
448
|
+
:::
|
|
269
449
|
|
|
270
|
-
@
|
|
450
|
+
@param templateName string
|
|
451
|
+
@return Instance?
|
|
271
452
|
]=]
|
|
272
|
-
function TemplateProvider:
|
|
273
|
-
|
|
453
|
+
function TemplateProvider:CloneTemplate(templateName)
|
|
454
|
+
assert(type(templateName) == "string", "templateName must be a string")
|
|
274
455
|
|
|
275
|
-
local
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
end
|
|
279
|
-
return list
|
|
280
|
-
end
|
|
456
|
+
local template = self._templateMapList:GetItemForKeyAtIndex(templateName, -1)
|
|
457
|
+
if not template then
|
|
458
|
+
local unreplicated = self._unreplicatedTemplateMapList:GetListForKey(templateName)
|
|
281
459
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
self:Init()
|
|
460
|
+
if unreplicated then
|
|
461
|
+
error(string.format("[TemplateProvider.%s.CloneTemplate] - Template %q is not replicated. Use PromiseCloneTemplate instead", self.ServiceName, tostring(templateName)))
|
|
462
|
+
else
|
|
463
|
+
error(string.format("[TemplateProvider.%s.CloneTemplate] - Cannot provide template %q", self.ServiceName, tostring(templateName)))
|
|
287
464
|
end
|
|
465
|
+
|
|
466
|
+
return nil
|
|
288
467
|
end
|
|
289
468
|
|
|
290
|
-
|
|
469
|
+
return self:_cloneTemplate(template)
|
|
291
470
|
end
|
|
292
471
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
472
|
+
--[=[
|
|
473
|
+
Adds a new container to the provider for provision of assets. The initial container
|
|
474
|
+
is considered a template. Additionally, we will include any children that are in a folder
|
|
475
|
+
as a potential root
|
|
476
|
+
|
|
477
|
+
:::tip
|
|
478
|
+
The last template with a given name added will be considered the canonical template.
|
|
479
|
+
:::
|
|
480
|
+
|
|
481
|
+
@param container Template
|
|
482
|
+
@return MaidTask
|
|
483
|
+
]=]
|
|
484
|
+
function TemplateProvider:AddTemplates(container)
|
|
485
|
+
assert(self:_isValidTemplate(container), "Bad container")
|
|
486
|
+
|
|
487
|
+
if typeof(container) == "Instance" then
|
|
488
|
+
-- Always add this instance as we explicitly asked for it to be added as a root. This could be a
|
|
489
|
+
-- module script, or other component.
|
|
490
|
+
return self._containerRootCountingMap:Add(container)
|
|
491
|
+
elseif Observable.isObservable(container) then
|
|
492
|
+
local topMaid = Maid.new()
|
|
493
|
+
|
|
494
|
+
self:_addObservableTemplates(topMaid, container)
|
|
495
|
+
|
|
496
|
+
self._maid[topMaid] = topMaid
|
|
497
|
+
topMaid:GiveTask(function()
|
|
498
|
+
self._maid[topMaid] = nil
|
|
499
|
+
end)
|
|
500
|
+
|
|
501
|
+
return topMaid
|
|
502
|
+
elseif type(container) == "table" then
|
|
503
|
+
local topMaid = Maid.new()
|
|
504
|
+
|
|
505
|
+
for _, value in pairs(container) do
|
|
506
|
+
if typeof(value) == "Instance" then
|
|
507
|
+
-- Always add these as we explicitly ask for this to be a root too.
|
|
508
|
+
topMaid:GiveTask(self._containerRootCountingMap:Add(value))
|
|
509
|
+
elseif Observable.isObservable(value) then
|
|
510
|
+
self:_addObservableTemplates(topMaid, value)
|
|
305
511
|
else
|
|
306
|
-
|
|
307
|
-
maid:GiveTask(self:_loadFolder(result))
|
|
512
|
+
error(string.format("[TemplateProvider.%s] - Bad value of type %q in container table", self.ServiceName, typeof(value)))
|
|
308
513
|
end
|
|
309
|
-
end
|
|
310
|
-
end
|
|
514
|
+
end
|
|
311
515
|
|
|
312
|
-
|
|
313
|
-
|
|
516
|
+
self._maid[topMaid] = topMaid
|
|
517
|
+
topMaid:GiveTask(function()
|
|
518
|
+
self._maid[topMaid] = nil
|
|
519
|
+
end)
|
|
314
520
|
|
|
315
|
-
|
|
316
|
-
if typeof(getParent) == "Instance" then
|
|
317
|
-
return getParent
|
|
318
|
-
elseif type(getParent) == "function" then
|
|
319
|
-
local container = getParent()
|
|
320
|
-
assert(typeof(container) == "Instance", "Bad container")
|
|
321
|
-
return container
|
|
521
|
+
return topMaid
|
|
322
522
|
else
|
|
323
|
-
error("Bad
|
|
523
|
+
error(string.format("[TemplateProvider.%s] - Bad container of type %s", self.ServiceName, typeof(container)))
|
|
324
524
|
end
|
|
325
525
|
end
|
|
326
526
|
|
|
327
|
-
function TemplateProvider:
|
|
328
|
-
|
|
527
|
+
function TemplateProvider:_addObservableTemplates(topMaid, observable)
|
|
528
|
+
topMaid:GiveTask(observable:Subscribe(function(result)
|
|
529
|
+
if Brio.isBrio(result) then
|
|
530
|
+
if result:IsDead() then
|
|
531
|
+
return
|
|
532
|
+
end
|
|
329
533
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
534
|
+
local maid, template = result:ToMaidAndValue()
|
|
535
|
+
if typeof(template) == "Instance" then
|
|
536
|
+
self:_addInstanceTemplate(maid, template)
|
|
537
|
+
else
|
|
538
|
+
error("Cannot add non-instance from observable template")
|
|
539
|
+
end
|
|
540
|
+
else
|
|
541
|
+
error("Cannot add non Brio<Instance> from observable")
|
|
542
|
+
end
|
|
543
|
+
end))
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
function TemplateProvider:_addInstanceTemplate(topMaid, template)
|
|
547
|
+
if self:_shouldAddChildrenAsTemplates(template) then
|
|
548
|
+
topMaid:GiveTask(self._containerRootCountingMap:Add(template))
|
|
338
549
|
end
|
|
339
550
|
|
|
340
|
-
|
|
341
|
-
self:
|
|
551
|
+
if template:GetAttribute(TOMBSTONE_ID_ATTRIBUTE) then
|
|
552
|
+
topMaid:GiveTask(self._unreplicatedTemplateMapList:Push(RxInstanceUtils.observeProperty(template, "Name"):Pipe({
|
|
553
|
+
Rx.map(function(name)
|
|
554
|
+
if String.endsWith(name, TOMBSTONE_NAME_POSTFIX_UNLOADED) then
|
|
555
|
+
return String.removePostfix(name, TOMBSTONE_NAME_POSTFIX_UNLOADED)
|
|
556
|
+
elseif String.endsWith(name, TOMBSTONE_NAME_POSTFIX_LOADED) then
|
|
557
|
+
return String.removePostfix(name, TOMBSTONE_NAME_POSTFIX_LOADED)
|
|
558
|
+
else
|
|
559
|
+
return name
|
|
560
|
+
end
|
|
561
|
+
end);
|
|
562
|
+
Rx.distinct();
|
|
563
|
+
}), template))
|
|
564
|
+
else
|
|
565
|
+
topMaid:GiveTask(self._templateMapList:Push(RxInstanceUtils.observeProperty(template, "Name"), template))
|
|
342
566
|
end
|
|
567
|
+
end
|
|
343
568
|
|
|
344
|
-
|
|
345
|
-
|
|
569
|
+
--[=[
|
|
570
|
+
Returns whether or not a template is registered at the time
|
|
346
571
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
572
|
+
@param templateName string
|
|
573
|
+
@return boolean
|
|
574
|
+
]=]
|
|
575
|
+
function TemplateProvider:IsTemplateAvailable(templateName)
|
|
576
|
+
assert(type(templateName) == "string", "templateName must be a string")
|
|
352
577
|
|
|
353
|
-
return
|
|
578
|
+
return self._templateMapList:GetItemForKeyAtIndex(templateName, -1) ~= nil
|
|
354
579
|
end
|
|
355
580
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
self:_removeFromRegistry(child)
|
|
359
|
-
end
|
|
581
|
+
--[=[
|
|
582
|
+
Returns all current registered items.
|
|
360
583
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
self:_addToRegistery(child)
|
|
366
|
-
end
|
|
584
|
+
@return { Instance }
|
|
585
|
+
]=]
|
|
586
|
+
function TemplateProvider:GetTemplateList()
|
|
587
|
+
return self._templateMapList:GetListOfValuesAtListIndex(-1)
|
|
367
588
|
end
|
|
368
589
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
590
|
+
--[=[
|
|
591
|
+
Gets all current the containers.
|
|
592
|
+
|
|
593
|
+
@return { Instance }
|
|
594
|
+
]=]
|
|
595
|
+
function TemplateProvider:GetContainerList()
|
|
596
|
+
return self._containerRootCountingMap:GetList()
|
|
597
|
+
end
|
|
374
598
|
|
|
375
|
-
|
|
599
|
+
-- Backwards compatibility
|
|
600
|
+
TemplateProvider.IsAvailable = assert(TemplateProvider.IsTemplateAvailable, "Missing method")
|
|
601
|
+
TemplateProvider.Get = assert(TemplateProvider.GetTemplate, "Missing method")
|
|
602
|
+
TemplateProvider.Clone = assert(TemplateProvider.CloneTemplate, "Missing method")
|
|
603
|
+
TemplateProvider.PromiseClone = assert(TemplateProvider.PromiseCloneTemplate, "Missing method")
|
|
604
|
+
TemplateProvider.GetAllTemplates = assert(TemplateProvider.GetTemplateList, "Missing method")
|
|
376
605
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
606
|
+
function TemplateProvider:_cloneTemplate(template)
|
|
607
|
+
local newItem = template:Clone()
|
|
608
|
+
newItem.Name = String.removePostfix(template.Name, "Template")
|
|
609
|
+
return newItem
|
|
381
610
|
end
|
|
382
611
|
|
|
383
|
-
function TemplateProvider:
|
|
384
|
-
|
|
612
|
+
function TemplateProvider:_shouldAddChildrenAsTemplates(container)
|
|
613
|
+
return container:IsA("Folder")
|
|
614
|
+
end
|
|
385
615
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
616
|
+
function TemplateProvider:_isValidTemplate(container)
|
|
617
|
+
return typeof(container) == "Instance"
|
|
618
|
+
or Observable.isObservable(container)
|
|
619
|
+
or type(container) == "table"
|
|
389
620
|
end
|
|
390
621
|
|
|
391
622
|
--[=[
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
--[=[
|
|
2
|
-
Utility functions for the TemplateProvider
|
|
3
|
-
@class TemplateContainerUtils
|
|
4
|
-
]=]
|
|
5
|
-
|
|
6
|
-
local Workspace = game:GetService("Workspace")
|
|
7
|
-
local RunService = game:GetService("RunService")
|
|
8
|
-
|
|
9
|
-
local TemplateContainerUtils = {}
|
|
10
|
-
|
|
11
|
-
function TemplateContainerUtils.reparentFromWorkspaceIfNeeded(parent, name)
|
|
12
|
-
assert(typeof(parent) == "Instance", "Bad parent")
|
|
13
|
-
assert(type(name) == "string", "Bad name")
|
|
14
|
-
|
|
15
|
-
local workspaceContainer = Workspace:FindFirstChild(name)
|
|
16
|
-
local parentedContainer = parent:FindFirstChild(name)
|
|
17
|
-
if workspaceContainer then
|
|
18
|
-
if parentedContainer then
|
|
19
|
-
error(string.format("Duplicate container in %q and %q",
|
|
20
|
-
workspaceContainer:GetFullName(),
|
|
21
|
-
parentedContainer:GetFullName()))
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
-- Reparent
|
|
25
|
-
if RunService:IsRunning() then
|
|
26
|
-
workspaceContainer.Parent = parent
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
return workspaceContainer
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
if not parentedContainer then
|
|
33
|
-
error(string.format("No template container with name %q in %q", parent:GetFullName(), name))
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
return parentedContainer
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
return TemplateContainerUtils
|
|
File without changes
|
|
File without changes
|