Attempting To load 70k assets before the player joins the game. Content Provider keeps yielding indefinitely

Before reading, if you have a good method/suggestion for loading in big maps then please let me know.

I have placed a Boolean value called “mapLoaded” inside of the ServerStorage that I want to be marked to true once the map has loaded.

I want to ensure that the map is loaded before spawning players. Since there are so many assets, the player falls through the ground when they spawn in a new server.

There are roughly 70k workspace assets in total

Here are some shots of the map if youre wondering how a map can have 70k assets

I have CharacterAutoLoad disabled. The players character is loaded from a function inside of my DataManager module. This function runs but halts if mapLoaded is false. It gives their player the attribute “waiting”

Now the concept is:
In a server script called “PlayerLoader”, I intend on calling content providers “PreloadAsync” for each asset inside of the players workspace.
& Once each asset is loaded, I set the “mapLoaded” value to true and call the character load function in the DataManager module for every waiting player.

First I attempted to preload all 70k assets and 2 issues occurred.

  • The callback function I used prints (“LoadedAssets”) but it was fired for every single asset. (Im not sure if that is how the callback function for PreloadAsync works)

  • The 2nd issue was that the script yielded forever after only printing “LoadedAssets” around 3k times.

The 2nd issue being the main issue I decided to preload the assets in chunks because I speculated that the volume was too much to handle.

  • I set the max amount of collected assets to 100 just to test the problem… & each chunk size was 10.
  • I also scripted timeout functionality for each PreloadAsync call. This found an error but it just returned “nil”.

Here is the code for this:

local SSS = game:GetService("ServerScriptService")
local GameModules = SSS.GameModules
local DataManager = require(GameModules.Managers.DataManager)

local function PlayerAdded(Player)
	DataManager.PlayerAdded(Player)
end

local function PlayerRemoving(Player)
	DataManager.PlayerRemoving(Player)
end

local function collectAssets(parent)
	local assets = {}
	
	for _, object in pairs(parent:GetDescendants()) do
		if object:IsA("Model") or object:IsA("Decal") or object:IsA("Texture") or object:IsA("Sound") then
			if #assets >= 100 then continue end
			table.insert(assets, object)
		end
	end
	
	print("Assets Collected", #assets)
	return assets
end



local function onAssetsPreloaded()
	print("Assets have been loaded")
	
	--task.wait(5)
	local LoadSignal = SS.LoadSignal
	LoadSignal.Value = true

	for _, player in pairs(Players:GetPlayers()) do
		print("checked")
		if player:GetAttribute("waiting") then
			warn("reloading")
			DataManager.Reload(player)
		end
	end

end


local function timeOutPreload(chunk, timeout)
	local completed = false
	local success, err
	
	local function preloadAssets()
		print("function called")
		success, err = pcall(function()
			game:GetService("ContentProvider"):PreloadAsync(chunk)
		end)
		completed = true
	end
	
	spawn(preloadAssets)
	
	local startTime = tick()
	while not completed and tick() - startTime < timeout do -- to timeout the preload async call
		wait(0.1)
	end
	
	if not completed then
		print("not completed", chunk, err) -- error here is "nil"
		for _, asset in ipairs(chunk) do --doing this to attempt to pinpoint asset thats erroring
			local succ, errmsg = pcall(function() 
				game:GetService("ContentProvider"):PreloadAsync({asset})  
			end)
			--It yields forever here because PreloadAsync errored.  So the code below never gets read
			if not succ then
				print("Error preloading asset:", asset:GetFullName(), errmsg)
			end
		end
	elseif not success then
		print("Error preloading asset:", err)
	end
end

local function loadChunk(assets, chunkSize, waitTime, timeout)
	local totalAssets = #assets
	local currentIndex = 1
	
	print(totalAssets)
	while currentIndex <= totalAssets do
		local chunk = {}
		for i=0, chunkSize -1 do
			if currentIndex + i <= totalAssets then
				table.insert(chunk, assets[currentIndex + i]) 
			else
				print("broken")
				break
			end
		end
		
		print(#chunk)
		timeOutPreload(chunk, timeout)
		
		currentIndex += chunkSize
		
		wait(waitTime)
		
	end

	print("AssetsLoaded")
    onAssetsPreloaded()
end

Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(PlayerRemoving)

print("ScriptStarted")

local assets = collectAssets(workspace)
local chunkSize = 10
local waitTime = 0.1
local timeout = 5

coroutine.wrap(function()
	loadChunk(assets, chunkSize, waitTime, timeout)
end)()

I printed the chunk that was in iteration when the error occurred and it had 10 values inside of it (The correct amount). So im not sure why the error printed “nil”. I tried to find exact asset that causes the issue but like before the rest of the script yielded forever.

The last thing I tried was doing all of this on the client from a local script inside of ReplicatedFirst. However, the map still wasnt fully loaded and the first couple of players that join fall through the ground.

I dont know if this issue stems from me testing in studio or whether im using preloaded async incorrectly.

Ultimately a 15 second wait time for the 1st couple of players that join worked for the most part however my brain tells me this isnt something I should do. I have no idea why but I feel like relying on a wait shouldnt be done. I dont know where it came from (maybe because it feels too easy of a solution?)

Overall, if you have a better way of handling this or a fix for this issue, please let me know.

First of all, you don’t preload anything on the server. You’re supposed to be doing that on the client. The entire game is loaded and ready to be replicated to the client before server scripts even execute. The server itself doesn’t “download” any content assets.

Second, the purpose of preloading is to load assets that are immediately available to the client. It should not be used to preload an entire map. Assets load as the player plays along the way. Wait time should be as minimal as possible so the player doesn’t just leave from the long loading times. If you do want to preload, use it on the client and consider adding a skip button after x amount of seconds.

Thirdly, if you are using StreamingEnabled (which is on by default), with a huge map like yours, there is no point in preloading the entire map considering only parts of the map will be rendered to the player anyways (depending on their device specifications).

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.