123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300 |
- .. _doc_background_loading:
- Background loading
- ==================
- When switching the main scene of your game (for example going to a new
- level), you might want to show a loading screen with some indication
- that progress is being made. The main load method
- (``ResourceLoader::load`` or just ``load`` from gdscript) blocks your
- thread while the resource is being loaded, so It's not good. This
- document discusses the ``ResourceInteractiveLoader`` class for smoother
- load screens.
- ResourceInteractiveLoader
- -------------------------
- The ``ResourceInteractiveLoader`` class allows you to load a resource in
- stages. Every time the method ``poll`` is called, a new stage is loaded,
- and control is returned to the caller. Each stage is generally a
- sub-resource that is loaded by the main resource. For example, if you're
- loading a scene that loads 10 images, each image will be one stage.
- Usage
- -----
- Usage is generally as follows
- Obtaining a ResourceInteractiveLoader
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ::
- Ref<ResourceInteractiveLoader> ResourceLoader::load_interactive(String p_path);
- This method will give you a ResourceInteractiveLoader that you will use
- to manage the load operation.
- Polling
- ~~~~~~~
- ::
- Error ResourceInteractiveLoader::poll();
- Use this method to advance the progress of the load. Each call to
- ``poll`` will load the next stage of your resource. Keep in mind that
- each stage is one entire "atomic" resource, such as an image, or a mesh,
- so it will take several frames to load.
- Returns ``OK`` on no errors, ``ERR_FILE_EOF`` when loading is finished.
- Any other return value means there was an error and loading has stopped.
- Load progress (optional)
- ~~~~~~~~~~~~~~~~~~~~~~~~
- To query the progress of the load, use the following methods:
- ::
- int ResourceInteractiveLoader::get_stage_count() const;
- int ResourceInteractiveLoader::get_stage() const;
- ``get_stage_count`` returns the total number of stages to load.
- ``get_stage`` returns the current stage being loaded.
- Forcing completion (optional)
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- ::
- Error ResourceInteractiveLoader::wait();
- Use this method if you need to load the entire resource in the current
- frame, without any more steps.
- Obtaining the resource
- ~~~~~~~~~~~~~~~~~~~~~~
- ::
- Ref<Resource> ResourceInteractiveLoader::get_resource();
- If everything goes well, use this method to retrieve your loaded
- resource.
- Example
- -------
- This example demostrates how to load a new scene. Consider it in the
- context of the :ref:`doc_singletons_autoload` example.
- First we setup some variables and initialize the ``current_scene``
- with the main scene of the game:
- ::
- var loader
- var wait_frames
- var time_max = 100 # msec
- var current_scene
- func _ready():
- var root = get_tree().get_root()
- current_scene = root.get_child(root.get_child_count() -1)
- The function ``goto_scene`` is called from the game when the scene
- needs to be switched. It requests an interactive loader, and calls
- ``set_progress(true)`` to start polling the loader in the ``_progress``
- callback. It also starts a "loading" animation, which can show a
- progress bar or loading screen, etc.
- ::
- func goto_scene(path): # game requests to switch to this scene
- loader = ResourceLoader.load_interactive(path)
- if loader == null: # check for errors
- show_error()
- return
- set_process(true)
- current_scene.queue_free() # get rid of the old scene
- # start your "loading..." animation
- get_node("animation").play("loading")
- wait_frames = 1
- ``_process`` is where the loader is polled. ``poll`` is called, and then
- we deal with the return value from that call. ``OK`` means keep polling,
- ``ERR_FILE_EOF`` means load is done, anything else means there was an
- error. Also note we skip one frame (via ``wait_frames``, set on the
- ``goto_scene`` function) to allow the loading screen to show up.
- Note how use use ``OS.get_ticks_msec`` to control how long we block the
- thread. Some stages might load really fast, which means we might be able
- to cram more than one call to ``poll`` in one frame, some might take way
- more than your value for ``time_max``, so keep in mind we won't have
- precise control over the timings.
- ::
- func _process(time):
- if loader == null:
- # no need to process anymore
- set_process(false)
- return
- if wait_frames > 0: # wait for frames to let the "loading" animation to show up
- wait_frames -= 1
- return
- var t = OS.get_ticks_msec()
- while OS.get_ticks_msec() < t + time_max: # use "time_max" to control how much time we block this thread
- # poll your loader
- var err = loader.poll()
- if err == ERR_FILE_EOF: # load finished
- var resource = loader.get_resource()
- loader = null
- set_new_scene(resource)
- break
- elif err == OK:
- update_progress()
- else: # error during loading
- show_error()
- loader = null
- break
- Some extra helper functions. ``update_progress`` updates a progress bar,
- or can also update a paused animation (the animation represents the
- entire load process from beginning to end). ``set_new_scene`` puts the
- newly loaded scene on the tree. Because it's a scene being loaded,
- ``instance()`` needs to be called on the resource obtained from the
- loader.
- ::
- func update_progress():
- var progress = float(loader.get_stage()) / loader.get_stage_count()
- # update your progress bar?
- get_node("progress").set_progress(progress)
- # or update a progress animation?
- var len = get_node("animation").get_current_animation_length()
- # call this on a paused animation. use "true" as the second parameter to force the animation to update
- get_node("animation").seek(progress * len, true)
- func set_new_scene(scene_resource):
- current_scene = scene_resource.instance()
- get_node("/root").add_child(current_scene)
- Using multiple threads
- ----------------------
- ResourceInteractiveLoader can be used from multiple threads. A couple of
- things to keep in mind if you attempt it:
- Use a Semaphore
- ~~~~~~~~~~~~~~~
- While your thread waits for the main thread to request a new resource,
- use a Semaphore to sleep (instead of a busy loop or anything similar).
- Not blocking main thread during the polling
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- If you have a mutex to allow calls from the main thread to your loader
- class, don't lock it while you call ``poll`` on the loader. When a
- resource is finished loading, it might require some resources from the
- low level APIs (VisualServer, etc), which might need to lock the main
- thread to acquire them. This might cause a deadlock if the main thread
- is waiting for your mutex while your thread is waiting to load a
- resource.
- Example class
- -------------
- You can find an example class for loading resources in threads here:
- :download:`resource_queue.gd </files/resource_queue.gd>`. Usage is as follows:
- ::
- func start()
- Call after you instance the class to start the thread.
- ::
- func queue_resource(path, p_in_front = false)
- Queue a resource. Use optional parameter "p_in_front" to put it in
- front of the queue.
- ::
- func cancel_resource(path)
- Remove a resource from the queue, discarding any loading done.
- ::
- func is_ready(path)
- Returns true if a resource is done loading and ready to be retrieved.
- ::
- func get_progress(path)
- Get the progress of a resource. Returns -1 on error (for example if the
- resource is not on the queue), or a number between 0.0 and 1.0 with the
- progress of the load. Use mostly for cosmetic purposes (updating
- progress bars, etc), use ``is_ready`` to find out if a resource is
- actually ready.
- ::
- func get_resource(path)
- Returns the fully loaded resource, or null on error. If the resource is
- not done loading (``is_ready`` returns false), it will block your thread
- and finish the load. If the resource is not on the queue, it will call
- ``ResourceLoader::load`` to load it normally and return it.
- Example:
- ~~~~~~~~
- ::
- # initialize
- queue = preload("res://resource_queue.gd").new()
- queue.start()
- # suppose your game starts with a 10 second custscene, during which the user can't interact with the game.
- # For that time we know they won't use the pause menu, so we can queue it to load during the cutscene:
- queue.queue_resource("res://pause_menu.xml")
- start_curscene()
- # later when the user presses the pause button for the first time:
- pause_menu = queue.get_resource("res://pause_menu.xml").instance()
- pause_menu.show()
- # when you need a new scene:
- queue.queue_resource("res://level_1.xml", true) # use "true" as the second parameter to put it at the front
- # of the queue, pausing the load of any other resource
- # to check progress
- if queue.is_ready("res://level_1.xml"):
- show_new_level(queue.get_resource("res://level_1.xml"))
- else:
- update_progress(queue.get_progress("res://level_1.xml"))
- # when the user walks away from the trigger zone in your Metroidvania game:
- queue.cancel_resource("res://zone_2.xml")
- **Note**: this code in its current form is not tested in real world
- scenarios. Ask punto on IRC (#godotengine on irc.freenode.net) for help.
|