Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use new promises #88

Merged
merged 1 commit into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions godot/.godot/global_script_class_cache.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -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": "",
Expand Down Expand Up @@ -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": "",
Expand Down
2 changes: 1 addition & 1 deletion godot/src/decentraland_components/audio_source.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
2 changes: 1 addition & 1 deletion godot/src/decentraland_components/avatar.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down
2 changes: 1 addition & 1 deletion godot/src/decentraland_components/video_player.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 6 additions & 6 deletions godot/src/logic/content_thread.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
)
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion godot/src/logic/realm.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
6 changes: 3 additions & 3 deletions godot/src/logic/scene_fetcher.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion godot/src/ui/components/wearable_button/wearable_button.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion godot/src/ui/components/wearable_item/wearable_item.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion godot/src/ui/components/wearable_panel/wearable_panel.gd
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 0 additions & 35 deletions godot/src/utils/Awaiter.gd

This file was deleted.

146 changes: 141 additions & 5 deletions godot/src/utils/Promise.gd
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -8,11 +10,15 @@ 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()

Expand All @@ -22,16 +28,146 @@ 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


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()
14 changes: 0 additions & 14 deletions godot/src/utils/PromiseError.gd

This file was deleted.

Loading