background_loading.rst 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. .. _doc_background_loading:
  2. Background loading
  3. ==================
  4. When switching the main scene of your game (e.g. going to a new
  5. level), you might want to show a loading screen with some indication
  6. that progress is being made. The main load method
  7. (``ResourceLoader::load`` or just ``load`` from GDScript) blocks your
  8. thread, making your game appear frozen and unresponsive while the resource is being loaded. This
  9. document discusses the alternative of using the ``ResourceInteractiveLoader`` class for smoother
  10. load screens.
  11. ResourceInteractiveLoader
  12. -------------------------
  13. The ``ResourceInteractiveLoader`` class allows you to load a resource in
  14. stages. Every time the method ``poll`` is called, a new stage is loaded,
  15. and control is returned to the caller. Each stage is generally a
  16. sub-resource that is loaded by the main resource. For example, if you're
  17. loading a scene that loads 10 images, each image will be one stage.
  18. Usage
  19. -----
  20. Usage is generally as follows
  21. Obtaining a ResourceInteractiveLoader
  22. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  23. ::
  24. Ref<ResourceInteractiveLoader> ResourceLoader::load_interactive(String p_path);
  25. This method will give you a ResourceInteractiveLoader that you will use
  26. to manage the load operation.
  27. Polling
  28. ~~~~~~~
  29. ::
  30. Error ResourceInteractiveLoader::poll();
  31. Use this method to advance the progress of the load. Each call to
  32. ``poll`` will load the next stage of your resource. Keep in mind that
  33. each stage is one entire "atomic" resource, such as an image, or a mesh,
  34. so it will take several frames to load.
  35. Returns ``OK`` on no errors, ``ERR_FILE_EOF`` when loading is finished.
  36. Any other return value means there was an error and loading has stopped.
  37. Load progress (optional)
  38. ~~~~~~~~~~~~~~~~~~~~~~~~
  39. To query the progress of the load, use the following methods:
  40. ::
  41. int ResourceInteractiveLoader::get_stage_count() const;
  42. int ResourceInteractiveLoader::get_stage() const;
  43. ``get_stage_count`` returns the total number of stages to load.
  44. ``get_stage`` returns the current stage being loaded.
  45. Forcing completion (optional)
  46. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  47. ::
  48. Error ResourceInteractiveLoader::wait();
  49. Use this method if you need to load the entire resource in the current
  50. frame, without any more steps.
  51. Obtaining the resource
  52. ~~~~~~~~~~~~~~~~~~~~~~
  53. ::
  54. Ref<Resource> ResourceInteractiveLoader::get_resource();
  55. If everything goes well, use this method to retrieve your loaded
  56. resource.
  57. Example
  58. -------
  59. This example demonstrates how to load a new scene. Consider it in the
  60. context of the :ref:`doc_singletons_autoload` example.
  61. First, we set up some variables and initialize the ``current_scene``
  62. with the main scene of the game:
  63. ::
  64. var loader
  65. var wait_frames
  66. var time_max = 100 # msec
  67. var current_scene
  68. func _ready():
  69. var root = get_tree().get_root()
  70. current_scene = root.get_child(root.get_child_count() -1)
  71. The function ``goto_scene`` is called from the game when the scene
  72. needs to be switched. It requests an interactive loader, and calls
  73. ``set_process(true)`` to start polling the loader in the ``_process``
  74. callback. It also starts a "loading" animation, which could show a
  75. progress bar or loading screen.
  76. ::
  77. func goto_scene(path): # game requests to switch to this scene
  78. loader = ResourceLoader.load_interactive(path)
  79. if loader == null: # check for errors
  80. show_error()
  81. return
  82. set_process(true)
  83. current_scene.queue_free() # get rid of the old scene
  84. # start your "loading..." animation
  85. get_node("animation").play("loading")
  86. wait_frames = 1
  87. ``_process`` is where the loader is polled. ``poll`` is called, and then
  88. we deal with the return value from that call. ``OK`` means keep polling,
  89. ``ERR_FILE_EOF`` means loading is done, anything else means there was an
  90. error. Also note we skip one frame (via ``wait_frames``, set on the
  91. ``goto_scene`` function) to allow the loading screen to show up.
  92. Note how we use ``OS.get_ticks_msec`` to control how long we block the
  93. thread. Some stages might load fast, which means we might be able
  94. to cram more than one call to ``poll`` in one frame; some might take way
  95. more than your value for ``time_max``, so keep in mind we won't have
  96. precise control over the timings.
  97. ::
  98. func _process(time):
  99. if loader == null:
  100. # no need to process anymore
  101. set_process(false)
  102. return
  103. if wait_frames > 0: # wait for frames to let the "loading" animation show up
  104. wait_frames -= 1
  105. return
  106. var t = OS.get_ticks_msec()
  107. while OS.get_ticks_msec() < t + time_max: # use "time_max" to control for how long we block this thread
  108. # poll your loader
  109. var err = loader.poll()
  110. if err == ERR_FILE_EOF: # Finished loading.
  111. var resource = loader.get_resource()
  112. loader = null
  113. set_new_scene(resource)
  114. break
  115. elif err == OK:
  116. update_progress()
  117. else: # error during loading
  118. show_error()
  119. loader = null
  120. break
  121. Some extra helper functions. ``update_progress`` updates a progress bar,
  122. or can also update a paused animation (the animation represents the
  123. entire load process from beginning to end). ``set_new_scene`` puts the
  124. newly loaded scene on the tree. Because it's a scene being loaded,
  125. ``instance()`` needs to be called on the resource obtained from the
  126. loader.
  127. ::
  128. func update_progress():
  129. var progress = float(loader.get_stage()) / loader.get_stage_count()
  130. # Update your progress bar?
  131. get_node("progress").set_progress(progress)
  132. # ... or update a progress animation?
  133. var length = get_node("animation").get_current_animation_length()
  134. # Call this on a paused animation. Use "true" as the second argument to force the animation to update.
  135. get_node("animation").seek(progress * length, true)
  136. func set_new_scene(scene_resource):
  137. current_scene = scene_resource.instance()
  138. get_node("/root").add_child(current_scene)
  139. Using multiple threads
  140. ----------------------
  141. ResourceInteractiveLoader can be used from multiple threads. A couple of
  142. things to keep in mind if you attempt it:
  143. Use a semaphore
  144. ~~~~~~~~~~~~~~~
  145. While your thread waits for the main thread to request a new resource,
  146. use a ``Semaphore`` to sleep (instead of a busy loop or anything similar).
  147. Not blocking main thread during the polling
  148. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  149. If you have a mutex to allow calls from the main thread to your loader
  150. class, don't lock the main thread while you call ``poll`` on your loader class. When a
  151. resource is done loading, it might require some resources from the
  152. low-level APIs (VisualServer, etc), which might need to lock the main
  153. thread to acquire them. This might cause a deadlock if the main thread
  154. is waiting for your mutex while your thread is waiting to load a
  155. resource.
  156. Example class
  157. -------------
  158. You can find an example class for loading resources in threads here:
  159. :download:`resource_queue.gd <files/resource_queue.gd>`. Usage is as follows:
  160. ::
  161. func start()
  162. Call after you instance the class to start the thread.
  163. ::
  164. func queue_resource(path, p_in_front = false)
  165. Queue a resource. Use optional argument "p_in_front" to put it in
  166. front of the queue.
  167. ::
  168. func cancel_resource(path)
  169. Remove a resource from the queue, discarding any loading done.
  170. ::
  171. func is_ready(path)
  172. Returns true if a resource is fully loaded and ready to be retrieved.
  173. ::
  174. func get_progress(path)
  175. Get the progress of a resource. Returns -1 if there was an error (for example if the
  176. resource is not in the queue), or a number between 0.0 and 1.0 with the
  177. progress of the load. Use mostly for cosmetic purposes (updating
  178. progress bars, etc), use ``is_ready`` to find out if a resource is
  179. actually ready.
  180. ::
  181. func get_resource(path)
  182. Returns the fully loaded resource, or null on error. If the resource is
  183. not fully loaded (``is_ready`` returns false), it will block your thread
  184. and finish the load. If the resource is not on the queue, it will call
  185. ``ResourceLoader::load`` to load it normally and return it.
  186. Example:
  187. ~~~~~~~~
  188. ::
  189. # Initialize.
  190. queue = preload("res://resource_queue.gd").new()
  191. queue.start()
  192. # Suppose your game starts with a 10 second cutscene, during which the user can't interact with the game.
  193. # For that time, we know they won't use the pause menu, so we can queue it to load during the cutscene:
  194. queue.queue_resource("res://pause_menu.tres")
  195. start_cutscene()
  196. # Later, when the user presses the pause button for the first time:
  197. pause_menu = queue.get_resource("res://pause_menu.tres").instance()
  198. pause_menu.show()
  199. # when you need a new scene:
  200. queue.queue_resource("res://level_1.tscn", true) # Use "true" as the second argument to put it at the front
  201. # of the queue, pausing the load of any other resource.
  202. # to check progress
  203. if queue.is_ready("res://level_1.tscn"):
  204. show_new_level(queue.get_resource("res://level_1.tscn"))
  205. else:
  206. update_progress(queue.get_process("res://level_1.tscn"))
  207. # when the user walks away from the trigger zone in your Metroidvania game:
  208. queue.cancel_resource("res://zone_2.tscn")
  209. **Note**: this code, in its current form, is not tested in real world
  210. scenarios. Ask punto on IRC (#godotengine on irc.freenode.net) for help.