ContentProvider:PreloadAsync() not working?

Do you have StreamingEnabled turned on?

PreloadAsync is meant to load images, sounds, probably meshes as well

StreamingEnabled = false
It’s quite a small map (3000 parts)

are you using meshparts or the roblox parts?

If you are using the roblox parts, they are already stored inside the roblox files, so they don’t have to be loaded. It might be that the client is just not rendering it perhaps?

mainly meshparts
normal parts as well but mainly meshparts

Try loading each asset individually.

local assets = workspace:GetDescendants()
for i = 1, #assets do
    local asset = assets[i]
    ContentProvider:PreloadAsync({asset})
end
1 Like

The solution to this issue is more complicated than what the previous answers elude to, and has nothing to do with PreloadAsync except timing.

The map loads into the workspace on the server, then replicated to the client. After you load the assets on the client, you need to wait for a remote event on the client to pause execution until the server fires that event after the map is loaded. Then when the GUI is destroyed, the map appears to be fully loaded.

Server

-- ******** Services
local replicatedStorage = game:GetService("ReplicatedStorage")

-- ******** Local Variables
local eventFolder = replicatedStorage:FindFirstChild("Events")
local eventLoading = eventFolder:FindFirstChild("LoadCharacter")
local eventMapLoad = eventFolder:FindFirstChild("MapLoaded")
local mapLoaded = false

-- ******** Events

-- Called when the client has finished loading assets and is ready to load
-- the player's character model.
eventLoading.OnServerEvent:Connect(function(player)
	while mapLoaded == false do
		task.wait(0.1)
	end
	eventMapLoad:FireClient(player, mapLoaded)
	player:LoadCharacter()
end)

Client

-- ******** Services
local replicatedFirst = game:GetService("ReplicatedFirst")
local replicatedStorage = game:GetService("ReplicatedStorage")
local playerService = game:GetService("Players")

-- ******** Local Variables
local mapLoaded = false
local localPlayer = playerService.LocalPlayer
local playerGui = localPlayer:WaitForChild("PlayerGui")
local loadingScreen = replicatedFirst:WaitForChild("LoadingScreenGui")
local eventFolder = replicatedStorage:WaitForChild("Events")
local eventLoading = eventFolder:WaitForChild("LoadCharacter")
local eventMap = eventFolder:WaitForChild("MapLoaded")

-- ******** Events

-- Called when the server completes loading the game map.
replicatedStorage.MapLoaded:Connect(function(loaded)
	mapLoaded = loaded
end)


-- ******** Run

local clientGui = loadingScreen:Clone()
clientGui.Parent = playerGui
replicatedFirst:RemoveDefaultLoadingScreen()

-- Perform PreloadAsync

eventLoading:FireServer()
while mapLoaded == false do
	task.wait(0.1)
end
clientGui:Destroy()

Something like that should be what you need. When PreloadAsync has completed, it will send a request to the server indicating that it’s ready to proceed. On the server, the response is held until the mapLoaded variable is true. mapLoaded is set to true when the code that loads the map is done. When that happens, all the clients that have signaled that they are done loading assets are then responded to. When the clients receive the new value, they remove the loading screen Gui.

For clients that connect afterwards, they proceed immediately since the server has long completed the map loading. If there’s other conditions to check to see if the clients can be loaded, those can also be checked for too. You could probably use a remote function for this, but I prefer using remote events. If you’re using Parallel LUA, on the server, you can have the busy-wait loop run in parallel mode so as to free up the main thread, but the script has to be running under an actor for that to work.

I’m confused as to what’s setting mapLoaded to true on the server? Seems like it will wait infinitely.

1 Like

I probably should have made that more clear. The routine that’s loading the map does that. It may be better to make that a global variable with the _G property so no matter what script your map building routines are in, it can be accessed.

Are saying that the problem is that the client has loaded which is why the ContentProvider:PreloadAsync stopped yielding. But the server hasn’t fully loaded? I don’t understand because I thought it loads to the client being replicated from the server so how is it possible that the client has loaded in without the server having loaded in?

More or less. PreloadAsync doesn’t load in the map to the client. That’s done through the normal replication process. PreloadAsync just loads in the assets that are used in the map (images/textures, sounds, and meshes). If the asset load completes before 1) the server has finished building the map, or 2) the map hasn’t been replicated to the client yet, when the preload is finished and the loading gui goes away, visually the map hasn’t finished loading in, from the client’s perspective. The issue is more apparent when the server is first spun up since during server startup, as a lot of things are going on at the same time.

In other words, PreloadAsync is the what as in what’s being used. Replication is the how as how those assets are being used.

Ideally, both the asset preload and the replication happen at the same time, but in practice it doesn’t. When a player first logs in, there’s a massive data dump from the server to the client. This includes scripts, assets, instances, etc… There are a number of causes. Mainly limited network bandwidth or network congestion. Server lag can also be an issue, as is client performance since the client has to process all that data.

Assuming they are not the first person to join the server and the server has already loaded:
Is the problem that the PreloadAsync() is finishing before the server → client replication process?

I thought game.Loaded:Wait() waits for the client replication process to complete:
I have

if not game:IsLoaded() then game.Loaded:Wait() end
G.ContentProvider:PreloadAsync({Icon, GameTitle})
G.ContentProvider:PreloadAsync, G.GetDescendantsOfClass(workspace.Map.MainMap, "MeshPart"))
LoadingScreen:Destroy()

Yet the client sometimes looks like this after the loading screen completes (alot hasn’t loaded in yet)

In my case, the server does other things after the map loads, so it’s still working on things so I have to hold the client at the loading screen until the server finishes.

I’m confused how you’re measureing when the server has finished

Tweak this script to make it work for your situation.

local ContentProvider = game:GetService("ContentProvider")
local Players = game:GetService("Players")
local loadingScreen = script:WaitForChild("LoadingScreen"):Clone()

repeat task.wait() until game:IsLoaded()

local assets = workspace:GetDescendants()
local maxAssets = #assets
local client = Players.LocalPlayer
local plrGui = client:WaitForChild("PlayerGui")
loadingScreen.Parent = plrGui

for i, assetToLoad in assets do
	ContentProvider:PreloadAsync({assetToLoad})
	loadingScreen:WaitForChild("Assets").Text = "Assets loaded: "..i.."/"..maxAssets
end

loadingScreen["Loading Text"].Text = "Successfully loaded assets!"
loadingScreen.Assets.Visible = false

task.wait(5)

loadingScreen:Remove()

Hope this helped!

There is no measurement. When the last startup task is finished and the game is ready to play, then it spawns all the players in game and sends the event to release the loading screen. In other words, the startup sequence broadly follows this:

  1. Core package initialization
  2. Game initialization
  3. Load players
  4. Play game

Of course, every game is different.

How is that any different to my script which isn’t working
You’re just using a for loop instead which if anything would make it slower

ContentProvider also tends to yield in my case forever (Happened to a lot of my players), I would just add an automatic skip after 60 seconds has passed, which in this case would most likely be after all of the map parts have already replicated/loaded.

Seems like a non-ideal work around rather than a solution
There has to be better way as Pet Simulator X loading screen is short and always works perfectly and is never 60 seconds long.

Well, it was my only solution to ContentProvider hanging forever, you can also just load specific classes if needed.

local ToLoad = {}
local CC = game:GetService("ContentProvider")
for _,v in workspace:GetDescendants() do
	if v:IsA("Sound") then
		table.insert(ToLoad, v)
		continue
	end
	if v:IsA("MeshPart") then
		table.insert(ToLoad, v)
		continue
	end
end
for _,v in ToLoad do
	CC:PreloadAsync({v})
end
-- Make sure to clear ToLoad after usage.

Tried only loading meshparts (that’s the only thing I want to load) but it doesn’t seem to make the situation any better