How do I make this scalable?

So, I am creating a game that has levels in it. So far the game’s levels are sorted by pack, and each pack will have a finite amount of levels. When a player joins for the first time, my code sets up the levels for them. So far my code looks like this, and it is set up so I can recall information about the player and the level with the same modulescript.

local function setupPlayer(player)
	local ID = "Player_".. player.UserId
	local success, data = pcall(function() 
		return {PlayerData:GetAsync(ID), Leaderboard:GetAsync(ID),MarketData:GetAsync(ID),PlayerLevel:GetAsync(ID),CurrentLevel:GetAsync(ID)} 
	end)
	if success then
-- data in [1-3] is irrelevant to this question.
		if data[4] then levels[ID] = data[4]
		-- commenting level creation so I dont forget.
		-- this only defaults with 1 level, I must add the rest manually.
		else levels[ID] = {
			levels_main = {
				level_1 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_2 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_3 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_4 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_5 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_6 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_7 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_8 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_9 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_10 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_11 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_12 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_13 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_14 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_15 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_16 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_17 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_18 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_19 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_20 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_21 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_22 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_23 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_24 = {
					completed = false,
					passable = false,
					hintPower = 0
				},
				level_25 = {
					completed = false,
					passable = false,
					hintPower = 0
				}
			}
		}
		end
		print("Levels set.")
		if data[5] then currentLevel[ID] = data[5]
		else currentLevel[ID] = DataManager:setCurrentLevel(player,"main","1")	
		end
		print("Current Level is: "..currentLevel[ID][1].."_"..currentLevel[ID][2])
		loadLeaderstats(player)
	else
		-- Code here to prevent bad overwrite
		warn("Issue Accessing Player's Data")
	end
end

Is there any way to make this scalable? I understand that if I create more levels I would have to insert them into the table where I store playerdata, but I feel like there is a better way to do this. Any suggestions?

1 Like

I would recommend making your code more modular and, instead of storing the data fetched from a data store in a tuple (that is, an array-like table with fixed length), store it in a dictionary. Something along the lines of this:

local data = {
	currentLevel = currentLevelStore:GetAsync(player.UserId),
	leaderboard = leaderboardStore:GetAsync(player.UserId),
	marketData = marketDataStore:GetAsync(player.UserId),
	playerData = playerDataStore:GetAsync(player.UserId),
	playerLevel = playerLevelStore:GetAsync(player.UserId),
};

It must also be said that it might be best to use DataStore2 or, at the very least, consider using the “OrderedBackups” or “berezza” method of data stores, as it heavily minimizes the chance for data loss or your server dealing with the request limits imposed upon it. However, all of this concern is certainly premature.

If you need to store levels, just create a method that fetches it from a dictionary with number keys and the objects. If the object doesn’t exist, assume the defaults. You should never have to repeat yourself in a perfect world. Genuinely, what you have there with the level_x method is a maintaining nightmare.

1 Like

Is this for readability in that portion of my code? When my code finds the specified datastore or creates a value for it, it is stored in tables that are accessible via playerID

local highscore = {}
local sessionData = {}
local inventory = {}
local levels = {}
local currentLevel = {}

--sessionData[someUserID][coins] would return the amount of coins they have.
--I have a get method for this.

I understand this. I am looking at DataStore2 right now and once I am comfortable with utilizing it I will probably make a switch.

When you say this. Do you mean something like this?

local levels = {}

function levels.getBetaLevels()
	local main = {}
	for i = 1, 25 do
		main[i] = {
					completed = false,
					passable = false,
					hintPower = 0
				}
	end
	return main
end

function levels.getExtension1()
	local ext1 = {}
	for i = 1, 50 do
		ext1[i] = {
					completed = false,
					passable = false,
					hintPower = 0
				}
	end
	return ext1
end

return levels

-- In dataStore moduleScript:
else levels[ID] = {
    getLevelScript.getBetaLevels(),
    getLevelScript.getExtension1()
}

Thank you for your suggestions. I will try to utilize as much as I can.

1 Like

I meant something more along the lines of this:

local SomeTableUtil = require(Path.To.SomeTableUtil);

local LevelManager = {};

LevelManager.INITIAL_LEVEL_DATA = {
	completed = false,
	hintPower = 0,
	passable = false,
};

function LevelManager.getCurrentLevelData(player)
	local data = getExistingLevelData(player) or SomeTableUtil.clone(LevelManager.INITIAL_LEVEL_DATA);
	-- Do stuff with data
	return data;
end

return LevelManager;

There’s no real reason to initialize all that information before it’s needed.

1 Like

I am not completely understanding what you mean by this. Is SomeTableUtil a ModuleScript I create that will create the tables? And with this, am I able to add levels at later times after users have data within the datastore?

1 Like

Yeah. I imagine by now in your codebase you have some module for table utilities outside the use of table

1 Like

Understood. I actually did not have a table utility but deep cloning is just recursion so I made one :sweat_smile:. Can I use the solution you provided with my idea of level packs? I plan on releasing monetized extension levels at later points which would need to be a seperate table I can just insert them in later. I am pushing the idea of nested tables as levels[packName][levelNumber] so I create purchase prompts if there is a nil value for a [packname]

I additionally started to try to implement DataStore2, it’s making my setup() function look very nice.

1 Like