Background Loading Stalls/Stuck

Godot Version

Godot 4.4

Question

After updating my game from 4.3 to 4.4, LoadThreadRequest occassionally stalls while trying to load a large scene. I’ve had it get stuck at 50%, 83%, etc…

However, it occasionally just works, which makes this whole situation a lot more complicated to debug. This doesn’t seem to happen on small simple scenes.

Has this happened to anybody else after updating to 4.4 and does anybody know a fix?

This is my code if needed:

private void StartLoad(bool syncLoad)
{
	if(syncLoad)
	{
		waitingForSyncLoad = true;
		announcedSuccessfulLoad = false;
		syncLoadConfirmation = 0;
	}

	Error state = ResourceLoader.LoadThreadedRequest(incomingScenePath, "", useSubThread);
	
	if(state == Error.Ok)
	{
		loadingScreen.SetLoadingScreenStatus(true);
		GD.Print("=====================================================================");
		GD.Print("Attempting to load the following scene: " + incomingScenePath);
		GD.Print("=====================================================================");
		SetProcess(true);
	}
}

public override void _Process(double delta)
{
	ResourceLoader.ThreadLoadStatus status = ResourceLoader.LoadThreadedGetStatus(incomingScenePath, progress);
	if((int) status == 0 || (int) status == 2) // Load Fail.
	{
		GD.Print("=====================================================================");
		GD.Print("ERROR: Failed to load the following scene: " + incomingScenePath);
		GD.Print(status);
		GD.Print("=====================================================================");
		loadingScreen.SetLoadingScreenStatus(false);
		SetProcess(false);
		return;
	}
	else if((int) status == 1) // Loading...
	{
		GD.Print("LOADING: " + (float) progress[0] * 100f + "%");
	}
	else if((int) status == 3) // Load Success!
	{
		if(waitingForSyncLoad)
		{
			if(!announcedSuccessfulLoad)
			{
				Rpc("LoadConfirmation");
				announcedSuccessfulLoad = true;
				syncLoadConfirmation++;
				GD.Print("Load was succesful!");
				GD.Print("Waiting for all players to load...");
			}
			else
			{
				if(MultiplayerManager.instance.GetClientLobby().Count <= syncLoadConfirmation)
				{
					GD.Print("=====================================================================");
					GD.Print("Switching to the following scene: " + incomingScenePath);
					GD.Print("=====================================================================");
					PackedScene scene = (PackedScene)ResourceLoader.LoadThreadedGet(incomingScenePath);
					GetTree().ChangeSceneToPacked(scene);
					loadingScreen.SetLoadingScreenStatus(false);
					SetProcess(false);
				}
			}
		}
		else
		{
			GD.Print("=====================================================================");
			GD.Print("Switching to the following scene: " + incomingScenePath);
			GD.Print("=====================================================================");
			PackedScene scene = (PackedScene)ResourceLoader.LoadThreadedGet(incomingScenePath);
			GetTree().ChangeSceneToPacked(scene);
			loadingScreen.SetLoadingScreenStatus(false);
			SetProcess(false);
		}
	}
}

[Rpc(MultiplayerApi.RpcMode.AnyPeer, TransferMode = MultiplayerPeer.TransferModeEnum.Reliable)]
private void LoadConfirmation()
{
	syncLoadConfirmation++;
}

Yup. I’ve got the same issue - my loading screen was working fine in 4.3. Now it’s stuck at 50%.
I’m unable to reproduce it using Visual Studio Code + its debugger. Issue occurs only when the Game is launched from Editor.

using Godot;
using System;
using System.Collections;

public partial class CLoadingScreen : Control
{
	[Signal] public delegate void UnloadingFinishedEventHandler();
	[Signal] public delegate void SceneLoadedEventHandler();
	[Signal] public delegate void LoadingFinishedEventHandler();
	

	[Export] public string LevelToLoad;
	[Export] public float FadeOutLength = 0.1f;
	[Export] public float FadeInLength = 0.1f;
	[Export] public float FadeLengthMul = 10.0f;

	public enum Mode
	{
		Fade,
		Overlap,
	};
	[Export] public Mode LoadingMode = Mode.Fade;

	enum State
	{
		None,
		FadeOut,
		Processing,
		Loading,
	};

	State m_State = State.None;
	Label m_PercentLabel;
	Godot.Collections.Array m_PercentProgress = new Godot.Collections.Array();

	float m_LoadingScreenOpacity = 0.0f;
	float m_ScreenWidth = 0;
	string m_CurrentlyLoadedLevel;

	// Called when the node enters the scene tree for the first time.
	public override void _Ready()
	{
		m_PercentLabel = GetNode<Label>("Percent");
		m_ScreenWidth = Size.X;
		SetOpacity(0.0f);

		m_State = State.FadeOut;
		m_CurrentlyLoadedLevel = GetTree().CurrentScene.Name;
		ResourceLoader.LoadThreadedRequest(LevelToLoad);
		GD.Print("Loading " + LevelToLoad);
	}

	// Called every frame. 'delta' is the elapsed time since the previous frame.
	public override void _Process(double delta)
	{
		switch (m_State)
		{
			case State.None:
			{
				GD.Print("None");
				m_LoadingScreenOpacity = 0.0f;
				break;
			}
			case State.FadeOut:
			{
				GD.Print("FadeOut " + m_LoadingScreenOpacity);
				if (FadeOutLength > 0.0f)
				{
					m_LoadingScreenOpacity += (float) delta / (FadeOutLength * FadeLengthMul);
				}
				else
				{
					m_LoadingScreenOpacity = 1.0f;
				}

				if (m_LoadingScreenOpacity >= 1.0f)
				{
					if (GetTree().CurrentScene != null)
						GetTree().CurrentScene.QueueFree();

					m_State = State.Processing;
					EmitSignal(SignalName.UnloadingFinished);
					if (CDebug.DbgLoadingScreen)
						GD.Print("Unloading " + m_CurrentlyLoadedLevel + " completed " );
				}
				break;
			}
			case State.Processing:
			{
				ResourceLoader.ThreadLoadStatus status = ResourceLoader.LoadThreadedGetStatus(LevelToLoad, m_PercentProgress);
				GD.Print("Processing " + (float)m_PercentProgress[0] * 100 + "%");

				m_PercentLabel.Text = (float)m_PercentProgress[0] * 100 + "%";

				switch (status)
				{
					case ResourceLoader.ThreadLoadStatus.Loaded:
						m_State = State.Loading;
						GetTree().ChangeSceneToPacked(ResourceLoader.LoadThreadedGet(LevelToLoad) as PackedScene);
						EmitSignal(SignalName.SceneLoaded);
						if (CDebug.DbgLoadingScreen)
							GD.Print("Loaded " + LevelToLoad);
						break;
					default:
						break;
				}
				break;
			}
			case State.Loading:
			{
				GD.Print("FadeOut " + m_LoadingScreenOpacity);
				if (FadeInLength > 0.0f)
				{
					m_LoadingScreenOpacity -= (float) delta / (FadeInLength * FadeLengthMul);
				}
				else
				{
					m_LoadingScreenOpacity = 0.0f;
				}
				
				if (m_LoadingScreenOpacity <= 0.0f)
				{
					m_State = State.None;
					if (CDebug.DbgLoadingScreen)
						GD.Print("Loading " + LevelToLoad + " completed");
					EmitSignal(SignalName.LoadingFinished);
				}
				break;
			}
		}

		SetOpacity(m_LoadingScreenOpacity);
	}

	public void SetOpacity(float opacity)
	{
		opacity = Mathf.Clamp(opacity, 0.0f, 1.0f);

		switch (LoadingMode)
		{
			case Mode.Fade:
			{
				if (opacity == 0.0f)
				{
					Visible = false;
				}
				else 
				{
					Visible = true;

					Color clr = new Color(1.0f, 1.0f, 1.0f, opacity);
					Modulate = clr;
				}
				break;
			}
			case Mode.Overlap:
			{
				Vector2 p = Position;
				p.X = -m_ScreenWidth * (1.0f - opacity);
				Position = p;
				break;
			}
		}
	}
}

1 Like

Same issue on 4.4. It occasionally works but I can’t discern why. Almost always hangs at 50%.