|
@@ -1,5 +1,3 @@
|
|
|
-:article_outdated: True
|
|
|
-
|
|
|
.. _doc_background_loading:
|
|
|
|
|
|
Background loading
|
|
@@ -10,299 +8,80 @@ 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, making your game appear frozen and unresponsive while the resource is being loaded. This
|
|
|
-document discusses the alternative of using the ``ResourceInteractiveLoader`` class for smoother
|
|
|
+document discusses the alternative of using ``ResourceLoader``'s other methods 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.
|
|
|
-
|
|
|
+ResourceLoader
|
|
|
+--------------
|
|
|
Usage
|
|
|
-----
|
|
|
|
|
|
Usage is generally as follows
|
|
|
|
|
|
-Obtaining a ResourceInteractiveLoader
|
|
|
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
-
|
|
|
-.. code-block:: cpp
|
|
|
-
|
|
|
- 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
|
|
|
-~~~~~~~
|
|
|
-
|
|
|
-.. code-block:: cpp
|
|
|
-
|
|
|
- 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.
|
|
|
+Start a load request
|
|
|
+~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
-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.
|
|
|
+.. code-block:: gdscript
|
|
|
|
|
|
-Load progress (optional)
|
|
|
-~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
+ func ResourceLoader.load_threaded_request(path: String, type_hint: String = "", use_sub_threads: bool = false, cache_mode: int = 1) -> int
|
|
|
|
|
|
-To query the progress of the load, use the following methods:
|
|
|
+This method will start to load the resource using thread(s).
|
|
|
|
|
|
-.. code-block:: cpp
|
|
|
-
|
|
|
- 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)
|
|
|
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
+Checking Status
|
|
|
+~~~~~~~~~~~~~~~
|
|
|
|
|
|
-.. code-block:: cpp
|
|
|
+.. code-block:: gdscript
|
|
|
|
|
|
- Error ResourceInteractiveLoader::wait();
|
|
|
+ func ResourceLoader.load_threaded_get_status(path: String, progress: Array = []) -> int
|
|
|
|
|
|
-Use this method if you need to load the entire resource in the current
|
|
|
-frame, without any more steps.
|
|
|
+Returns ``ResourceLoader.THREAD_LOAD_FAILED``, ``ResourceLoader.THREAD_LOAD_IN_PROGRESS``,
|
|
|
+``ResourceLoader.THREAD_LOAD_INVALID_RESOURCE``, or ``ResourceLoader.THREAD_LOAD_LOADED``.
|
|
|
+The progress can be obtained by passing an array variable via progress which will return a one element array containing the percentage.
|
|
|
|
|
|
-Obtaining the resource
|
|
|
-~~~~~~~~~~~~~~~~~~~~~~
|
|
|
+Getting the resource (forces completion)
|
|
|
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
-.. code-block:: cpp
|
|
|
+.. code-block:: gdscript
|
|
|
|
|
|
- Ref<Resource> ResourceInteractiveLoader::get_resource();
|
|
|
+ func ResourceLoader.load_threaded_get(path: String) -> Resource
|
|
|
|
|
|
-If everything goes well, use this method to retrieve your loaded
|
|
|
-resource.
|
|
|
+To obtain the resource call this method. This will wait (block) until it can return the resource.
|
|
|
|
|
|
Example
|
|
|
-------
|
|
|
|
|
|
-This example demonstrates how to load a new scene. Consider it in the
|
|
|
-context of the :ref:`doc_singletons_autoload` example.
|
|
|
-
|
|
|
-First, we set up 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_process(true)`` to start polling the loader in the ``_process``
|
|
|
-callback. It also starts a "loading" animation, which could show a
|
|
|
-progress bar or loading screen.
|
|
|
-
|
|
|
-::
|
|
|
-
|
|
|
- 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 loading 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 we use ``OS.get_ticks_msec`` to control how long we block the
|
|
|
-thread. Some stages might load 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
|
|
|
-
|
|
|
- # Wait for frames to let the "loading" animation show up.
|
|
|
- if wait_frames > 0:
|
|
|
- wait_frames -= 1
|
|
|
- return
|
|
|
-
|
|
|
- var t = OS.get_ticks_msec()
|
|
|
- # Use "time_max" to control for how long we block this thread.
|
|
|
- while OS.get_ticks_msec() < t + time_max:
|
|
|
- # Poll your loader.
|
|
|
- var err = loader.poll()
|
|
|
-
|
|
|
- if err == ERR_FILE_EOF: # Finished loading.
|
|
|
- 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 length = get_node("animation").get_current_animation_length()
|
|
|
-
|
|
|
- # Call this on a paused animation. Use "true" as the second argument to
|
|
|
- # force the animation to update.
|
|
|
- get_node("animation").seek(progress * length, true)
|
|
|
-
|
|
|
-
|
|
|
- func set_new_scene(scene_resource):
|
|
|
- current_scene = scene_resource.instantiate()
|
|
|
- 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 the main thread while you call ``poll`` on your loader class. When a
|
|
|
-resource is done loading, it might require some resources from the
|
|
|
-low-level APIs (RenderingServer, 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 argument "p_in_front" to put it in
|
|
|
-front of the queue.
|
|
|
-
|
|
|
-::
|
|
|
+This example demonstrates how to load a scene in the background.
|
|
|
+In this example we will have a button spawn a enemy when pressed.
|
|
|
+The enemy will be ``Enemy.tscn`` which we will load on ``_ready`` and instantiate when pressed.
|
|
|
+The path will be ``"Enemy.tscn"`` which is located at ``res://Enemy.tscn``.
|
|
|
|
|
|
- func cancel_resource(path)
|
|
|
-
|
|
|
-Remove a resource from the queue, discarding any loading done.
|
|
|
+First, we will start a request to load the resource and connect the button:
|
|
|
|
|
|
::
|
|
|
|
|
|
- func is_ready(path)
|
|
|
-
|
|
|
-Returns ``true`` if a resource is fully loaded and ready to be retrieved.
|
|
|
+ # Define the path of the enemy
|
|
|
+ const ENEMY_SCENE_PATH : String = "Enemy.tscn"
|
|
|
+ # It isn't necessary to use constants but it can make it easier if the resource is moved around.
|
|
|
|
|
|
-::
|
|
|
+ func _ready():
|
|
|
+ # Start the request for Enemy.tscn , this will not block
|
|
|
+ ResourceLoader.load_threaded_request(ENEMY_SCENE_PATH)
|
|
|
+ # Connect the button to our method on_button_pressed
|
|
|
+ self.pressed.connect(on_button_pressed)
|
|
|
|
|
|
- func get_progress(path)
|
|
|
-
|
|
|
-Get the progress of a resource. Returns -1 if there was an error (for example if the
|
|
|
-resource is not in 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.
|
|
|
+Now on_button_pressed will be called when the button is pressed.
|
|
|
+This method will be used to spawn an enemy.
|
|
|
|
|
|
::
|
|
|
|
|
|
- func get_resource(path)
|
|
|
-
|
|
|
-Returns the fully loaded resource, or ``null`` on error. If the resource is
|
|
|
-not fully loaded (``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 cutscene, 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.tres")
|
|
|
- start_cutscene()
|
|
|
-
|
|
|
- # Later, when the user presses the pause button for the first time:
|
|
|
- pause_menu = queue.get_resource("res://pause_menu.tres").instantiate()
|
|
|
- pause_menu.show()
|
|
|
-
|
|
|
- # When you need a new scene:
|
|
|
- queue.queue_resource("res://level_1.tscn", true)
|
|
|
- # Use "true" as the second argument 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.tscn"):
|
|
|
- show_new_level(queue.get_resource("res://level_1.tscn"))
|
|
|
- else:
|
|
|
- update_progress(queue.get_progress("res://level_1.tscn"))
|
|
|
-
|
|
|
- # When the user walks away from the trigger zone in your Metroidvania game:
|
|
|
- queue.cancel_resource("res://zone_2.tscn")
|
|
|
+ func on_button_pressed(): # Button was pressed
|
|
|
+ # Obtain the resource now that we need it
|
|
|
+ var enemy_scene = ResourceLoader.load_threaded_get(ENEMY_SCENE_PATH)
|
|
|
+ # Instantiate the enemy scene
|
|
|
+ var enemy = enemy_scene.instantiate()
|
|
|
+ # Add it to the current scene
|
|
|
+ add_child(enemy)
|
|
|
+
|
|
|
|
|
|
**Note**: this code, in its current form, is not tested in real world
|
|
|
scenarios. If you run into any issues, ask for help in one of
|