ImmersiveRPG / GodotAsyncLoader

A Godot plugin to load, instance, and add scenes asynchronously using a background thread.
https://godotengine.org/asset-library/asset/1376
MIT License
37 stars 4 forks source link

Get some lag spikes while loading ? #19

Open Celthim opened 1 year ago

Celthim commented 1 year ago

Hi ! I searched for a plugin like yours because having my scene loading at start makes my game a bit long to load so I wanted to delay some loading while the player is in the menus. I managed to get the plugin to work even if I'm in 2D, but for some reasons I still get little lag spikes when it loads.

I don't know exactly what could cause this,

Here is basically the code I'm using (I removed all "position" related args everywhere as I don't need them and it was not compatible with 2D)

Init :

const GROUPS := [
    "Foreground",
    "UI",
    "Background",
]
const SLEEP_MSEC := 100

func _init():
    SceneAdder._sleep_msec = 100
    SceneAdder.set_groups(GROUPS)

async calls :

func async_load(path,type,to_assign=null):
    yield(get_tree(),"idle_frame")
    var target

    match type:
        "canvas":
            target = canvas_layer
        "background":
            target = background
        "foreground":
            target = foreground
        "middle":
            target = middle

    if to_assign == null:
        SceneLoader.load_scene_async(target, path)
    else:
        SceneLoader.load_scene_async_with_cb(target, path, funcref(self, "handle_load_callback"), {"type":type,"to_assign":to_assign})

Callback :

func handle_load_callback(path : String, instance : Node, data : Dictionary) -> void:
    propagate_call("add_"+data.type,[instance])

    match data.to_assign:
        "fight_scene":
            fight_scene = instance
        "codex":
            codex = instance

https://user-images.githubusercontent.com/34304819/220255250-029da988-b688-4a82-bc38-1b830d1c161a.mp4

workhorsy commented 1 year ago

Hello @Celthim . The problem of stuttering is usually caused by shader compilation, or from blocking code. Shader compilation happens the first time something is shown on the screen. If you start the game, load the menu, unload the menu, then reload the menu does it still stutter? The second time you load it, it should not stutter, as the shaders are cached.

There are a few ways to get around this:

  1. Try changing shader compilation mode to Asynchronous: Project -> Project Settings -> General -> Rendering -> GLES3 -> Shaders -> Shader Compilation Mode
  2. At program start, quickly show everything on screen
  3. I'm working on a plugin to do this. It isn't quite ready yet. But you can try it if you want: https://github.com/ImmersiveRPG/GodotPreShaderCache

Also note that if you use Godot 4.0, it uses Vulkan instead of OpenGL by default. Apparently Vulkan automatically compiles shaders async. So it does not suffer from this problem. I have no idea if it works yet though, as I haven't gotten it to work well enough to test.

Celthim commented 1 year ago

Thanks for your response !

I tried to change the Shader compilation mode to asynchronous, but it didn't changed. I'm not sure that shaders are the problem here as I don't have any heavy shader or anything atm.

Help me understand a little bit more, you're saying that stuttering is caused by blocking code. I saw your video explaining that loading scenes is blocking code. My goal was by delegating the loading to another thread, that the main thread won't be blocked at all. Is this a misunderstanding from me ?

To detail what happens :

What you see on the capture is a menu that appears as a main menu (it will be more of a main menu later) What I'm doing is that while the player is in that menu, I load the main scene in the background using your plugin (I tried things myself but your work seems to have better implementation). The main scene is a bit heavy and cause some loading time, that's the whole reason why I want to delegate it to another thread.

So I tried to show only the menu itself, without loading anything from the main scene and there's no stuttering at all. What I get from that is that the main thread still gets blocked from the scene loading operation is that right ?

Wich would mean that I would have to decompose the whole main scene bit by bit and load everything using the plugin ? But if I do so it will add a lot of complexity to the use of everything, as I will no longer will be able to gets my subscenes with the $Scene and all.

I'm currently on Godot 3.5, I'm using Spine wich wait for a stable release to gets any upgrade

Thanks !

Edit :

I talked to the community on the Godot discord. I got the confirmation that loading it shouldn't block the main thread. However the main lead of what's happening is that adding scenes to the tree is also a blocking operation that Godot don't thread cause it's considered unsafe if I understood well.

I'll try maybe to add the scene to the tree at the last moment, delaying the lag spike to when the player clicks on the button. If that doesn't work, the alternative solution would be to decompose my main scene into chunks to be loaded bit by bit but as I said earlier it complicate all the signals connexions and variable access so that would be in last resort.

If you have other leads or anything else to add I'm all ears !

workhorsy commented 1 year ago

Sorry I didn't comment earlier. I'm having some terrible real world stuff happening, and didn't have time to read this. It is hard to figure out what is causing the stuttering problem, without seeing the code. Adding things to the scene tree does indeed block the main thread. I've never used Spine, so I'm not sure how that plays into things. Let me know if you have any luck with getting it to work.