How do i access the same PROFILESTORE across multiple places

This is a question abt the datastore wrapper, profilestore, this is my first time doing anything datastore related

I want to load a profile from a key, which contains the players userid and a special number made into a string, loading, getting and deleting this works fine in the start place, which is where theyre created, but returns nil, kicking me in any other place, i believe this is due to me creating a new profilestore in the other place (using ProfileStore.new) with the same name as the one that holds the profiles in the start place, although it may not be, because when i check the sessioncount after loading into the second place on a new profile, it’s 2, how do i use the same store as normal?

Start Place
local Players: Players = game:GetService("Players")
local RunService:RunService = game:GetService("RunService")
local ServerScriptService: ServerScriptService = game:GetService("ServerScriptService")
local ReplicatedStorage: ReplicatedStorage = game:GetService("ReplicatedStorage")

local Modules = ServerScriptService.Modules
local Remotes = ReplicatedStorage.Remotes
local ProfileStore = require(Modules.ProfileStore)

local UpdateSlots: RemoteEvent = Remotes.UpdateSlots
local GetData: RemoteEvent = Remotes.GetData

local ErrorMessages = {
	["ServerShutdownDuringLoad"] = "Server cooked your data, rejoin",
	["SessionLocking"] = "Data error, rejoin"
}

local function GetStoreName()
	return RunService:IsStudio() and "Test" or "Live"
end
	
--Access Player Data
local ProfileTemplate = require(Modules.ProfileTemplate)
local SlotTemplate = require(Modules.SlotTemplate)
local DataManager = require(Modules.DataManager)

local PlayerStore = ProfileStore.New(GetStoreName(), ProfileTemplate)
local SlotStore = ProfileStore.New(GetStoreName().. "_Slots", SlotTemplate)

--Player has fully loaded
local function Initialize(Player: Player, Profile: typeof(PlayerStore:StartSessionAsync()))
	--Admin checks
	if table.find(DataManager.AdminList, Player.UserId) then
		Profile.Data.Admin = true
	else
		Profile.Data.Admin = false
	end
	
	local Profiles = {}
	
	for _, ID: number in Profile.Data.Slots do
		local SlotProfile = DataManager:GetSlotData(SlotStore, Player, ID)
		table.insert(Profiles, SlotProfile.Data.ID,{
			["Data"] = SlotProfile.Data,
			["Key"] = SlotProfile.Key
		})
	end
	GetData:FireClient(Player, Profiles)
end

--Creates & stores player profiles
local function PlayerAdded(Player: Player)
	--Start new session on Profile	
	local Profile = PlayerStore:StartSessionAsync("Player_".. Player.UserId, {
		Cancel = function ()
			return Player.Parent ~= Players
		end,
	})
	
	--Sanity check
	if Profile == nil then
		--Server shutdown while loading
		Player:Kick(ErrorMessages.ServerShutdownDuringLoad)
	else
		Profile:AddUserId(Player.UserId) --GDPR compliance
		Profile:Reconcile() -- Fill in missing data from ProfileTemplate (optional)
		
		--Session locking
		Profile.OnSessionEnd:Connect(function() 
			DataManager.Profiles[Player.Name] = nil
			Player:Kick(ErrorMessages.SessionLocking)
		end)
		
		if Player.Parent == Players then
			DataManager.Profiles[Player.Name] = Profile
			Initialize(Player, Profile)
		else
			Profile:EndSession()
		end
	end
end

--Early joiners
for _, Player: Player in Players:GetPlayers() do
	task.spawn(PlayerAdded, Player)
end

Players.PlayerAdded:Connect(PlayerAdded)

Players.PlayerRemoving:Connect(function(Player: Player) 
	local Profile: typeof(PlayerStore:StartSessionAsync()) = DataManager.Profiles[Player.Name]
	if not Profile then
		return
	end
	Profile:EndSession()
	DataManager.Profiles[Player.Name] = nil
end)

--Remotes
UpdateSlots.OnServerEvent:Connect(function(Player: Player, Function: string, ID: number)
	local Profile: typeof(PlayerStore:StartSessionAsync()) = DataManager.Profiles[Player.Name]
	if not Profile then
		return
	end
	if Function == "Start" then
		DataManager:StartSlots(SlotStore, Player)
		
		local Profiles = {}
		for _, ID: number in Profile.Data.Slots do
			local SlotProfile = DataManager:GetSlotData(SlotStore, Player, ID)
			table.insert(Profiles, SlotProfile.Data.ID,{
				["Data"] = SlotProfile.Data,
				["Key"] = SlotProfile.Key
			})
		end
		GetData:FireClient(Player, Profiles)
	elseif Function == "Wipe" then
		DataManager:WipeSlots(SlotStore, Player, ID)
		
		local Profiles = {}
		for _, ID: number in Profile.Data.Slots do
			local SlotProfile = DataManager:GetSlotData(SlotStore, Player, ID)
			table.insert(Profiles, SlotProfile.Data.ID, {
				["Data"] = SlotProfile.Data,
				["Key"] = SlotProfile.Key
			})
		end
		GetData:FireClient(Player, Profiles)
	end
end)

GetData.OnServerEvent:Connect(function(player: Player) 
	GetData:FireClient(player, DataManager:GetProfileData(player))
end)

First Place DataManager
local DataManager = {}

local Players: Players = game:GetService("Players")
local RunService:RunService = game:GetService("RunService")
local ServerScriptService: ServerScriptService = game:GetService("ServerScriptService")

local Modules = ServerScriptService.Modules
local ProfileStore = require(Modules.ProfileStore)

local SlotTemplate = require(Modules.SlotTemplate)

local function GetStoreName()
	return RunService:IsStudio() and "Test" or "Live"
end

DataManager.AdminList = {
}
table.freeze(DataManager.AdminList)

--Store ProfileStore profiles
DataManager.Profiles = {}

function DataManager:GetSlotData(SlotStore: typeof(ProfileStore), Player: Player, ID: number)
	local SlotProfile = SlotStore:GetAsync("Player_".. Player.UserId.. "_Slot_".. ID)
	if SlotProfile then
		return SlotProfile
	else
		return
	end
end

--Starts a profile with an ID or if a profile doesn't exist for that ID, it creates one.
function DataManager:StartSlots(SlotStore: typeof(ProfileStore), Player: Player)
	local Profile = DataManager.Profiles[Player.Name]
	if not Profile then
		return
	end
	
	local RandNum: number = nil
	repeat
		RandNum = math.random(1, 999999999)
	until
	not table.find(Profile.Data.Slots, RandNum) and RandNum > 0
	local newKey = "Player_".. Player.UserId.. "_Slot_".. RandNum
	
	local SlotProfile = SlotStore:StartSessionAsync(newKey, {
		Cancel = function ()
			return Player.Parent ~= Players
		end,
	})
	--Sanity check
	if SlotProfile == nil then
		--Server shutdown while loading
		Player:Kick("Server cooked your slots data, rejoin")
	else
		SlotProfile:AddUserId(Player.UserId) --GDPR compliance
		SlotProfile:Reconcile() -- Fill in missing data from SlotTemplate (optional)
		
		--Session locking
		SlotProfile.OnSessionEnd:Connect(function() 
			if Player.Parent == Players then
				
			else
				DataManager.Profiles[newKey] = nil
				Player:Kick("Slot data error, rejoin")
			end
		end)

		if Player.Parent == Players then
			DataManager.Profiles[newKey] = SlotProfile
			SlotProfile.Data.ID = RandNum
			table.insert(Profile.Data.Slots, RandNum)
			SlotProfile:EndSession()
			DataManager.Profiles[newKey] = nil
		else
			SlotProfile:EndSession()
		end
	end
end

--Deletes a profile of the provided ID
function DataManager:WipeSlots(SlotStore: typeof(ProfileStore), Player: Player, ID: number)
	if ID == nil then
		warn("ID cannot be nil")
	end
	
	local Profile = DataManager.Profiles[Player.Name]
	if not Profile then
		return
	end
	local SlotProfile = DataManager:GetSlotData(SlotStore, Player, ID)
	if SlotProfile then
		SlotStore:RemoveAsync(SlotProfile.Key)
		table.remove(Profile.Data.Slots, table.find(Profile.Data.Slots, ID))
	else
		return
	end
end

return DataManager

Second Place
local Players = game:GetService("Players")
local ReplicatedStorage: ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerScriptService: ServerScriptService = game:GetService("ServerScriptService")
local RunService: RunService = game:GetService("RunService")

local ServerModules: Folder = ServerScriptService.Modules

local ProfileStore = require(ServerModules.ProfileStore)

local GetData: RemoteEvent = ReplicatedStorage.Remotes.GetData

local function GetStoreName()
	return RunService:IsStudio() and "Test" or "Live"
end

--Access Player Data
local ProfileTemplate = require(ServerModules.ProfileTemplate)
local SlotTemplate = require(ServerModules.SlotTemplate)
local DataManager = require(ServerModules.DataManager)

local PlayerStore = ProfileStore.New(GetStoreName(), ProfileTemplate)
local SlotStore = ProfileStore.New(GetStoreName().. "_Slots", SlotTemplate)

local ErrorMessages = {
	["SessionLocking"] = "Data error, rejoin"
}

local function Initialize(Player: Player, Profile: typeof(PlayerStore:StartSessionAsync()), SlotProfile: typeof(PlayerStore:StartSessionAsync()))
	print(Profile, SlotProfile)
end

local function onPlayerAdded(Player: Player)
	local joinData = Player:GetJoinData()
	local teleportData: Instance? = joinData.TeleportData

	GetData:FireClient(Player)
	
	--Start new session on Profile	
	local Profile = PlayerStore:GetAsync("Player_".. Player.UserId)
	--Start new session on SlotProfile	
	local SlotProfile = SlotStore:StartSessionAsync("Player_".. Player.UserId.. "_Slot_".. teleportData.ID, {
		Cancel = function ()
			return Player.Parent ~= Players
		end,
	})
	
	--Sanity check
	if SlotProfile == nil then
		--Server shutdown while loading
		Player:Kick("Server cooked your profile data, rejoin")
	else
		SlotProfile:AddUserId(Player.UserId) --GDPR compliance
		SlotProfile:Reconcile() -- Fill in missing data from SlotTemplate (optional)

		--Session locking
		SlotProfile.OnSessionEnd:Connect(function() 
			DataManager.Profiles[SlotProfile.Key] = nil
			Player:Kick("Slot data error, rejoin")
		end)

		if Player.Parent == Players then
			DataManager.Profiles[SlotProfile.Key] = SlotProfile
			Initialize(Player, Profile, SlotProfile)
		else
			SlotProfile:EndSession()
		end
	end
end

--Early joiners
for _, Player: Player in Players:GetPlayers() do
	task.spawn(onPlayerAdded(), Player)
end

Players.PlayerAdded:Connect(onPlayerAdded)

Players.PlayerRemoving:Connect(function(Player: Player) 
	--WIP
	local Profile: typeof(PlayerStore:StartSessionAsync()) = DataManager.Profiles["Player_".. Player.UserId.. "_Slot_"]
	if not Profile then
		return
	end
	Profile:EndSession()
	DataManager.Profiles[Player.Name] = nil
end)

Players.PlayerAdded:Connect(onPlayerAdded)

(80% of this is from a tutorial, seconds places datamanager only has 1 table)

i dont think its possible, just read whats below

unless profilestore has a way to combat the original problem

its says:

which is exactly what i’m trying to do

If you use the same store_name when calling the ProfileStore constructor in both places it should allow you to use the same profile key to get the same data

1 Like

very strange, maybe it isn’t the store name, as i copy&pasted it over, same with the key (as in i printed the key both times and theyre exact matches)

Note that it is now possible with [Beta] Use Open Cloud via HttpService Without Proxies. Under the datastore drop down is a list of OpenCloud endpoints that can be accessed


Note that I have yet to know a way to write data using these OpenCloud urls that is safe against race conditions. A manual locking system would probably have to be built, or you’d have to write to a key nothing else will write to, as OpenCloud endpoints do not have a way to lock a key in between the time it is read, and written to (like what UpdateAsync() does, from the roblox api) (you can use etags for that)

It is however possible to do a lot with only read requests. If you have 2 servers sharing currency, each server can still have its own currency datastore, but to share the currency, the server (on which the player is) can request the currency from the other server and add it to the player’s currency. This does allow for potentially negative currency on either server, but the sum should stay positive (unless your game allows for debt lol)

1 Like

Is the teleportData being set correctly? I’ve read your code over and I don’t see any obvious holes in it, but there’s no mention of teleport service in it

1 Like

tp data is just {[“ID”] = randomNumber}, i printed it when i set it and its good, i printed it when i recieved it and its matching the sent one

I ACCIDENTALLY CALLED onPlayerAdded TWICE

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.