Need a some help with build a good structure of code

Hello, I want to understand how to properly create my own code and script structure.

I have a modular script DataStoreManager and it also has a PlayerDataService, also a modular script. All this is in ServerScriptService.

For example, I need to initialize a player, for example, switch the music in the lobby depending on the value in the Music field and, for example, initialize the player’s interface, for example, set the token values ​​there.

I don’t know which libraries to use for a convenient structure. Maybe Replica, Packet, Blink.

And how can I get data about the player from the server.
Need some help with building a good structude of code
How should I do this?

DataStoreManager
local StorageDataManager = {}

local DataStoreService = game:GetService("DataStoreService")
local RunService = game:GetService("RunService")

StorageDataManager.DataStore = DataStoreService:GetDataStore(RunService:IsStudio() and "TestData" or "Data")

return StorageDataManager
PlayerDataService
local PlayerDataService = {}

local ServerScriptService = game:GetService("ServerScriptService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Utils = require(ReplicatedStorage.Modules.Utils)
local StorageDataManager = require(script.Parent)

local InitPlayerUI = ReplicatedStorage.Events.UI.InitPlayerUI
local InitPlayerData = ReplicatedStorage.Events.PlayerData.InitPlayerData
local UpdatePlayerData = ReplicatedStorage.Events.PlayerData.UpdatePlayerData
local DataField = Utils.DataField
local DataStore = StorageDataManager.DataStore

--local InitPlayer = Packet("InitPlayer", {Packet.Any})

local PLAYER_DATA_FIELDS = {
	[DataField.TOKENS] = {
		default = 0
	},
	[DataField.MUSIC] = {
		default = true
	},
	[DataField.SFX] = {
		default = true
	}
}

local playerData = {}

local function getPlayerKey(player)
	return "Player_"..player.UserId
end

local function loadPlayerData(player)	
	local key = getPlayerKey(player)
	local success, data = pcall(function()
		return DataStore:GetAsync(key) or {}
	end)
	
	if success then
		for fieldName, config in pairs(PLAYER_DATA_FIELDS) do
			if data[fieldName] == nil then
				data[fieldName] = config.default
			end
		end
	else
		warn(`[{player.UserId}] - Data failed to load for player!`)
	end

	playerData[player] = data

	return success
end

local function updatePlayerData(player, data, shouldSave)
	playerData[player] = data

	if not shouldSave then
		print(`[{player.UserId}] - Data was successfully updated, without save!`)
		return
	end

	local key = getPlayerKey(player)
	local success, err = pcall(function()
		DataStore:UpdateAsync(key, function()
			return data
		end)
	end)

	if success then
		print(`[{player.UserId}] - Data was successfully updated!`)
	else
		warn(err)
	end

end

function PlayerDataService.getPlayerData(player)
	return playerData[player]
end

function PlayerDataService.savePlayerData(player)
	local key = getPlayerKey(player)
	local success, err = pcall(function()
		DataStore:UpdateAsync(key, function()
			return playerData[player]
		end)
	end)

	if success then
		print(`[{player.UserId}] - Data was successfully saved!`)
	else
		warn(err)
	end

	playerData[player] = nil
end

local function initLocalPlayerData(player)
	InitPlayerData:FireClient(player, playerData[player])
end

local function initLocalPlayerUI(player)
	InitPlayerUI:FireClient(player, playerData[player])
end

function PlayerDataService.initPlayerData(player)
	local success = loadPlayerData(player)
	if not success then
		return
	end

	initLocalPlayerData(player)
	initLocalPlayerUI(player)
	--InitPlayer:FireClient(player, {playerData[player]})
end

UpdatePlayerData.OnServerEvent:Connect(function(player, data, shouldSave)
	updatePlayerData(player, data, shouldSave)
end)

return PlayerDataService

LocalPlayerDataManager

local LocalPlayerDataManager = {}

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Utils = require(ReplicatedStorage.Modules.Utils)

local InitPlayerData = ReplicatedStorage.Events.PlayerData.InitPlayerData
local UpdatePlayerData = ReplicatedStorage.Events.PlayerData.UpdatePlayerData

local data = {}

function LocalPlayerDataManager.get(fieldName)
	return data[fieldName]
end

function LocalPlayerDataManager.update(fieldName, value, shouldSave)
	data[fieldName] = value
	UpdatePlayerData:FireServer(data, shouldSave)
end

local function init(initData)
	data = initData
end

InitPlayerData.OnClientEvent:Connect(function(data)
	init(data)
end)


return LocalPlayerDataManager

InitPlayer

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Utils = require(ReplicatedStorage.Modules.Utils)
local ReplicaClient = require(ReplicatedStorage.ReplicaClient)
local PlayerAudioManager = require(ReplicatedStorage.Modules.PlayerAudioManager)
--local InitPlayer = Packet("InitPlayer", {Packet.Any})

--InitPlayer.OnClientEvent:Connect(function(data)
--	PlayerAudioManager.toggleLobbyMusic(data[Utils.DataField.MUSIC])
--end)
2 Likes

I recommend using Packet instead fo remote/bindable events! It’s easy and better than traditional built-in Roblox events. It’ll also simplify the whole structure.
The rest is aight imo!

P.S. I just noticed the “player added” and “player removed” scripts. If you can place everything in 1 unique script, unless it’s really long stuff. No point in separating small functions that are logically contingent.

3 Likes

These scripts have 5-6 lines of code

1 Like

Yeah definitely a good idea to place everything together in 1 unique script. Pointless keeping them separated. And, to be fair, the whole merged script could be placed in another script.
Usually scripts deserve their autonomy when they either are long or have logically separated functions.