@quenty/loader 5.0.1 → 5.1.0-canary.288.30808b9.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.
@@ -0,0 +1,549 @@
1
+ --[=[
2
+ Monitors dependencies primarily for replication. Handles the following scenarios.
3
+
4
+ This system dynamically replicates whatever state exists in the tree except we
5
+ filter out server-specific assets while any client-side assets are still replicated
6
+ even if deeper in the tree.
7
+
8
+ By separating out the replication component of the loader from the loading logic
9
+ we can more easily support hot reloading and future loading scenarios.
10
+
11
+ Repliation rules:
12
+ 1. Replicate the whole tree, including any changes
13
+ 2. Module scripts named Server are replaced with a folder
14
+ 3. Module scripts that are in server mode won't replicate unless a client dependency is needed or found.
15
+ 4. Once we hit a "Template" object we stop trying to be smart since Mesh parts are not API accessible.
16
+ 5. References are preserved for ObjectValues.
17
+
18
+ This system is designed to minimize changes such that hot reloading can be easily
19
+ implemented.
20
+
21
+ Right now it fails to be performance friendly with module scripts under another
22
+ module script.
23
+
24
+ @class Replicator
25
+ ]=]
26
+
27
+ local Maid = require(script.Parent.Parent.Maid)
28
+ local ReplicationType = require(script.Parent.ReplicationType)
29
+ local ReplicationTypeUtils = require(script.Parent.ReplicationTypeUtils)
30
+ local ReplicatorUtils = require(script.Parent.ReplicatorUtils)
31
+ local ReplicatorReferences = require(script.Parent.ReplicatorReferences)
32
+
33
+ local Replicator = {}
34
+ Replicator.ClassName = "Replicator"
35
+ Replicator.__index = Replicator
36
+
37
+ --[=[
38
+ Constructs a new Replicator which will do the syncing.
39
+
40
+ @param references ReplicatorReferences
41
+ @return Replicator
42
+ ]=]
43
+ function Replicator.new(references)
44
+ local self = setmetatable({}, Replicator)
45
+
46
+ assert(ReplicatorReferences.isReplicatorReferences(references), "Bad references")
47
+
48
+ self._maid = Maid.new()
49
+ self._references = references
50
+
51
+ self._target = Instance.new("ObjectValue")
52
+ self._target.Value = nil
53
+ self._maid:GiveTask(self._target)
54
+
55
+ self._replicatedDescendantCount = Instance.new("IntValue")
56
+ self._replicatedDescendantCount.Value = 0
57
+ self._maid:GiveTask(self._replicatedDescendantCount)
58
+ self._hasReplicatedDescendants = Instance.new("BoolValue")
59
+ self._hasReplicatedDescendants.Value = false
60
+ self._maid:GiveTask(self._hasReplicatedDescendants)
61
+
62
+ self._replicationType = Instance.new("StringValue")
63
+ self._replicationType.Value = ReplicationType.SHARED
64
+ self._maid:GiveTask(self._replicationType)
65
+
66
+ self._maid:GiveTask(self._replicatedDescendantCount.Changed:Connect(function()
67
+ self._hasReplicatedDescendants.Value = self._replicatedDescendantCount.Value > 0
68
+ end))
69
+
70
+ return self
71
+ end
72
+
73
+ --[=[
74
+ Replicates children from the given root
75
+
76
+ @param root Instance
77
+ ]=]
78
+ function Replicator:ReplicateFrom(root)
79
+ assert(typeof(root) == "Instance", "Bad root")
80
+
81
+ assert(not self._replicationStarted, "Already bound events")
82
+ self._replicationStarted = true
83
+
84
+ self._maid:GiveTask(root.ChildAdded:Connect(function(child)
85
+ self:_handleChildAdded(child)
86
+ end))
87
+ self._maid:GiveTask(root.ChildRemoved:Connect(function(child)
88
+ self:_handleChildRemoved(child)
89
+ end))
90
+ for _, child in pairs(root:GetChildren()) do
91
+ self:_handleChildAdded(child)
92
+ end
93
+ end
94
+
95
+ --[=[
96
+ Returns true if the argument is a replicator
97
+
98
+ @param replicator any?
99
+ @return boolean
100
+ ]=]
101
+ function Replicator.isReplicator(replicator)
102
+ return type(replicator) == "table" and
103
+ getmetatable(replicator) == Replicator
104
+ end
105
+
106
+ --[=[
107
+ Returns the replicated descendant count value.
108
+ @return IntValue
109
+ ]=]
110
+ function Replicator:GetReplicatedDescendantCountValue()
111
+ return self._replicatedDescendantCount
112
+ end
113
+
114
+ --[=[
115
+ Sets the replication type for this replicator
116
+
117
+ @param replicationType ReplicationType
118
+ ]=]
119
+ function Replicator:SetReplicationType(replicationType)
120
+ assert(ReplicationTypeUtils.isReplicationType(replicationType), "Bad replicationType")
121
+
122
+ self._replicationType.Value = replicationType
123
+ end
124
+
125
+ --[=[
126
+ Sets the target for the replicator where the results will be parented.
127
+
128
+ @param target Instance?
129
+ ]=]
130
+ function Replicator:SetTarget(target)
131
+ assert(typeof(target) == "Instance" or target == nil, "Bad target")
132
+
133
+ self._target.Value = target
134
+ end
135
+
136
+ --[=[
137
+ Gets the current target for the replicator.
138
+
139
+ @return Instance?
140
+ ]=]
141
+ function Replicator:GetTarget()
142
+ return self._target.Value
143
+ end
144
+
145
+ --[=[
146
+ Gets a value representing if there's any replicated children. Used to
147
+ avoid leaking more server-side information than needed for the user.
148
+
149
+ @return BoolValue
150
+ ]=]
151
+ function Replicator:GetHasReplicatedChildrenValue()
152
+ return self._hasReplicatedDescendants
153
+ end
154
+
155
+ function Replicator:GetReplicationTypeValue()
156
+ return self._replicationType
157
+ end
158
+
159
+ function Replicator:_handleChildRemoved(child)
160
+ self._maid[child] = nil
161
+ end
162
+
163
+ function Replicator:_handleChildAdded(child)
164
+ assert(typeof(child) == "Instance", "Bad child")
165
+
166
+ local maid = Maid.new()
167
+
168
+ if child.Archivable then
169
+ maid._current = self:_renderChild(child)
170
+ end
171
+
172
+ maid:GiveTask(child:GetPropertyChangedSignal("Archivable"):Connect(function()
173
+ if child.Archivable then
174
+ maid._current = self:_renderChild(child)
175
+ else
176
+ maid._current = nil
177
+ end
178
+ end))
179
+
180
+ self._maid[child] = maid
181
+ end
182
+
183
+ function Replicator:_renderChild(child)
184
+ local maid = Maid.new()
185
+
186
+ local replicator = Replicator.new(self._references)
187
+ self:_setupReplicatorDescendantCount(maid, replicator)
188
+ maid:GiveTask(replicator)
189
+
190
+ if child:IsA("Folder") then
191
+ self:_setupReplicatorTypeFromFolderName(maid, replicator, child)
192
+ else
193
+ self:_setupReplicatorType(maid, replicator)
194
+ end
195
+
196
+ local replicationTypeValue = replicator:GetReplicationTypeValue()
197
+ maid._current = self:_replicateBasedUponMode(replicator, replicationTypeValue.Value, child)
198
+ maid:GiveTask(replicationTypeValue.Changed:Connect(function()
199
+ maid._current = nil
200
+ maid._current = self:_replicateBasedUponMode(replicator, replicationTypeValue.Value, child)
201
+ end))
202
+
203
+ replicator:ReplicateFrom(child)
204
+
205
+ return maid
206
+ end
207
+
208
+
209
+ function Replicator:_replicateBasedUponMode(replicator, replicationType, child)
210
+ assert(Replicator.isReplicator(replicator), "Bad replicator")
211
+ assert(ReplicationTypeUtils.isReplicationType(replicationType), "Bad replicationType")
212
+ assert(typeof(child) == "Instance", "Bad child")
213
+
214
+ if replicationType == ReplicationType.SERVER then
215
+ return self:_doReplicationServer(replicator, child)
216
+ elseif replicationType == ReplicationType.SHARED
217
+ or replicationType == ReplicationType.CLIENT then
218
+ return self:_doReplicationClient(replicator, child)
219
+ else
220
+ error("[Replicator] - Unknown replicationType")
221
+ end
222
+ end
223
+
224
+ function Replicator:_doReplicationServer(replicator, child)
225
+ local maid = Maid.new()
226
+
227
+ local hasReplicatedChildren = replicator:GetHasReplicatedChildrenValue()
228
+ maid:GiveTask(hasReplicatedChildren.Changed:Connect(function()
229
+ if hasReplicatedChildren.Value then
230
+ maid._current = nil
231
+ maid._current = self:_doServerClone(replicator, child)
232
+ else
233
+ maid._current = nil
234
+ end
235
+ end))
236
+
237
+ if hasReplicatedChildren.Value then
238
+ maid._current = self:_doServerClone(replicator, child)
239
+ end
240
+ end
241
+
242
+ function Replicator:_doServerClone(replicator, child)
243
+ -- Always a folder to prevent information from leaking...
244
+ local maid = Maid.new()
245
+ local copy = Instance.new("Folder")
246
+
247
+ self:_setupNameReplication(maid, child, copy)
248
+ self:_setupParentReplication(maid, copy)
249
+ self:_setupReference(maid, child, copy)
250
+ maid:GiveTask(copy)
251
+
252
+ -- Setup replication for this specific instance.
253
+ self:_setupReplicatorTarget(maid, replicator, copy)
254
+
255
+ return maid
256
+ end
257
+
258
+ function Replicator:_doReplicationClient(replicator, child)
259
+ local maid = Maid.new()
260
+
261
+ if child:IsA("ModuleScript") then
262
+ self:_setupReplicatedDescendantCountAdd(maid, 1)
263
+
264
+ maid._current = self:_doModuleScriptCloneClient(replicator, child)
265
+ maid:GiveTask(child.Changed:Connect(function(property)
266
+ if property == "Source" then
267
+ maid._current = nil
268
+ maid._current = self:_doModuleScriptCloneClient(replicator, child)
269
+ end
270
+ end))
271
+ elseif child:IsA("Folder") then
272
+ local copy = Instance.new("Folder")
273
+
274
+ self:_doStandardReplication(maid, replicator, child, copy)
275
+
276
+ elseif child:IsA("ObjectValue") then
277
+ local copy = Instance.new("ObjectValue")
278
+
279
+ self:_setupObjectValueReplication(maid, child, copy)
280
+ self:_doStandardReplication(maid, replicator, child, copy)
281
+ else
282
+ local copy = ReplicatorUtils.cloneWithoutChildren(child)
283
+
284
+ -- TODO: Maybe do better
285
+ self:_setupReplicatedDescendantCountAdd(maid, 1)
286
+ self:_doStandardReplication(maid, replicator, child, copy)
287
+ end
288
+
289
+ return maid
290
+ end
291
+
292
+ function Replicator:_doModuleScriptCloneClient(replicator, child)
293
+ assert(Replicator.isReplicator(replicator), "Bad replicator")
294
+ assert(typeof(child) == "Instance", "Bad child")
295
+
296
+ local copy = ReplicatorUtils.cloneWithoutChildren(child)
297
+ local maid = Maid.new()
298
+
299
+ self:_doStandardReplication(maid, replicator, child, copy)
300
+
301
+ return maid
302
+ end
303
+
304
+ function Replicator:_doStandardReplication(maid, replicator, child, copy)
305
+ assert(Replicator.isReplicator(replicator), "Bad replicator")
306
+ assert(typeof(copy) == "Instance", "Bad copy")
307
+ assert(typeof(child) == "Instance", "Bad child")
308
+
309
+ self:_setupAttributeReplication(maid, child, copy)
310
+ self:_setupNameReplication(maid, child, copy)
311
+ self:_setupParentReplication(maid, copy)
312
+ self:_setupReference(maid, child, copy)
313
+ maid:GiveTask(copy)
314
+
315
+ -- Setup replication for this specific instance.
316
+ self:_setupReplicatorTarget(maid, replicator, copy)
317
+ end
318
+
319
+ function Replicator:_setupReplicatedDescendantCountAdd(maid, amount)
320
+ assert(Maid.isMaid(maid), "Bad maid")
321
+ assert(type(amount) == "number", "Bad amount")
322
+
323
+ -- Do this replication count here so when the source changes we don't
324
+ -- have any flickering.
325
+ self._replicatedDescendantCount.Value = self._replicatedDescendantCount.Value + amount
326
+ maid:GiveTask(function()
327
+ self._replicatedDescendantCount.Value = self._replicatedDescendantCount.Value - amount
328
+ end)
329
+ end
330
+
331
+ --[[
332
+ Sets up the replicator target so children can be parented into the
333
+ instance.
334
+
335
+ Sets the target to the copy
336
+
337
+ @param maid Maid
338
+ @param replicator Replicator
339
+ @param copy Instance
340
+ ]]
341
+ function Replicator:_setupReplicatorTarget(maid, replicator, copy)
342
+ assert(Maid.isMaid(maid), "Bad maid")
343
+ assert(Replicator.isReplicator(replicator), "Bad replicator")
344
+ assert(typeof(copy) == "Instance", "Bad copy")
345
+
346
+ replicator:SetTarget(copy)
347
+
348
+ maid:GiveTask(function()
349
+ if replicator.Destroy and replicator:GetTarget() == copy then
350
+ replicator:SetTarget(nil)
351
+ end
352
+ end)
353
+ end
354
+
355
+ --[[
356
+ Adds the children count of a child replicator to this replicators
357
+ count.
358
+
359
+ We use this to determine if we need to build the whole tree or not.
360
+
361
+ @param maid Maid
362
+ @param replicator Replicator
363
+ ]]
364
+ function Replicator:_setupReplicatorDescendantCount(maid, replicator)
365
+ assert(Maid.isMaid(maid), "Bad maid")
366
+ assert(Replicator.isReplicator(replicator), "Bad replicator")
367
+
368
+ local replicatedChildrenCount = replicator:GetReplicatedDescendantCountValue()
369
+ local lastValue = replicatedChildrenCount.Value
370
+ self._replicatedDescendantCount.Value = self._replicatedDescendantCount.Value + lastValue
371
+
372
+ maid:GiveTask(replicatedChildrenCount.Changed:Connect(function()
373
+ local value = replicatedChildrenCount.Value
374
+ local delta = value - lastValue
375
+ lastValue = value
376
+ self._replicatedDescendantCount.Value = self._replicatedDescendantCount.Value + delta
377
+ end))
378
+
379
+ maid:GiveTask(function()
380
+ local value = lastValue
381
+ lastValue = 0
382
+ self._replicatedDescendantCount.Value = self._replicatedDescendantCount.Value - value
383
+ end)
384
+ end
385
+
386
+ --[[
387
+ Sets up references from original to the copy. This allows
388
+
389
+ @param maid Maid
390
+ @param child Instance
391
+ @param copy Instance
392
+ ]]
393
+ function Replicator:_setupReference(maid, child, copy)
394
+ assert(Maid.isMaid(maid), "Bad maid")
395
+ assert(typeof(child) == "Instance", "Bad child")
396
+ assert(typeof(copy) == "Instance", "Bad copy")
397
+
398
+ -- Setup references
399
+ self._references:SetReference(child, copy)
400
+ maid:GiveTask(function()
401
+ self._references:UnsetReference(child, copy)
402
+ end)
403
+ end
404
+
405
+ --[[
406
+ Sets up replication type from the folder name. This sort of thing controls
407
+ replication hierarchy for instances we want to have.
408
+
409
+ @param maid Maid
410
+ @param replicator
411
+ @param child Instance
412
+ ]]
413
+ function Replicator:_setupReplicatorTypeFromFolderName(maid, replicator, child)
414
+ assert(Maid.isMaid(maid), "Bad maid")
415
+ assert(Replicator.isReplicator(replicator), "Bad replicator")
416
+ assert(typeof(child) == "Instance", "Bad child")
417
+
418
+ maid:GiveTask(self._replicationType.Changed:Connect(function()
419
+ replicator:SetReplicationType(self:_getFolderReplicationType(child.Name))
420
+ end))
421
+ maid:GiveTask(child:GetPropertyChangedSignal("Name"):Connect(function()
422
+ replicator:SetReplicationType(self:_getFolderReplicationType(child.Name))
423
+ end))
424
+ replicator:SetReplicationType(self:_getFolderReplicationType(child.Name))
425
+ end
426
+
427
+ function Replicator:_setupReplicatorType(maid, replicator)
428
+ assert(Maid.isMaid(maid), "Bad maid")
429
+ assert(Replicator.isReplicator(replicator), "Bad replicator")
430
+
431
+ replicator:SetReplicationType(self._replicationType.Value)
432
+ maid:GiveTask(self._replicationType.Changed:Connect(function()
433
+ replicator:SetReplicationType(self._replicationType.Value)
434
+ end))
435
+ end
436
+
437
+ --[[
438
+ Sets up name replication explicitly.
439
+
440
+ @param maid Maid
441
+ @param child Instance
442
+ @param copy Instance
443
+ ]]
444
+ function Replicator:_setupNameReplication(maid, child, copy)
445
+ assert(Maid.isMaid(maid), "Bad maid")
446
+ assert(typeof(child) == "Instance", "Bad child")
447
+ assert(typeof(copy) == "Instance", "Bad copy")
448
+
449
+ copy.Name = child.Name
450
+ maid:GiveTask(child:GetPropertyChangedSignal("Name"):Connect(function()
451
+ copy.Name = child.Name
452
+ end))
453
+ end
454
+
455
+ --[[
456
+ Sets up the parent replication.
457
+
458
+ @param maid Maid
459
+ @param copy Instance
460
+ ]]
461
+ function Replicator:_setupParentReplication(maid, copy)
462
+ assert(Maid.isMaid(maid), "Bad maid")
463
+ assert(typeof(copy) == "Instance", "Bad copy")
464
+
465
+ maid:GiveTask(self._target.Changed:Connect(function()
466
+ copy.Parent = self._target.Value
467
+ end))
468
+ copy.Parent = self._target.Value
469
+ end
470
+
471
+ --[[
472
+ Sets up the object value replication to point towards new values.
473
+
474
+ @param maid Maid
475
+ @param child Instance
476
+ @param copy Instance
477
+ ]]
478
+ function Replicator:_setupObjectValueReplication(maid, child, copy)
479
+ assert(Maid.isMaid(maid), "Bad maid")
480
+ assert(typeof(child) == "Instance", "Bad child")
481
+ assert(typeof(copy) == "Instance", "Bad copy")
482
+
483
+ local symbol = newproxy(true)
484
+
485
+ maid:GiveTask(child:GetPropertyChangedSignal("Value"):Connect(function()
486
+ maid[symbol] = self:_doObjectValueReplication(child, copy)
487
+ end))
488
+ maid[symbol] = self:_doObjectValueReplication(child, copy)
489
+ end
490
+
491
+ function Replicator:_doObjectValueReplication(child, copy)
492
+ assert(typeof(child) == "Instance", "Bad child")
493
+ assert(typeof(copy) == "Instance", "Bad copy")
494
+
495
+ local childValue = child.Value
496
+ if childValue then
497
+ local maid = Maid.new()
498
+
499
+ maid:GiveTask(self._references:ObserveReferenceChanged(childValue,
500
+ function(newValue)
501
+ if newValue then
502
+ copy.Value = newValue
503
+ else
504
+ -- Fall back to original value (pointing outside of tree)
505
+ newValue = childValue
506
+ end
507
+ end))
508
+
509
+ return maid
510
+ else
511
+ copy.Value = nil
512
+ return nil
513
+ end
514
+ end
515
+
516
+ --[[
517
+ Computes folder replication type based upon the folder name
518
+ and inherited folder replication type.
519
+
520
+ @param childName string
521
+ ]]
522
+ function Replicator:_getFolderReplicationType(childName)
523
+ assert(type(childName) == "string", "Bad childName")
524
+
525
+ return ReplicationTypeUtils.getFolderReplicationType(
526
+ childName,
527
+ self._replicationType.Value)
528
+ end
529
+
530
+ function Replicator:_setupAttributeReplication(maid, child, copy)
531
+ for key, value in pairs(child:GetAttributes()) do
532
+ child:SetAttribute(key, value)
533
+ end
534
+
535
+ maid:GiveTask(child.AttributeChanged:Connect(function(attribute)
536
+ copy:SetAttribute(attribute, child:GetAttribute(attribute))
537
+ end))
538
+ end
539
+
540
+ --[=[
541
+ Cleans up the replicator disconnecting all events and cleaning up
542
+ created instances.
543
+ ]=]
544
+ function Replicator:Destroy()
545
+ self._maid:DoCleaning()
546
+ setmetatable(self, nil)
547
+ end
548
+
549
+ return Replicator
@@ -0,0 +1,103 @@
1
+ --[=[
2
+ Handles mapping of references to the new value.
3
+
4
+ @class ReplicatorReferences
5
+ ]=]
6
+
7
+ local ReplicatorReferences = {}
8
+ ReplicatorReferences.ClassName = "ReplicatorReferences"
9
+ ReplicatorReferences.__index = ReplicatorReferences
10
+
11
+ function ReplicatorReferences.new()
12
+ local self = setmetatable({}, ReplicatorReferences)
13
+
14
+ self._lookup = {}
15
+ self._listeners = {} --[orig] = { callback }
16
+
17
+ return self
18
+ end
19
+
20
+ --[=[
21
+ Returns true if the argument is a replicator references
22
+
23
+ @param replicatorReferences any?
24
+ @return boolean
25
+ ]=]
26
+ function ReplicatorReferences.isReplicatorReferences(replicatorReferences)
27
+ return type(replicatorReferences) == "table" and
28
+ getmetatable(replicatorReferences) == ReplicatorReferences
29
+ end
30
+
31
+ function ReplicatorReferences:SetReference(orig, replicated)
32
+ assert(typeof(orig) == "Instance", "Bad orig")
33
+ assert(typeof(replicated) == "Instance", "Bad replicated")
34
+
35
+ if self._lookup[orig] ~= replicated then
36
+ self._lookup[orig] = replicated
37
+ self:_fireSubs(orig, replicated)
38
+ end
39
+ end
40
+
41
+ function ReplicatorReferences:UnsetReference(orig, replicated)
42
+ assert(typeof(orig) == "Instance", "Bad orig")
43
+ assert(typeof(replicated) == "Instance", "Bad replicated")
44
+
45
+ if self._lookup[orig] == replicated then
46
+ self._lookup[orig] = nil
47
+ self:_fireSubs(orig, nil)
48
+ end
49
+ end
50
+
51
+ function ReplicatorReferences:_fireSubs(orig, newValue)
52
+ assert(typeof(orig) == "Instance", "Bad orig")
53
+
54
+ local listeners = self._listeners[orig]
55
+ if not listeners then
56
+ return
57
+ end
58
+
59
+ for _, callback in pairs(listeners) do
60
+ task.spawn(callback, newValue)
61
+ end
62
+ end
63
+
64
+ --[=[
65
+ Observes when a reference changes. Discount Rx observable since we're
66
+ the loader and don't want a whole copy of Rx.
67
+
68
+ @param orig Instance
69
+ @param callback function
70
+ @return function -- Call to disconnect
71
+ ]=]
72
+ function ReplicatorReferences:ObserveReferenceChanged(orig, callback)
73
+ assert(typeof(orig) == "Instance", "Bad orig")
74
+ assert(type(callback) == "function", "Bad callback")
75
+
76
+ -- register
77
+ do
78
+ local listeners = self._listeners[orig]
79
+ if not listeners then
80
+ listeners = {}
81
+ self._listeners[orig] = listeners
82
+ end
83
+
84
+ table.insert(listeners, callback)
85
+ end
86
+
87
+ task.spawn(callback, self._lookup[orig])
88
+
89
+ -- Unregister
90
+ return function()
91
+ local listeners = self._listeners[orig]
92
+ if not listeners then
93
+ return
94
+ end
95
+
96
+ local index = table.find(listeners, callback)
97
+ if index then
98
+ table.remove(listeners, index)
99
+ end
100
+ end
101
+ end
102
+
103
+ return ReplicatorReferences
@@ -0,0 +1,24 @@
1
+ --[=[
2
+ @class ReplicatorUtils
3
+ ]=]
4
+
5
+ local ReplicatorUtils = {}
6
+
7
+ function ReplicatorUtils.cloneWithoutChildren(value)
8
+ local original = {}
9
+ for _, item in pairs(value:GetChildren()) do
10
+ if item.Archivable then
11
+ original[item] = true
12
+ item.Archivable = false
13
+ end
14
+ end
15
+ local copy = value:Clone()
16
+
17
+ for item, _ in pairs(original) do
18
+ item.Archivable = true
19
+ end
20
+
21
+ return copy
22
+ end
23
+
24
+ return ReplicatorUtils