From c0126b33a6fc8e6b87515f83a68d2ae0fd06bb8e Mon Sep 17 00:00:00 2001 From: Mateo Kuruk Miccino Date: Fri, 10 Nov 2023 18:33:19 -0300 Subject: [PATCH] refactor: use new promises --- godot/.godot/global_script_class_cache.cfg | 12 -- .../decentraland_components/audio_source.gd | 2 +- godot/src/decentraland_components/avatar.gd | 2 +- .../decentraland_components/video_player.gd | 2 +- godot/src/logic/content_thread.gd | 12 +- godot/src/logic/realm.gd | 2 +- godot/src/logic/scene_fetcher.gd | 6 +- .../wearable_button/wearable_button.gd | 2 +- .../components/wearable_item/wearable_item.gd | 2 +- .../wearable_panel/wearable_panel.gd | 2 +- godot/src/utils/Awaiter.gd | 35 ----- godot/src/utils/Promise.gd | 133 +++++++++++++++++- godot/src/utils/PromiseError.gd | 14 -- 13 files changed, 144 insertions(+), 82 deletions(-) delete mode 100644 godot/src/utils/Awaiter.gd delete mode 100644 godot/src/utils/PromiseError.gd diff --git a/godot/.godot/global_script_class_cache.cfg b/godot/.godot/global_script_class_cache.cfg index 4a637aca8..8553224d0 100644 --- a/godot/.godot/global_script_class_cache.cfg +++ b/godot/.godot/global_script_class_cache.cfg @@ -17,12 +17,6 @@ list=Array[Dictionary]([{ "language": &"GDScript", "path": "res://src/tool/avatar_renderer/avatar_renderer_helper.gd" }, { -"base": &"RefCounted", -"class": &"Awaiter", -"icon": "", -"language": &"GDScript", -"path": "res://src/utils/Awaiter.gd" -}, { "base": &"Node", "class": &"Comms", "icon": "", @@ -71,12 +65,6 @@ list=Array[Dictionary]([{ "language": &"GDScript", "path": "res://src/utils/Promise.gd" }, { -"base": &"RefCounted", -"class": &"PromiseError", -"icon": "", -"language": &"GDScript", -"path": "res://src/utils/PromiseError.gd" -}, { "base": &"DclRealm", "class": &"Realm", "icon": "", diff --git a/godot/src/decentraland_components/audio_source.gd b/godot/src/decentraland_components/audio_source.gd index 8447e75fe..7b653385b 100644 --- a/godot/src/decentraland_components/audio_source.gd +++ b/godot/src/decentraland_components/audio_source.gd @@ -46,7 +46,7 @@ func _refresh_data(): last_loaded_audio_clip, content_mapping ) var res = await promise.co_awaiter() - if res is PromiseError: + if res is Promise.Error: self.stop() self.stream = null printerr("Error on fetch audio: ", res.get_error()) diff --git a/godot/src/decentraland_components/avatar.gd b/godot/src/decentraland_components/avatar.gd index 458b37691..70cbef02a 100644 --- a/godot/src/decentraland_components/avatar.gd +++ b/godot/src/decentraland_components/avatar.gd @@ -181,7 +181,7 @@ func fetch_wearables_dependencies(): for file_name in content_to_fetch: async_calls.push_back(_fetch_texture_or_gltf(file_name, content_mapping)) - await Awaiter.co_all(async_calls) + await Promise.co_all(async_calls) load_wearables() diff --git a/godot/src/decentraland_components/video_player.gd b/godot/src/decentraland_components/video_player.gd index 5bf22c741..cb045745e 100644 --- a/godot/src/decentraland_components/video_player.gd +++ b/godot/src/decentraland_components/video_player.gd @@ -13,7 +13,7 @@ func request_video(file_hash): var promise = Global.content_manager.fetch_video(file_hash, content_mapping) var res = await promise.co_awaiter() - if res is PromiseError: + if res is Promise.Error: printerr("Error on fetching video: ", res.get_error()) else: _on_video_loaded(file_hash) diff --git a/godot/src/logic/content_thread.gd b/godot/src/logic/content_thread.gd index a0cc09f07..0119b17b1 100644 --- a/godot/src/logic/content_thread.gd +++ b/godot/src/logic/content_thread.gd @@ -112,7 +112,7 @@ func _co_process_loading_wearable( ) var content_result = await promise.co_awaiter() - if content_result is PromiseError: + if content_result is Promise.Error: printerr("Failing on loading wearable ", url, " reason: ", content_result.get_error()) return @@ -184,7 +184,7 @@ func _co_process_loading_gltf(content: Dictionary, content_cache_map: Dictionary var request_promise = _http_requester.request_file(file_hash_path, absolute_file_path) var content_result = await request_promise.co_awaiter() - if content_result is PromiseError: + if content_result is Promise.Error: printerr( "Failing on loading gltf ", file_hash_path, " reason: ", content_result.get_error() ) @@ -227,7 +227,7 @@ func _co_process_loading_gltf(content: Dictionary, content_cache_map: Dictionary content["gltf_mappings"] = mappings - await Awaiter.co_all(promises_dependencies) + await Promise.co_all(promises_dependencies) # final processing var new_gltf := GLTFDocument.new() @@ -272,7 +272,7 @@ func _co_process_loading_texture( var promise: Promise = _http_requester.request_file(file_hash_path, absolute_file_path) var content_result = await promise.co_awaiter() - if content_result is PromiseError: + if content_result is Promise.Error: printerr( "Failing on loading gltf ", file_hash_path, " reason: ", content_result.get_error() ) @@ -319,7 +319,7 @@ func _co_process_loading_audio( var promise: Promise = _http_requester.request_file(file_hash_path, absolute_file_path) var content_result = await promise.co_awaiter() - if content_result is PromiseError: + if content_result is Promise.Error: printerr( "Failing on loading wearable ", file_hash_path, @@ -380,7 +380,7 @@ func _co_process_loading_video( base_url + file_hash, absolute_file_path ) var content_result = await promise.co_awaiter() - if content_result is PromiseError: + if content_result is Promise.Error: printerr( "Failing on loading wearable ", file_hash_path, diff --git a/godot/src/logic/realm.gd b/godot/src/logic/realm.gd index 53c276f30..334fc16b0 100644 --- a/godot/src/logic/realm.gd +++ b/godot/src/logic/realm.gd @@ -65,7 +65,7 @@ func set_realm(new_realm_string: String) -> void: ) var res = await promise.co_awaiter() - if res is PromiseError: + if res is Promise.Error: printerr( "Rejected request change realm to: ", new_realm_string, diff --git a/godot/src/logic/scene_fetcher.gd b/godot/src/logic/scene_fetcher.gd index a131d7218..99404d510 100644 --- a/godot/src/logic/scene_fetcher.gd +++ b/godot/src/logic/scene_fetcher.gd @@ -228,7 +228,7 @@ func load_scene(scene_entity_id: String, entity: Dictionary): ) var res = await promise.co_awaiter() - if res is PromiseError: + if res is Promise.Error: printerr( "Scene ", scene_entity_id, @@ -244,7 +244,7 @@ func load_scene(scene_entity_id: String, entity: Dictionary): local_main_js_path.replace("user:/", OS.get_user_data_dir()) ) var res = await promise.co_awaiter() - if res is PromiseError: + if res is Promise.Error: printerr( "Scene ", scene_entity_id, @@ -263,7 +263,7 @@ func load_scene(scene_entity_id: String, entity: Dictionary): ) var res = await promise.co_awaiter() - if res is PromiseError: + if res is Promise.Error: printerr( "Scene ", scene_entity_id, diff --git a/godot/src/ui/components/wearable_button/wearable_button.gd b/godot/src/ui/components/wearable_button/wearable_button.gd index 7e1ab68c2..a9e246b28 100644 --- a/godot/src/ui/components/wearable_button/wearable_button.gd +++ b/godot/src/ui/components/wearable_button/wearable_button.gd @@ -136,7 +136,7 @@ func set_wearable(wearable: Dictionary): } var promise = Global.content_manager.fetch_texture(wearable_thumbnail, content_mapping) var res = await promise.co_awaiter() - if res is PromiseError: + if res is Promise.Error: printerr("Fetch texture error on ", wearable_thumbnail) else: texture_rect_preview.texture = res diff --git a/godot/src/ui/components/wearable_item/wearable_item.gd b/godot/src/ui/components/wearable_item/wearable_item.gd index 589963ded..778ee4f6c 100644 --- a/godot/src/ui/components/wearable_item/wearable_item.gd +++ b/godot/src/ui/components/wearable_item/wearable_item.gd @@ -52,7 +52,7 @@ func set_wearable(wearable: Dictionary): wearable_thumbnail, content_mapping ) var res = await promise.co_awaiter() - if res is PromiseError: + if res is Promise.Error: printerr("Fetch texture error on ", wearable_thumbnail) else: texture_rect_preview.texture = res diff --git a/godot/src/ui/components/wearable_panel/wearable_panel.gd b/godot/src/ui/components/wearable_panel/wearable_panel.gd index cfc39ab74..c37f7648d 100644 --- a/godot/src/ui/components/wearable_panel/wearable_panel.gd +++ b/godot/src/ui/components/wearable_panel/wearable_panel.gd @@ -87,7 +87,7 @@ func set_wearable(wearable: Dictionary, _wearable_id: String): } var promise = Global.content_manager.fetch_texture(wearable_thumbnail, content_mapping) var res = await promise.co_awaiter() - if res is PromiseError: + if res is Promise.Error: printerr("Fetch texture error on ", wearable_thumbnail) else: texture_rect_preview.texture = res diff --git a/godot/src/utils/Awaiter.gd b/godot/src/utils/Awaiter.gd deleted file mode 100644 index 70f17a240..000000000 --- a/godot/src/utils/Awaiter.gd +++ /dev/null @@ -1,35 +0,0 @@ -# util.gd -class_name Awaiter - - -class AllAwaiter: - var _mask: int - var _promise: Promise = Promise.new() - - func _init(funcs: Array) -> void: - var size := funcs.size() - if size == 0: # inmediate resolve, no funcs to await... - _promise.resolve() - return - - assert(size < 64) - _mask = (1 << size) - 1 - for i in size: - _call_func(i, funcs[i]) - - func _call_func(i: int, f) -> void: - @warning_ignore("redundant_await") - if f is Promise: - await f.co_awaiter() - elif f is Callable: - var res: Promise = f.call() - if res != null: - await res.co_awaiter() - _mask &= ~(1 << i) - - if not _mask and not _promise.is_resolved(): - _promise.resolve() - - -static func co_all(funcs: Array) -> void: - await AllAwaiter.new(funcs)._promise.co_awaiter() diff --git a/godot/src/utils/Promise.gd b/godot/src/utils/Promise.gd index dc2ba65d3..830f9c190 100644 --- a/godot/src/utils/Promise.gd +++ b/godot/src/utils/Promise.gd @@ -1,4 +1,6 @@ -extends RefCounted +# Promises for GDScript +# Every function that must be awaited has an `co_` prefix + class_name Promise signal _on_resolved @@ -8,11 +10,13 @@ var _data: Variant = null func resolve(): - _on_resolved.emit() + if is_resolved(): return _resolved = true + _on_resolved.emit() func resolve_with_data(data): + if is_resolved(): return _data = data resolve() @@ -22,10 +26,12 @@ func get_data(): func reject(reason: String): - _data = PromiseError.create(reason) - printerr("Promise rejected, reason: ", reason) + if is_resolved(): return + _data = Promise.Error.create(reason) resolve() +func is_rejected() -> bool: + return _data is Promise.Error func is_resolved() -> bool: return _resolved @@ -34,4 +40,121 @@ func is_resolved() -> bool: func co_awaiter() -> Variant: if !_resolved: await _on_resolved - return _data + if _data is Promise: # Chain promises + return _data.co_awaiter() + else: + return _data + +class Error: + var _error_description: String = "" + + static func create(description: String) -> Promise.Error: + var error = Promise.Error.new() + error._error_description = description + return error + + func get_error() -> String: + return _error_description + +# Internal helper function +static func _co_call_and_get_promise(f) -> Promise: + if f is Promise: + return f + elif f is Callable: + var res = await f.call() + if res is Promise: + return res + else: + printerr("Func doesn't return a Promise") + return null + else: + printerr("Func is not a callable nor promise") + return null + +class AllAwaiter: + var _mask: int + var _promise: Promise = Promise.new() + var results: Array = [] + + func _init(funcs: Array) -> void: + var size := funcs.size() + if size == 0: # inmediate resolve, no funcs to await... + _promise.resolve() + return + + results.resize(size) + results.fill(null) # by default, the return will be null + assert(size < 64) + _mask = (1 << size) - 1 + for i in size: + _call_func(i, funcs[i]) + + func _call_func(i: int, f) -> void: + @warning_ignore("redundant_await") + var promise = await Promise._co_call_and_get_promise(f) + var data = await promise.co_awaiter() + results[i] = data + + _mask &= ~(1 << i) + + if not _mask and not _promise.is_resolved(): + _promise.resolve_with_data(results) + +class AnyAwaiter: + var _promise: Promise = Promise.new() + + func _init(funcs: Array) -> void: + var size := funcs.size() + if size == 0: # inmediate resolve, no funcs to await... + _promise.resolve() + return + for i in size: + _call_func(i, funcs[i]) + + func _call_func(i: int, f) -> void: + @warning_ignore("redundant_await") + var promise: Promise = await Promise._co_call_and_get_promise(f) + var res = await promise.co_awaiter() + + # Promise.co_any ignores promises with errors + if !promise.is_rejected() and not _promise.is_resolved(): + _promise.resolve_with_data(res) + +class RaceAwaiter: + var _promise: Promise = Promise.new() + + func _init(funcs: Array) -> void: + var size := funcs.size() + if size == 0: # inmediate resolve, no funcs to await... + _promise.resolve() + return + for i in size: + _call_func(i, funcs[i]) + + func _call_func(i: int, f) -> void: + @warning_ignore("redundant_await") + var promise: Promise = await Promise._co_call_and_get_promise(f) + var res = await promise.co_awaiter() + + # Promise.co_race doesn't ignore on error, you get the first one, with or without an error + if not _promise.is_resolved(): + _promise.resolve_with_data(res) + +# `co_all` is a static function that takes an array of functions (`funcs`) +# and returns an array. It awaits the resolution of all the given functions. +# Each function in the array is expected to be a coroutine or a function +# that returns a promise. +static func co_all(funcs: Array) -> Array: + return await AllAwaiter.new(funcs)._promise.co_awaiter() + +# `co_any` is a static function similar to `co_all`, but it resolves as soon as any of the +# functions in the provided array resolves. It returns the result of the first function +# that resolves. It ignores the rejections (differently from co_race) +static func co_any(funcs: Array) -> Variant: + return await AnyAwaiter.new(funcs)._promise.co_awaiter() + +# `co_race` is another static function that takes an array of functions and returns +# a variant. It behaves like a race condition, returning the result of the function +# that completes first, even if it fails (differently from co_any) +static func co_race(funcs: Array) -> Variant: + return await RaceAwaiter.new(funcs)._promise.co_awaiter() diff --git a/godot/src/utils/PromiseError.gd b/godot/src/utils/PromiseError.gd deleted file mode 100644 index 26d35c1a9..000000000 --- a/godot/src/utils/PromiseError.gd +++ /dev/null @@ -1,14 +0,0 @@ -extends RefCounted -class_name PromiseError - -var _error_description: String = "" - - -static func create(description: String) -> PromiseError: - var error = PromiseError.new() - error._error_description = description - return error - - -func get_error() -> String: - return _error_description