@quenty/datastore 7.23.0 → 7.23.1-canary.402.5852ffd.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 +20 -0
- package/README.md +11 -0
- package/package.json +14 -11
- package/src/Server/DataStore.lua +253 -94
- package/src/Server/GameDataStoreService.lua +7 -1
- package/src/Server/Modules/DataStoreSnapshotUtils.lua +13 -0
- package/src/Server/Modules/DataStoreStage.lua +724 -242
- package/src/Server/Modules/DataStoreWriter.lua +235 -25
- package/src/Server/PlayerDataStoreManager.lua +1 -0
- package/src/Server/Utility/DataStorePromises.lua +3 -2
- package/test/default.project.json +21 -0
- package/test/scripts/Client/ClientMain.client.lua +10 -0
- package/test/scripts/Server/ServerMain.server.lua +120 -0
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
at children level. This minimizes accidently overwriting.
|
|
5
5
|
The big cost here is that we may leave keys that can't be removed.
|
|
6
6
|
|
|
7
|
+
Layers in priority order:
|
|
8
|
+
|
|
9
|
+
1. Save data
|
|
10
|
+
2. Substores
|
|
11
|
+
3. Base layer
|
|
12
|
+
|
|
7
13
|
@server
|
|
8
14
|
@class DataStoreStage
|
|
9
15
|
]=]
|
|
@@ -13,13 +19,15 @@ local require = require(script.Parent.loader).load(script)
|
|
|
13
19
|
local BaseObject = require("BaseObject")
|
|
14
20
|
local DataStoreDeleteToken = require("DataStoreDeleteToken")
|
|
15
21
|
local DataStoreWriter = require("DataStoreWriter")
|
|
22
|
+
local GoodSignal = require("GoodSignal")
|
|
16
23
|
local Maid = require("Maid")
|
|
24
|
+
local Observable = require("Observable")
|
|
25
|
+
local ObservableSubscriptionTable = require("ObservableSubscriptionTable")
|
|
17
26
|
local Promise = require("Promise")
|
|
18
27
|
local PromiseUtils = require("PromiseUtils")
|
|
19
|
-
local
|
|
28
|
+
local Set = require("Set")
|
|
20
29
|
local Table = require("Table")
|
|
21
|
-
local
|
|
22
|
-
local ObservableSubscriptionTable = require("ObservableSubscriptionTable")
|
|
30
|
+
local DataStoreSnapshotUtils = require("DataStoreSnapshotUtils")
|
|
23
31
|
|
|
24
32
|
local DataStoreStage = setmetatable({}, BaseObject)
|
|
25
33
|
DataStoreStage.ClassName = "DataStoreStage"
|
|
@@ -28,6 +36,14 @@ DataStoreStage.__index = DataStoreStage
|
|
|
28
36
|
--[=[
|
|
29
37
|
Constructs a new DataStoreStage to load from. Prefer to use DataStore because this doesn't
|
|
30
38
|
have any way to retrieve this.
|
|
39
|
+
|
|
40
|
+
See [DataStore], [GameDataStoreService], and [PlayerDataStoreService].
|
|
41
|
+
|
|
42
|
+
```lua
|
|
43
|
+
-- Data store inherits from DataStoreStage
|
|
44
|
+
local dataStore = serviceBag:GetService(PlayerDataStoreService):PromiseDataStore(player):Yield()
|
|
45
|
+
```
|
|
46
|
+
|
|
31
47
|
@param loadName string
|
|
32
48
|
@param loadParent DataStoreStage?
|
|
33
49
|
@return DataStoreStage
|
|
@@ -39,136 +55,218 @@ function DataStoreStage.new(loadName, loadParent)
|
|
|
39
55
|
self._loadName = loadName
|
|
40
56
|
self._loadParent = loadParent
|
|
41
57
|
|
|
42
|
-
self.
|
|
43
|
-
self.
|
|
58
|
+
self.Changed = GoodSignal.new() -- :Fire(viewSnapshot)
|
|
59
|
+
self._maid:GiveTask(self.Changed)
|
|
60
|
+
|
|
61
|
+
self.DataStored = GoodSignal.new()
|
|
62
|
+
self._maid:GiveTask(self.DataStored)
|
|
63
|
+
|
|
64
|
+
-- Stores the actual data loaded and synced (but not pending written data)
|
|
65
|
+
self._saveDataSnapshot = nil
|
|
44
66
|
self._stores = {} -- [name] = dataSubStore
|
|
67
|
+
self._baseDataSnapshot = nil
|
|
68
|
+
|
|
69
|
+
-- View data
|
|
70
|
+
self._viewSnapshot = nil
|
|
45
71
|
|
|
46
|
-
self.
|
|
47
|
-
|
|
72
|
+
self._savingCallbacks = {} -- [func, ...]
|
|
73
|
+
|
|
74
|
+
self._keySubscriptions = ObservableSubscriptionTable.new()
|
|
75
|
+
self._maid:GiveTask(self._keySubscriptions)
|
|
48
76
|
|
|
49
77
|
return self
|
|
50
78
|
end
|
|
51
79
|
|
|
52
|
-
--
|
|
53
|
-
|
|
54
|
-
if not next(self._savingCallbacks) then
|
|
55
|
-
return nil
|
|
56
|
-
end
|
|
80
|
+
--[=[
|
|
81
|
+
Stores the value, firing off events and queuing the item for save.
|
|
57
82
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if Promise.isPromise(result) then
|
|
62
|
-
table.insert(removingPromises, result)
|
|
63
|
-
end
|
|
64
|
-
end
|
|
83
|
+
```lua
|
|
84
|
+
dataStore:Store("money", 25)
|
|
85
|
+
```
|
|
65
86
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
87
|
+
@param key string | number
|
|
88
|
+
@param value any
|
|
89
|
+
]=]
|
|
90
|
+
function DataStoreStage:Store(key, value)
|
|
91
|
+
assert(type(key) == "string", "Bad key")
|
|
92
|
+
|
|
93
|
+
if value == nil then
|
|
94
|
+
value = DataStoreDeleteToken
|
|
71
95
|
end
|
|
72
96
|
|
|
73
|
-
|
|
97
|
+
-- Ensure that we at least start loading (and thus the autosave loop) for write
|
|
98
|
+
self:PromiseViewUpToDate()
|
|
99
|
+
|
|
100
|
+
self:_storeAtKey(key, value)
|
|
74
101
|
end
|
|
75
102
|
|
|
76
103
|
--[=[
|
|
77
|
-
|
|
78
|
-
@param callback function -- May return a promise
|
|
79
|
-
@return function -- Call to remove
|
|
80
|
-
]=]
|
|
81
|
-
function DataStoreStage:AddSavingCallback(callback)
|
|
82
|
-
assert(type(callback) == "function", "Bad callback")
|
|
104
|
+
Loads the data at the `key` and returns a promise with that value
|
|
83
105
|
|
|
84
|
-
|
|
106
|
+
```lua
|
|
107
|
+
dataStore:Load():Then(function(data)
|
|
108
|
+
print(data)
|
|
109
|
+
end)
|
|
110
|
+
```
|
|
85
111
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
112
|
+
@param key string | number
|
|
113
|
+
@param defaultValue T?
|
|
114
|
+
@return Promise<T>
|
|
115
|
+
]=]
|
|
116
|
+
function DataStoreStage:Load(key, defaultValue)
|
|
117
|
+
assert(type(key) == "string" or type(key) == "number", "Bad key")
|
|
118
|
+
|
|
119
|
+
return self:PromiseViewUpToDate():Then(function()
|
|
120
|
+
if type(self._viewSnapshot) == "table" then
|
|
121
|
+
local value = self._viewSnapshot[key]
|
|
122
|
+
if value ~= nil then
|
|
123
|
+
return value
|
|
124
|
+
else
|
|
125
|
+
return defaultValue
|
|
126
|
+
end
|
|
127
|
+
else
|
|
128
|
+
return defaultValue
|
|
89
129
|
end
|
|
90
|
-
end
|
|
130
|
+
end)
|
|
91
131
|
end
|
|
92
132
|
|
|
93
133
|
--[=[
|
|
94
|
-
|
|
95
|
-
@param callback function
|
|
96
|
-
]=]
|
|
97
|
-
function DataStoreStage:RemoveSavingCallback(callback)
|
|
98
|
-
assert(type(callback) == "function", "Bad callback")
|
|
134
|
+
Promises the full content for the datastore
|
|
99
135
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
end
|
|
136
|
+
```lua
|
|
137
|
+
dataStore:LoadAll():Then(function(data)
|
|
138
|
+
print(data)
|
|
139
|
+
end)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
@return Promise<any>
|
|
143
|
+
]=]
|
|
144
|
+
function DataStoreStage:LoadAll()
|
|
145
|
+
return self:PromiseViewUpToDate():Then(function()
|
|
146
|
+
return self._viewSnapshot
|
|
147
|
+
end)
|
|
104
148
|
end
|
|
105
149
|
|
|
106
150
|
--[=[
|
|
107
|
-
Gets
|
|
108
|
-
|
|
151
|
+
Gets a sub-datastore that will write at the given key. This will have the same
|
|
152
|
+
helper methods as any other data store object.
|
|
153
|
+
|
|
154
|
+
```lua
|
|
155
|
+
local dataStore = DataStore.new()
|
|
156
|
+
|
|
157
|
+
local saveslot = dataStore:GetSubStore("saveslot0")
|
|
158
|
+
saveslot:Store("Money", 0)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
@param key string | number
|
|
162
|
+
@return DataStoreStage
|
|
109
163
|
]=]
|
|
110
|
-
function DataStoreStage:
|
|
111
|
-
|
|
112
|
-
|
|
164
|
+
function DataStoreStage:GetSubStore(key)
|
|
165
|
+
assert(type(key) == "string" or type(key) == "number", "Bad key")
|
|
166
|
+
|
|
167
|
+
if self._stores[key] then
|
|
168
|
+
return self._stores[key]
|
|
113
169
|
end
|
|
114
170
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
171
|
+
local maid = Maid.new()
|
|
172
|
+
local newStore = DataStoreStage.new(key, self)
|
|
173
|
+
maid:GiveTask(newStore)
|
|
174
|
+
|
|
175
|
+
if type(self._baseDataSnapshot) == "table" then
|
|
176
|
+
local baseDataToTransfer = self._baseDataSnapshot[key]
|
|
177
|
+
if baseDataToTransfer ~= nil then
|
|
178
|
+
local newSnapshot = table.clone(self._baseDataSnapshot)
|
|
179
|
+
newSnapshot[key] = nil
|
|
180
|
+
newStore:MergeDiffSnapshot(baseDataToTransfer)
|
|
181
|
+
self._baseDataSnapshot = table.freeze(newSnapshot)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
119
184
|
|
|
120
|
-
--
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
185
|
+
-- Transfer save data to substore
|
|
186
|
+
if type(self._saveDataSnapshot) == "table" then
|
|
187
|
+
local saveDataToTransfer = self._saveDataSnapshot[key]
|
|
188
|
+
|
|
189
|
+
if saveDataToTransfer ~= nil then
|
|
190
|
+
local newSnapshot = table.clone(self._saveDataSnapshot)
|
|
191
|
+
newSnapshot[key] = nil
|
|
192
|
+
|
|
193
|
+
newStore:Overwrite(saveDataToTransfer)
|
|
194
|
+
|
|
195
|
+
if DataStoreSnapshotUtils.isEmptySnapshot(newSnapshot) then
|
|
196
|
+
self._saveDataSnapshot = nil
|
|
197
|
+
else
|
|
198
|
+
self._saveDataSnapshot = table.freeze(newSnapshot)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
129
201
|
end
|
|
202
|
+
|
|
203
|
+
self._stores[key] = newStore
|
|
204
|
+
self._maid[maid] = maid
|
|
205
|
+
|
|
206
|
+
maid:GiveTask(newStore.Changed:Connect(function()
|
|
207
|
+
self:_updateViewSnapshotAtKey(key)
|
|
208
|
+
end))
|
|
209
|
+
self:_updateViewSnapshotAtKey(key)
|
|
210
|
+
|
|
211
|
+
return newStore
|
|
130
212
|
end
|
|
131
213
|
|
|
132
214
|
--[=[
|
|
133
|
-
|
|
215
|
+
Explicitely deletes data at the key
|
|
134
216
|
|
|
135
|
-
@param
|
|
136
|
-
@param defaultValue T?
|
|
137
|
-
@return Promise<T>
|
|
217
|
+
@param key string | number
|
|
138
218
|
]=]
|
|
139
|
-
function DataStoreStage:
|
|
140
|
-
assert(type(
|
|
219
|
+
function DataStoreStage:Delete(key)
|
|
220
|
+
assert(type(key) == "string", "Bad key")
|
|
141
221
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return Promise.resolved(defaultValue)
|
|
145
|
-
else
|
|
146
|
-
return Promise.resolved(self._dataToSave[name])
|
|
147
|
-
end
|
|
148
|
-
end
|
|
222
|
+
self:_storeAtKey(key, DataStoreDeleteToken)
|
|
223
|
+
end
|
|
149
224
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
225
|
+
--[=[
|
|
226
|
+
Queues up a wipe of all values. This will completely set the data to nil.
|
|
227
|
+
]=]
|
|
228
|
+
function DataStoreStage:Wipe()
|
|
229
|
+
self:Overwrite(DataStoreDeleteToken)
|
|
153
230
|
end
|
|
154
231
|
|
|
155
232
|
--[=[
|
|
156
233
|
Observes the current value for the stage itself
|
|
157
234
|
|
|
158
|
-
|
|
235
|
+
If no key is passed than it will observe the whole view snapshot
|
|
236
|
+
|
|
237
|
+
@param key string | number | nil
|
|
159
238
|
@param defaultValue T?
|
|
160
239
|
@return Observable<T>
|
|
161
240
|
]=]
|
|
162
|
-
function DataStoreStage:Observe(
|
|
163
|
-
assert(type(
|
|
241
|
+
function DataStoreStage:Observe(key, defaultValue)
|
|
242
|
+
assert(type(key) == "string" or type(key) == "number" or key == nil, "Bad key")
|
|
243
|
+
|
|
244
|
+
if key == nil then
|
|
245
|
+
return Observable.new(function(sub)
|
|
246
|
+
local maid = Maid.new()
|
|
247
|
+
maid:GivePromise(self:LoadAll())
|
|
248
|
+
:Then(function()
|
|
249
|
+
-- Only connect once loaded
|
|
250
|
+
maid:GiveTask(self.Changed:Connect(function(viewSnapshot)
|
|
251
|
+
sub:Fire(viewSnapshot)
|
|
252
|
+
end))
|
|
253
|
+
|
|
254
|
+
sub:Fire(self._viewSnapshot)
|
|
255
|
+
end, function(...)
|
|
256
|
+
sub:Fail(...)
|
|
257
|
+
end)
|
|
258
|
+
|
|
259
|
+
return maid
|
|
260
|
+
end)
|
|
261
|
+
end
|
|
164
262
|
|
|
165
263
|
return Observable.new(function(sub)
|
|
166
264
|
local maid = Maid.new()
|
|
167
265
|
|
|
168
|
-
maid:GiveTask(self.
|
|
266
|
+
maid:GiveTask(self._keySubscriptions:Observe(key):Subscribe(sub:GetFireFailComplete()))
|
|
169
267
|
|
|
170
268
|
-- Load initially
|
|
171
|
-
maid:GivePromise(self:Load(
|
|
269
|
+
maid:GivePromise(self:Load(key, defaultValue))
|
|
172
270
|
:Then(function(value)
|
|
173
271
|
sub:Fire(value)
|
|
174
272
|
end, function(...)
|
|
@@ -179,61 +277,61 @@ function DataStoreStage:Observe(name, defaultValue)
|
|
|
179
277
|
end)
|
|
180
278
|
end
|
|
181
279
|
|
|
182
|
-
--
|
|
183
|
-
|
|
184
|
-
assert(type(name) == "string" or type(name) == "number", "Bad name")
|
|
280
|
+
--[=[
|
|
281
|
+
Adds a callback to be called before save. This may return a promise.
|
|
185
282
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
end
|
|
192
|
-
elseif self._stores[name] then
|
|
193
|
-
if self._stores[name]:HasWritableData() then
|
|
194
|
-
local writer = self._stores[name]:GetNewWriter()
|
|
195
|
-
local original = Table.deepCopy(data[name] or {})
|
|
196
|
-
writer:WriteMerge(original)
|
|
197
|
-
return original
|
|
198
|
-
end
|
|
199
|
-
end
|
|
283
|
+
@param callback function -- May return a promise
|
|
284
|
+
@return function -- Call to remove
|
|
285
|
+
]=]
|
|
286
|
+
function DataStoreStage:AddSavingCallback(callback)
|
|
287
|
+
assert(type(callback) == "function", "Bad callback")
|
|
200
288
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
289
|
+
table.insert(self._savingCallbacks, callback)
|
|
290
|
+
|
|
291
|
+
return function()
|
|
292
|
+
if self.Destroy then
|
|
293
|
+
self:RemoveSavingCallback(callback)
|
|
294
|
+
end
|
|
205
295
|
end
|
|
206
296
|
end
|
|
207
297
|
|
|
208
298
|
--[=[
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
@param name string | number
|
|
299
|
+
Removes a saving callback from the data store stage
|
|
300
|
+
@param callback function
|
|
212
301
|
]=]
|
|
213
|
-
function DataStoreStage:
|
|
214
|
-
assert(type(
|
|
302
|
+
function DataStoreStage:RemoveSavingCallback(callback)
|
|
303
|
+
assert(type(callback) == "function", "Bad callback")
|
|
215
304
|
|
|
216
|
-
|
|
217
|
-
|
|
305
|
+
local index = table.find(self._savingCallbacks, callback)
|
|
306
|
+
if index then
|
|
307
|
+
table.remove(self._savingCallbacks, index)
|
|
218
308
|
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
--[=[
|
|
312
|
+
Gets an event that will fire off whenever something is stored at this level
|
|
219
313
|
|
|
220
|
-
|
|
314
|
+
@return Signal
|
|
315
|
+
]=]
|
|
316
|
+
function DataStoreStage:GetTopLevelDataStoredSignal()
|
|
317
|
+
return self.DataStored
|
|
221
318
|
end
|
|
222
319
|
|
|
223
320
|
--[=[
|
|
224
|
-
|
|
321
|
+
Retrieves the full path of this datastore stage for diagnostic purposes.
|
|
322
|
+
|
|
323
|
+
@return string
|
|
225
324
|
]=]
|
|
226
|
-
function DataStoreStage:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
end)
|
|
325
|
+
function DataStoreStage:GetFullPath()
|
|
326
|
+
if self._fullPath then
|
|
327
|
+
return self._fullPath
|
|
328
|
+
elseif self._loadParent then
|
|
329
|
+
self._fullPath = self._loadParent:GetFullPath() .. "." .. tostring(self._loadName)
|
|
330
|
+
return self._fullPath
|
|
331
|
+
else
|
|
332
|
+
self._fullPath = tostring(self._loadName)
|
|
333
|
+
return self._fullPath
|
|
334
|
+
end
|
|
237
335
|
end
|
|
238
336
|
|
|
239
337
|
--[=[
|
|
@@ -258,78 +356,105 @@ end
|
|
|
258
356
|
@return Promise<{ [string]: true }>
|
|
259
357
|
]=]
|
|
260
358
|
function DataStoreStage:PromiseKeySet()
|
|
261
|
-
return self:
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if value ~= DataStoreDeleteToken then
|
|
266
|
-
keySet[key] = true
|
|
267
|
-
end
|
|
268
|
-
end
|
|
359
|
+
return self:PromiseViewUpToDate():Then(function()
|
|
360
|
+
return Set.fromKeys(self._viewSnapshot)
|
|
361
|
+
end)
|
|
362
|
+
end
|
|
269
363
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
keySet[key] = true
|
|
274
|
-
end
|
|
275
|
-
end
|
|
276
|
-
end
|
|
364
|
+
--[=[
|
|
365
|
+
This will always prioritize our own view of the world over
|
|
366
|
+
incoming data.
|
|
277
367
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
keySet[key] = true
|
|
282
|
-
end
|
|
283
|
-
end
|
|
368
|
+
:::tip
|
|
369
|
+
This is a helper method that helps load diff data into the data store.
|
|
370
|
+
:::
|
|
284
371
|
|
|
285
|
-
|
|
286
|
-
|
|
372
|
+
@param diffSnapshot any
|
|
373
|
+
]=]
|
|
374
|
+
function DataStoreStage:MergeDiffSnapshot(diffSnapshot)
|
|
375
|
+
self:_checkIntegrity()
|
|
376
|
+
self._baseDataSnapshot = self:_updateStoresAndComputeBaseDataSnapshotFromDiffSnapshot(diffSnapshot)
|
|
377
|
+
self:_updateViewSnapshot()
|
|
378
|
+
self:_checkIntegrity()
|
|
287
379
|
end
|
|
288
380
|
|
|
289
381
|
--[=[
|
|
290
|
-
|
|
382
|
+
Updates the base data to the saved / written data.
|
|
291
383
|
|
|
292
|
-
|
|
384
|
+
This will always prioritize our own view of the world over
|
|
385
|
+
incoming data.
|
|
386
|
+
|
|
387
|
+
@param parentWriter DataStoreWriter
|
|
293
388
|
]=]
|
|
294
|
-
function DataStoreStage:
|
|
295
|
-
|
|
296
|
-
|
|
389
|
+
function DataStoreStage:MarkDataAsSaved(parentWriter)
|
|
390
|
+
-- Update all children first
|
|
391
|
+
for key, subwriter in pairs(parentWriter:GetSubWritersMap()) do
|
|
392
|
+
local store = self._stores[key]
|
|
393
|
+
if store then
|
|
394
|
+
store:MarkDataAsSaved(subwriter)
|
|
395
|
+
else
|
|
396
|
+
warn("[DataStoreStage] - Store removed, but writer persists")
|
|
397
|
+
end
|
|
398
|
+
end
|
|
297
399
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
400
|
+
local dataToSave = parentWriter:GetDataToSave()
|
|
401
|
+
if self._saveDataSnapshot == DataStoreDeleteToken or dataToSave == DataStoreDeleteToken then
|
|
402
|
+
if self._saveDataSnapshot == dataToSave then
|
|
403
|
+
self._baseDataSnapshot = nil
|
|
404
|
+
self._saveDataSnapshot = nil
|
|
405
|
+
end
|
|
406
|
+
elseif type(self._saveDataSnapshot) == "table" or type(dataToSave) == "table" then
|
|
407
|
+
if type(self._saveDataSnapshot) == "table" and type(dataToSave) == "table" then
|
|
408
|
+
local newSaveSnapshot = table.clone(self._saveDataSnapshot)
|
|
409
|
+
local newBaseDataSnapshot
|
|
410
|
+
if type(self._baseDataSnapshot) == "table" then
|
|
411
|
+
newBaseDataSnapshot = table.clone(self._baseDataSnapshot)
|
|
303
412
|
else
|
|
304
|
-
|
|
413
|
+
newBaseDataSnapshot = {}
|
|
305
414
|
end
|
|
306
|
-
end
|
|
307
415
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
result[key] = Table.deepCopy(value)
|
|
314
|
-
else
|
|
315
|
-
result[key] = value
|
|
416
|
+
for key, value in pairs(dataToSave) do
|
|
417
|
+
if self._saveDataSnapshot[key] == value then
|
|
418
|
+
-- This shouldn't fire any event because our save data is matching
|
|
419
|
+
newBaseDataSnapshot[key] = self:_updateStoresAndComputeBaseDataSnapshotValueFromDiffSnapshot(key, value)
|
|
420
|
+
newSaveSnapshot[key] = nil
|
|
316
421
|
end
|
|
317
422
|
end
|
|
318
|
-
end
|
|
319
423
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
424
|
+
self._baseDataSnapshot = table.freeze(newBaseDataSnapshot)
|
|
425
|
+
|
|
426
|
+
if DataStoreSnapshotUtils.isEmptySnapshot(newSaveSnapshot) then
|
|
427
|
+
self._saveDataSnapshot = nil
|
|
428
|
+
else
|
|
429
|
+
self._saveDataSnapshot = table.freeze(newSaveSnapshot)
|
|
325
430
|
end
|
|
326
431
|
end
|
|
432
|
+
else
|
|
433
|
+
assert(type(self._saveDataSnapshot) ~= "table", "Case is covered above")
|
|
434
|
+
assert(self._saveDataSnapshot ~= DataStoreDeleteToken, "Case is covered above")
|
|
435
|
+
assert(dataToSave ~= DataStoreDeleteToken, "Case is covered above")
|
|
436
|
+
assert(type(dataToSave) ~= "table", "Case is covered above")
|
|
437
|
+
|
|
438
|
+
-- In the none-table scenario move stuff
|
|
439
|
+
if self._saveDataSnapshot == dataToSave then
|
|
440
|
+
self._baseDataSnapshot = dataToSave
|
|
441
|
+
self._saveDataSnapshot = nil
|
|
442
|
+
end
|
|
443
|
+
end
|
|
327
444
|
|
|
328
|
-
|
|
329
|
-
end)
|
|
445
|
+
self:_checkIntegrity()
|
|
330
446
|
end
|
|
331
447
|
|
|
332
|
-
|
|
448
|
+
--[=[
|
|
449
|
+
Helper method that when invokes ensures the data view.
|
|
450
|
+
|
|
451
|
+
:::tip
|
|
452
|
+
This is a helper method. You probably want [DataStore.LoadAll] instead.
|
|
453
|
+
:::
|
|
454
|
+
|
|
455
|
+
@return Promise
|
|
456
|
+
]=]
|
|
457
|
+
function DataStoreStage:PromiseViewUpToDate()
|
|
333
458
|
if not self._loadParent then
|
|
334
459
|
error("[DataStoreStage.Load] - Failed to load, no loadParent!")
|
|
335
460
|
end
|
|
@@ -337,54 +462,85 @@ function DataStoreStage:_promiseLoadParentContent()
|
|
|
337
462
|
error("[DataStoreStage.Load] - Failed to load, no loadName!")
|
|
338
463
|
end
|
|
339
464
|
|
|
340
|
-
return self._loadParent:
|
|
465
|
+
return self._loadParent:PromiseViewUpToDate()
|
|
341
466
|
end
|
|
342
467
|
|
|
343
468
|
--[=[
|
|
344
|
-
|
|
345
|
-
for save.
|
|
469
|
+
Ovewrites the full stage with the data specified.
|
|
346
470
|
|
|
347
|
-
|
|
348
|
-
|
|
471
|
+
:::tip
|
|
472
|
+
Use this method carefully as it can lead to data loss in ways that a specific :Store() call
|
|
473
|
+
on the right stage would do better.
|
|
474
|
+
:::
|
|
475
|
+
|
|
476
|
+
@param data any
|
|
349
477
|
]=]
|
|
350
|
-
function DataStoreStage:
|
|
351
|
-
|
|
478
|
+
function DataStoreStage:Overwrite(data)
|
|
479
|
+
-- Ensure that we at least start loading (and thus the autosave loop) for write
|
|
480
|
+
self:PromiseViewUpToDate()
|
|
352
481
|
|
|
353
|
-
if
|
|
354
|
-
|
|
482
|
+
if data == nil then
|
|
483
|
+
data = DataStoreDeleteToken
|
|
355
484
|
end
|
|
356
485
|
|
|
357
|
-
if
|
|
358
|
-
|
|
359
|
-
end
|
|
486
|
+
if type(data) == "table" then
|
|
487
|
+
local newSaveSnapshot = {}
|
|
360
488
|
|
|
361
|
-
|
|
362
|
-
|
|
489
|
+
local remaining = Set.fromKeys(self._stores)
|
|
490
|
+
for key, store in pairs(self._stores) do
|
|
491
|
+
-- Update each store
|
|
492
|
+
store:Overwrite(data[key])
|
|
493
|
+
end
|
|
363
494
|
|
|
364
|
-
|
|
365
|
-
|
|
495
|
+
for key, value in pairs(data) do
|
|
496
|
+
remaining[key] = nil
|
|
497
|
+
if self._stores[key] then
|
|
498
|
+
self._stores[key]:Overwrite(value)
|
|
499
|
+
else
|
|
500
|
+
newSaveSnapshot[key] = value
|
|
501
|
+
end
|
|
502
|
+
end
|
|
366
503
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
function DataStoreStage:GetSubStore(name)
|
|
371
|
-
assert(type(name) == "string" or type(name) == "number", "Bad name")
|
|
504
|
+
for key, _ in pairs(remaining) do
|
|
505
|
+
self._stores[key]:Overwrite(DataStoreDeleteToken)
|
|
506
|
+
end
|
|
372
507
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
508
|
+
self._saveDataSnapshot = table.freeze(newSaveSnapshot)
|
|
509
|
+
else
|
|
510
|
+
for _, store in pairs(self._stores) do
|
|
511
|
+
store:Overwrite(DataStoreDeleteToken)
|
|
512
|
+
end
|
|
376
513
|
|
|
377
|
-
|
|
378
|
-
error(("[DataStoreStage.GetSubStore] - Already have a writer for %q"):format(name))
|
|
514
|
+
self._saveDataSnapshot = data
|
|
379
515
|
end
|
|
380
516
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
self._maid:GiveTask(newStore)
|
|
517
|
+
self:_updateViewSnapshot()
|
|
518
|
+
end
|
|
384
519
|
|
|
385
|
-
|
|
520
|
+
--[=[
|
|
521
|
+
Ovewrites the full stage with the data specified. However, it will merge the data
|
|
522
|
+
to help prevent data-loss.
|
|
386
523
|
|
|
387
|
-
|
|
524
|
+
:::tip
|
|
525
|
+
Use this method carefully as it can lead to data loss in ways that a specific :Store() call
|
|
526
|
+
on the right stage would do better.
|
|
527
|
+
:::
|
|
528
|
+
|
|
529
|
+
@param data any
|
|
530
|
+
]=]
|
|
531
|
+
function DataStoreStage:OverwriteMerge(data)
|
|
532
|
+
-- Ensure that we at least start loading (and thus the autosave loop) for write
|
|
533
|
+
self:PromiseViewUpToDate()
|
|
534
|
+
|
|
535
|
+
if type(data) == "table" and data ~= DataStoreDeleteToken then
|
|
536
|
+
-- Note we explicitly don't wipe values here! Need delete token if we want to delete!
|
|
537
|
+
for key, value in pairs(data) do
|
|
538
|
+
self:_storeAtKey(key, value)
|
|
539
|
+
end
|
|
540
|
+
else
|
|
541
|
+
-- Non-tables
|
|
542
|
+
self:Overwrite(data)
|
|
543
|
+
end
|
|
388
544
|
end
|
|
389
545
|
|
|
390
546
|
--[=[
|
|
@@ -398,19 +554,10 @@ function DataStoreStage:StoreOnValueChange(name, valueObj)
|
|
|
398
554
|
assert(type(name) == "string" or type(name) == "number", "Bad name")
|
|
399
555
|
assert(typeof(valueObj) == "Instance" or (type(valueObj) == "table" and valueObj.Changed), "Bad valueObj")
|
|
400
556
|
|
|
401
|
-
if self._takenKeys[name] then
|
|
402
|
-
error(("[DataStoreStage] - Already have a writer for %q"):format(name))
|
|
403
|
-
end
|
|
404
|
-
|
|
405
557
|
local maid = Maid.new()
|
|
406
558
|
|
|
407
|
-
self._takenKeys[name] = true
|
|
408
|
-
maid:GiveTask(function()
|
|
409
|
-
self._takenKeys[name] = nil
|
|
410
|
-
end)
|
|
411
|
-
|
|
412
559
|
maid:GiveTask(valueObj.Changed:Connect(function()
|
|
413
|
-
self:
|
|
560
|
+
self:_storeAtKey(name, valueObj.Value)
|
|
414
561
|
end))
|
|
415
562
|
|
|
416
563
|
return maid
|
|
@@ -422,7 +569,7 @@ end
|
|
|
422
569
|
@return boolean
|
|
423
570
|
]=]
|
|
424
571
|
function DataStoreStage:HasWritableData()
|
|
425
|
-
if self.
|
|
572
|
+
if self._saveDataSnapshot ~= nil then
|
|
426
573
|
return true
|
|
427
574
|
end
|
|
428
575
|
|
|
@@ -441,57 +588,392 @@ function DataStoreStage:HasWritableData()
|
|
|
441
588
|
end
|
|
442
589
|
|
|
443
590
|
--[=[
|
|
444
|
-
Constructs a writer which provides a snapshot of the current data state to write
|
|
591
|
+
Constructs a writer which provides a snapshot of the current data state to write.
|
|
592
|
+
|
|
593
|
+
:::tip
|
|
594
|
+
This is automatically invoked during saving and is public so [DataStore] can invoke it.
|
|
595
|
+
:::
|
|
445
596
|
|
|
446
597
|
@return DataStoreWriter
|
|
447
598
|
]=]
|
|
448
599
|
function DataStoreStage:GetNewWriter()
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
600
|
+
self:_checkIntegrity()
|
|
601
|
+
|
|
602
|
+
local writer = DataStoreWriter.new(self:GetFullPath())
|
|
603
|
+
|
|
604
|
+
local fullBaseDataSnapshot = self:_createFullBaseDataSnapshot()
|
|
605
|
+
|
|
606
|
+
if self._saveDataSnapshot ~= nil then
|
|
607
|
+
writer:SetSaveDataSnapshot(self._saveDataSnapshot)
|
|
452
608
|
end
|
|
453
609
|
|
|
454
|
-
for
|
|
610
|
+
for key, store in pairs(self._stores) do
|
|
455
611
|
if not store.Destroy then
|
|
456
|
-
warn(("[DataStoreStage] - Substore %q destroyed"):format(
|
|
612
|
+
warn(("[DataStoreStage] - Substore %q destroyed"):format(key))
|
|
457
613
|
continue
|
|
458
614
|
end
|
|
459
615
|
|
|
460
616
|
if store:HasWritableData() then
|
|
461
|
-
writer:
|
|
617
|
+
writer:AddSubWriter(key, store:GetNewWriter())
|
|
462
618
|
end
|
|
463
619
|
end
|
|
464
620
|
|
|
621
|
+
writer:SetFullBaseDataSnapshot(fullBaseDataSnapshot)
|
|
622
|
+
|
|
465
623
|
return writer
|
|
466
624
|
end
|
|
467
625
|
|
|
626
|
+
--[=[
|
|
627
|
+
Invokes all saving callbacks
|
|
628
|
+
|
|
629
|
+
:::tip
|
|
630
|
+
This is automatically invoked before saving and is public so [DataStore] can invoke it.
|
|
631
|
+
:::
|
|
632
|
+
|
|
633
|
+
@return Promise
|
|
634
|
+
]=]
|
|
635
|
+
function DataStoreStage:PromiseInvokeSavingCallbacks()
|
|
636
|
+
if not next(self._savingCallbacks) then
|
|
637
|
+
return Promise.resolved()
|
|
638
|
+
end
|
|
639
|
+
|
|
640
|
+
local removingPromises = {}
|
|
641
|
+
for _, func in pairs(self._savingCallbacks) do
|
|
642
|
+
local result = func()
|
|
643
|
+
if Promise.isPromise(result) then
|
|
644
|
+
table.insert(removingPromises, result)
|
|
645
|
+
end
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
for _, substore in pairs(self._stores) do
|
|
649
|
+
local promise = substore:PromiseInvokeSavingCallbacks()
|
|
650
|
+
if promise then
|
|
651
|
+
table.insert(removingPromises, promise)
|
|
652
|
+
end
|
|
653
|
+
end
|
|
654
|
+
|
|
655
|
+
return PromiseUtils.all(removingPromises)
|
|
656
|
+
end
|
|
657
|
+
|
|
658
|
+
function DataStoreStage:_createFullBaseDataSnapshot()
|
|
659
|
+
if self._baseDataSnapshot == DataStoreDeleteToken then
|
|
660
|
+
error("BadDataSnapshot cannot be a delete token")
|
|
661
|
+
elseif type(self._baseDataSnapshot) == "table" or self._baseDataSnapshot == nil then
|
|
662
|
+
local newSnapshot
|
|
663
|
+
if type(self._baseDataSnapshot) == "table" then
|
|
664
|
+
newSnapshot = table.clone(self._baseDataSnapshot)
|
|
665
|
+
else
|
|
666
|
+
newSnapshot = {}
|
|
667
|
+
end
|
|
668
|
+
|
|
669
|
+
for key, store in pairs(self._stores) do
|
|
670
|
+
if not store.Destroy then
|
|
671
|
+
warn(("[DataStoreStage] - Substore %q destroyed"):format(key))
|
|
672
|
+
continue
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
if not store:HasWritableData() then
|
|
676
|
+
newSnapshot[key] = store:_createFullBaseDataSnapshot()
|
|
677
|
+
end
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
if DataStoreSnapshotUtils.isEmptySnapshot(newSnapshot) then
|
|
681
|
+
return nil
|
|
682
|
+
else
|
|
683
|
+
return table.freeze(newSnapshot)
|
|
684
|
+
end
|
|
685
|
+
else
|
|
686
|
+
return self._baseDataSnapshot
|
|
687
|
+
end
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
function DataStoreStage:_updateStoresAndComputeBaseDataSnapshotFromDiffSnapshot(diffSnapshot)
|
|
691
|
+
if diffSnapshot == DataStoreDeleteToken then
|
|
692
|
+
return nil
|
|
693
|
+
elseif type(diffSnapshot) == "table" then
|
|
694
|
+
local newBaseDataSnapshot
|
|
695
|
+
if type(self._baseDataSnapshot) == "table" then
|
|
696
|
+
newBaseDataSnapshot = table.clone(self._baseDataSnapshot)
|
|
697
|
+
else
|
|
698
|
+
newBaseDataSnapshot = {}
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
-- Merge all of our newly downloaded data here into our base layer.
|
|
702
|
+
for key, value in pairs(diffSnapshot) do
|
|
703
|
+
newBaseDataSnapshot[key] = self:_updateStoresAndComputeBaseDataSnapshotValueFromDiffSnapshot(key, value)
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
return table.freeze(newBaseDataSnapshot)
|
|
707
|
+
else
|
|
708
|
+
return diffSnapshot
|
|
709
|
+
end
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
function DataStoreStage:_updateStoresAndComputeBaseDataSnapshotValueFromDiffSnapshot(key, value)
|
|
713
|
+
assert(type(key) == "string" or type(key) == "number", "Bad key")
|
|
714
|
+
|
|
715
|
+
if self._stores[key] then
|
|
716
|
+
self._stores[key]:MergeDiffSnapshot(value)
|
|
717
|
+
return nil
|
|
718
|
+
elseif value == DataStoreDeleteToken then
|
|
719
|
+
return nil
|
|
720
|
+
elseif type(value) == "table" and type(self._baseDataSnapshot) == "table" and type(self._baseDataSnapshot[key]) == "table" then
|
|
721
|
+
return self:_recurseMergeTable(self._baseDataSnapshot[key], value)
|
|
722
|
+
else
|
|
723
|
+
return value
|
|
724
|
+
end
|
|
725
|
+
end
|
|
726
|
+
|
|
727
|
+
function DataStoreStage:_recurseMergeTable(original, incoming)
|
|
728
|
+
if incoming == DataStoreDeleteToken then
|
|
729
|
+
return nil
|
|
730
|
+
elseif type(incoming) == "table" and type(original) == "table" then
|
|
731
|
+
-- Merge
|
|
732
|
+
local newSnapshot = table.clone(original)
|
|
733
|
+
|
|
734
|
+
-- Overwerite with merged values...
|
|
735
|
+
for key, value in pairs(incoming) do
|
|
736
|
+
newSnapshot[key] = self:_recurseMergeTable(original[key], value)
|
|
737
|
+
end
|
|
738
|
+
|
|
739
|
+
return table.freeze(newSnapshot)
|
|
740
|
+
else
|
|
741
|
+
return incoming
|
|
742
|
+
end
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
function DataStoreStage:_updateViewSnapshot()
|
|
746
|
+
self:_checkIntegrity()
|
|
747
|
+
|
|
748
|
+
local newViewSnapshot = self:_computeNewViewSnapshot()
|
|
749
|
+
|
|
750
|
+
-- This will only filter out a few items
|
|
751
|
+
if self._viewSnapshot == newViewSnapshot then
|
|
752
|
+
return
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
local previousView = self._viewSnapshot
|
|
756
|
+
|
|
757
|
+
-- Detect keys that changed
|
|
758
|
+
local changedKeys
|
|
759
|
+
if type(previousView) == "table" and type(newViewSnapshot) == "table" then
|
|
760
|
+
changedKeys = {}
|
|
761
|
+
local keys = Set.union(Set.fromKeys(previousView), Set.fromKeys(newViewSnapshot))
|
|
762
|
+
for key, _ in pairs(keys) do
|
|
763
|
+
if previousView[key] ~= newViewSnapshot[key] then
|
|
764
|
+
changedKeys[key] = true
|
|
765
|
+
end
|
|
766
|
+
end
|
|
767
|
+
elseif type(newViewSnapshot) == "table" then
|
|
768
|
+
-- Swap to table, all keys change
|
|
769
|
+
changedKeys = Set.fromKeys(newViewSnapshot)
|
|
770
|
+
elseif type(previousView) == "table" then
|
|
771
|
+
-- Swap from table, all keys change
|
|
772
|
+
changedKeys = Set.fromKeys(previousView)
|
|
773
|
+
else
|
|
774
|
+
changedKeys = {}
|
|
775
|
+
end
|
|
776
|
+
|
|
777
|
+
if next(changedKeys) ~= nil then
|
|
778
|
+
self._viewSnapshot = newViewSnapshot
|
|
779
|
+
|
|
780
|
+
if type(newViewSnapshot) == "table" then
|
|
781
|
+
for key, value in pairs(changedKeys) do
|
|
782
|
+
self._keySubscriptions:Fire(key, newViewSnapshot[value])
|
|
783
|
+
end
|
|
784
|
+
else
|
|
785
|
+
for key, _ in pairs(changedKeys) do
|
|
786
|
+
self._keySubscriptions:Fire(key, nil)
|
|
787
|
+
end
|
|
788
|
+
end
|
|
789
|
+
|
|
790
|
+
self.Changed:Fire(self._viewSnapshot)
|
|
791
|
+
end
|
|
792
|
+
|
|
793
|
+
self:_checkIntegrity()
|
|
794
|
+
end
|
|
795
|
+
|
|
796
|
+
function DataStoreStage:_updateViewSnapshotAtKey(key)
|
|
797
|
+
assert(type(key) == "string" or type(key) == "number", "Bad key")
|
|
798
|
+
|
|
799
|
+
if type(self._viewSnapshot) ~= "table" then
|
|
800
|
+
self:_updateViewSnapshot()
|
|
801
|
+
return
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
local newValue = self:_computeViewValueForKey(key)
|
|
805
|
+
if self._viewSnapshot[key] == newValue then
|
|
806
|
+
return
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
local newSnapshot = table.clone(self._viewSnapshot)
|
|
810
|
+
newSnapshot[key] = newValue
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
self._viewSnapshot = table.freeze(newSnapshot)
|
|
814
|
+
self._keySubscriptions:Fire(key, newValue)
|
|
815
|
+
self.Changed:Fire(self._viewSnapshot)
|
|
816
|
+
|
|
817
|
+
self:_checkIntegrity()
|
|
818
|
+
end
|
|
819
|
+
|
|
820
|
+
function DataStoreStage:_computeNewViewSnapshot()
|
|
821
|
+
-- This prioritizes save data first, then stores, then base data
|
|
822
|
+
|
|
823
|
+
if self._saveDataSnapshot == DataStoreDeleteToken then
|
|
824
|
+
return nil
|
|
825
|
+
elseif self._saveDataSnapshot == nil or type(self._saveDataSnapshot) == "table" then
|
|
826
|
+
-- Compute a new view
|
|
827
|
+
|
|
828
|
+
-- Start with base data
|
|
829
|
+
local newView
|
|
830
|
+
if type(self._baseDataSnapshot) == "table" then
|
|
831
|
+
newView = table.clone(self._baseDataSnapshot)
|
|
832
|
+
else
|
|
833
|
+
newView = {}
|
|
834
|
+
end
|
|
835
|
+
|
|
836
|
+
-- Add in stores
|
|
837
|
+
for key, store in pairs(self._stores) do
|
|
838
|
+
newView[key] = store._viewSnapshot
|
|
839
|
+
end
|
|
840
|
+
|
|
841
|
+
-- Then finally save data
|
|
842
|
+
if type(self._saveDataSnapshot) == "table" then
|
|
843
|
+
for key, value in pairs(self._saveDataSnapshot) do
|
|
844
|
+
if value == DataStoreDeleteToken then
|
|
845
|
+
newView[key] = nil
|
|
846
|
+
else
|
|
847
|
+
newView[key] = value
|
|
848
|
+
end
|
|
849
|
+
end
|
|
850
|
+
end
|
|
851
|
+
|
|
852
|
+
if next(newView) == nil and not (type(self._baseDataSnapshot) == "table" or type(self._saveDataSnapshot) == "table") then
|
|
853
|
+
-- We haev no reason to be a table, make sure we return nil
|
|
854
|
+
return nil
|
|
855
|
+
end
|
|
856
|
+
|
|
857
|
+
return table.freeze(newView)
|
|
858
|
+
else
|
|
859
|
+
-- If save data isn't nil or a table then we are to return the save table
|
|
860
|
+
return self._saveDataSnapshot
|
|
861
|
+
end
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
function DataStoreStage:_computeViewValueForKey(key)
|
|
865
|
+
-- This prioritizes save data first, then stores, then base data
|
|
866
|
+
|
|
867
|
+
if self._saveDataSnapshot == DataStoreDeleteToken then
|
|
868
|
+
return nil
|
|
869
|
+
elseif self._saveDataSnapshot == nil or type(self._saveDataSnapshot) == "table" then
|
|
870
|
+
if type(self._saveDataSnapshot) == "table" then
|
|
871
|
+
if self._saveDataSnapshot[key] ~= nil then
|
|
872
|
+
local value = self._saveDataSnapshot[key]
|
|
873
|
+
if value == DataStoreDeleteToken then
|
|
874
|
+
return nil
|
|
875
|
+
else
|
|
876
|
+
return value
|
|
877
|
+
end
|
|
878
|
+
end
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
if self._stores[key] then
|
|
882
|
+
local value = self._stores[key]._viewSnapshot
|
|
883
|
+
if value == DataStoreDeleteToken then
|
|
884
|
+
return nil
|
|
885
|
+
else
|
|
886
|
+
return value
|
|
887
|
+
end
|
|
888
|
+
end
|
|
889
|
+
|
|
890
|
+
if type(self._baseDataSnapshot) == "table" then
|
|
891
|
+
if self._baseDataSnapshot[key] ~= nil then
|
|
892
|
+
return self._baseDataSnapshot[key]
|
|
893
|
+
end
|
|
894
|
+
end
|
|
895
|
+
|
|
896
|
+
return nil
|
|
897
|
+
else
|
|
898
|
+
-- If save data isn't nil or a table then we are to return nil.
|
|
899
|
+
return nil
|
|
900
|
+
end
|
|
901
|
+
end
|
|
902
|
+
|
|
468
903
|
-- Stores the data for overwrite.
|
|
469
|
-
function DataStoreStage:
|
|
470
|
-
assert(type(
|
|
904
|
+
function DataStoreStage:_storeAtKey(key, value)
|
|
905
|
+
assert(type(key) == "string" or type(key) == "number", "Bad key")
|
|
471
906
|
assert(value ~= nil, "Bad value")
|
|
472
907
|
|
|
473
|
-
local
|
|
474
|
-
if value ==
|
|
475
|
-
|
|
476
|
-
elseif type(value) == "table" then
|
|
477
|
-
newValue = Table.deepCopy(value)
|
|
908
|
+
local deepClonedSaveValue
|
|
909
|
+
if type(value) == "table" then
|
|
910
|
+
deepClonedSaveValue = table.freeze(Table.deepCopy(value))
|
|
478
911
|
else
|
|
479
|
-
|
|
912
|
+
deepClonedSaveValue = value
|
|
480
913
|
end
|
|
481
914
|
|
|
482
|
-
if
|
|
483
|
-
self.
|
|
915
|
+
if self._stores[key] then
|
|
916
|
+
self._stores[key]:Overwrite(value)
|
|
917
|
+
return
|
|
484
918
|
end
|
|
485
919
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
920
|
+
local swappedSaveSnapshotType = false
|
|
921
|
+
local newSnapshot
|
|
922
|
+
|
|
923
|
+
if type(self._saveDataSnapshot) == "table" then
|
|
924
|
+
newSnapshot = table.clone(self._saveDataSnapshot)
|
|
925
|
+
else
|
|
926
|
+
swappedSaveSnapshotType = true
|
|
927
|
+
newSnapshot = {}
|
|
489
928
|
end
|
|
490
929
|
|
|
491
|
-
|
|
492
|
-
|
|
930
|
+
newSnapshot[key] = deepClonedSaveValue
|
|
931
|
+
|
|
932
|
+
self._saveDataSnapshot = table.freeze(newSnapshot)
|
|
933
|
+
|
|
934
|
+
self.DataStored:Fire()
|
|
935
|
+
|
|
936
|
+
if swappedSaveSnapshotType then
|
|
937
|
+
self:_updateViewSnapshot()
|
|
493
938
|
else
|
|
494
|
-
self
|
|
939
|
+
self:_updateViewSnapshotAtKey(key)
|
|
940
|
+
end
|
|
941
|
+
self:_checkIntegrity()
|
|
942
|
+
end
|
|
943
|
+
|
|
944
|
+
function DataStoreStage:_checkIntegrity()
|
|
945
|
+
assert(self._baseDataSnapshot ~= DataStoreDeleteToken, "BaseDataSnapshot should not be DataStoreDeleteToken")
|
|
946
|
+
assert(self._viewSnapshot ~= DataStoreDeleteToken, "ViewSnapshot should not be DataStoreDeleteToken")
|
|
947
|
+
|
|
948
|
+
if type(self._baseDataSnapshot) == "table" then
|
|
949
|
+
assert(table.isfrozen(self._baseDataSnapshot), "Base snapshot should be frozen")
|
|
950
|
+
end
|
|
951
|
+
|
|
952
|
+
if type(self._saveDataSnapshot) == "table" then
|
|
953
|
+
assert(table.isfrozen(self._saveDataSnapshot), "Save snapshot should be frozen")
|
|
954
|
+
end
|
|
955
|
+
|
|
956
|
+
if type(self._viewSnapshot) == "table" then
|
|
957
|
+
assert(table.isfrozen(self._viewSnapshot), "View snapshot should be frozen")
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
for key, _ in pairs(self._stores) do
|
|
961
|
+
if type(self._baseDataSnapshot) == "table" and self._baseDataSnapshot[key] ~= nil then
|
|
962
|
+
error(string.format("[DataStoreStage] - Duplicate baseData at key %q", key))
|
|
963
|
+
end
|
|
964
|
+
|
|
965
|
+
if type(self._saveDataSnapshot) == "table" and self._saveDataSnapshot[key] ~= nil then
|
|
966
|
+
error(string.format("[DataStoreStage] - Duplicate saveData at key %q", key))
|
|
967
|
+
end
|
|
968
|
+
end
|
|
969
|
+
|
|
970
|
+
if type(self._viewSnapshot) == "table" then
|
|
971
|
+
for key, value in pairs(self._viewSnapshot) do
|
|
972
|
+
assert(type(key) == "string" or type(key) == "number", "Bad key")
|
|
973
|
+
if value == DataStoreDeleteToken then
|
|
974
|
+
error(string.format("[DataStoreStage] - View at key %q is delete token", key))
|
|
975
|
+
end
|
|
976
|
+
end
|
|
495
977
|
end
|
|
496
978
|
end
|
|
497
979
|
|