background_loading.rst 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. .. _doc_background_loading:
  2. Background loading
  3. ==================
  4. When switching the main scene of your game (for example 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 while the resource is being loaded, so It's not good. This
  9. document discusses 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 demostrates how to load a new scene. Consider it in the
  60. context of the :ref:`doc_singletons_autoload` example.
  61. First we setup 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_progress(true)`` to start polling the loader in the ``_progress``
  74. callback. It also starts a "loading" animation, which can show a
  75. progress bar or loading screen, etc.
  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 load 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 use use ``OS.get_ticks_msec`` to control how long we block the
  93. thread. Some stages might load really 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 to 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 how much time we block this thread
  108. # poll your loader
  109. var err = loader.poll()
  110. if err == ERR_FILE_EOF: # load finished
  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 len = get_node("animation").get_current_animation_length()
  134. # call this on a paused animation. use "true" as the second parameter to force the animation to update
  135. get_node("animation").seek(progress * len, 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 it while you call ``poll`` on the loader. When a
  151. resource is finished 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 parameter "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 done loading and ready to be retrieved.
  173. ::
  174. func get_progress(path)
  175. Get the progress of a resource. Returns -1 on error (for example if the
  176. resource is not on 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 done loading (``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 custscene, 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.xml")
  195. start_curscene()
  196. # later when the user presses the pause button for the first time:
  197. pause_menu = queue.get_resource("res://pause_menu.xml").instance()
  198. pause_menu.show()
  199. # when you need a new scene:
  200. queue.queue_resource("res://level_1.xml", true) # use "true" as the second parameter 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.xml"):
  204. show_new_level(queue.get_resource("res://level_1.xml"))
  205. else:
  206. update_progress(queue.get_progress("res://level_1.xml"))
  207. # when the user walks away from the trigger zone in your Metroidvania game:
  208. queue.cancel_resource("res://zone_2.xml")
  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.