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