@quenty/datastore 8.0.0-canary.367.903617a.0 → 8.0.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 +136 -1
- package/README.md +11 -0
- package/package.json +14 -11
- 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 +758 -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
|
|
219
331
|
|
|
220
|
-
|
|
332
|
+
--[=[
|
|
333
|
+
Gets an event that will fire off whenever something is stored at this level
|
|
334
|
+
|
|
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,108 @@ end
|
|
|
258
377
|
@return Promise<{ [string]: true }>
|
|
259
378
|
]=]
|
|
260
379
|
function DataStoreStage:PromiseKeySet()
|
|
261
|
-
return self:
|
|
262
|
-
|
|
380
|
+
return self:PromiseViewUpToDate():Then(function()
|
|
381
|
+
return Set.fromKeys(self._viewSnapshot)
|
|
382
|
+
end)
|
|
383
|
+
end
|
|
263
384
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
end
|
|
268
|
-
end
|
|
385
|
+
--[=[
|
|
386
|
+
This will always prioritize our own view of the world over
|
|
387
|
+
incoming data.
|
|
269
388
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
keySet[key] = true
|
|
274
|
-
end
|
|
275
|
-
end
|
|
276
|
-
end
|
|
389
|
+
:::tip
|
|
390
|
+
This is a helper method that helps load diff data into the data store.
|
|
391
|
+
:::
|
|
277
392
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
end
|
|
283
|
-
end
|
|
393
|
+
@param diffSnapshot any
|
|
394
|
+
]=]
|
|
395
|
+
function DataStoreStage:MergeDiffSnapshot(diffSnapshot)
|
|
396
|
+
self:_checkIntegrity()
|
|
284
397
|
|
|
285
|
-
|
|
286
|
-
|
|
398
|
+
self._baseDataSnapshot = self:_updateStoresAndComputeBaseDataSnapshotFromDiffSnapshot(diffSnapshot)
|
|
399
|
+
|
|
400
|
+
self:_updateViewSnapshot()
|
|
401
|
+
self:_checkIntegrity()
|
|
287
402
|
end
|
|
288
403
|
|
|
289
404
|
--[=[
|
|
290
|
-
|
|
405
|
+
Updates the base data to the saved / written data.
|
|
291
406
|
|
|
292
|
-
|
|
407
|
+
This will always prioritize our own view of the world over
|
|
408
|
+
incoming data.
|
|
409
|
+
|
|
410
|
+
@param parentWriter DataStoreWriter
|
|
293
411
|
]=]
|
|
294
|
-
function DataStoreStage:
|
|
295
|
-
|
|
296
|
-
|
|
412
|
+
function DataStoreStage:MarkDataAsSaved(parentWriter)
|
|
413
|
+
-- Update all children first
|
|
414
|
+
for key, subwriter in pairs(parentWriter:GetSubWritersMap()) do
|
|
415
|
+
local store = self._stores[key]
|
|
416
|
+
if store then
|
|
417
|
+
store:MarkDataAsSaved(subwriter)
|
|
418
|
+
else
|
|
419
|
+
warn("[DataStoreStage] - Store removed, but writer persists")
|
|
420
|
+
end
|
|
421
|
+
end
|
|
297
422
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
423
|
+
local dataToSave = parentWriter:GetDataToSave()
|
|
424
|
+
if self._saveDataSnapshot == DataStoreDeleteToken or dataToSave == DataStoreDeleteToken then
|
|
425
|
+
if self._saveDataSnapshot == dataToSave then
|
|
426
|
+
self._baseDataSnapshot = nil
|
|
427
|
+
self._saveDataSnapshot = nil
|
|
428
|
+
end
|
|
429
|
+
elseif type(self._saveDataSnapshot) == "table" or type(dataToSave) == "table" then
|
|
430
|
+
if type(self._saveDataSnapshot) == "table" and type(dataToSave) == "table" then
|
|
431
|
+
local newSaveSnapshot = table.clone(self._saveDataSnapshot)
|
|
432
|
+
local newBaseDataSnapshot
|
|
433
|
+
if type(self._baseDataSnapshot) == "table" then
|
|
434
|
+
newBaseDataSnapshot = table.clone(self._baseDataSnapshot)
|
|
303
435
|
else
|
|
304
|
-
|
|
436
|
+
newBaseDataSnapshot = {}
|
|
305
437
|
end
|
|
306
|
-
end
|
|
307
438
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if value ==
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
else
|
|
315
|
-
result[key] = value
|
|
439
|
+
for key, value in pairs(dataToSave) do
|
|
440
|
+
local ourSnapshotValue = self._saveDataSnapshot[key]
|
|
441
|
+
if Table.deepEquivalent(ourSnapshotValue, value) or ourSnapshotValue == nil then
|
|
442
|
+
-- This shouldn't fire any event because our save data is matching
|
|
443
|
+
newBaseDataSnapshot[key] = self:_updateStoresAndComputeBaseDataSnapshotValueFromDiffSnapshot(key, value)
|
|
444
|
+
newSaveSnapshot[key] = nil
|
|
316
445
|
end
|
|
317
446
|
end
|
|
318
|
-
end
|
|
319
447
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
448
|
+
self._baseDataSnapshot = table.freeze(newBaseDataSnapshot)
|
|
449
|
+
|
|
450
|
+
if DataStoreSnapshotUtils.isEmptySnapshot(newSaveSnapshot) then
|
|
451
|
+
self._saveDataSnapshot = nil
|
|
452
|
+
else
|
|
453
|
+
self._saveDataSnapshot = table.freeze(newSaveSnapshot)
|
|
325
454
|
end
|
|
326
455
|
end
|
|
456
|
+
else
|
|
457
|
+
assert(type(self._saveDataSnapshot) ~= "table", "Case is covered above")
|
|
458
|
+
assert(self._saveDataSnapshot ~= DataStoreDeleteToken, "Case is covered above")
|
|
459
|
+
assert(dataToSave ~= DataStoreDeleteToken, "Case is covered above")
|
|
460
|
+
assert(type(dataToSave) ~= "table", "Case is covered above")
|
|
461
|
+
|
|
462
|
+
-- In the none-table scenario move stuff
|
|
463
|
+
if self._saveDataSnapshot == dataToSave then
|
|
464
|
+
self._baseDataSnapshot = dataToSave
|
|
465
|
+
self._saveDataSnapshot = nil
|
|
466
|
+
end
|
|
467
|
+
end
|
|
327
468
|
|
|
328
|
-
|
|
329
|
-
end)
|
|
469
|
+
self:_checkIntegrity()
|
|
330
470
|
end
|
|
331
471
|
|
|
332
|
-
|
|
472
|
+
--[=[
|
|
473
|
+
Helper method that when invokes ensures the data view.
|
|
474
|
+
|
|
475
|
+
:::tip
|
|
476
|
+
This is a helper method. You probably want [DataStore.LoadAll] instead.
|
|
477
|
+
:::
|
|
478
|
+
|
|
479
|
+
@return Promise
|
|
480
|
+
]=]
|
|
481
|
+
function DataStoreStage:PromiseViewUpToDate()
|
|
333
482
|
if not self._loadParent then
|
|
334
483
|
error("[DataStoreStage.Load] - Failed to load, no loadParent!")
|
|
335
484
|
end
|
|
@@ -337,54 +486,85 @@ function DataStoreStage:_promiseLoadParentContent()
|
|
|
337
486
|
error("[DataStoreStage.Load] - Failed to load, no loadName!")
|
|
338
487
|
end
|
|
339
488
|
|
|
340
|
-
return self._loadParent:
|
|
489
|
+
return self._loadParent:PromiseViewUpToDate()
|
|
341
490
|
end
|
|
342
491
|
|
|
343
492
|
--[=[
|
|
344
|
-
|
|
345
|
-
for save.
|
|
493
|
+
Ovewrites the full stage with the data specified.
|
|
346
494
|
|
|
347
|
-
|
|
348
|
-
|
|
495
|
+
:::tip
|
|
496
|
+
Use this method carefully as it can lead to data loss in ways that a specific :Store() call
|
|
497
|
+
on the right stage would do better.
|
|
498
|
+
:::
|
|
499
|
+
|
|
500
|
+
@param data any
|
|
349
501
|
]=]
|
|
350
|
-
function DataStoreStage:
|
|
351
|
-
|
|
502
|
+
function DataStoreStage:Overwrite(data)
|
|
503
|
+
-- Ensure that we at least start loading (and thus the autosave loop) for write
|
|
504
|
+
self:PromiseViewUpToDate()
|
|
352
505
|
|
|
353
|
-
if
|
|
354
|
-
|
|
506
|
+
if data == nil then
|
|
507
|
+
data = DataStoreDeleteToken
|
|
355
508
|
end
|
|
356
509
|
|
|
357
|
-
if
|
|
358
|
-
|
|
359
|
-
end
|
|
510
|
+
if type(data) == "table" then
|
|
511
|
+
local newSaveSnapshot = {}
|
|
360
512
|
|
|
361
|
-
|
|
362
|
-
|
|
513
|
+
local remaining = Set.fromKeys(self._stores)
|
|
514
|
+
for key, store in pairs(self._stores) do
|
|
515
|
+
-- Update each store
|
|
516
|
+
store:Overwrite(data[key])
|
|
517
|
+
end
|
|
363
518
|
|
|
364
|
-
|
|
365
|
-
|
|
519
|
+
for key, value in pairs(data) do
|
|
520
|
+
remaining[key] = nil
|
|
521
|
+
if self._stores[key] then
|
|
522
|
+
self._stores[key]:Overwrite(value)
|
|
523
|
+
else
|
|
524
|
+
newSaveSnapshot[key] = value
|
|
525
|
+
end
|
|
526
|
+
end
|
|
366
527
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
function DataStoreStage:GetSubStore(name)
|
|
371
|
-
assert(type(name) == "string" or type(name) == "number", "Bad name")
|
|
528
|
+
for key, _ in pairs(remaining) do
|
|
529
|
+
self._stores[key]:Overwrite(DataStoreDeleteToken)
|
|
530
|
+
end
|
|
372
531
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
532
|
+
self._saveDataSnapshot = table.freeze(newSaveSnapshot)
|
|
533
|
+
else
|
|
534
|
+
for _, store in pairs(self._stores) do
|
|
535
|
+
store:Overwrite(DataStoreDeleteToken)
|
|
536
|
+
end
|
|
376
537
|
|
|
377
|
-
|
|
378
|
-
error(("[DataStoreStage.GetSubStore] - Already have a writer for %q"):format(name))
|
|
538
|
+
self._saveDataSnapshot = data
|
|
379
539
|
end
|
|
380
540
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
541
|
+
self:_updateViewSnapshot()
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
--[=[
|
|
545
|
+
Ovewrites the full stage with the data specified. However, it will merge the data
|
|
546
|
+
to help prevent data-loss.
|
|
384
547
|
|
|
385
|
-
|
|
548
|
+
:::tip
|
|
549
|
+
Use this method carefully as it can lead to data loss in ways that a specific :Store() call
|
|
550
|
+
on the right stage would do better.
|
|
551
|
+
:::
|
|
386
552
|
|
|
387
|
-
|
|
553
|
+
@param data any
|
|
554
|
+
]=]
|
|
555
|
+
function DataStoreStage:OverwriteMerge(data)
|
|
556
|
+
-- Ensure that we at least start loading (and thus the autosave loop) for write
|
|
557
|
+
self:PromiseViewUpToDate()
|
|
558
|
+
|
|
559
|
+
if type(data) == "table" and data ~= DataStoreDeleteToken then
|
|
560
|
+
-- Note we explicitly don't wipe values here! Need delete token if we want to delete!
|
|
561
|
+
for key, value in pairs(data) do
|
|
562
|
+
self:_storeAtKey(key, value)
|
|
563
|
+
end
|
|
564
|
+
else
|
|
565
|
+
-- Non-tables
|
|
566
|
+
self:Overwrite(data)
|
|
567
|
+
end
|
|
388
568
|
end
|
|
389
569
|
|
|
390
570
|
--[=[
|
|
@@ -398,19 +578,10 @@ function DataStoreStage:StoreOnValueChange(name, valueObj)
|
|
|
398
578
|
assert(type(name) == "string" or type(name) == "number", "Bad name")
|
|
399
579
|
assert(typeof(valueObj) == "Instance" or (type(valueObj) == "table" and valueObj.Changed), "Bad valueObj")
|
|
400
580
|
|
|
401
|
-
if self._takenKeys[name] then
|
|
402
|
-
error(("[DataStoreStage] - Already have a writer for %q"):format(name))
|
|
403
|
-
end
|
|
404
|
-
|
|
405
581
|
local maid = Maid.new()
|
|
406
582
|
|
|
407
|
-
self._takenKeys[name] = true
|
|
408
|
-
maid:GiveTask(function()
|
|
409
|
-
self._takenKeys[name] = nil
|
|
410
|
-
end)
|
|
411
|
-
|
|
412
583
|
maid:GiveTask(valueObj.Changed:Connect(function()
|
|
413
|
-
self:
|
|
584
|
+
self:_storeAtKey(name, valueObj.Value)
|
|
414
585
|
end))
|
|
415
586
|
|
|
416
587
|
return maid
|
|
@@ -422,7 +593,7 @@ end
|
|
|
422
593
|
@return boolean
|
|
423
594
|
]=]
|
|
424
595
|
function DataStoreStage:HasWritableData()
|
|
425
|
-
if self.
|
|
596
|
+
if self._saveDataSnapshot ~= nil then
|
|
426
597
|
return true
|
|
427
598
|
end
|
|
428
599
|
|
|
@@ -441,57 +612,403 @@ function DataStoreStage:HasWritableData()
|
|
|
441
612
|
end
|
|
442
613
|
|
|
443
614
|
--[=[
|
|
444
|
-
Constructs a writer which provides a snapshot of the current data state to write
|
|
615
|
+
Constructs a writer which provides a snapshot of the current data state to write.
|
|
616
|
+
|
|
617
|
+
:::tip
|
|
618
|
+
This is automatically invoked during saving and is public so [DataStore] can invoke it.
|
|
619
|
+
:::
|
|
445
620
|
|
|
446
621
|
@return DataStoreWriter
|
|
447
622
|
]=]
|
|
448
623
|
function DataStoreStage:GetNewWriter()
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
624
|
+
self:_checkIntegrity()
|
|
625
|
+
|
|
626
|
+
local writer = DataStoreWriter.new(self:GetFullPath())
|
|
627
|
+
|
|
628
|
+
local fullBaseDataSnapshot = self:_createFullBaseDataSnapshot()
|
|
629
|
+
|
|
630
|
+
if self._saveDataSnapshot ~= nil then
|
|
631
|
+
writer:SetSaveDataSnapshot(self._saveDataSnapshot)
|
|
452
632
|
end
|
|
453
633
|
|
|
454
|
-
for
|
|
634
|
+
for key, store in pairs(self._stores) do
|
|
455
635
|
if not store.Destroy then
|
|
456
|
-
warn(("[DataStoreStage] - Substore %q destroyed"):format(
|
|
636
|
+
warn(("[DataStoreStage] - Substore %q destroyed"):format(key))
|
|
457
637
|
continue
|
|
458
638
|
end
|
|
459
639
|
|
|
460
640
|
if store:HasWritableData() then
|
|
461
|
-
writer:
|
|
641
|
+
writer:AddSubWriter(key, store:GetNewWriter())
|
|
462
642
|
end
|
|
463
643
|
end
|
|
464
644
|
|
|
645
|
+
writer:SetFullBaseDataSnapshot(fullBaseDataSnapshot)
|
|
646
|
+
|
|
465
647
|
return writer
|
|
466
648
|
end
|
|
467
649
|
|
|
650
|
+
--[=[
|
|
651
|
+
Invokes all saving callbacks
|
|
652
|
+
|
|
653
|
+
:::tip
|
|
654
|
+
This is automatically invoked before saving and is public so [DataStore] can invoke it.
|
|
655
|
+
:::
|
|
656
|
+
|
|
657
|
+
@return Promise
|
|
658
|
+
]=]
|
|
659
|
+
function DataStoreStage:PromiseInvokeSavingCallbacks()
|
|
660
|
+
if not next(self._savingCallbacks) then
|
|
661
|
+
return Promise.resolved()
|
|
662
|
+
end
|
|
663
|
+
|
|
664
|
+
local removingPromises = {}
|
|
665
|
+
for _, func in pairs(self._savingCallbacks) do
|
|
666
|
+
local result = func()
|
|
667
|
+
if Promise.isPromise(result) then
|
|
668
|
+
table.insert(removingPromises, result)
|
|
669
|
+
end
|
|
670
|
+
end
|
|
671
|
+
|
|
672
|
+
for _, substore in pairs(self._stores) do
|
|
673
|
+
local promise = substore:PromiseInvokeSavingCallbacks()
|
|
674
|
+
if promise then
|
|
675
|
+
table.insert(removingPromises, promise)
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
return PromiseUtils.all(removingPromises)
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
function DataStoreStage:_createFullBaseDataSnapshot()
|
|
683
|
+
if self._baseDataSnapshot == DataStoreDeleteToken then
|
|
684
|
+
error("BadDataSnapshot cannot be a delete token")
|
|
685
|
+
elseif type(self._baseDataSnapshot) == "table" or self._baseDataSnapshot == nil then
|
|
686
|
+
local newSnapshot
|
|
687
|
+
if type(self._baseDataSnapshot) == "table" then
|
|
688
|
+
newSnapshot = table.clone(self._baseDataSnapshot)
|
|
689
|
+
else
|
|
690
|
+
newSnapshot = {}
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
for key, store in pairs(self._stores) do
|
|
694
|
+
if not store.Destroy then
|
|
695
|
+
warn(("[DataStoreStage] - Substore %q destroyed"):format(key))
|
|
696
|
+
continue
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
if not store:HasWritableData() then
|
|
700
|
+
newSnapshot[key] = store:_createFullBaseDataSnapshot()
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
if DataStoreSnapshotUtils.isEmptySnapshot(newSnapshot) then
|
|
705
|
+
return nil
|
|
706
|
+
else
|
|
707
|
+
return table.freeze(newSnapshot)
|
|
708
|
+
end
|
|
709
|
+
else
|
|
710
|
+
return self._baseDataSnapshot
|
|
711
|
+
end
|
|
712
|
+
end
|
|
713
|
+
|
|
714
|
+
function DataStoreStage:_updateStoresAndComputeBaseDataSnapshotFromDiffSnapshot(diffSnapshot)
|
|
715
|
+
if diffSnapshot == DataStoreDeleteToken then
|
|
716
|
+
return nil
|
|
717
|
+
elseif type(diffSnapshot) == "table" then
|
|
718
|
+
local newBaseDataSnapshot
|
|
719
|
+
if type(self._baseDataSnapshot) == "table" then
|
|
720
|
+
newBaseDataSnapshot = table.clone(self._baseDataSnapshot)
|
|
721
|
+
else
|
|
722
|
+
newBaseDataSnapshot = {}
|
|
723
|
+
end
|
|
724
|
+
|
|
725
|
+
-- Merge all of our newly downloaded data here into our base layer.
|
|
726
|
+
for key, value in pairs(diffSnapshot) do
|
|
727
|
+
newBaseDataSnapshot[key] = self:_updateStoresAndComputeBaseDataSnapshotValueFromDiffSnapshot(key, value)
|
|
728
|
+
end
|
|
729
|
+
|
|
730
|
+
return table.freeze(newBaseDataSnapshot)
|
|
731
|
+
elseif diffSnapshot == nil then
|
|
732
|
+
return self._baseDataSnapshot
|
|
733
|
+
else
|
|
734
|
+
return diffSnapshot
|
|
735
|
+
end
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
function DataStoreStage:_updateStoresAndComputeBaseDataSnapshotValueFromDiffSnapshot(key, value)
|
|
739
|
+
assert(type(key) == "string" or type(key) == "number", "Bad key")
|
|
740
|
+
|
|
741
|
+
if self._stores[key] then
|
|
742
|
+
self._stores[key]:MergeDiffSnapshot(value)
|
|
743
|
+
return nil
|
|
744
|
+
elseif value == DataStoreDeleteToken then
|
|
745
|
+
return nil
|
|
746
|
+
elseif type(value) == "table" and type(self._baseDataSnapshot) == "table" and type(self._baseDataSnapshot[key]) == "table" then
|
|
747
|
+
return self:_recurseMergeTable(self._baseDataSnapshot[key], value)
|
|
748
|
+
else
|
|
749
|
+
return value
|
|
750
|
+
end
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
function DataStoreStage:_recurseMergeTable(original, incoming)
|
|
754
|
+
if incoming == DataStoreDeleteToken then
|
|
755
|
+
return nil
|
|
756
|
+
elseif type(incoming) == "table" and type(original) == "table" then
|
|
757
|
+
-- Merge
|
|
758
|
+
local newSnapshot = table.clone(original)
|
|
759
|
+
|
|
760
|
+
-- Overwerite with merged values...
|
|
761
|
+
for key, value in pairs(incoming) do
|
|
762
|
+
newSnapshot[key] = self:_recurseMergeTable(original[key], value)
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
return table.freeze(newSnapshot)
|
|
766
|
+
else
|
|
767
|
+
return incoming
|
|
768
|
+
end
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
function DataStoreStage:_updateViewSnapshot()
|
|
772
|
+
self:_checkIntegrity()
|
|
773
|
+
|
|
774
|
+
local newViewSnapshot = self:_computeNewViewSnapshot()
|
|
775
|
+
|
|
776
|
+
if not Table.deepEquivalent(self._viewSnapshot, newViewSnapshot) then
|
|
777
|
+
local previousViewSnapshot = self._viewSnapshot
|
|
778
|
+
self._viewSnapshot = newViewSnapshot
|
|
779
|
+
|
|
780
|
+
-- Fire off changed keys
|
|
781
|
+
local changedKeys = self:_computeChangedKeys(previousViewSnapshot, newViewSnapshot)
|
|
782
|
+
if next(changedKeys) ~= nil then
|
|
783
|
+
if type(newViewSnapshot) == "table" then
|
|
784
|
+
for key, _ in pairs(changedKeys) do
|
|
785
|
+
self._keySubscriptions:Fire(key, newViewSnapshot[key])
|
|
786
|
+
end
|
|
787
|
+
else
|
|
788
|
+
for key, _ in pairs(changedKeys) do
|
|
789
|
+
self._keySubscriptions:Fire(key, nil)
|
|
790
|
+
end
|
|
791
|
+
end
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
self.Changed:Fire(self._viewSnapshot)
|
|
795
|
+
end
|
|
796
|
+
|
|
797
|
+
self:_checkIntegrity()
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
function DataStoreStage:_computeChangedKeys(previousViewSnapshot, newViewSnapshot)
|
|
801
|
+
-- Detect keys that changed
|
|
802
|
+
if type(previousViewSnapshot) == "table" and type(newViewSnapshot) == "table" then
|
|
803
|
+
local changedKeys = {}
|
|
804
|
+
|
|
805
|
+
local keys = Set.union(Set.fromKeys(previousViewSnapshot), Set.fromKeys(newViewSnapshot))
|
|
806
|
+
for key, _ in pairs(keys) do
|
|
807
|
+
if not Table.deepEquivalent(previousViewSnapshot[key], newViewSnapshot[key]) then
|
|
808
|
+
changedKeys[key] = true
|
|
809
|
+
end
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
return changedKeys
|
|
813
|
+
elseif type(newViewSnapshot) == "table" then
|
|
814
|
+
-- Swap to table, all keys change
|
|
815
|
+
return Set.fromKeys(newViewSnapshot)
|
|
816
|
+
elseif type(previousViewSnapshot) == "table" then
|
|
817
|
+
-- Swap from table, all keys change
|
|
818
|
+
return Set.fromKeys(previousViewSnapshot)
|
|
819
|
+
else
|
|
820
|
+
return {}
|
|
821
|
+
end
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
function DataStoreStage:_updateViewSnapshotAtKey(key)
|
|
825
|
+
assert(type(key) == "string" or type(key) == "number", "Bad key")
|
|
826
|
+
|
|
827
|
+
if type(self._viewSnapshot) ~= "table" then
|
|
828
|
+
self:_updateViewSnapshot()
|
|
829
|
+
return
|
|
830
|
+
end
|
|
831
|
+
|
|
832
|
+
local newValue = self:_computeViewValueForKey(key)
|
|
833
|
+
if self._viewSnapshot[key] == newValue then
|
|
834
|
+
return
|
|
835
|
+
end
|
|
836
|
+
|
|
837
|
+
local newSnapshot = table.clone(self._viewSnapshot)
|
|
838
|
+
newSnapshot[key] = newValue
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
self._viewSnapshot = table.freeze(newSnapshot)
|
|
842
|
+
self._keySubscriptions:Fire(key, newValue)
|
|
843
|
+
self.Changed:Fire(self._viewSnapshot)
|
|
844
|
+
|
|
845
|
+
self:_checkIntegrity()
|
|
846
|
+
end
|
|
847
|
+
|
|
848
|
+
function DataStoreStage:_computeNewViewSnapshot()
|
|
849
|
+
-- This prioritizes save data first, then stores, then base data
|
|
850
|
+
|
|
851
|
+
if self._saveDataSnapshot == DataStoreDeleteToken then
|
|
852
|
+
return nil
|
|
853
|
+
elseif self._saveDataSnapshot == nil or type(self._saveDataSnapshot) == "table" then
|
|
854
|
+
-- Compute a new view
|
|
855
|
+
|
|
856
|
+
-- Start with base data
|
|
857
|
+
local newView
|
|
858
|
+
if type(self._baseDataSnapshot) == "table" then
|
|
859
|
+
newView = table.clone(self._baseDataSnapshot)
|
|
860
|
+
else
|
|
861
|
+
newView = {}
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
-- Add in stores
|
|
865
|
+
for key, store in pairs(self._stores) do
|
|
866
|
+
newView[key] = store._viewSnapshot
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
-- Then finally save data
|
|
870
|
+
if type(self._saveDataSnapshot) == "table" then
|
|
871
|
+
for key, value in pairs(self._saveDataSnapshot) do
|
|
872
|
+
if value == DataStoreDeleteToken then
|
|
873
|
+
newView[key] = nil
|
|
874
|
+
else
|
|
875
|
+
newView[key] = value
|
|
876
|
+
end
|
|
877
|
+
end
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
if next(newView) == nil and not (type(self._baseDataSnapshot) == "table" or type(self._saveDataSnapshot) == "table") then
|
|
881
|
+
-- We haev no reason to be a table, make sure we return nil
|
|
882
|
+
return nil
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
return table.freeze(newView)
|
|
886
|
+
else
|
|
887
|
+
assert(self._saveDataSnapshot ~= nil, "Bad _saveDataSnapshot")
|
|
888
|
+
assert(type(self._saveDataSnapshot) ~= "table", "Bad self._saveDataSnapshot")
|
|
889
|
+
|
|
890
|
+
-- If save data isn't nil or a table then we are to return the save table
|
|
891
|
+
return self._saveDataSnapshot
|
|
892
|
+
end
|
|
893
|
+
end
|
|
894
|
+
|
|
895
|
+
function DataStoreStage:_computeViewValueForKey(key)
|
|
896
|
+
-- This prioritizes save data first, then stores, then base data
|
|
897
|
+
|
|
898
|
+
if self._saveDataSnapshot == DataStoreDeleteToken then
|
|
899
|
+
return nil
|
|
900
|
+
elseif self._saveDataSnapshot == nil or type(self._saveDataSnapshot) == "table" then
|
|
901
|
+
if type(self._saveDataSnapshot) == "table" then
|
|
902
|
+
if self._saveDataSnapshot[key] ~= nil then
|
|
903
|
+
local value = self._saveDataSnapshot[key]
|
|
904
|
+
if value == DataStoreDeleteToken then
|
|
905
|
+
return nil
|
|
906
|
+
else
|
|
907
|
+
return value
|
|
908
|
+
end
|
|
909
|
+
end
|
|
910
|
+
end
|
|
911
|
+
|
|
912
|
+
if self._stores[key] then
|
|
913
|
+
local value = self._stores[key]._viewSnapshot
|
|
914
|
+
if value == DataStoreDeleteToken then
|
|
915
|
+
return nil
|
|
916
|
+
else
|
|
917
|
+
return value
|
|
918
|
+
end
|
|
919
|
+
end
|
|
920
|
+
|
|
921
|
+
if type(self._baseDataSnapshot) == "table" then
|
|
922
|
+
if self._baseDataSnapshot[key] ~= nil then
|
|
923
|
+
return self._baseDataSnapshot[key]
|
|
924
|
+
end
|
|
925
|
+
end
|
|
926
|
+
|
|
927
|
+
return nil
|
|
928
|
+
else
|
|
929
|
+
-- If save data isn't nil or a table then we are to return nil.
|
|
930
|
+
return nil
|
|
931
|
+
end
|
|
932
|
+
end
|
|
933
|
+
|
|
468
934
|
-- Stores the data for overwrite.
|
|
469
|
-
function DataStoreStage:
|
|
470
|
-
assert(type(
|
|
935
|
+
function DataStoreStage:_storeAtKey(key, value)
|
|
936
|
+
assert(type(key) == "string" or type(key) == "number", "Bad key")
|
|
471
937
|
assert(value ~= nil, "Bad value")
|
|
472
938
|
|
|
473
|
-
local
|
|
474
|
-
if value ==
|
|
475
|
-
|
|
476
|
-
elseif type(value) == "table" then
|
|
477
|
-
newValue = Table.deepCopy(value)
|
|
939
|
+
local deepClonedSaveValue
|
|
940
|
+
if type(value) == "table" then
|
|
941
|
+
deepClonedSaveValue = table.freeze(Table.deepCopy(value))
|
|
478
942
|
else
|
|
479
|
-
|
|
943
|
+
deepClonedSaveValue = value
|
|
480
944
|
end
|
|
481
945
|
|
|
482
|
-
if
|
|
483
|
-
self.
|
|
946
|
+
if self._stores[key] then
|
|
947
|
+
self._stores[key]:Overwrite(value)
|
|
948
|
+
return
|
|
484
949
|
end
|
|
485
950
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
951
|
+
local swappedSaveSnapshotType = false
|
|
952
|
+
local newSnapshot
|
|
953
|
+
|
|
954
|
+
if type(self._saveDataSnapshot) == "table" then
|
|
955
|
+
newSnapshot = table.clone(self._saveDataSnapshot)
|
|
956
|
+
else
|
|
957
|
+
swappedSaveSnapshotType = true
|
|
958
|
+
newSnapshot = {}
|
|
489
959
|
end
|
|
490
960
|
|
|
491
|
-
|
|
492
|
-
|
|
961
|
+
newSnapshot[key] = deepClonedSaveValue
|
|
962
|
+
|
|
963
|
+
self._saveDataSnapshot = table.freeze(newSnapshot)
|
|
964
|
+
|
|
965
|
+
self.DataStored:Fire()
|
|
966
|
+
|
|
967
|
+
if swappedSaveSnapshotType then
|
|
968
|
+
self:_updateViewSnapshot()
|
|
493
969
|
else
|
|
494
|
-
self
|
|
970
|
+
self:_updateViewSnapshotAtKey(key)
|
|
971
|
+
end
|
|
972
|
+
self:_checkIntegrity()
|
|
973
|
+
end
|
|
974
|
+
|
|
975
|
+
function DataStoreStage:_checkIntegrity()
|
|
976
|
+
if not SLOW_INTEGRITY_CHECK_ENABLED then
|
|
977
|
+
return
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
assert(self._baseDataSnapshot ~= DataStoreDeleteToken, "BaseDataSnapshot should not be DataStoreDeleteToken")
|
|
981
|
+
assert(self._viewSnapshot ~= DataStoreDeleteToken, "ViewSnapshot should not be DataStoreDeleteToken")
|
|
982
|
+
|
|
983
|
+
if type(self._baseDataSnapshot) == "table" then
|
|
984
|
+
assert(table.isfrozen(self._baseDataSnapshot), "Base snapshot should be frozen")
|
|
985
|
+
end
|
|
986
|
+
|
|
987
|
+
if type(self._saveDataSnapshot) == "table" then
|
|
988
|
+
assert(table.isfrozen(self._saveDataSnapshot), "Save snapshot should be frozen")
|
|
989
|
+
end
|
|
990
|
+
|
|
991
|
+
if type(self._viewSnapshot) == "table" then
|
|
992
|
+
assert(table.isfrozen(self._viewSnapshot), "View snapshot should be frozen")
|
|
993
|
+
end
|
|
994
|
+
|
|
995
|
+
for key, _ in pairs(self._stores) do
|
|
996
|
+
if type(self._baseDataSnapshot) == "table" and self._baseDataSnapshot[key] ~= nil then
|
|
997
|
+
error(string.format("[DataStoreStage] - Duplicate baseData at key %q", key))
|
|
998
|
+
end
|
|
999
|
+
|
|
1000
|
+
if type(self._saveDataSnapshot) == "table" and self._saveDataSnapshot[key] ~= nil then
|
|
1001
|
+
error(string.format("[DataStoreStage] - Duplicate saveData at key %q", key))
|
|
1002
|
+
end
|
|
1003
|
+
end
|
|
1004
|
+
|
|
1005
|
+
if type(self._viewSnapshot) == "table" then
|
|
1006
|
+
for key, value in pairs(self._viewSnapshot) do
|
|
1007
|
+
assert(type(key) == "string" or type(key) == "number", "Bad key")
|
|
1008
|
+
if value == DataStoreDeleteToken then
|
|
1009
|
+
error(string.format("[DataStoreStage] - View at key %q is delete token", key))
|
|
1010
|
+
end
|
|
1011
|
+
end
|
|
495
1012
|
end
|
|
496
1013
|
end
|
|
497
1014
|
|