Save your player data with ProfileService! (DataStore Module)

Oh okay thanks for telling me. Edit - It works now!

How would you go about saving and loading folders using this? @EncodedLua

Today, i just got a error
21:50:54.441 - ServerScriptService.Leaderboard.ProfileData.ProfileService:1052: Argument 2 missing or nil
21:51:24.353 - Not running script because past shutdown deadline (x18)
I don’t know if this is caused by my scripts.

Send me a screenshot of line 1052 in your ProfileService as it might be an outdated version - just send me the line anyways so I could tell where it is in the new code.

2 Likes

@loleris
Do you know how I would create a system to merge for profileservice:


local saveStructure = {
	Coins = 0;
	Gems = 0; -- what if I added gems in an update
}

local PlayerProfileStore = ProfileService.GetProfileStore("PlayerSaveData", saveStructure)

Also how would you go about saving a table with ProfileService?

Profile.Data is already a table. You can store nested tables within tables - see Lua documentation.

If your data structure is not complex, you can do a first branch merge (will not merge keys inside nested tables) as soon as you :LoadProfileAsync():

local function DeepCopy(original)
    local copy = {}
    for k, v in pairs(original) do
        if type(v) == "table" then
            v = DeepCopy(v)
        end
        copy[k] = v
    end
    return copy
end

for key, value in pairs(saveStructure) do
    if profile.Data[key] == nil then
        if type(value) == "table" then
            profile.Data[key] = DeepCopy(value)
        else
            profile.Data[key] = value
        end
    end
end
3 Likes

What exactly do you mean it won’t merge keys inside the nested table? @loleris

Just use this:

local SaveStructure = {
	Coins = 0,
	Experience = 0,
	Items = {
		Owned = {},
		Renting = {},
	}
}

local function DeepCopy(original)
    local copy = {}
    for k, v in pairs(original) do
        if type(v) == "table" then
            v = DeepCopy(v)
        end
        copy[k] = v
    end
    return copy
end

local function MergeDataWithTemplate(data, template)
	for k, v in pairs(template) do
		if type(k) == "string" then -- Only string keys will be merged
			if data[k] == nil then
				if type(v) == "table" then
					data[k] = DeepCopy(v)
				else
					data[k] = v
				end
			elseif type(data[k]) == "table" and type(v) == "table" then
				MergeDataWithTemplate(data[k], v)
			end
		end
	end
end

MergeDataWithTemplate(profile.Data, SaveStructure)
6 Likes

Thanks I figured out what to do. Do you think I should create a community tutorial about ProfileService to help people?

Your knowledge might be limited for making a completely correct tutorial, but you can still make one and share with your fellow developers!

1 Like

Also about the saving folders. You would do Example: (table.insert(profile.Data.Inventory,item.Name). Correct? @loleris

That can be however you like it to be set up.

Do you have an example in the github?? Nevermind I’m trying it out. Done now it works.


coroutine.wrap(SaveProfileAsync)(self, true) – Call save function in a new thread with release_from_session = true

does :LoadProfileAsync() support only strings for the profile_key, or can I use numbers too?

You’ll have to use tostring() on your number keys before passing to :LoadProfileAsync().

If you’ve been using numbers as keys, they were being casted to strings, based on the official API:
image

1 Like

Would the code break if I were to automatically tostring it in your ProfileStore:LoadProfileAsync function (line 1064)?
I’ve been using numerical keys for my data store and am about to switch to this module.

I would not break it, though, as minuscule as this issue is, it would probably be more professional to convert the value before passing it in. You would also avoid the risk of forgetting custom changes when switching out to an updated ProfileService version in the future.

I’m currently making a module that pretty much just uses your profileservice, but with some renamed functions to fit my coding habits (e.g

function Network:GetProfileStore(StoreName) 
    return ProfileService.GetProfileStore(StoreName) 
end

), I’d assume it’d work fine if I were to just wrap your profilestore in my module to automatically tostring the profile_key argument of LoadProfileAsync?
Something along the lines of:

function Network:GetProfileStore(StoreName)
	local ProfileStore = ProfileService.GetProfileStore(StoreName)

	return setmetatable({
		LoadProfileAsync = function(self, profile_key, not_released_handler)
			return ProfileStore:LoadProfileAsync(tostring(profile_key), not_released_handler)
		end
	}, {__index = ProfileStore})
end

Forgetting to implement custom changes to your module is a good concern, so I’d assume this would be the safest approach?

Sure, that looks like it should work fine.

2 Likes