@quenty/datastore 8.0.0-canary.367.e9fdcbc.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
|
@@ -8,6 +8,11 @@ local require = require(script.Parent.loader).load(script)
|
|
|
8
8
|
|
|
9
9
|
local Table = require("Table")
|
|
10
10
|
local DataStoreDeleteToken = require("DataStoreDeleteToken")
|
|
11
|
+
local Symbol = require("Symbol")
|
|
12
|
+
local Set = require("Set")
|
|
13
|
+
local DataStoreSnapshotUtils = require("DataStoreSnapshotUtils")
|
|
14
|
+
|
|
15
|
+
local UNSET_TOKEN = Symbol.named("unsetValue")
|
|
11
16
|
|
|
12
17
|
local DataStoreWriter = {}
|
|
13
18
|
DataStoreWriter.ClassName = "DataStoreWriter"
|
|
@@ -16,12 +21,17 @@ DataStoreWriter.__index = DataStoreWriter
|
|
|
16
21
|
--[=[
|
|
17
22
|
Constructs a new DataStoreWriter. In general, you will not use this API directly.
|
|
18
23
|
|
|
24
|
+
@param debugName string
|
|
19
25
|
@return DataStoreWriter
|
|
20
26
|
]=]
|
|
21
|
-
function DataStoreWriter.new()
|
|
27
|
+
function DataStoreWriter.new(debugName)
|
|
22
28
|
local self = setmetatable({}, DataStoreWriter)
|
|
23
29
|
|
|
24
|
-
self.
|
|
30
|
+
self._debugName = assert(debugName, "No debugName")
|
|
31
|
+
self._saveDataSnapshot = UNSET_TOKEN
|
|
32
|
+
self._fullBaseDataSnapshot = UNSET_TOKEN
|
|
33
|
+
self._userIdList = UNSET_TOKEN
|
|
34
|
+
|
|
25
35
|
self._writers = {}
|
|
26
36
|
|
|
27
37
|
return self
|
|
@@ -29,10 +39,40 @@ end
|
|
|
29
39
|
|
|
30
40
|
--[=[
|
|
31
41
|
Sets the ray data to write
|
|
32
|
-
@param
|
|
42
|
+
@param saveDataSnapshot table | any
|
|
33
43
|
]=]
|
|
34
|
-
function DataStoreWriter:
|
|
35
|
-
|
|
44
|
+
function DataStoreWriter:SetSaveDataSnapshot(saveDataSnapshot)
|
|
45
|
+
assert(type(saveDataSnapshot) ~= "table" or table.isfrozen(saveDataSnapshot), "saveDataSnapshot should be frozen")
|
|
46
|
+
|
|
47
|
+
if saveDataSnapshot == DataStoreDeleteToken then
|
|
48
|
+
self._saveDataSnapshot = DataStoreDeleteToken
|
|
49
|
+
elseif type(saveDataSnapshot) == "table" then
|
|
50
|
+
self._saveDataSnapshot = Table.deepCopy(saveDataSnapshot)
|
|
51
|
+
else
|
|
52
|
+
self._saveDataSnapshot = saveDataSnapshot
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
function DataStoreWriter:GetDataToSave()
|
|
57
|
+
if self._saveDataSnapshot == UNSET_TOKEN then
|
|
58
|
+
return nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
return self._saveDataSnapshot
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
function DataStoreWriter:GetSubWritersMap()
|
|
65
|
+
return self._writers
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
function DataStoreWriter:SetFullBaseDataSnapshot(fullBaseDataSnapshot)
|
|
69
|
+
assert(type(fullBaseDataSnapshot) ~= "table" or table.isfrozen(fullBaseDataSnapshot), "fullBaseDataSnapshot should be frozen")
|
|
70
|
+
|
|
71
|
+
if fullBaseDataSnapshot == DataStoreDeleteToken then
|
|
72
|
+
error("[DataStoreWriter] - fullBaseDataSnapshot should not be a delete token")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
self._fullBaseDataSnapshot = fullBaseDataSnapshot
|
|
36
76
|
end
|
|
37
77
|
|
|
38
78
|
--[=[
|
|
@@ -40,7 +80,7 @@ end
|
|
|
40
80
|
@param name string
|
|
41
81
|
@param writer DataStoreWriter
|
|
42
82
|
]=]
|
|
43
|
-
function DataStoreWriter:
|
|
83
|
+
function DataStoreWriter:AddSubWriter(name, writer)
|
|
44
84
|
assert(type(name) == "string", "Bad name")
|
|
45
85
|
assert(not self._writers[name], "Writer already exists for name")
|
|
46
86
|
assert(writer, "Bad writer")
|
|
@@ -49,37 +89,207 @@ function DataStoreWriter:AddWriter(name, writer)
|
|
|
49
89
|
end
|
|
50
90
|
|
|
51
91
|
--[=[
|
|
52
|
-
|
|
92
|
+
Gets a sub writer
|
|
53
93
|
|
|
54
|
-
@param
|
|
55
|
-
@return
|
|
94
|
+
@param name string
|
|
95
|
+
@return DataStoreWriter
|
|
56
96
|
]=]
|
|
57
|
-
function DataStoreWriter:
|
|
58
|
-
|
|
97
|
+
function DataStoreWriter:GetWriter(name)
|
|
98
|
+
assert(type(name) == "string", "Bad name")
|
|
99
|
+
|
|
100
|
+
return self._writers[name]
|
|
101
|
+
end
|
|
59
102
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
103
|
+
--[=[
|
|
104
|
+
Merges the incoming data.
|
|
105
|
+
|
|
106
|
+
Won't really perform a delete operation because we can't be sure if we were suppose to have reified this stuff or not.
|
|
107
|
+
|
|
108
|
+
@param incoming any
|
|
109
|
+
]=]
|
|
110
|
+
function DataStoreWriter:ComputeDiffSnapshot(incoming)
|
|
111
|
+
assert(incoming ~= DataStoreDeleteToken, "Incoming value should not be DataStoreDeleteToken")
|
|
112
|
+
|
|
113
|
+
if type(incoming) == "table" then
|
|
114
|
+
local keys = Set.union(Set.fromKeys(self._writers), Set.fromKeys(incoming))
|
|
115
|
+
|
|
116
|
+
local baseSnapshot
|
|
117
|
+
if type(self._fullBaseDataSnapshot) == "table" then
|
|
118
|
+
baseSnapshot = self._fullBaseDataSnapshot
|
|
119
|
+
Set.unionUpdate(keys, Set.fromKeys(self._fullBaseDataSnapshot))
|
|
63
120
|
else
|
|
64
|
-
|
|
121
|
+
baseSnapshot = {}
|
|
65
122
|
end
|
|
66
|
-
end
|
|
67
123
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
:
|
|
124
|
+
local diffSnapshot = {}
|
|
125
|
+
for key, _ in pairs(keys) do
|
|
126
|
+
if self._writers[key] then
|
|
127
|
+
diffSnapshot[key] = self._writers[key]:ComputeDiffSnapshot(incoming[key])
|
|
128
|
+
else
|
|
129
|
+
diffSnapshot[key] = self:_computeValueDiff(baseSnapshot[key], incoming[key])
|
|
130
|
+
end
|
|
72
131
|
end
|
|
73
132
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
133
|
+
if not DataStoreSnapshotUtils.isEmptySnapshot(diffSnapshot) then
|
|
134
|
+
return table.freeze(diffSnapshot)
|
|
135
|
+
else
|
|
136
|
+
if next(keys) then
|
|
137
|
+
return nil -- No delta
|
|
138
|
+
else
|
|
139
|
+
return DataStoreDeleteToken
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
else
|
|
143
|
+
return self:_computeValueDiff(self._fullBaseDataSnapshot, incoming)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
function DataStoreWriter:_computeValueDiff(original, incoming)
|
|
148
|
+
assert(original ~= DataStoreDeleteToken, "original cannot be DataStoreDeleteToken")
|
|
149
|
+
assert(incoming ~= DataStoreDeleteToken, "incoming cannot be DataStoreDeleteToken")
|
|
150
|
+
|
|
151
|
+
if original == incoming then
|
|
152
|
+
return nil
|
|
153
|
+
elseif original ~= nil and incoming == nil then
|
|
154
|
+
return DataStoreDeleteToken
|
|
155
|
+
elseif type(original) == "table" and type(incoming) == "table" then
|
|
156
|
+
return self:_computeTableDiff(original, incoming)
|
|
157
|
+
else
|
|
158
|
+
return incoming
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
function DataStoreWriter:_computeTableDiff(original, incoming)
|
|
163
|
+
assert(type(original) == "table", "Bad original")
|
|
164
|
+
assert(type(incoming) == "table", "Bad incoming")
|
|
165
|
+
|
|
166
|
+
local keys = Set.union(Set.fromKeys(original), Set.fromKeys(incoming))
|
|
167
|
+
|
|
168
|
+
local diffSnapshot = {}
|
|
169
|
+
for key, _ in pairs(keys) do
|
|
170
|
+
diffSnapshot[key] = self:_computeValueDiff(original[key], incoming[key])
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
if not DataStoreSnapshotUtils.isEmptySnapshot(diffSnapshot) then
|
|
174
|
+
return table.freeze(diffSnapshot)
|
|
175
|
+
else
|
|
176
|
+
if next(keys) then
|
|
177
|
+
return nil -- No delta
|
|
77
178
|
else
|
|
78
|
-
|
|
179
|
+
return DataStoreDeleteToken
|
|
79
180
|
end
|
|
80
181
|
end
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
--[=[
|
|
185
|
+
Set of user ids to write with the data (only applies to top-level writer)
|
|
186
|
+
|
|
187
|
+
@param userIdList { number }
|
|
188
|
+
]=]
|
|
189
|
+
function DataStoreWriter:SetUserIdList(userIdList)
|
|
190
|
+
assert(type(userIdList) == "table" or userIdList == nil, "Bad userIdList")
|
|
191
|
+
|
|
192
|
+
self._userIdList = userIdList
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
--[=[
|
|
196
|
+
User ids to associate with data
|
|
197
|
+
|
|
198
|
+
@return userIdList { number }
|
|
199
|
+
]=]
|
|
200
|
+
function DataStoreWriter:GetUserIdList()
|
|
201
|
+
if self._userIdList == UNSET_TOKEN then
|
|
202
|
+
return nil
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
return self._userIdList
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
function DataStoreWriter:_writeMergeWriters(original)
|
|
209
|
+
local copy
|
|
210
|
+
if type(original) == "table" then
|
|
211
|
+
copy = table.clone(original)
|
|
212
|
+
else
|
|
213
|
+
copy = original
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
if next(self._writers) ~= nil then
|
|
217
|
+
-- Original was not a table. We need to swap to one.
|
|
218
|
+
if type(copy) ~= "table" then
|
|
219
|
+
copy = {}
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
-- Write our writers first...
|
|
223
|
+
for key, writer in pairs(self._writers) do
|
|
224
|
+
local result = writer:WriteMerge(copy[key])
|
|
225
|
+
if result == DataStoreDeleteToken then
|
|
226
|
+
copy[key] = nil
|
|
227
|
+
else
|
|
228
|
+
copy[key] = result
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
-- Write our save data next
|
|
234
|
+
if type(self._saveDataSnapshot) == "table" and next(self._saveDataSnapshot) ~= nil then
|
|
235
|
+
-- Original was not a table. We need to swap to one.
|
|
236
|
+
if type(copy) ~= "table" then
|
|
237
|
+
copy = {}
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
for key, value in pairs(self._saveDataSnapshot) do
|
|
241
|
+
if self._writers[key] then
|
|
242
|
+
warn(string.format("[DataStoreWriter._writeMergeWriters] - Overwriting key %q already saved as rawData with a writer with %q (was %q)", key, tostring(value), tostring(copy[key])))
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
if value == DataStoreDeleteToken then
|
|
246
|
+
copy[key] = nil
|
|
247
|
+
else
|
|
248
|
+
copy[key] = value
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
-- Handle empty table scenario..
|
|
254
|
+
-- This would also imply our original is nil somehow...
|
|
255
|
+
if type(copy) == "table" and next(copy) == nil then
|
|
256
|
+
if type(self._saveDataSnapshot) ~= "table" then
|
|
257
|
+
return nil
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
return copy
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
--[=[
|
|
265
|
+
Merges the new data into the original value
|
|
266
|
+
|
|
267
|
+
@param original any
|
|
268
|
+
@return any -- The original value
|
|
269
|
+
]=]
|
|
270
|
+
function DataStoreWriter:WriteMerge(original)
|
|
271
|
+
-- Prioritize save value first, followed by writers, followed by original value
|
|
272
|
+
|
|
273
|
+
if self._saveDataSnapshot == DataStoreDeleteToken then
|
|
274
|
+
return DataStoreDeleteToken
|
|
275
|
+
elseif self._saveDataSnapshot == UNSET_TOKEN or self._saveDataSnapshot == nil or type(self._saveDataSnapshot) == "table" then
|
|
276
|
+
return self:_writeMergeWriters(original)
|
|
277
|
+
else
|
|
278
|
+
-- Save data must be a boolean or something
|
|
279
|
+
return self._saveDataSnapshot
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
function DataStoreWriter:IsCompleteWipe()
|
|
284
|
+
if self._saveDataSnapshot == UNSET_TOKEN then
|
|
285
|
+
return false
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
if self._saveDataSnapshot == DataStoreDeleteToken then
|
|
289
|
+
return true
|
|
290
|
+
end
|
|
81
291
|
|
|
82
|
-
return
|
|
292
|
+
return false
|
|
83
293
|
end
|
|
84
294
|
|
|
85
295
|
return DataStoreWriter
|
|
@@ -171,6 +171,7 @@ function PlayerDataStoreManager:_createDataStore(player)
|
|
|
171
171
|
assert(not self._datastores[player], "Bad player")
|
|
172
172
|
|
|
173
173
|
local datastore = DataStore.new(self._robloxDataStore, self:_getKey(player))
|
|
174
|
+
datastore:SetUserIdList({ player.UserId })
|
|
174
175
|
|
|
175
176
|
self._maid._savingConns[player] = datastore.Saving:Connect(function(promise)
|
|
176
177
|
self._pendingSaves:Add(promise)
|
|
@@ -69,13 +69,14 @@ function DataStorePromises.getAsync(robloxDataStore, key)
|
|
|
69
69
|
|
|
70
70
|
return Promise.spawn(function(resolve, reject)
|
|
71
71
|
local result = nil
|
|
72
|
+
local dataStoreKeyInfo = nil
|
|
72
73
|
local ok, err = pcall(function()
|
|
73
|
-
result = robloxDataStore:GetAsync(key)
|
|
74
|
+
result, dataStoreKeyInfo = robloxDataStore:GetAsync(key)
|
|
74
75
|
end)
|
|
75
76
|
if not ok then
|
|
76
77
|
return reject(err)
|
|
77
78
|
end
|
|
78
|
-
return resolve(result)
|
|
79
|
+
return resolve(result, dataStoreKeyInfo)
|
|
79
80
|
end)
|
|
80
81
|
end
|
|
81
82
|
|
|
@@ -17,12 +17,16 @@ function DataStoreStringUtils.isValidUTF8(str)
|
|
|
17
17
|
return false, "Not a string"
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
if not utf8.len(str) then
|
|
21
|
+
return false, "Invalid string"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
-- https://gist.github.com/TheGreatSageEqualToHeaven/e0e1dc2698307c93f6013b9825705899#patch-1
|
|
25
|
+
if string.match(str, "[^\0-\127]") then
|
|
22
26
|
return false, "Invalid string"
|
|
23
27
|
end
|
|
24
28
|
|
|
25
29
|
return true
|
|
26
30
|
end
|
|
27
31
|
|
|
28
|
-
return DataStoreStringUtils
|
|
32
|
+
return DataStoreStringUtils
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "DataStoreTest",
|
|
3
|
+
"tree": {
|
|
4
|
+
"$className": "DataModel",
|
|
5
|
+
"ServerScriptService": {
|
|
6
|
+
"datastore": {
|
|
7
|
+
"$path": ".."
|
|
8
|
+
},
|
|
9
|
+
"Script": {
|
|
10
|
+
"$path": "scripts/Server"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"StarterPlayer": {
|
|
14
|
+
"StarterPlayerScripts": {
|
|
15
|
+
"Main": {
|
|
16
|
+
"$path": "scripts/Client"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
--[[
|
|
2
|
+
@class ServerMain
|
|
3
|
+
]]
|
|
4
|
+
|
|
5
|
+
local ServerScriptService = game:GetService("ServerScriptService")
|
|
6
|
+
local HttpService = game:GetService("HttpService")
|
|
7
|
+
|
|
8
|
+
local loader = ServerScriptService:FindFirstChild("LoaderUtils", true).Parent
|
|
9
|
+
local packages = require(loader).bootstrapGame(ServerScriptService.datastore)
|
|
10
|
+
|
|
11
|
+
local Maid = require(packages.Maid)
|
|
12
|
+
local Promise = require(packages.Promise)
|
|
13
|
+
|
|
14
|
+
local TURN_TIME = 8
|
|
15
|
+
|
|
16
|
+
local function spinUpGameCopy(prefix)
|
|
17
|
+
assert(type(prefix) == "string", "Bad prefix")
|
|
18
|
+
|
|
19
|
+
local serviceBag = require(packages.ServiceBag).new()
|
|
20
|
+
serviceBag:GetService(require(packages.GameDataStoreService))
|
|
21
|
+
serviceBag:GetService(require(packages.PlayerDataStoreService))
|
|
22
|
+
|
|
23
|
+
serviceBag:Init()
|
|
24
|
+
serviceBag:Start()
|
|
25
|
+
|
|
26
|
+
local guid = prefix .. " " .. HttpService:GenerateGUID(false)
|
|
27
|
+
local maid = Maid.new()
|
|
28
|
+
|
|
29
|
+
local gameDataStore = serviceBag:GetService(require(packages.GameDataStoreService))
|
|
30
|
+
local bindToCloseService = serviceBag:GetService(require(packages.BindToCloseService))
|
|
31
|
+
|
|
32
|
+
-- This would be an aggressive usage of this area, it probably won't scale well enough.
|
|
33
|
+
-- But writing some shared code or something like API keys should scale fine.
|
|
34
|
+
maid:GivePromise(gameDataStore:PromiseDataStore()):Then(function(dataStore)
|
|
35
|
+
local substore = dataStore:GetSubStore("AliveServers")
|
|
36
|
+
substore:Store(guid, true)
|
|
37
|
+
|
|
38
|
+
-- maid:GiveTask(dataStore:Observe():Subscribe(function(viewSnapshot)
|
|
39
|
+
-- print(string.format("(%s) dataStore:Observe()", prefix), viewSnapshot)
|
|
40
|
+
-- end))
|
|
41
|
+
|
|
42
|
+
if prefix == "blue" then
|
|
43
|
+
dataStore:SetDoDebugWriting(true)
|
|
44
|
+
dataStore:SetSyncOnSave(true)
|
|
45
|
+
dataStore:SetAutoSaveTimeSeconds(4)
|
|
46
|
+
|
|
47
|
+
-- maid:GiveTask(dataStore:Observe():Subscribe(function(viewSnapshot)
|
|
48
|
+
-- print(string.format("(%s) dataStore:Observe()", prefix), viewSnapshot)
|
|
49
|
+
-- end))
|
|
50
|
+
|
|
51
|
+
task.delay(4*TURN_TIME, function()
|
|
52
|
+
warn("Blue server is restoring data")
|
|
53
|
+
|
|
54
|
+
substore:Store(guid, true)
|
|
55
|
+
end)
|
|
56
|
+
elseif prefix == "red" then
|
|
57
|
+
warn(string.format("%s server is storing data", prefix))
|
|
58
|
+
|
|
59
|
+
-- dataStore:SetDoDebugWriting(true)
|
|
60
|
+
dataStore:SetSyncOnSave(true)
|
|
61
|
+
dataStore:SetAutoSaveTimeSeconds(4)
|
|
62
|
+
-- dataStore:Save()
|
|
63
|
+
|
|
64
|
+
task.delay(TURN_TIME, function()
|
|
65
|
+
warn(string.format("%s server is wiping data", prefix))
|
|
66
|
+
|
|
67
|
+
substore:Wipe()
|
|
68
|
+
-- dataStore:Save()
|
|
69
|
+
|
|
70
|
+
task.delay(TURN_TIME, function()
|
|
71
|
+
warn(string.format("%s server is adding substore data", prefix))
|
|
72
|
+
|
|
73
|
+
substore:Store(guid, {
|
|
74
|
+
playerCount = 5;
|
|
75
|
+
startTime = DateTime.now().UnixTimestamp
|
|
76
|
+
})
|
|
77
|
+
-- dataStore:Save()
|
|
78
|
+
|
|
79
|
+
task.delay(TURN_TIME, function()
|
|
80
|
+
warn(string.format("%s server is changing player count", prefix))
|
|
81
|
+
local guidStore = substore:GetSubStore(guid)
|
|
82
|
+
guidStore:Store("playerCount", 25)
|
|
83
|
+
-- dataStore:Save()
|
|
84
|
+
end)
|
|
85
|
+
end)
|
|
86
|
+
end)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
-- TODO: Update some random numbers every second for a while....
|
|
90
|
+
|
|
91
|
+
-- TODO: Force saving twice
|
|
92
|
+
|
|
93
|
+
maid:GiveTask(dataStore:Observe():Subscribe(function(viewSnapshot)
|
|
94
|
+
print(string.format("(%s) dataStore:Observe()", prefix), viewSnapshot)
|
|
95
|
+
end))
|
|
96
|
+
|
|
97
|
+
-- dataStore:LoadAll():Then(function(data)
|
|
98
|
+
-- -- print(string.format("[%s][LoadAll] - Load all", prefix), data)
|
|
99
|
+
-- end)
|
|
100
|
+
|
|
101
|
+
-- local entrySubstore = substore:GetSubStore(guid)
|
|
102
|
+
-- entrySubstore:LoadAll():Then(function(data)
|
|
103
|
+
-- -- print(string.format("[%s][SUBSTORE][LoadAll] Loaded substore", prefix), data)
|
|
104
|
+
-- end)
|
|
105
|
+
|
|
106
|
+
-- entrySubstore:Overwrite(os.clock())
|
|
107
|
+
|
|
108
|
+
maid:GiveTask(bindToCloseService:RegisterPromiseOnCloseCallback(function()
|
|
109
|
+
substore:Delete(guid)
|
|
110
|
+
return Promise.resolved()
|
|
111
|
+
end))
|
|
112
|
+
|
|
113
|
+
end)
|
|
114
|
+
|
|
115
|
+
return maid
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
spinUpGameCopy("red")
|
|
119
|
+
spinUpGameCopy("blue")
|
|
120
|
+
spinUpGameCopy("green")
|