Code Template
Module template
It's important to keep code consistent this is what I have found to work really well. Check out the MadLife - code_guidebook
& Roblox Style Guide
--!strict
--[[
{C.G.T}
-[ModuleName]---------------------------------------
Module description
Members:
Functions:
Members [ClassName]:
Methods [ClassName]:
--]]
----- Loaded Modules -----
local SETTINGS = {}
----- Module Table -----
local Module = {}
----- Private Variables -----
----- Private functions -----
----- Public -----
----- Initialize & Connections -----
return Module
Basic Usage
--!strict
--[[
{C.G.T}
-[PlayerDataService]---------------------------------------
A module to manage the loading and unloading of player data.
Links: IMPORTANT❗, It's essential that you understand how these modules work to efficiently use C.G.T and understand how my code works 👍
https://madstudioroblox.github.io/ReplicaService/
https://madstudioroblox.github.io/ProfileService/
https://sleitnick.github.io/Knit/
https://github.com/osyrisrblx/t
https://eryn.io/roblox-lua-promise/docs/WhyUsePromises
Members [PlayerDataService]:
ProfileStore: --> [ProfileStore] (To view a player's date through DataStoreEditor "PlayerData" --> "Player_"..UserId (Player_1).)
GlobalUpdateHandlers: --> [Folder] (To handle a new sort of Update you need to add a module to this folder that returns the handler function.)
Profiles: --> [{player_profile}] (Table of player profiles, removed once player leaves the server and can be retrieved using PlayerDataService:GetProfile().)
Replicas: --> [{player_profile_Replica}] (Table of player replicas, removed once player leaves the server and can be retrieved using PlayerDataService:GetReplica(). It is used to replicate data to the client.)
Methods [PlayerDataService]:
GetProfile(player: Player) --> Promise<player_profile>
GetData(player: Player) --> Promise<player_profile.Data> (The format of Profile.Data is based on SETTINGS.SaveStructure)
GetDataReplica(player: Player) --> Promise<Replica> (Use this object to edit player data)
AddGlobalUpdate(UpdateData: { Id: string, SenderId: number, ReceiverId: number, Data: {} }) GlobalUpdates are used to send info to players across servers and regardless of whether or not they are online.
--]]
----- Loaded Modules -----
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local ProfileService = require(ReplicatedStorage.Packages.profileservice)
local ReplicaService = require(ReplicatedStorage.Packages.replicaservice)
local Knit = require(ReplicatedStorage.Packages.knit)
local Promise = require(ReplicatedStorage.Packages.promise)
local t = require(ReplicatedStorage.Packages.t)
local SETTINGS = {
MockProfiles = false,
ClassToken = ReplicaService.NewClassToken("PlayerData"),
SaveStructure = {
SomeData = 0,
},
}
type UpdateData = { Id: string, SenderId: number, TimeSent: number, ReceiverId: number, Data: {} }
----- Module Table -----
local PlayerDataService = Knit.CreateService({
Name = "PlayerDataService",
ProfileStore = ProfileService.GetProfileStore("PlayerData", SETTINGS.SaveStructure),
Profiles = {},
ProfileDataReplicas = {},
})
----- Private Variables -----
local TYPES = {
UpdateData = t.strictInterface({
Id = t.string,
SenderId = t.number,
ReceiverId = t.number,
TimeSent = t.number,
Data = t.optional(t.table),
}),
}
local GlobalUpdateHandlers = script.GlobalUpdateHandlers
local ProfileServiceErrorHandlers = script.ProfileServiceErrorHandlers
----- Private functions -----
local function OnPlayerJoining(player: Player)
local player_profile
if RunService:IsStudio() and SETTINGS.MockProfiles then
player_profile = PlayerDataService.ProfileStore.Mock:LoadProfileAsync("Player_" .. tostring(player.UserId))
else
player_profile = PlayerDataService.ProfileStore:LoadProfileAsync("Player_" .. tostring(player.UserId))
end
if player_profile ~= nil then
player_profile:AddUserId(player.UserId)
player_profile:Reconcile()
player_profile:ListenToRelease(function()
PlayerDataService.Profiles[player] = nil
local ProfileReplica = PlayerDataService.ProfileDataReplicas[player]
if ProfileReplica ~= nil then
ProfileReplica:Destroy()
PlayerDataService.ProfileDataReplicas[player] = nil
end
player:Kick()
end)
if player:IsDescendantOf(Players) == true then
local ProfileReplica = ReplicaService.NewReplica({
ClassToken = SETTINGS.ClassToken,
Tags = { Player = player },
Data = player_profile.Data,
Replication = "All",
})
PlayerDataService.Profiles[player] = player_profile
PlayerDataService.ProfileDataReplicas[player] = ProfileReplica
local function OnActiveGlobalUpdate(UpdateId: number, UpdateData: UpdateData)
assert(t.number(UpdateId) and TYPES.UpdateData(UpdateData))
player_profile.GlobalUpdates:LockActiveUpdate(UpdateId)
end
local function OnLockedGlobalUpdate(UpdateId: number, UpdateData: UpdateData)
assert(t.number(UpdateId) and TYPES.UpdateData(UpdateData))
local LockedUpdateHandler = GlobalUpdateHandlers:FindFirstChild(UpdateData.Id)
if LockedUpdateHandler then
Promise.try(require(LockedUpdateHandler), UpdateId, UpdateData)
:andThen(function(ClearUpdate: boolean)
if ClearUpdate ~= false then
player_profile.GlobalUpdates:ClearLockedUpdate(UpdateId)
end
end)
:catch(warn)
:await()
else
warn("[PlayerDataService]: GlobalUpdateHandler not found")
end
end
for _, ActiveUpdate in pairs(player_profile.GlobalUpdates:GetActiveUpdates()) do
OnActiveGlobalUpdate(ActiveUpdate[1], ActiveUpdate[2])
end
for _, LockedUpdate in pairs(player_profile.GlobalUpdates:GetLockedUpdates()) do
task.spawn(OnLockedGlobalUpdate, LockedUpdate[1], LockedUpdate[2])
end
player_profile.GlobalUpdates:ListenToNewActiveUpdate(OnActiveGlobalUpdate)
player_profile.GlobalUpdates:ListenToNewLockedUpdate(OnLockedGlobalUpdate)
else
player_profile:Release()
end
else
player:Kick()
end
end
local function OnPlayerLeaving(player: Player)
local player_profile = PlayerDataService.Profiles[player]
if player_profile ~= nil then
player_profile:Release()
end
end
----- Public -----
function PlayerDataService:GetProfile(player: Player | any)
assert(t.instance("Player")(player))
return Promise.new(function(resolve, reject)
repeat
if not player:IsDescendantOf(Players) then
reject("Player left the game")
end
task.wait(1)
until PlayerDataService.Profiles[player] ~= nil
local player_profile = PlayerDataService.Profiles[player]
if player_profile ~= nil then
if player_profile:IsActive() then
resolve(player_profile)
end
else
reject("Profile is nil")
end
reject("Player left the game")
end)
end
function PlayerDataService:GetData(player: Player | any)
assert(t.instance("Player")(player))
return Promise.new(function(resolve, reject)
repeat
if not player:IsDescendantOf(Players) then
reject("Player left the game")
end
task.wait(1)
until PlayerDataService.Profiles[player] ~= nil
local player_profile = PlayerDataService.Profiles[player]
if player_profile ~= nil then
if player_profile:IsActive() then
resolve(player_profile.Data)
end
else
reject("Profile is nil")
end
reject("Player left the game")
end)
end
function PlayerDataService:GetDataReplica(player: Player | any)
assert(t.instance("Player")(player))
return Promise.new(function(resolve, reject)
repeat
if not player:IsDescendantOf(Players) then
reject("Player left the game")
end
task.wait(1)
until PlayerDataService.ProfileDataReplicas[player] ~= nil
local player_profile_replica = PlayerDataService.ProfileDataReplicas[player]
if player_profile_replica ~= nil then
if player_profile_replica:IsActive() then
resolve(player_profile_replica)
end
else
reject("player_profile_replica is nil")
end
reject("Player left the game")
end)
end
function PlayerDataService:AddGlobalUpdate(update_data: UpdateData)
assert(TYPES.UpdateData(update_data))
return Promise.new(function(resolve, reject)
PlayerDataService.ProfileStore:GlobalUpdateProfileAsync(
"Player_" .. tostring(update_data.ReceiverId),
function(globalUpdates)
globalUpdates:AddActiveUpdate(update_data)
end
)
resolve()
end)
end
----- Initialize & Connections -----
function PlayerDataService:KnitInit()
for _, player in pairs(Players:GetPlayers()) do
task.spawn(OnPlayerJoining, player)
end
-- Used to connect datastore errors to game analytics endpoints
for _, ErrorHandler: ModuleScript in pairs(ProfileServiceErrorHandlers:GetChildren()) do
if ErrorHandler:IsA("ModuleScript") then
if ProfileService[ErrorHandler.Name] then
ProfileService[ErrorHandler.Name]:Connect(require(ErrorHandler))
end
end
end
Players.PlayerAdded:Connect(OnPlayerJoining)
Players.PlayerRemoving:Connect(OnPlayerLeaving)
end
return PlayerDataService