How can I optimize my module

Hello, nothing much to say, but I have a wrapper for ProfileService that I use for most of my game and I want to know how I can optimize it for the code to be more efficient. There are no problems with the code, I just want it to optimize it (if I can).

Code:

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

local ProfileService = require(script.ProfileService) 
local ReplicaService = require(script.ReplicaService)
local Signal = require(script.Signal)
local Config = require(script.Config)
local ProfileRemoving = require(script.ProfileRemoving)

local ProfileServicePlus = { 
	ProfileAdded = Signal.new(),
	ProfileRemoving = ProfileRemoving.Signal,
	Initialized = false,
}


local DEBUG_MODE = true

local Global_Update_Types = {}
local Loaded_Profiles = {}
local Profile_Replicas = {}
local Loaded_Profile_Stores = {}


local function debugPrint(message)
	if DEBUG_MODE then
		print(message)
	end
end


local function debugWarn(message)
	if DEBUG_MODE then
		warn(message)
	end
end


local function handleLockedUpdate(profile: table, update)
	local globalUpdates = profile.GlobalUpdates
	debugWarn("[PSPlus]: New locked update added")

	local updateID = update[1]
	local updateData = update[2]

	local listener = Global_Update_Types[updateData.Type]
	if listener ~= nil then
		task.spawn(listener, profile, updateData)
	else
		debugWarn(("[PSPlus]: No listener found for update %s!"):format(updateID))
	end
	globalUpdates:ClearLockedUpdate(updateID)
	debugWarn("[PSPlus]: Cleared locked update")
end


local function handleGlobalUpdates(profile)
	local globalUpdates = profile.GlobalUpdates

	for i, update in pairs(globalUpdates:GetActiveUpdates()) do
		globalUpdates:LockActiveUpdate(update[1])
	end

	for i, lockedUpdate in pairs(globalUpdates:GetLockedUpdates()) do
		handleLockedUpdate(profile, lockedUpdate)
	end

	globalUpdates:ListenToNewActiveUpdate(function(updateID, updateData)
		globalUpdates:LockActiveUpdate(updateID)
	end)

	globalUpdates:ListenToNewLockedUpdate(function(updateID, updateData)
		handleLockedUpdate(profile, {updateID, updateData})
	end)
end


local function getDefaultKey(player: string, profileStoreKey: string): string
	local playerKey = profileStoreKey.. "-%s"

	return playerKey:format(tostring(player.UserId))
end


local function getPlayerkey(player: string, profileStore: table)
	local playerKey
	local profileKey = profileStore._Key

	local stringStart, stringEnd = string.find(profileKey, "UserId")
	
	local keyStart = string.sub(profileKey, 0, stringStart - 1)
	local keyEnd = string.sub(profileKey, stringEnd + 1, #profileKey)
	playerKey = keyStart.. player.UserId.. keyEnd

	return playerKey
end


local function loadPlayerProfile(player, storeKey, not_released_handler)
	local loadedProfileStore = Loaded_Profile_Stores[storeKey]
	local data = Config.GAME_PROFILE_TEMPLATES.PLAYER_PROFILES[storeKey]

	local playerKey
	if data._Key ~= nil then
		playerKey = getPlayerkey(player, data)
	else
		playerKey = getDefaultKey(player, storeKey)
	end


	local playerProfile = loadedProfileStore:LoadProfileAsync(
		playerKey,
		not_released_handler or Config.DEFAULT_NOT_RELEASED_HANDLER 
	)

	if playerProfile then	
		playerProfile:Reconcile()
		playerProfile:ListenToRelease(function()
			player:Kick("Profile could've loaded on another Roblox server")
		end)

		if player:IsDescendantOf(Players) then	
			Loaded_Profiles[storeKey][playerKey] = playerProfile

			playerProfile.Player = player
			playerProfile.DataChanged = Signal.new()
			playerProfile.ProfileKey = playerKey
			handleGlobalUpdates(playerProfile)
			
			local replica = ReplicaService.NewReplica({
				ClassToken = ReplicaService.NewClassToken(playerKey),
				Tags = {Player = player},
				Data = playerProfile.Data,
				Replication = "All",
			})
			
			function playerProfile:SetData(dataToSet, newData)
				local oldData = playerProfile.Data[dataToSet]
				
				if typeof(oldData) ~= typeof(newData) then return end
				
				playerProfile.Data[dataToSet] = newData
				replica:SetValue({dataToSet}, newData)
				playerProfile.DataChanged:Fire(dataToSet, newData)
			end
			
			Profile_Replicas[data._Key] = replica
			
			ProfileServicePlus.ProfileAdded:Fire(playerProfile, loadedProfileStore)
			return playerProfile, playerKey
		else
			debugPrint("[PSPlus]: Player left before profile loaded, releasing profile")
			playerProfile:release()
		end
	else
		player:Kick("Profile could not load because other Roblox servers were trying to load it")
	end
end


local function loadNonPlayerProfile(storeKey, not_released_handler)
	local loadedProfileStore = Loaded_Profile_Stores[storeKey]
	
	local data = Config.GAME_PROFILE_TEMPLATES.NON_PLAYER_PROFILES[storeKey]
	
	
	local profile = loadedProfileStore:LoadProfileAsync(
		data._Key,
		not_released_handler or Config.DEFAULT_NOT_RELEASED_HANDLER 
	)
	
	if profile then
		profile:Reconcile()
		profile:ListenToRelease(function()
			warn("Profile coud have loaded on another Roblox server")
		end)
		
		Loaded_Profiles[storeKey][data._Key] = profile
		
		profile.DataChanged = Signal.new()
		profile.ProfileKey = data._Key
		handleGlobalUpdates(profile)
		
		local replica = ReplicaService.NewReplica({
			ClassToken = ReplicaService.NewClassToken(data._Key),
			Tags = {},
			Data = profile.Data,
			Replication = "All",
		})
		
		function profile:SetData(dataToSet, newData)
			local oldData = profile.Data[dataToSet]

			profile.Data[dataToSet] = newData
			replica:SetValue({dataToSet}, newData)
			profile.DataChanged:Fire(dataToSet, newData)
		end
		
		Profile_Replicas[data._Key] = replica
		
		ProfileServicePlus.ProfileAdded:Fire(profile, loadedProfileStore)
		return profile
	end
end


local function onPlayerAdded(player)
	local total = 0
	local loaded = 0

	for _, _ in pairs(Config.GAME_PROFILE_TEMPLATES.PLAYER_PROFILES) do
		total += 1
	end

	for storeKey, data in pairs(Config.GAME_PROFILE_TEMPLATES.PLAYER_PROFILES) do
		if data._LoadOnJoin == false then
			continue
		end
		
		local playerProfile, profileKey = loadPlayerProfile(player, storeKey)
		if playerProfile then
			Loaded_Profiles[storeKey][profileKey] = playerProfile
			loaded += 1
		end
	end

	debugPrint(("[PSPlus]: Successfully loaded %s/%s profiles"):format(loaded, total))
end


local function onPlayerRemoving(player)
	for storeKey, profileStore in pairs(Loaded_Profiles) do
		local data = Config.GAME_PROFILE_TEMPLATES.PLAYER_PROFILES[storeKey]
		local profileKey = getPlayerkey(player, data)
		local playerProfile = profileStore[profileKey]
		if playerProfile ~= nil then
			playerProfile:Release()
		end
	end
end


local function Init()
	ProfileServicePlus.Initialized = true

	for _, template in pairs(Config.GAME_PROFILE_TEMPLATES) do
		for storeKey, data in pairs(template) do
			Loaded_Profiles[storeKey] = {}
			Loaded_Profile_Stores[storeKey] = ProfileService.GetProfileStore(
				storeKey,
				data
			)
			Loaded_Profile_Stores[storeKey].Key = storeKey
		end
	end

	for _, player in ipairs(Players:GetPlayers()) do
		task.spawn(onPlayerAdded, player)
	end

	Players.PlayerAdded:Connect(onPlayerAdded)
	Players.PlayerRemoving:Connect(onPlayerRemoving)
	print("[PSPlus]: Initialized ProfileServicePlus")
end


if ProfileServicePlus.Initialized == false then
	Init()
end


function ProfileServicePlus:AddGlobalUpdateType(updateType, listener, overwriteExisting)
	if overwriteExisting then
		Global_Update_Types[updateType] = listener
	else
		if Global_Update_Types[updateType] ~= nil then
			warn(("[PSPlus]: Global update type %s already exists!"):format(updateType))
			return
		end

		Global_Update_Types[updateType] = listener
	end
end


function ProfileServicePlus:GetPlayerProfileKey(storeKey: string, player: Instance)
	local profileStore = Config.GAME_PROFILE_TEMPLATES.PLAYER_PROFILES[storeKey]

	return getPlayerkey(player, profileStore)
end


function ProfileServicePlus:GetProfileStore(storeKey: string)
	local profileStore = Loaded_Profile_Stores[storeKey]

	return profileStore
end


function ProfileServicePlus:GetProfile(profileKey, storeKey)
	local profile = Loaded_Profiles[storeKey][profileKey]

	return profile
end


function ProfileServicePlus:WaitForProfileAsync(storeKey, profileKey, timeout)
	local looping = true
	timeout = timeout or 20
	
	task.delay(timeout, function()
		looping = false
		if Loaded_Profiles[storeKey][profileKey] == nil then
			warn(("Infinite yield possible on Loaded_Profiles[%s][%s]"):format(storeKey, profileKey))		
		end
	end)
	
	while looping and Loaded_Profiles[storeKey][profileKey] == nil do	
		task.wait()
	end
	
	return Loaded_Profiles[storeKey][profileKey]
end


function ProfileServicePlus:GetReplicaOfProfile(profile)
	return Profile_Replicas[profile.ProfileKey]
end


function ProfileServicePlus:LoadProfileAsync(storeKey, not_released_handler, player)
	if player then
		return loadPlayerProfile(player, storeKey, not_released_handler)
	end
	return loadNonPlayerProfile(storeKey, not_released_handler)
end


function ProfileServicePlus:GetProfiles(profileStoreKey)
	local profiles = Loaded_Profiles[profileStoreKey] 
	return profiles or Loaded_Profiles
end


return ProfileServicePlus
1 Like
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local ProfileService = require(script.ProfileService) 
local ReplicaService = require(script.ReplicaService)
local Signal = require(script.Signal)
local Config = require(script.Config)
local ProfileRemoving = require(script.ProfileRemoving)

local ProfileServicePlus = { 
	ProfileAdded = Signal.new(),
	ProfileRemoving = ProfileRemoving.Signal,
	Initialized = false,
}


local DEBUG_MODE = true

local Global_Update_Types = {}
local Loaded_Profiles = {}
local Profile_Replicas = {}
local Loaded_Profile_Stores = {}

local function debug(type, message)
	if DEBUG_MODE then
		type(message)
	end
end

local function handleLockedUpdate(profile: table, update)
	local globalUpdates = profile.GlobalUpdates
	debug(warn, "[PSPlus]: New locked update added")

	local updateID = update[1]
	local updateData = update[2]

	local listener = Global_Update_Types[updateData.Type]
	if listener ~= nil then
		task.spawn(listener, profile, updateData)
	else
		debug(warn, ("[PSPlus]: No listener found for update %s!"):format(updateID))
	end
	globalUpdates:ClearLockedUpdate(updateID)
	debug(warn, "[PSPlus]: Cleared locked update")
end


local function handleGlobalUpdates(profile)
	local globalUpdates = profile.GlobalUpdates

	for i, update in pairs(globalUpdates:GetActiveUpdates()) do
		globalUpdates:LockActiveUpdate(update[1])
	end

	for i, lockedUpdate in pairs(globalUpdates:GetLockedUpdates()) do
		handleLockedUpdate(profile, lockedUpdate)
	end

	globalUpdates:ListenToNewActiveUpdate(function(updateID, updateData)
		globalUpdates:LockActiveUpdate(updateID)
	end)

	globalUpdates:ListenToNewLockedUpdate(function(updateID, updateData)
		handleLockedUpdate(profile, {updateID, updateData})
	end)
end


local function getDefaultKey(player: string, profileStoreKey: string): string
	local playerKey = profileStoreKey.. "-%s"

	return playerKey:format(tostring(player.UserId))
end


local function getPlayerkey(player: string, profileStore: table)
	local playerKey
	local profileKey = profileStore._Key

	local stringStart, stringEnd = string.find(profileKey, "UserId")
	
	local keyStart = string.sub(profileKey, 0, stringStart - 1)
	local keyEnd = string.sub(profileKey, stringEnd + 1, #profileKey)
	playerKey = keyStart.. player.UserId.. keyEnd

	return playerKey
end


local function loadPlayerProfile(player, storeKey, not_released_handler)
	local loadedProfileStore = Loaded_Profile_Stores[storeKey]
	local data = Config.GAME_PROFILE_TEMPLATES.PLAYER_PROFILES[storeKey]

	local playerKey
	if data._Key ~= nil then
		playerKey = getPlayerkey(player, data)
	else
		playerKey = getDefaultKey(player, storeKey)
	end


	local playerProfile = loadedProfileStore:LoadProfileAsync(
		playerKey,
		not_released_handler or Config.DEFAULT_NOT_RELEASED_HANDLER 
	)

	if playerProfile then	
		playerProfile:Reconcile()
		playerProfile:ListenToRelease(function()
			player:Kick("Profile could've loaded on another Roblox server")
		end)

		if player:IsDescendantOf(Players) then	
			Loaded_Profiles[storeKey][playerKey] = playerProfile

			playerProfile.Player = player
			playerProfile.DataChanged = Signal.new()
			playerProfile.ProfileKey = playerKey
			handleGlobalUpdates(playerProfile)
			
			local replica = ReplicaService.NewReplica({
				ClassToken = ReplicaService.NewClassToken(playerKey),
				Tags = {Player = player},
				Data = playerProfile.Data,
				Replication = "All",
			})
			
			function playerProfile:SetData(dataToSet, newData)
				local oldData = playerProfile.Data[dataToSet]
				
				if typeof(oldData) ~= typeof(newData) then return end
				
				playerProfile.Data[dataToSet] = newData
				replica:SetValue({dataToSet}, newData)
				playerProfile.DataChanged:Fire(dataToSet, newData)
			end
			
			Profile_Replicas[data._Key] = replica
			
			ProfileServicePlus.ProfileAdded:Fire(playerProfile, loadedProfileStore)
			return playerProfile, playerKey
		else
			debug(print, "[PSPlus]: Player left before profile loaded, releasing profile")
			playerProfile:release()
		end
	else
		player:Kick("Profile could not load because other Roblox servers were trying to load it")
	end
end


local function loadNonPlayerProfile(storeKey, not_released_handler)
	local loadedProfileStore = Loaded_Profile_Stores[storeKey]
	
	local data = Config.GAME_PROFILE_TEMPLATES.NON_PLAYER_PROFILES[storeKey]
	
	
	local profile = loadedProfileStore:LoadProfileAsync(
		data._Key,
		not_released_handler or Config.DEFAULT_NOT_RELEASED_HANDLER 
	)
	
	if profile then
		profile:Reconcile()
		profile:ListenToRelease(function()
			warn("Profile could have loaded on another Roblox server")
		end)
		
		Loaded_Profiles[storeKey][data._Key] = profile
		
		profile.DataChanged = Signal.new()
		profile.ProfileKey = data._Key
		handleGlobalUpdates(profile)
		
		local replica = ReplicaService.NewReplica({
			ClassToken = ReplicaService.NewClassToken(data._Key),
			Tags = {},
			Data = profile.Data,
			Replication = "All",
		})
		
		function profile:SetData(dataToSet, newData)
			local oldData = profile.Data[dataToSet]

			profile.Data[dataToSet] = newData
			replica:SetValue({dataToSet}, newData)
			profile.DataChanged:Fire(dataToSet, newData)
		end
		
		Profile_Replicas[data._Key] = replica
		
		ProfileServicePlus.ProfileAdded:Fire(profile, loadedProfileStore)
		return profile
	end
end


local function onPlayerAdded(player)
	local total = 0
	local loaded = 0

	for _, _ in pairs(Config.GAME_PROFILE_TEMPLATES.PLAYER_PROFILES) do
		total += 1
	end

	for storeKey, data in pairs(Config.GAME_PROFILE_TEMPLATES.PLAYER_PROFILES) do
		if data._LoadOnJoin == false then
			continue
		end
		
		local playerProfile, profileKey = loadPlayerProfile(player, storeKey)
		if playerProfile then
			Loaded_Profiles[storeKey][profileKey] = playerProfile
			loaded += 1
		end
	end

	debug(print, ("[PSPlus]: Successfully loaded %s/%s profiles"):format(loaded, total))
end


local function onPlayerRemoving(player)
	for storeKey, profileStore in pairs(Loaded_Profiles) do
		local data = Config.GAME_PROFILE_TEMPLATES.PLAYER_PROFILES[storeKey]
		local profileKey = getPlayerkey(player, data)
		local playerProfile = profileStore[profileKey]
		if playerProfile ~= nil then
			playerProfile:Release()
		end
	end
end


local function Init()
	ProfileServicePlus.Initialized = true

	for _, template in pairs(Config.GAME_PROFILE_TEMPLATES) do
		for storeKey, data in pairs(template) do
			Loaded_Profiles[storeKey] = {}
			Loaded_Profile_Stores[storeKey] = ProfileService.GetProfileStore(
				storeKey,
				data
			)
			Loaded_Profile_Stores[storeKey].Key = storeKey
		end
	end

	for _, player in ipairs(Players:GetPlayers()) do
		task.spawn(onPlayerAdded, player)
	end

	Players.PlayerAdded:Connect(onPlayerAdded)
	Players.PlayerRemoving:Connect(onPlayerRemoving)
	print("[PSPlus]: Initialized ProfileServicePlus")
end


if ProfileServicePlus.Initialized == false then
	Init()
end


function ProfileServicePlus:AddGlobalUpdateType(updateType, listener, overwriteExisting)
	if overwriteExisting then
		Global_Update_Types[updateType] = listener
	else
		if Global_Update_Types[updateType] ~= nil then
			warn(("[PSPlus]: Global update type %s already exists!"):format(updateType))
			return
		end

		Global_Update_Types[updateType] = listener
	end
end


function ProfileServicePlus:GetPlayerProfileKey(storeKey: string, player: Instance)
	local profileStore = Config.GAME_PROFILE_TEMPLATES.PLAYER_PROFILES[storeKey]

	return getPlayerkey(player, profileStore)
end


function ProfileServicePlus:GetProfileStore(storeKey: string)
	local profileStore = Loaded_Profile_Stores[storeKey]

	return profileStore
end


function ProfileServicePlus:GetProfile(profileKey, storeKey)
	local profile = Loaded_Profiles[storeKey][profileKey]

	return profile
end


function ProfileServicePlus:WaitForProfileAsync(storeKey, profileKey, timeout)
	local looping = true
	timeout = timeout or 20
	
	task.delay(timeout, function()
		looping = false
		if Loaded_Profiles[storeKey][profileKey] == nil then
			warn(("Infinite yield possible on Loaded_Profiles[%s][%s]"):format(storeKey, profileKey))		
		end
	end)
	
	while looping and Loaded_Profiles[storeKey][profileKey] == nil do	
		task.wait()
	end
	
	return Loaded_Profiles[storeKey][profileKey]
end


function ProfileServicePlus:GetReplicaOfProfile(profile)
	return Profile_Replicas[profile.ProfileKey]
end


function ProfileServicePlus:LoadProfileAsync(storeKey, not_released_handler, player)
	if player then
		return loadPlayerProfile(player, storeKey, not_released_handler)
	end
	return loadNonPlayerProfile(storeKey, not_released_handler)
end


function ProfileServicePlus:GetProfiles(profileStoreKey)
	local profiles = Loaded_Profiles[profileStoreKey] 
	return profiles or Loaded_Profiles
end


return ProfileServicePlus

Personally, I believe your module is very ideal, but if I’m going to be nitpicky you don’t need debugPrint and debugWarn, you can just make a debug function which you just give the type of message you want. I already incorporated this into the script itself. There was also a grammar error in a message so I fixed it.

Thanks for the feedback and the help! I’m planning to release the module soon but Im having second thoughts, what do you think?

Bumping this post because I need more feedback.