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