Slot-based datastore system problem

Okay, I know this is going to sound crazy because at first I tried to make the datastore work with 5 SLOTS!!! (crazy, it would consume a lot of memory, so I reduced it to 1), but now with some modifications I think it would be working. Now, a problem arises with saving the positions of objects within the map, since although they are saved, only one is saved and not that of the other parts, so the script what it does is give the same positions to all objects, which I don’t like at all, how could I change this? I’ve been complaining too much about the positions of objects because the Roblox datastore doesn’t allow me to save it in Vector3.

I’ve tried to store it in tables to use strings since storing intvalues cost alot of memory, and then i converted the strings to CFrames with a function.

How can i solve this problem? only the parts i move have this.

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local playerDataStore = DataStoreService:GetDataStore("PlayerDataStore")

local function createLeaderstats(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "PlayerData"
	leaderstats.Parent = player

	local MaximumHealth = Instance.new("IntValue")
	MaximumHealth.Name = "MaximumHealth"
	MaximumHealth.Value = 100
	MaximumHealth.Parent = leaderstats

	local CurrentHealth = Instance.new("IntValue")
	CurrentHealth.Name = "CurrentHealth"
	CurrentHealth.Value = 100
	CurrentHealth.Parent = leaderstats

	local HasGame = Instance.new("IntValue")
	HasGame.Name = "HasGame"
	HasGame.Value = 0
	HasGame.Parent = leaderstats

	local Glock17Ammo = Instance.new("IntValue")
	Glock17Ammo.Name = "Glock17"
	Glock17Ammo.Value = 0 
	Glock17Ammo.Parent = leaderstats

	local Model76Ammo = Instance.new("IntValue")
	Model76Ammo.Name = "Model76"
	Model76Ammo.Value = 0
	Model76Ammo.Parent = leaderstats

	local Spas12Ammo = Instance.new("IntValue")
	Spas12Ammo.Name = "Spas12"
	Spas12Ammo.Value = 0
	Spas12Ammo.Parent = leaderstats

	local Checkpoint = Instance.new("IntValue")
	Checkpoint.Name = "Chapter"
	Checkpoint.Value = 1
	Checkpoint.Parent = leaderstats

	local Characternumber = Instance.new("IntValue")
	Characternumber.Name = "Characternumber"
	Characternumber.Value = 1
	Characternumber.Parent = leaderstats

	return leaderstats
end

local function saveCheckpoint(player, slot)
	print("Saving checkpoint for player:", player.Name, "to slot:", slot)
	local leaderstats = player:FindFirstChild("PlayerData")
	if leaderstats then
		local data = {
			position = player.Character and tostring(player.Character:GetPivot()) or tostring(CFrame.new()),
			health = leaderstats.CurrentHealth.Value,
			chapter = leaderstats.Chapter.Value,
			characterNumber = leaderstats.Characternumber.Value,
			tools = {},
			gameTime = game.ReplicatedStorage.GameTemp.GameTime.Value,
			worldState = {},
			puzzlesCompleted = {}
		}

		for _, tool in ipairs(player.Backpack:GetChildren()) do
			local toolData = {
				name = tool.Name,
				mag = tool:FindFirstChild("GunScript_Server") and tool.GunScript_Server.Mag.Value or 0,
				ammo = tool:FindFirstChild("GunScript_Server") and tool.GunScript_Server.Ammo.Value or 0
			}
			table.insert(data.tools, toolData)
		end

		local puzzlesFolder = game.Workspace:FindFirstChild("Puzzles") 
		if puzzlesFolder then
			for _, puzzle in ipairs(puzzlesFolder:GetChildren()) do
				if puzzle:IsA("Model") then
					local puzzleCompleted = puzzle:FindFirstChild("Completed") 
					if puzzleCompleted and puzzleCompleted:IsA("BoolValue") then
						data.puzzlesCompleted[puzzle.Name] = puzzleCompleted.Value
					end
				end
			end
		end

		local world = game.Workspace:FindFirstChild("World") 
		if world then
			for _, object in ipairs(world:GetChildren()) do
				if object then
					local state = {
						Name = object.Name,
						CFrame = {object:GetPivot():components()}, 
						Transparency = object.Transparency,
					}
					if object:IsA("Model") then
						state.Models = {}
						for _, child in ipairs(object:GetChildren()) do
							if child and child:IsA("BasePart") then
								table.insert(state.Models, {
									Name = child.Name,
									CFrame = {child:GetPivot():components()}
								})
							end
						end
					end
					table.insert(data.worldState, state)
				end
			end
		end

		local checkPointDataStore = DataStoreService:GetDataStore("Checkpoints")
		local success, errorMessage = pcall(function()
			checkPointDataStore:SetAsync(player.UserId .. "_slot" .. slot, data)
		end)

		if not success then
			warn("Failed to save checkpoint: ", errorMessage)
		else
			print("Checkpoint saved successfully for player:", player.Name, "in slot:", slot)
		end
	end
end

function StringToCFrame(String: string) : CFrame
	local Split = string.split(String, ",")
	return CFrame.new(Split[1],Split[2],Split[3],Split[4],Split[5],Split[6],Split[7],Split[8],Split[9],Split[10],Split[11],Split[12])
end

local function loadCheckpoint(player, slot)
	print("Loading checkpoint for player:", player.Name, "from slot:", slot)
	local checkPointDataStore = DataStoreService:GetDataStore("Checkpoints")
	local success, data = pcall(function()
		return checkPointDataStore:GetAsync(player.UserId .. "_slot" .. slot)
	end)

	if success and data then
		print("Checkpoint data loaded successfully.", data)
		local leaderstats = createLeaderstats(player)
		if player.Character then
			player.Character:PivotTo(StringToCFrame(data.position))
			player.Character.Humanoid.Health = data.health
		end
		leaderstats.CurrentHealth.Value = data.health
		leaderstats.Chapter.Value = data.chapter
		leaderstats.Characternumber.Value = data.characterNumber

		for _, toolData in ipairs(data.tools) do
			local tool = game.ServerStorage.Tools:FindFirstChild(toolData.name)
			if tool then
				local newTool = tool:Clone()
				newTool.Parent = player.Backpack
				if newTool:FindFirstChild("GunScript_Server") then
					newTool.GunScript_Server.Mag.Value = toolData.mag
					newTool.GunScript_Server.Ammo.Value = toolData.ammo
				end
			end
		end

		local world = game.Workspace:FindFirstChild("World")
		if world then
			for _, objectData in ipairs(data.worldState) do
				local object = world:FindFirstChild(objectData.Name)
				if object then
					object:PivotTo(CFrame.new(unpack(objectData.CFrame))) 
					object.Transparency = objectData.Transparency

					if object:IsA("Model") then
						for _, childData in ipairs(objectData.Models) do
							local child = object:FindFirstChild(childData.Name)
							if child and child:IsA("BasePart") then
								child:PivotTo(CFrame.new(unpack(childData.CFrame))) 
							end
						end
					end
				end
			end
		end

		local puzzlesFolder = game.Workspace:FindFirstChild("Puzzles")
		if puzzlesFolder then
			for puzzleName, isCompleted in pairs(data.puzzlesCompleted) do
				local puzzle = puzzlesFolder:FindFirstChild(puzzleName)
				if puzzle and puzzle:IsA("Model") then
					local puzzleCompleted = puzzle:FindFirstChild("Completed")
					if puzzleCompleted and puzzleCompleted:IsA("BoolValue") then
						puzzleCompleted.Value = isCompleted
					end
				end
			end
		end

		if game.ReplicatedStorage.GameTemp.GameTime then
			game.ReplicatedStorage.GameTemp.GameTime.Value = data.gameTime
		end
	else
		print("Failed to load checkpoint data.")
	end
end

local function handlePlayerDeath(player)
	print("Player " .. player.Name .. " has died. Loading last checkpoint.")
	loadCheckpoint(player, 1)
	player:LoadCharacter() 
	player.CharacterAdded:Wait()
	if player.Character then
		local humanoidRootPart = player.Character:WaitForChild("HumanoidRootPart")
	end
end

local function loadPlayerData(player)
	local data = playerDataStore:GetAsync(player.UserId)
	if data then
		local leaderstats = createLeaderstats(player)
		for name, value in pairs(data) do
			if leaderstats:FindFirstChild(name) then
				leaderstats[name].Value = value
			end
		end
	else
		createLeaderstats(player)
	end
end

game.Players.PlayerAdded:Connect(function(player)
		loadPlayerData(player)
		player.CharacterAdded:Connect(function(character)
			local humanoid = character:WaitForChild("Humanoid")
			humanoid.Died:Connect(function()
			handlePlayerDeath(player)
		end)
	end)
end)

return {
	createLeaderstats = createLeaderstats,
	saveCheckpoint = saveCheckpoint,
	loadCheckpoint = loadCheckpoint,
	handlePlayerDeath = handlePlayerDeath,
	loadPlayerData = loadPlayerData
}```

where in your code does it handle saving the position of parts? there are no comments so it’s a bit time consuming to look through it to find the one thing.

regarding saving positions, you would probably benefit from a serialization library (which you can find on the devforum if you search), or alternatively I would store the vectors as a set of 3 numbers instead of a string since using a string is a bit of a waste of space and you want to fit as much as possible into a datastore

Here’s where the code handles saving and loading the position of parts

local world = game.Workspace:FindFirstChild("World") 
if world then
    for _, object in ipairs(world:GetChildren()) do
        if object then
            local state = {
                Name = object.Name,
                CFrame = {object:GetPivot():components()}, 
                Transparency = object.Transparency,
            }
            if object:IsA("Model") then
                state.Models = {}
                for _, child in ipairs(object:GetChildren()) do
                    if child and child:IsA("BasePart") then
                        table.insert(state.Models, {
                            Name = child.Name,
                            CFrame = {child:GetPivot():components()}
                        })
                    end
                end
            end
            table.insert(data.worldState, state)
        end
    end

Few notes:

  • You can use typechecking & for loop to create all of your leaderstats values, althought they should be named PlayerData because leaderstats are name specific to leaderboard
  • You can compress numbers into the buffers, i doubt that your game’s map or building area is beyond 6k blocks, you can use 16 bit integers instead of 32 bit floats to save 2x more data, if you want floating points, save your block’s position multiplied by let’s say 100, remember to not go outside the buffer’s limit!
  • Your Code is very nested and unredable at some points, try to invert some if statements and use guard clausess, you should also split code into modules

Would something like this work to compress? I will use 32-bit integers for accuracy but compress to string for storage because this can still reduce data size by avoiding float precision

local CompressionUtil = {}

function CompressionUtil.compressFloatToShort(float)
    return string.pack("i4", math.floor(float * 100))
end

function CompressionUtil.decompressShortToFloat(compressed)
    return string.unpack("i4", compressed) / 100
end

function CompressionUtil.compressVector3(v)
    return {
        CompressionUtil.compressFloatToShort(v.X),
        CompressionUtil.compressFloatToShort(v.Y),
        CompressionUtil.compressFloatToShort(v.Z)
    }
end

function CompressionUtil.decompressVector3(compressedVector)
    return Vector3.new(
        CompressionUtil.decompressShortToFloat(compressedVector[1]),
        CompressionUtil.decompressShortToFloat(compressedVector[2]),
        CompressionUtil.decompressShortToFloat(compressedVector[3])
    )
end

return CompressionUtil

Buffers are still more precise, also if you have small saving area below 2k studs from center you can definitevely use 16 bit integers, you can also use only positive side of your map to save stuff and have 2x precision

My map is huge. It has like 50k parts.

Not part count matters but size in studs, if your SAVING AREA is below 2k studs away from 0,0 (4k if u consider positives and using positive side of the map) soo you can optimize data stores