I have probably never seen such a well written, well organized and overall excellent place template as this. Even if it is just core’s this was very helpful (Especially the Profile Store).
I did have ONE tiny problem (I dont actually know what it is (I have never used data storing before, just learning about how to use it today) ).
This weird error (which, according to context). Shouldnt break anything. Keeps appearing no matter how many bug fixes I may make.
Im using your serializer, Datareplica and dataservice code. BUT, in a Knit Framework Format.
DataService
--//Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local InsertService = game:GetService("InsertService")
local MarketplaceService = game:GetService("MarketplaceService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
--//Modules
local ClientModules = ReplicatedStorage.Modules
local ServerModules = ServerStorage.Modules
local Serializer = require(ServerModules.Serializer)
local ProfileStore = require(ServerModules.ProfileStore)
local DataReplica = ServerModules.DataReplica
local Event = require(ClientModules.Utility.Event)
local TableUtil = require(ReplicatedStorage.Modules.Utility.TableUtil)
--//Template
local DataTemplate = {
DataVersion = 1,
Purchases = {
GamePasses = {},
Products = {}
},
}
--//Variables
local ServerUpdateTime = tick()
--//Main
local Knit = require(ReplicatedStorage.Packages.Knit)
--//Module
local DataService = Knit.CreateService {
Name = "DataService",
NoWipe = {},
ChangedSignals = {},
DebugData = {},
Profiles = {} :: {[Player] : typeof(DataStore:StartSessionAsync())},
PlayerLoaded = Event.new() :: Event.Event<Player>,
Config = {
Tag = "PlayerData",
DataVersion = 1,
KickOnDataFailure = true,
Mock = RunService:IsStudio() and (ReplicatedStorage:GetAttribute("DisableMock") ~= true),
},
Client = {
SendDataSignal = Knit.CreateSignal(),
GetDataSignal = Knit.CreateSignal(),
},
}
--//Global Session
local DataStore = ProfileStore.new(DataService.Config.Tag, DataTemplate)
--//Start
function DataService:KnitStart()
local success, gameVersion = pcall(function()
return InsertService:GetLatestAssetVersionAsync(game.PlaceId)
end)
if not success then
gameVersion = 0
end
task.spawn(function()
local GamePlaceInfo = MarketplaceService:GetProductInfo(game.PlaceId, Enum.InfoType.Asset)
if not GamePlaceInfo then return end
ServerUpdateTime = DateTime.fromIsoDate(GamePlaceInfo.Updated).UnixTimestamp
end)
if DataService.Config.Mock then
DataStore = DataStore.Mock
end
Players.PlayerAdded:Connect(DataService.PlayerAdded)
Players.PlayerRemoving:Connect(DataService.PlayerRemoving)
for _, player in Players:GetPlayers() do
DataService.PlayerAdded(player)
end
end
--//Methods
function DataService.SanitizeData(Player: Player, Data: typeof(DataTemplate))
end
function DataService:GetDataChangedSignal(Player: Player)
return DataService.ChangedSignals[Player]
end
function DataService.PlayerAdded(Player: Player)
DataService.ChangedSignals[Player] = Event.new()
local profile = DataStore:StartSessionAsync(`{Player.UserId}`, {
Cancel = function()
return Player.Parent ~= Players
end
})
if not profile then
Player:Kick("Failed to load data. Please relog.")
return
end
profile:AddUserId(Player.UserId)
profile:Reconcile()
profile.OnSessionEnd:Connect(function()
DataService.Profiles[Player] = nil
end)
if Player.Parent ~= Players then
profile:EndSession()
return
end
DataService.Profiles[Player] = profile
local dataReplica = DataReplica:Clone()
dataReplica.Parent = Player
local replica = require(dataReplica)
replica.Player = Player
local Data = DataService.Profiles[Player].Data
if Data.DataVersion ~= DataService.Config.DataVersion then
for key, value in TableUtil.DeepCopy(DataTemplate) do
if table.find(DataService.NoWipe, key) then
continue
end
Data[key] = value
end
end
DataService.SanitizeData(Player,Data)
if (RunService:IsStudio() or game.PlaceId == 92564039248116) and DataService.Config.Mock then
for key, value in DataService.DebugData do
Data[key] = value
end
end
task.defer(function()
for key, value in Data do
replica[key] = value
end
end)
Player:SetAttribute("DataLoaded", true)
DataService.PlayerLoaded:Fire(Player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = Player
end
function DataService.PlayerRemoving(Player: Player)
local profile = DataService.Profiles[Player]
if not profile then return end
DataService.ChangedSignals[Player] = nil
local data = profile.Data
profile:EndSession()
end
function DataService:GetData(Player: Player) : (typeof(DataTemplate))?
while not Player:GetAttribute("DataLoaded") do
if not Player:IsDescendantOf(Players) then return end
task.wait()
end
local profile = DataService.Profiles[Player]
if not profile then return end
local data = profile.Data
while not data do
if not Player:IsDescendantOf(Players) then return end
task.wait()
data = profile.Data
end
return setmetatable({} :: {[any]: any}, {
__index = function(_, key)
return data[key]
end,
__newindex = function(_, key, value)
error(`Cannot set data from GetData. Please use UpdateData instead.`)
end
}) :: never
end
function DataService:UpdateData(Player: Player, Key: string, Callback: (Data: {[number | string]: any}) -> ())
local Profile = DataService.Profiles[Player]
if not Profile then return end
local Data = Profile.Data
local success, result = pcall(Callback, Data[Key])
if not success then
warn('UpdateData: ' .. result)
return
end
local newData = result
if newData == nil then
warn(`UpdateData: {Key} did not return a value. Was this intentional?`)
end
Data[Key] = newData
DataService.ChangedSignals[Player]:Fire()
local DataReplica = Player:FindFirstChild("DataReplica")
if not DataReplica then return end
local replica = require(DataReplica)
replica[Key] = Data[Key]
end
return DataService
DataReplica
local DataReplica = {}
--//Services
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
--//Events
local Events = ReplicatedStorage:WaitForChild("Events")
local DataEvents = Events.Data
local ReplicateData = DataEvents:WaitForChild("ReplicateData")
local GetDataReplica = DataEvents:WaitForChild("GetDataReplica")
local Proxy = setmetatable({}, {
__newindex = function(tbl, index, value)
if RunService:IsClient() then return end
DataReplica[index] = value
if index == "Player" then
DataReplica.Player = value
return
end
ReplicateData:FireClient(tbl.Player, index, value)
end,
__tostring = function(tbl)
return tostring(DataReplica)
end,
__index = function(tbl, index)
return DataReplica[index]
end
})
if RunService:IsClient() then
DataReplica = GetDataReplica:InvokeServer()
ReplicateData.OnClientEvent:Connect(function(index, value)
DataReplica[index] = value
local player = Players.LocalPlayer
local PlayerScripts = player:WaitForChild("PlayerScripts")
end)
else
GetDataReplica.OnServerInvoke = function(Player)
return DataReplica
end
end
return Proxy
(Serializer was left as is.)
(“Events”) folder
Any idea on how to fix it?
Thank you for this resource and for the help!