diff --git a/godot/.godot/global_script_class_cache.cfg b/godot/.godot/global_script_class_cache.cfg index 2c0231bc..85f7be6c 100644 --- a/godot/.godot/global_script_class_cache.cfg +++ b/godot/.godot/global_script_class_cache.cfg @@ -5,7 +5,7 @@ list=Array[Dictionary]([{ "language": &"GDScript", "path": "res://src/logic/animation_importer.gd" }, { -"base": &"Node3D", +"base": &"DclAvatar", "class": &"Avatar", "icon": "", "language": &"GDScript", @@ -41,6 +41,12 @@ list=Array[Dictionary]([{ "language": &"GDScript", "path": "res://src/logic/content_thread.gd" }, { +"base": &"AvatarScene", +"class": &"CustomAvatarScene", +"icon": "", +"language": &"GDScript", +"path": "res://src/logic/avatar_scene.gd" +}, { "base": &"MeshInstance3D", "class": &"DclMeshRenderer", "icon": "", @@ -53,12 +59,6 @@ list=Array[Dictionary]([{ "language": &"GDScript", "path": "res://src/logic/camera.gd" }, { -"base": &"RefCounted", -"class": &"Math", -"icon": "", -"language": &"GDScript", -"path": "res://src/utils/Math.gd" -}, { "base": &"DclPortableExperienceController", "class": &"PortableExperienceController", "icon": "", @@ -69,7 +69,7 @@ list=Array[Dictionary]([{ "class": &"Promise", "icon": "", "language": &"GDScript", -"path": "res://src/utils/Promise.gd" +"path": "res://src/utils/promise.gd" }, { "base": &"DclRealm", "class": &"Realm", diff --git a/godot/src/decentraland_components/avatar.gd b/godot/src/decentraland_components/avatar.gd index ff5f9c5d..eb07cf38 100644 --- a/godot/src/decentraland_components/avatar.gd +++ b/godot/src/decentraland_components/avatar.gd @@ -1,4 +1,4 @@ -extends Node3D +extends DclAvatar class_name Avatar signal avatar_loaded @@ -14,20 +14,6 @@ var avatar_name: String = "" var avatar_id: String = "" var playing_emote = false -# Parcel Position -var parcel_position: Vector2i = Vector2i.ZERO -var last_parcel_position: Vector2i = Vector2i(Math.INT32_MAX, Math.INT32_MAX) # Vector2i.MAX is coming: https://github.com/godotengine/godot/pull/81741 -var scene_id: int = Math.INT32_MIN -var last_scene_id: int = Math.INT32_MAX -signal change_parcel_position(parcel_position: Vector2) -signal change_scene_id(scene_id: Vector2) - -# Position Lerp -var last_position: Vector3 = Vector3.ZERO -var target_position: Vector3 = Vector3.ZERO -var t: float = 0.0 -var target_distance: float = 0.0 - # Wearable requesting var current_content_url: String = "" @@ -131,7 +117,7 @@ func play_emote(emote_id: String): playing_emote = true -func play_remote_emote(emote_src: String, looping: bool): +func play_remote_emote(_emote_src: String, _looping: bool): # TODO: Implement downloading emote from the scene content, adding to the avatar and then playing the emote # Test scene: https://github.com/decentraland/unity-renderer/pull/5501 pass @@ -427,52 +413,9 @@ func apply_texture_and_mask( mesh.mesh.surface_set_material(0, current_material) -func set_target(target: Transform3D) -> void: - target_distance = target_position.distance_to(target.origin) - - last_position = target_position - target_position = target.origin - - self.global_rotation = target.basis.get_euler() - self.global_position = last_position - - t = 0 - - -func _check_parcel_position(): - parcel_position = Math.get_parcel_position_by_world_position(self.get_global_position()) - - if last_parcel_position != parcel_position: - last_parcel_position = parcel_position - change_parcel_position.emit(parcel_position) - scene_id = Global.scene_runner.get_scene_id_by_parcel_position(parcel_position) - if last_scene_id != scene_id: - last_scene_id = scene_id - change_scene_id.emit(scene_id) - - func _process(delta): - _check_parcel_position() - - if skip_process: - return - - if t < 2: - t += 10 * delta - if t < 1: - if t > 1.0: - t = 1.0 - - self.global_position = last_position.lerp(target_position, t) - - if target_distance > 0: - if target_distance > 0.6: - set_running() - else: - set_walking() - - elif t > 1.5: - self.set_idle() + # TODO: maybe a gdext crate bug? when process implement the Node3DVirtual, super(delta) doesn't work :/ + self.process(delta) func set_walking(): diff --git a/godot/src/decentraland_components/avatar.tscn b/godot/src/decentraland_components/avatar.tscn index afd6db40..c9cc16c2 100644 --- a/godot/src/decentraland_components/avatar.tscn +++ b/godot/src/decentraland_components/avatar.tscn @@ -1163,7 +1163,7 @@ material = SubResource("StandardMaterial3D_6htl0") radius = 0.1 height = 0.1 -[node name="Avatar" type="Node3D"] +[node name="Avatar" type="DclAvatar"] script = ExtResource("1_kkscc") [node name="Armature" type="Node3D" parent="."] diff --git a/godot/src/decentraland_components/avatar_modifier_area.gd b/godot/src/decentraland_components/avatar_modifier_area.gd index 94069d96..641eabb7 100644 --- a/godot/src/decentraland_components/avatar_modifier_area.gd +++ b/godot/src/decentraland_components/avatar_modifier_area.gd @@ -2,7 +2,8 @@ extends DclAvatarModifierArea3D @onready var collision_shape_3d = $CollisionShape3D -var scene_id: int = 0 +# Initial value SceneId::INVALID +var scene_id: int = -1 # Called when the node enters the scene tree for the first time. diff --git a/godot/src/decentraland_components/gltf_container.gd b/godot/src/decentraland_components/gltf_container.gd index dbd43dc7..6b9d6867 100644 --- a/godot/src/decentraland_components/gltf_container.gd +++ b/godot/src/decentraland_components/gltf_container.gd @@ -52,9 +52,16 @@ func _on_gltf_loaded(): self.deferred_add_child.call_deferred(gltf_node) -func deferred_add_child(gltf_node): - add_child(gltf_node) - await get_tree().process_frame +func deferred_add_child(new_gltf_node): + # Corner case, when the scene is unloaded before the gltf is loaded + var main_tree = get_tree() + if not is_instance_valid(main_tree): + dcl_gltf_loading_state = GltfContainerLoadingState.FinishedWithError + return + + add_child(new_gltf_node) + + await main_tree.process_frame # Colliders and rendering is ensured to be ready at this point dcl_gltf_loading_state = GltfContainerLoadingState.Finished diff --git a/godot/src/global.gd b/godot/src/global.gd index a1552ce8..d304255b 100644 --- a/godot/src/global.gd +++ b/godot/src/global.gd @@ -56,6 +56,9 @@ func _ready(): self.portable_experience_controller = PortableExperienceController.new() self.portable_experience_controller.set_name("portable_experience_controller") + self.avatars = CustomAvatarScene.new() + self.avatars.set_name("avatar_scene") + get_tree().root.add_child.call_deferred(self.scene_fetcher) get_tree().root.add_child.call_deferred(self.content_manager) get_tree().root.add_child.call_deferred(self.scene_runner) @@ -86,5 +89,5 @@ func print_node_tree(node: Node, prefix = ""): print_node_tree(child, prefix + node.name + "/") -func _process(dt: float): +func _process(_dt: float): http_requester.poll() diff --git a/godot/src/helpers_components/avatar_modifier_area_detector.gd b/godot/src/helpers_components/avatar_modifier_area_detector.gd index da9bf42d..c86cb357 100644 --- a/godot/src/helpers_components/avatar_modifier_area_detector.gd +++ b/godot/src/helpers_components/avatar_modifier_area_detector.gd @@ -15,12 +15,16 @@ var overlapping_areas: Array[Area3D] = [] func _ready(): avatar.change_scene_id.connect(self._on_avatar_change_scene_id) + # Arbitrary order + for area in self.get_overlapping_areas(): + _on_area_entered(area) + func _on_tree_exiting(): avatar.change_scene_id.disconnect(self._on_avatar_change_scene_id) -func _on_avatar_change_scene_id(new_scene_id: int): +func _on_avatar_change_scene_id(_new_scene_id: int, _prev_scene_id: int): check_areas() @@ -41,7 +45,7 @@ func get_last_dcl_avatar_modifier_area_3d(areas: Array[Area3D]) -> DclAvatarModi func check_areas(): - var avatar_scene_id = avatar.scene_id + var avatar_scene_id = avatar.current_parcel_scene_id # only areas that have the same scene id than the player... var areas = overlapping_areas.filter(func(area): return area.scene_id == avatar_scene_id) diff --git a/godot/src/helpers_components/force_global_scale_component.gd b/godot/src/helpers_components/force_global_scale_component.gd index 1d22d4e1..5d71eee0 100644 --- a/godot/src/helpers_components/force_global_scale_component.gd +++ b/godot/src/helpers_components/force_global_scale_component.gd @@ -3,8 +3,8 @@ extends Node3D @export var target: Node3D -func _process(dt): - var global_scale = target.get_global_transform().basis.get_scale() +func _process(_dt): + var current_global_scale = target.get_global_transform().basis.get_scale() # If the global scale is not 1,1,1, adjust it - if global_scale != Vector3.ONE: - target.scale = target.scale / global_scale + if current_global_scale != Vector3.ONE: + target.scale = target.scale / current_global_scale diff --git a/godot/src/helpers_components/on_active_scene.gd b/godot/src/helpers_components/on_active_scene.gd index a6c55385..22a6fcdb 100644 --- a/godot/src/helpers_components/on_active_scene.gd +++ b/godot/src/helpers_components/on_active_scene.gd @@ -4,7 +4,8 @@ extends Node3D signal on_scene_active(active: bool) -var _my_scene_id: int = 0 +# Initial value SceneId::INVALID +var _my_scene_id: int = -1 func _ready(): @@ -17,5 +18,5 @@ func _ready(): Global.scene_runner.on_change_scene_id.connect(self._on_change_scene_id) -func _on_change_scene_id(scene_id: int): +func _on_change_scene_id(scene_id: int, _prev_scene_id: int): on_scene_active.emit(scene_id == _my_scene_id) diff --git a/godot/src/logic/avatar_scene.gd b/godot/src/logic/avatar_scene.gd new file mode 100644 index 00000000..1161dc6a --- /dev/null +++ b/godot/src/logic/avatar_scene.gd @@ -0,0 +1,8 @@ +extends AvatarScene +class_name CustomAvatarScene #workaround + + +# TODO: when 4.2 is used, this could be removed +# It's not possible to bind a custom callable to a signal in Rust before 4.2 +func _temp_get_custom_callable_on_avatar_changed(avatar_entity_id): + return self.on_avatar_changed_scene.bind(avatar_entity_id) diff --git a/godot/src/logic/content_thread.gd b/godot/src/logic/content_thread.gd index 0119b17b..864ee114 100644 --- a/godot/src/logic/content_thread.gd +++ b/godot/src/logic/content_thread.gd @@ -31,9 +31,9 @@ func content_processing_count(): return _pending_content.size() -func _init(id: int, thread: Thread): - self.thread = thread - self.id = id +func _init(param_id: int, param_thread: Thread): + self.thread = param_thread + self.id = param_id func process(content_cache_map: Dictionary): # not a coroutine diff --git a/godot/src/logic/player/player.gd b/godot/src/logic/player/player.gd index 3604e2f1..9aef41ba 100644 --- a/godot/src/logic/player/player.gd +++ b/godot/src/logic/player/player.gd @@ -26,7 +26,7 @@ var stored_camera_mode_before_block: Global.CameraMode func _on_camera_mode_area_detector_block_camera_mode(forced_mode): if !camera_mode_change_blocked: # if it's already blocked, we don't store the state again... - stored_camera_mode_before_block = camera.get_camera_mode() + stored_camera_mode_before_block = camera.get_camera_mode() as Global.CameraMode camera_mode_change_blocked = true set_camera_mode(forced_mode, false) @@ -173,11 +173,14 @@ func _physics_process(delta: float) -> void: func avatar_look_at(target_position: Vector3): - var direction = target_position - get_global_position() - direction = direction.normalized() - - var y_rot = atan2(direction.x, direction.z) - var x_rot = atan2(direction.y, sqrt(direction.x * direction.x + direction.z * direction.z)) + var target_direction = target_position - get_global_position() + target_direction = direction.normalized() + + var y_rot = atan2(target_direction.x, target_direction.z) + var x_rot = atan2( + target_direction.y, + sqrt(target_direction.x * target_direction.x + target_direction.z * target_direction.z) + ) rotation.y = y_rot + PI avatar.set_rotation(Vector3(0, 0, 0)) diff --git a/godot/src/logic/portable_experience_controller.gd b/godot/src/logic/portable_experience_controller.gd index 72dcc3a4..d3b98ea4 100644 --- a/godot/src/logic/portable_experience_controller.gd +++ b/godot/src/logic/portable_experience_controller.gd @@ -15,7 +15,7 @@ func _ready(): Global.scene_runner.scene_spawned.connect(self._on_scene_spawned) -func _process(delta): +func _process(_delta): var to_spawn := self.consume_requested_spawn() if not to_spawn.is_empty(): spawn_many_portables(to_spawn) diff --git a/godot/src/logic/realm.gd b/godot/src/logic/realm.gd index a53b4601..392faf31 100644 --- a/godot/src/logic/realm.gd +++ b/godot/src/logic/realm.gd @@ -69,8 +69,8 @@ static func parse_urn(urn: String): func set_realm(new_realm_string: String) -> void: realm_string = new_realm_string - realm_url = ensure_ends_with_slash(resolve_realm_url(realm_string)) - realm_url = ensure_starts_with_https(realm_url) + realm_url = Realm.ensure_ends_with_slash(Realm.resolve_realm_url(realm_string)) + realm_url = Realm.ensure_starts_with_https(realm_url) var promise: Promise = http_requester.request_json( realm_url + "about", HTTPClient.METHOD_GET, "", [] ) @@ -102,13 +102,13 @@ func set_realm(new_realm_string: String) -> void: realm_scene_urns.clear() for urn in configuration.get("scenesUrn", []): - var parsed_urn = parse_urn(urn) + var parsed_urn = Realm.parse_urn(urn) if parsed_urn != null: realm_scene_urns.push_back(parsed_urn) realm_global_scene_urns.clear() for urn in configuration.get("globalScenesUrn", []): - var parsed_urn = parse_urn(urn) + var parsed_urn = Realm.parse_urn(urn) if parsed_urn != null: realm_global_scene_urns.push_back(parsed_urn) @@ -116,7 +116,9 @@ func set_realm(new_realm_string: String) -> void: realm_name = configuration.get("realmName", "no_realm_name") - content_base_url = ensure_ends_with_slash(realm_about.get("content", {}).get("publicUrl")) + content_base_url = Realm.ensure_ends_with_slash( + realm_about.get("content", {}).get("publicUrl") + ) Global.config.last_realm_joined = realm_url Global.config.save_to_settings_file() diff --git a/godot/src/logic/scene_fetcher.gd b/godot/src/logic/scene_fetcher.gd index 99404d51..831e4f17 100644 --- a/godot/src/logic/scene_fetcher.gd +++ b/godot/src/logic/scene_fetcher.gd @@ -30,7 +30,7 @@ func _ready(): Global.scene_runner.scene_killed.connect(self.on_scene_killed) -func on_scene_killed(killed_scene_id, entity_id): +func on_scene_killed(killed_scene_id, _entity_id): for scene_id in loaded_scenes.keys(): var scene = loaded_scenes[scene_id] var scene_number_id: int = scene.get("scene_number_id", -1) @@ -211,7 +211,6 @@ func load_scene(scene_entity_id: String, entity: Dictionary): } var is_sdk7 = metadata.get("runtimeVersion", null) == "7" - var main_js_request_id := -1 var local_main_js_path = "" if is_sdk7: diff --git a/godot/src/test/avatar/spawn_and_move.gd b/godot/src/test/avatar/spawn_and_move.gd index 495d1d61..def3bd44 100644 --- a/godot/src/test/avatar/spawn_and_move.gd +++ b/godot/src/test/avatar/spawn_and_move.gd @@ -65,7 +65,7 @@ func _process(dt): var alias = 10000 + spawning_i Global.avatars.add_avatar(alias, "") Global.avatars.update_avatar_profile(alias, avatar_data) - Global.avatars.update_avatar_transform(alias, transform) + Global.avatars.update_avatar_transform_with_godot_transform(alias, transform) spawning_position.append(initial_position) @@ -91,4 +91,4 @@ func _process(dt): spawning_position[i] = target_position var alias = 10000 + i - Global.avatars.update_avatar_transform(alias, transform) + Global.avatars.update_avatar_transform_with_godot_transform(alias, transform) diff --git a/godot/src/ui/components/backpack/backpack.gd b/godot/src/ui/components/backpack/backpack.gd index caddfc1c..ecf3fb37 100644 --- a/godot/src/ui/components/backpack/backpack.gd +++ b/godot/src/ui/components/backpack/backpack.gd @@ -163,7 +163,7 @@ func _on_wearable_button_filter_type(type): skin_color_picker.show() -func _on_wearable_button_clear_filter(type): +func _on_wearable_button_clear_filter(_type): filtered_data = [] show_wearables() diff --git a/godot/src/ui/components/chat/chat.gd b/godot/src/ui/components/chat/chat.gd index eb30702a..03e08597 100644 --- a/godot/src/ui/components/chat/chat.gd +++ b/godot/src/ui/components/chat/chat.gd @@ -23,8 +23,8 @@ const ACK: String = "␆" func _on_chats_arrived(chats: Array): for chat in chats: var address: String = chat[0] - var profile_name: StringName = chat[1] - var timestamp: float = chat[2] + var _profile_name: StringName = chat[1] + var _timestamp: float = chat[2] var message: StringName = chat[3] var avatar = Global.avatars.get_avatar_by_address(address) diff --git a/godot/src/utils/Math.gd b/godot/src/utils/Math.gd deleted file mode 100644 index bd46bffa..00000000 --- a/godot/src/utils/Math.gd +++ /dev/null @@ -1,19 +0,0 @@ -class_name Math - -const UINT8_MAX = (1 << 8) - 1 # 255 -const UINT16_MAX = (1 << 16) - 1 # 65535 -const UINT32_MAX = (1 << 32) - 1 # 4294967295 - -const INT8_MIN = -(1 << 7) # -128 -const INT16_MIN = -(1 << 15) # -32768 -const INT32_MIN = -(1 << 31) # -2147483648 -const INT64_MIN = -(1 << 63) # -9223372036854775808 - -const INT8_MAX = (1 << 7) - 1 # 127 -const INT16_MAX = (1 << 15) - 1 # 32767 -const INT32_MAX = (1 << 31) - 1 # 2147483647 -const INT64_MAX = (1 << 63) - 1 # 9223372036854775807 - - -static func get_parcel_position_by_world_position(world_position: Vector3) -> Vector2i: - return Vector2i(floor(world_position.x / 16.0), floor(-world_position.z / 16.0)) diff --git a/godot/src/utils/Promise.gd b/godot/src/utils/promise.gd similarity index 97% rename from godot/src/utils/Promise.gd rename to godot/src/utils/promise.gd index 14748bf7..7aa3be94 100644 --- a/godot/src/utils/Promise.gd +++ b/godot/src/utils/promise.gd @@ -120,7 +120,7 @@ class AnyAwaiter: for i in size: _call_func(i, funcs[i]) - func _call_func(i: int, f) -> void: + 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() @@ -141,7 +141,7 @@ class RaceAwaiter: for i in size: _call_func(i, funcs[i]) - func _call_func(i: int, f) -> void: + 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() @@ -156,6 +156,8 @@ class RaceAwaiter: # 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: + if funcs.is_empty(): + return [] return await AllAwaiter.new(funcs)._promise.co_awaiter() diff --git a/rust/decentraland-godot-lib/src/avatars/avatar_scene.rs b/rust/decentraland-godot-lib/src/avatars/avatar_scene.rs index cf133f13..3cac2465 100644 --- a/rust/decentraland-godot-lib/src/avatars/avatar_scene.rs +++ b/rust/decentraland-godot-lib/src/avatars/avatar_scene.rs @@ -10,11 +10,19 @@ use crate::{ proto_components::kernel::comms::rfc4, transform_and_parent::DclTransformAndParent, SceneEntityId, }, - crdt::{last_write_wins::LastWriteWinsComponentOperation, SceneCrdtState}, + crdt::{ + last_write_wins::LastWriteWinsComponentOperation, SceneCrdtState, + SceneCrdtStateProtoComponents, + }, + SceneId, }, + godot_classes::dcl_avatar::{AvatarMovementType, DclAvatar}, + godot_classes::dcl_global::DclGlobal, wallet::AsH160, }; +type AvatarAlias = u32; + #[derive(GodotClass)] #[class(base=Node)] pub struct AvatarScene { @@ -22,13 +30,11 @@ pub struct AvatarScene { base: Base, // map alias to the entity_id - avatar_entity: HashMap, - avatar_godot_scene: HashMap>, - avatar_address: HashMap, + avatar_entity: HashMap, + avatar_godot_scene: HashMap>, + avatar_address: HashMap, - // scenes_dirty: HashMap>, - // - crdt: SceneCrdtState, + crdt_state: SceneCrdtState, last_updated_profile: HashMap, } @@ -39,7 +45,7 @@ impl NodeVirtual for AvatarScene { AvatarScene { base, avatar_entity: HashMap::new(), - crdt: SceneCrdtState::from_proto(), + crdt_state: SceneCrdtState::from_proto(), avatar_godot_scene: HashMap::new(), avatar_address: HashMap::new(), last_updated_profile: HashMap::new(), @@ -47,6 +53,25 @@ impl NodeVirtual for AvatarScene { } } +macro_rules! sync_crdt_lww_component { + ($entity_id:ident, $target_component:ident, $local_component:ident) => { + let local_value_entry = $local_component.get($entity_id); + + if let Some(value_entry) = local_value_entry { + let diff_timestamp = local_value_entry.map(|v| v.timestamp) + != $target_component.get($entity_id).map(|v| v.timestamp); + + if diff_timestamp { + $target_component.set( + *$entity_id, + value_entry.timestamp, + value_entry.value.clone(), + ); + } + } + }; +} + #[godot_api] impl AvatarScene { #[func] @@ -65,7 +90,11 @@ impl AvatarScene { } #[func] - pub fn update_avatar_transform(&mut self, alias: u32, transform: Transform3D) { + pub fn update_avatar_transform_with_godot_transform( + &mut self, + alias: u32, + transform: Transform3D, + ) { let entity_id = if let Some(entity_id) = self.avatar_entity.get(&alias) { *entity_id } else { @@ -74,19 +103,7 @@ impl AvatarScene { }; let dcl_transform = DclTransformAndParent::from_godot(&transform, Vector3::ZERO); - - // let avatar_scene = self.avatar_godot_scene.get_mut(&entity_id).unwrap(); - - // // TODO: the scale seted in the transform is local - // avatar_scene.set_transform(dcl_transform.to_godot_transform_3d()); - self.avatar_godot_scene.get_mut(&entity_id).unwrap().call( - "set_target".into(), - &[dcl_transform.to_godot_transform_3d().to_variant()], - ); - - self.crdt - .get_transform_mut() - .put(entity_id, Some(dcl_transform)); + self._update_avatar_transform(&entity_id, dcl_transform); } #[func] @@ -95,26 +112,51 @@ impl AvatarScene { let entity_id = self .get_next_entity_id() .unwrap_or(SceneEntityId::new(Self::MAX_ENTITY_ID + 1, 0)); - self.crdt.entities.try_init(entity_id); + self.crdt_state.entities.try_init(entity_id); self.avatar_entity.insert(alias, entity_id); - let new_avatar = + let mut new_avatar: Gd = godot::engine::load::("res://src/decentraland_components/avatar.tscn") .instantiate() .unwrap() - .cast::(); + .cast::(); if let Some(address) = address.to_string().as_h160() { self.avatar_address.insert(address, alias); } + new_avatar + .bind_mut() + .set_movement_type(AvatarMovementType::LerpTwoPoints as i32); + + // TODO: when updating to 4.2, change this to Callable:from_custom + if self + .base + .has_method("_temp_get_custom_callable_on_avatar_changed".into()) + { + // let on_change_scene_id_callable = self + // .base + // .get("on_avatar_changed_scene".into()) + // .to::(); + + let on_change_scene_id_callable = self + .base + .call( + "_temp_get_custom_callable_on_avatar_changed".into(), + &[entity_id.as_i32().to_variant()], + ) + .to::(); + + new_avatar.connect("change_scene_id".into(), on_change_scene_id_callable); + } + self.base.add_child(new_avatar.clone().upcast()); self.avatar_godot_scene.insert(entity_id, new_avatar); } #[func] - pub fn get_avatar_by_address(&self, address: GodotString) -> Option> { + pub fn get_avatar_by_address(&self, address: GodotString) -> Option> { if let Some(address) = address.to_string().as_h160() { if let Some(alias) = self.avatar_address.get(&address) { if let Some(entity_id) = self.avatar_entity.get(alias) { @@ -124,17 +166,49 @@ impl AvatarScene { } None } + + #[func] + fn on_avatar_changed_scene(&self, scene_id: i32, prev_scene_id: i32, avatar_entity_id: i32) { + let scene_id = SceneId(scene_id); + let prev_scene_id = SceneId(prev_scene_id); + let avatar_entity_id = SceneEntityId::from_i32(avatar_entity_id); + + // TODO: as this function was deferred called, check if the current_parcel_entity_id is the same + // maybe it's better to cache the last parcel here instead of using prev_scene_id + // the state of to what parcel the avatar belongs is stored in the avatar_scene + + let mut scene_runner = DclGlobal::singleton().bind().scene_runner.clone(); + let mut scene_runner = scene_runner.bind_mut(); + if let Some(prev_scene) = scene_runner.get_scene_mut(&prev_scene_id) { + prev_scene + .avatar_scene_updates + .transform + .insert(avatar_entity_id, None); + } + + if let Some(scene) = scene_runner.get_scene_mut(&scene_id) { + let dcl_transform = DclTransformAndParent::default(); // TODO: get real transform with scene_offset + + let mut avatar_scene_transform = dcl_transform.clone(); + avatar_scene_transform.translation.x -= (scene.definition.base.x as f32) * 16.0; + avatar_scene_transform.translation.z -= (scene.definition.base.y as f32) * 16.0; + + scene + .avatar_scene_updates + .transform + .insert(avatar_entity_id, Some(dcl_transform.clone())); + } + } } impl AvatarScene { const FROM_ENTITY_ID: u16 = 32; const MAX_ENTITY_ID: u16 = 256; - // const AVATAR_COMPONENTS: &[SceneComponentId] = &[SceneComponentId::AVATAR_ATTACH]; // This function is not optimized, it will iterate over all the entities but this happens only when add an player fn get_next_entity_id(&self) -> Result { for entity_number in Self::FROM_ENTITY_ID..Self::MAX_ENTITY_ID { - let (version, live) = self.crdt.entities.get_entity_stat(entity_number); + let (version, live) = self.crdt_state.entities.get_entity_stat(entity_number); if !live { let entity_id = SceneEntityId::new(entity_number, *version); @@ -156,17 +230,75 @@ impl AvatarScene { pub fn remove_avatar(&mut self, alias: u32) { if let Some(entity_id) = self.avatar_entity.remove(&alias) { - self.crdt.kill_entity(&entity_id); + self.crdt_state.kill_entity(&entity_id); let mut avatar = self.avatar_godot_scene.remove(&entity_id).unwrap(); self.base.remove_child(avatar.clone().upcast()); self.avatar_address.retain(|_, v| *v != alias); avatar.queue_free(); + + // Push dirty state in all the scenes + let mut scene_runner = DclGlobal::singleton().bind().scene_runner.clone(); + let mut scene_runner = scene_runner.bind_mut(); + for (_, scene) in scene_runner.get_all_scenes_mut().iter_mut() { + scene + .avatar_scene_updates + .deleted_entities + .insert(entity_id); + } + } + } + + fn _update_avatar_transform( + &mut self, + avatar_entity_id: &SceneEntityId, + dcl_transform: DclTransformAndParent, + ) { + let avatar_scene = self + .avatar_godot_scene + .get_mut(avatar_entity_id) + .expect("avatar not found"); + avatar_scene + .bind_mut() + .set_target_position(dcl_transform.to_godot_transform_3d()); + + let mut scene_runner = DclGlobal::singleton().bind().scene_runner.clone(); + let mut scene_runner = scene_runner.bind_mut(); + + let avatar_current_parcel_scene_id = avatar_scene.bind().get_current_parcel_scene_id(); + let avatar_active_scenes = { + let mut scenes = scene_runner.get_global_scenes(); + if avatar_current_parcel_scene_id != SceneId::INVALID.0 { + scenes.push(SceneId(avatar_current_parcel_scene_id)); + } + scenes + }; + + // Push dirty state only in active scenes + for scene_id in avatar_active_scenes { + if let Some(scene) = scene_runner.get_scene_mut(&scene_id) { + let mut avatar_scene_transform = dcl_transform.clone(); + avatar_scene_transform.translation.x -= (scene.definition.base.x as f32) * 16.0; + avatar_scene_transform.translation.z -= (scene.definition.base.y as f32) * 16.0; + + scene + .avatar_scene_updates + .transform + .insert(*avatar_entity_id, Some(dcl_transform.clone())); + } } + + self.crdt_state + .get_transform_mut() + .put(*avatar_entity_id, Some(dcl_transform)); } - pub fn update_transform(&mut self, alias: u32, transform: &rfc4::Position) { + pub fn update_avatar_transform_with_rfc4_position( + &mut self, + alias: u32, + transform: &rfc4::Position, + ) { let entity_id = if let Some(entity_id) = self.avatar_entity.get(&alias) { *entity_id } else { @@ -190,18 +322,7 @@ impl AvatarScene { parent: SceneEntityId::ROOT, }; - // let avatar_scene = self.avatar_godot_scene.get_mut(&entity_id).unwrap(); - - // // TODO: the scale seted in the transform is local - // avatar_scene.set_transform(dcl_transform.to_godot_transform_3d()); - self.avatar_godot_scene.get_mut(&entity_id).unwrap().call( - "set_target".into(), - &[dcl_transform.to_godot_transform_3d().to_variant()], - ); - - self.crdt - .get_transform_mut() - .put(entity_id, Some(dcl_transform)); + self._update_avatar_transform(&entity_id, dcl_transform); } pub fn update_avatar(&mut self, alias: u32, profile: &SerializedProfile, base_url: &str) { @@ -224,6 +345,71 @@ impl AvatarScene { "update_avatar".into(), &[profile.to_godot_dictionary(base_url).to_variant()], ); + + let new_avatar_base = Some(profile.to_pb_avatar_base()); + let avatar_base_component = + SceneCrdtStateProtoComponents::get_avatar_base(&self.crdt_state); + let avatar_base_component_value = avatar_base_component + .get(&entity_id) + .and_then(|v| v.value.clone()); + if avatar_base_component_value != new_avatar_base { + // Push dirty state in all the scenes + let mut scene_runner = DclGlobal::singleton().bind().scene_runner.clone(); + let mut scene_runner = scene_runner.bind_mut(); + for (_, scene) in scene_runner.get_all_scenes_mut().iter_mut() { + scene.avatar_scene_updates.avatar_base.insert( + entity_id, + new_avatar_base.clone().expect("value was assigned above"), + ); + } + SceneCrdtStateProtoComponents::get_avatar_base_mut(&mut self.crdt_state) + .put(entity_id, new_avatar_base); + } + + let new_avatar_equipped_data = Some(profile.to_pb_avatar_equipped_data()); + let avatar_equipped_data_component = + SceneCrdtStateProtoComponents::get_avatar_equipped_data(&self.crdt_state); + let avatar_equipped_data_value = avatar_equipped_data_component + .get(&entity_id) + .and_then(|v| v.value.clone()); + if avatar_equipped_data_value != new_avatar_equipped_data { + // Push dirty state in all the scenes + let mut scene_runner = DclGlobal::singleton().bind().scene_runner.clone(); + let mut scene_runner = scene_runner.bind_mut(); + for (_, scene) in scene_runner.get_all_scenes_mut().iter_mut() { + scene.avatar_scene_updates.avatar_equipped_data.insert( + entity_id, + new_avatar_equipped_data + .clone() + .expect("value was assigned above"), + ); + } + SceneCrdtStateProtoComponents::get_avatar_equipped_data_mut(&mut self.crdt_state) + .put(entity_id, new_avatar_equipped_data); + } + + let new_player_identity_data = Some(profile.to_pb_player_identity_data()); + let player_identity_data_component = + SceneCrdtStateProtoComponents::get_player_identity_data(&self.crdt_state); + let player_identity_data_value = player_identity_data_component + .get(&entity_id) + .and_then(|v| v.value.clone()); + if player_identity_data_value != new_player_identity_data { + // Push dirty state in all the scenes + let mut scene_runner = DclGlobal::singleton().bind().scene_runner.clone(); + let mut scene_runner = scene_runner.bind_mut(); + for (_, scene) in scene_runner.get_all_scenes_mut().iter_mut() { + scene.avatar_scene_updates.player_identity_data.insert( + entity_id, + new_player_identity_data + .clone() + .expect("value was assigned above"), + ); + } + + SceneCrdtStateProtoComponents::get_player_identity_data_mut(&mut self.crdt_state) + .put(entity_id, new_player_identity_data); + } } pub fn spawn_voice_channel( @@ -265,4 +451,82 @@ impl AvatarScene { .unwrap() .call("push_voice_frame".into(), &[frame.to_variant()]); } + + // This function should be only called in the first tick + pub fn first_sync_crdt_state( + &self, + target_crdt_state: &mut SceneCrdtState, + filter_by_scene_id: Option, + ) { + for entity_number in Self::FROM_ENTITY_ID..Self::MAX_ENTITY_ID { + let (local_version, local_live) = + self.crdt_state.entities.get_entity_stat(entity_number); + let (target_version, target_live) = + target_crdt_state.entities.get_entity_stat(entity_number); + + if local_version != target_version || local_live != target_live { + if *local_live { + target_crdt_state + .entities + .try_init(SceneEntityId::new(entity_number, *local_version)); + } else { + target_crdt_state + .entities + .kill(SceneEntityId::new(entity_number, *local_version - 1)); + } + } + } + + let local_transform_component = self.crdt_state.get_transform(); + let local_player_identity_data = + SceneCrdtStateProtoComponents::get_player_identity_data(&self.crdt_state); + let local_avatar_equipped_data = + SceneCrdtStateProtoComponents::get_avatar_equipped_data(&self.crdt_state); + let local_avatar_base = SceneCrdtStateProtoComponents::get_avatar_base(&self.crdt_state); + + for (entity_id, avatar_scene) in self.avatar_godot_scene.iter() { + let target_transform_component = target_crdt_state.get_transform_mut(); + + let null_transform: bool = if let Some(scene_id_int) = filter_by_scene_id { + scene_id_int.0 != avatar_scene.bind().get_current_parcel_scene_id() + } else { + false + }; + + if null_transform { + if let Some(value) = target_transform_component.get(entity_id) { + if value.value.is_some() { + target_transform_component.put(*entity_id, None); + } + } + } else { + // todo: transform to local coordinates + sync_crdt_lww_component!( + entity_id, + target_transform_component, + local_transform_component + ); + } + + let target_player_identity_data = + SceneCrdtStateProtoComponents::get_player_identity_data_mut(target_crdt_state); + sync_crdt_lww_component!( + entity_id, + target_player_identity_data, + local_player_identity_data + ); + + let target_avatar_base = + SceneCrdtStateProtoComponents::get_avatar_base_mut(target_crdt_state); + sync_crdt_lww_component!(entity_id, target_avatar_base, local_avatar_base); + + let target_avatar_equipped_data = + SceneCrdtStateProtoComponents::get_avatar_equipped_data_mut(target_crdt_state); + sync_crdt_lww_component!( + entity_id, + target_avatar_equipped_data, + local_avatar_equipped_data + ); + } + } } diff --git a/rust/decentraland-godot-lib/src/common/rpc.rs b/rust/decentraland-godot-lib/src/common/rpc.rs index e30123bc..4503dc9c 100644 --- a/rust/decentraland-godot-lib/src/common/rpc.rs +++ b/rust/decentraland-godot-lib/src/common/rpc.rs @@ -17,6 +17,34 @@ pub struct SpawnResponse { pub ens: Option, } +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct AvatarForUserData { + pub body_shape: String, + pub skin_color: String, + pub hair_color: String, + pub eye_color: String, + pub wearables: Vec, + pub snapshots: Option, +} + +#[derive(Serialize, Debug)] +pub struct Snapshots { + pub face256: String, + pub body: String, +} + +#[derive(Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct UserData { + pub display_name: String, + pub public_key: Option, + pub has_connected_web3: bool, + pub user_id: String, + pub version: u32, + pub avatar: Option, +} + #[derive(Debug, Clone)] pub struct RpcResultSender(Arc>>>); diff --git a/rust/decentraland-godot-lib/src/comms/livekit.rs b/rust/decentraland-godot-lib/src/comms/livekit.rs index 47a823a2..591fb025 100644 --- a/rust/decentraland-godot-lib/src/comms/livekit.rs +++ b/rust/decentraland-godot-lib/src/comms/livekit.rs @@ -125,7 +125,8 @@ impl LivekitRoom { match message.message { ToSceneMessage::Rfc4(rfc4::packet::Message::Position(position)) => { - avatar_scene.update_transform(peer.alias, &position); + avatar_scene + .update_avatar_transform_with_rfc4_position(peer.alias, &position); } ToSceneMessage::Rfc4(rfc4::packet::Message::Chat(chat)) => { let address = format!("{:#x}", message.address); diff --git a/rust/decentraland-godot-lib/src/comms/profile.rs b/rust/decentraland-godot-lib/src/comms/profile.rs index 790f8a4f..50feb80f 100644 --- a/rust/decentraland-godot-lib/src/comms/profile.rs +++ b/rust/decentraland-godot-lib/src/comms/profile.rs @@ -1,6 +1,11 @@ use godot::prelude::*; use serde::{Deserialize, Serialize}; +use crate::dcl::components::proto_components::{ + common::Color3, + sdk::components::{PbAvatarBase, PbAvatarEquippedData, PbPlayerIdentityData}, +}; + #[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)] pub struct AvatarColor3 { pub r: f32, @@ -31,6 +36,16 @@ impl From<&AvatarColor> for godot::prelude::Color { } } +impl From<&AvatarColor> for Color3 { + fn from(val: &AvatarColor) -> Self { + Color3 { + r: val.color.r, + g: val.color.g, + b: val.color.b, + } + } +} + impl From<&godot::prelude::Color> for AvatarColor { fn from(val: &godot::prelude::Color) -> Self { AvatarColor { @@ -271,6 +286,40 @@ impl SerializedProfile { }) .collect(); } + + pub fn to_pb_avatar_base(&self) -> PbAvatarBase { + PbAvatarBase { + skin_color: self.avatar.skin.map(|c| Color3::from(&c)), + eyes_color: self.avatar.eyes.map(|c| Color3::from(&c)), + hair_color: self.avatar.hair.map(|c| Color3::from(&c)), + body_shape_urn: self + .avatar + .body_shape + .as_deref() + .map(ToString::to_string) + .unwrap_or("urn:decentraland:off-chain:base-avatars:BaseFemale".to_owned()), + name: self.avatar.name.as_deref().unwrap_or("???").to_owned(), + } + } + pub fn to_pb_player_identity_data(&self) -> PbPlayerIdentityData { + PbPlayerIdentityData { + address: self.user_id.clone().unwrap_or("unknown".to_owned()), + is_guest: !self.has_connected_web3.as_ref().unwrap_or(&false), + } + } + pub fn to_pb_avatar_equipped_data(&self) -> PbAvatarEquippedData { + PbAvatarEquippedData { + wearable_urns: self.avatar.wearables.to_vec(), + emotes_urns: self + .avatar + .emotes + .as_ref() + .unwrap_or(&Vec::default()) + .iter() + .map(|emote| emote.urn.clone()) + .collect(), + } + } } #[derive(Serialize, Deserialize, Clone, Debug)] diff --git a/rust/decentraland-godot-lib/src/comms/ws_room.rs b/rust/decentraland-godot-lib/src/comms/ws_room.rs index 8c1b88e4..5a4f875b 100644 --- a/rust/decentraland-godot-lib/src/comms/ws_room.rs +++ b/rust/decentraland-godot-lib/src/comms/ws_room.rs @@ -408,7 +408,10 @@ impl WebSocketRoom { rfc4::packet::Message::Position(position) => { self.avatars .bind_mut() - .update_transform(update.from_alias, &position); + .update_avatar_transform_with_rfc4_position( + update.from_alias, + &position, + ); } rfc4::packet::Message::Chat(chat) => { let address = format!("{:#x}", peer.address); diff --git a/rust/decentraland-godot-lib/src/dcl/components/mod.rs b/rust/decentraland-godot-lib/src/dcl/components/mod.rs index 4367c052..706fe154 100644 --- a/rust/decentraland-godot-lib/src/dcl/components/mod.rs +++ b/rust/decentraland-godot-lib/src/dcl/components/mod.rs @@ -53,6 +53,11 @@ impl SceneEntityId { Self { number, version: 0 } } + // Note: this is not actually an invalid id, but it's used to represent an invalid id in some cases + pub const INVALID: SceneEntityId = Self { + number: 0xFFFF, + version: 0xFFFF, + }; pub const ROOT: SceneEntityId = Self::reserved(0); pub const PLAYER: SceneEntityId = Self::reserved(1); pub const CAMERA: SceneEntityId = Self::reserved(2); diff --git a/rust/decentraland-godot-lib/src/dcl/crdt/last_write_wins.rs b/rust/decentraland-godot-lib/src/dcl/crdt/last_write_wins.rs index a6fc1c08..cb8c82f5 100644 --- a/rust/decentraland-godot-lib/src/dcl/crdt/last_write_wins.rs +++ b/rust/decentraland-godot-lib/src/dcl/crdt/last_write_wins.rs @@ -28,7 +28,7 @@ pub trait LastWriteWinsComponentOperation { value: Option, ) -> bool; fn put(&mut self, entity: SceneEntityId, value: Option) -> bool; - fn get(&self, entity: SceneEntityId) -> Option<&LWWEntry>; + fn get(&self, entity: &SceneEntityId) -> Option<&LWWEntry>; } // The generic trait is only applied to the component with the types that sastifies the implementation @@ -83,8 +83,8 @@ impl LastWriteWinsComponentOperation for LastWriteWins { true } - fn get(&self, entity: SceneEntityId) -> Option<&LWWEntry> { - self.values.get(&entity) + fn get(&self, entity: &SceneEntityId) -> Option<&LWWEntry> { + self.values.get(entity) } fn put(&mut self, entity: SceneEntityId, value: Option) -> bool { @@ -173,23 +173,23 @@ mod test { get_i32_component_and_helper(); // 1) should not exist the entry - assert!(i32_component.get(entity).is_none()); + assert!(i32_component.get(&entity).is_none()); // 2) should put the initial value assert!(i32_component.set(entity, SceneCrdtTimestamp(0), Some(a_value))); - assert_eq!(i32_component.get(entity).unwrap().value, Some(a_value)); + assert_eq!(i32_component.get(&entity).unwrap().value, Some(a_value)); // 3) should not put a value with the same timestamp (should not update) assert!(!i32_component.set(entity, SceneCrdtTimestamp(0), Some(b_value))); - assert_eq!(i32_component.get(entity).unwrap().value, Some(a_value)); + assert_eq!(i32_component.get(&entity).unwrap().value, Some(a_value)); // 4) should put a value with a higher timestamp (should update) assert!(i32_component.set(entity, SceneCrdtTimestamp(1), Some(c_value))); - assert_eq!(i32_component.get(entity).unwrap().value, Some(c_value)); + assert_eq!(i32_component.get(&entity).unwrap().value, Some(c_value)); // 5) should not work if the timestamp is lower than the current one (should not update) assert!(!i32_component.set(entity, SceneCrdtTimestamp(0), Some(d_value))); - assert_eq!(i32_component.get(entity).unwrap().value, Some(c_value)); + assert_eq!(i32_component.get(&entity).unwrap().value, Some(c_value)); } #[test] @@ -197,9 +197,9 @@ mod test { let (mut i32_component, entity, a_value, _, _, _) = get_i32_component_and_helper(); assert!(i32_component.set(entity, SceneCrdtTimestamp(123), Some(a_value))); - assert_eq!(i32_component.get(entity).unwrap().value, Some(a_value)); + assert_eq!(i32_component.get(&entity).unwrap().value, Some(a_value)); assert!(i32_component.set_none(entity, SceneCrdtTimestamp(124))); - assert_eq!(i32_component.get(entity).unwrap().value, None); + assert_eq!(i32_component.get(&entity).unwrap().value, None); } #[test] @@ -230,13 +230,13 @@ mod test { assert!(!i32_component.take_dirty().is_empty()); // clean the flag // Check that the entry is present - assert!(i32_component.get(entity).is_some()); + assert!(i32_component.get(&entity).is_some()); // Remove the entry without marking as dirty i32_component.remove_without_dirty(entity); // Check that the entry is not present - assert!(i32_component.get(entity).is_none()); + assert!(i32_component.get(&entity).is_none()); // Check that the dirty set is empty assert!(i32_component.take_dirty().is_empty()); @@ -250,13 +250,13 @@ mod test { assert!(i32_component.set(entity, SceneCrdtTimestamp(0), Some(a_value))); // Check that the entry is present - assert!(i32_component.get(entity).is_some()); + assert!(i32_component.get(&entity).is_some()); // Remove the entry i32_component.remove(entity); // Check that the entry is not present - assert!(i32_component.get(entity).is_none()); + assert!(i32_component.get(&entity).is_none()); // Check that the entity is marked as dirty let dirty_set = i32_component.take_dirty(); @@ -279,7 +279,7 @@ mod test { assert!(component.set_from_binary(entity, timestamp, &mut reader)); // Check that the component has the correct value - let entry = component.get(entity).unwrap(); + let entry = component.get(&entity).unwrap(); assert_eq!(entry.timestamp, timestamp); assert_eq!(*entry.value.as_ref().unwrap(), data); } diff --git a/rust/decentraland-godot-lib/src/dcl/crdt/mod.rs b/rust/decentraland-godot-lib/src/dcl/crdt/mod.rs index 6d28a451..680555ca 100644 --- a/rust/decentraland-godot-lib/src/dcl/crdt/mod.rs +++ b/rust/decentraland-godot-lib/src/dcl/crdt/mod.rs @@ -298,7 +298,7 @@ mod test { Some(some_mesh_renderer), ); - let mesh_renderer = mesh_renderer_component.get(SceneEntityId::new(0, 0)); + let mesh_renderer = mesh_renderer_component.get(&SceneEntityId::new(0, 0)); assert_eq!( *mesh_renderer.unwrap(), LWWEntry { @@ -327,7 +327,7 @@ mod test { Some(new_mesh_renderer), ); - let mesh_renderer = mesh_renderer_component.get(SceneEntityId::new(0, 0)); + let mesh_renderer = mesh_renderer_component.get(&SceneEntityId::new(0, 0)); assert_eq!( *mesh_renderer.unwrap(), LWWEntry { @@ -352,7 +352,7 @@ mod test { SceneCrdtStateProtoComponents::get_mesh_renderer_mut(&mut crdt_state); mesh_renderer_component.set(SceneEntityId::new(0, 0), SceneCrdtTimestamp(0), None); - let mesh_renderer = mesh_renderer_component.get(SceneEntityId::new(0, 0)); + let mesh_renderer = mesh_renderer_component.get(&SceneEntityId::new(0, 0)); assert_eq!( *mesh_renderer.unwrap(), LWWEntry { @@ -362,7 +362,7 @@ mod test { ); mesh_renderer_component.remove(SceneEntityId::new(0, 0)); - let mesh_renderer = mesh_renderer_component.get(SceneEntityId::new(0, 0)); + let mesh_renderer = mesh_renderer_component.get(&SceneEntityId::new(0, 0)); assert!(mesh_renderer.is_none()); } diff --git a/rust/decentraland-godot-lib/src/dcl/mod.rs b/rust/decentraland-godot-lib/src/dcl/mod.rs index 981e754c..56178ebe 100644 --- a/rust/decentraland-godot-lib/src/dcl/mod.rs +++ b/rust/decentraland-godot-lib/src/dcl/mod.rs @@ -18,7 +18,11 @@ use std::{ }; #[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug)] -pub struct SceneId(pub u32); +pub struct SceneId(pub i32); + +impl SceneId { + pub const INVALID: SceneId = SceneId(-1); +} // scene metadata #[derive(Clone, Default, Debug)] diff --git a/rust/decentraland-godot-lib/src/godot_classes/dcl_audio_source.rs b/rust/decentraland-godot-lib/src/godot_classes/dcl_audio_source.rs index 6948dbd2..4d508429 100644 --- a/rust/decentraland-godot-lib/src/godot_classes/dcl_audio_source.rs +++ b/rust/decentraland-godot-lib/src/godot_classes/dcl_audio_source.rs @@ -28,7 +28,7 @@ pub struct DclAudioSource { dcl_audio_clip_url: GodotString, #[var] - dcl_scene_id: u32, + dcl_scene_id: i32, #[base] _base: Base, diff --git a/rust/decentraland-godot-lib/src/godot_classes/dcl_avatar.rs b/rust/decentraland-godot-lib/src/godot_classes/dcl_avatar.rs new file mode 100644 index 00000000..56d547c1 --- /dev/null +++ b/rust/decentraland-godot-lib/src/godot_classes/dcl_avatar.rs @@ -0,0 +1,175 @@ +use godot::engine::Node3D; +use godot::prelude::*; + +use crate::dcl::SceneId; + +use super::dcl_global::DclGlobal; + +#[derive(Property, Export)] +#[repr(i32)] +pub enum AvatarMovementType { + ExternalController = 0, + LerpTwoPoints = 1, +} + +#[derive(Default)] +struct LerpState { + initial_position: Vector3, + target_position: Vector3, + target_distance: f32, + factor: f32, +} + +#[derive(GodotClass)] +#[class(base=Node3D)] +pub struct DclAvatar { + #[export] + movement_type: AvatarMovementType, + + #[var] + current_parcel_scene_id: i32, + + #[var] + current_parcel_position: Vector2i, + + lerp_state: LerpState, + #[base] + base: Base, +} + +#[godot_api] +impl Node3DVirtual for DclAvatar { + fn init(base: Base) -> Self { + Self { + movement_type: AvatarMovementType::ExternalController, + current_parcel_scene_id: SceneId::INVALID.0, + current_parcel_position: Vector2i::new(i32::MAX, i32::MAX), + lerp_state: Default::default(), + base, + } + } +} + +#[godot_api] +impl DclAvatar { + #[signal] + fn change_parcel_position(&self, parcel_position: Vector2) {} + + #[signal] + fn change_scene_id(&self, new_scene_id: i32, prev_scene_id: i32) {} + + #[func] + pub fn set_target_position(&mut self, new_target: Transform3D) { + self.lerp_state.target_distance = self + .lerp_state + .target_position + .distance_to(new_target.origin); + + self.lerp_state.initial_position = self.lerp_state.target_position; + self.lerp_state.target_position = new_target.origin; + self.lerp_state.factor = 0.0; + + // TODO: check euler order + self.base + .set_global_rotation(new_target.basis.to_euler(EulerOrder::YXZ)); + self.base + .set_global_position(self.lerp_state.initial_position); + + self.update_parcel_position(self.lerp_state.target_position); + } + + #[func] + pub fn update_parcel_position(&mut self, position: Vector3) -> bool { + let godot_parcel_position = position / 16.0; + let parcel_position = Vector2i::new( + f32::floor(godot_parcel_position.x) as i32, + f32::floor(-godot_parcel_position.z) as i32, + ); + + if self.current_parcel_position != parcel_position { + self.current_parcel_position = parcel_position; + self.base.call_deferred( + "emit_signal".into(), + &[ + "change_parcel_position".to_variant(), + parcel_position.to_variant(), + ], + ); + + let scene_runner = DclGlobal::singleton().bind().get_scene_runner(); + let scene_id: i32 = scene_runner + .bind() + .get_scene_id_by_parcel_position(parcel_position); + + if self.current_parcel_scene_id != scene_id { + let prev_scene_id = self.current_parcel_scene_id; + self.current_parcel_scene_id = scene_id; + self.base.call_deferred( + "emit_signal".into(), + &[ + "change_scene_id".to_variant(), + scene_id.to_variant(), + prev_scene_id.to_variant(), + ], + ); + } + return true; + } else if self.current_parcel_scene_id == -1 { + let scene_runner = DclGlobal::singleton().bind().get_scene_runner(); + let scene_id: i32 = scene_runner + .bind() + .get_scene_id_by_parcel_position(parcel_position); + + if scene_id != SceneId::INVALID.0 { + let prev_scene_id = self.current_parcel_scene_id; + self.current_parcel_scene_id = scene_id; + self.base.call_deferred( + "emit_signal".into(), + &[ + "change_scene_id".to_variant(), + scene_id.to_variant(), + prev_scene_id.to_variant(), + ], + ); + return true; + } + } + false + } + + #[func] + fn process(&mut self, dt: f64) { + match self.movement_type { + AvatarMovementType::ExternalController => { + self.lerp_state.factor += dt as f32; + if self.lerp_state.factor > 0.1 { + self.update_parcel_position(self.base.get_global_position()); + } + } + AvatarMovementType::LerpTwoPoints => { + self.lerp_state.factor += 10.0 * dt as f32; + if self.lerp_state.factor < 1.0 { + if self.lerp_state.factor > 1.0 { + self.lerp_state.factor = 1.0; + } + + self.base.set_global_position( + self.lerp_state + .initial_position + .lerp(self.lerp_state.target_position, self.lerp_state.factor), + ); + + if self.lerp_state.target_distance > 0.0 { + if self.lerp_state.target_distance > 0.6 { + self.base.call("set_running".into(), &[]); + } else { + self.base.call("set_walking".into(), &[]); + } + } + } else if self.lerp_state.factor > 1.5 { + self.base.call("set_idle".into(), &[]); + } + } + } + } +} diff --git a/rust/decentraland-godot-lib/src/godot_classes/dcl_gltf_container.rs b/rust/decentraland-godot-lib/src/godot_classes/dcl_gltf_container.rs index c2758ba9..a7346801 100644 --- a/rust/decentraland-godot-lib/src/godot_classes/dcl_gltf_container.rs +++ b/rust/decentraland-godot-lib/src/godot_classes/dcl_gltf_container.rs @@ -1,6 +1,9 @@ use godot::engine::Node3D; use godot::prelude::*; +use crate::dcl::components::SceneEntityId; +use crate::dcl::SceneId; + #[repr(i32)] #[derive(Property, Export, PartialEq, Debug)] pub enum GltfContainerLoadingState { @@ -92,10 +95,10 @@ impl NodeVirtual for DclGltfContainer { fn init(base: Base) -> Self { Self { dcl_gltf_src: "".into(), - dcl_scene_id: -1, + dcl_scene_id: SceneId::INVALID.0, dcl_visible_cmask: 0, dcl_invisible_cmask: 3, - dcl_entity_id: -1, + dcl_entity_id: SceneEntityId::INVALID.as_i32(), dcl_gltf_loading_state: GltfContainerLoadingState::Unknown, _base: base, } diff --git a/rust/decentraland-godot-lib/src/godot_classes/dcl_scene_node.rs b/rust/decentraland-godot-lib/src/godot_classes/dcl_scene_node.rs index 0794ede4..bfa9cf82 100644 --- a/rust/decentraland-godot-lib/src/godot_classes/dcl_scene_node.rs +++ b/rust/decentraland-godot-lib/src/godot_classes/dcl_scene_node.rs @@ -4,7 +4,7 @@ use godot::prelude::*; #[derive(GodotClass)] #[class(init, base=Node3D)] pub struct DclSceneNode { - scene_id: u32, + scene_id: i32, is_global: bool, @@ -16,7 +16,7 @@ pub struct DclSceneNode { #[godot_api] impl DclSceneNode { - pub fn new_alloc(scene_id: u32, is_global: bool) -> Gd { + pub fn new_alloc(scene_id: i32, is_global: bool) -> Gd { let mut obj = Gd::with_base(|_base| { // accepts the base and returns a constructed object containing it DclSceneNode { @@ -34,7 +34,7 @@ impl DclSceneNode { } #[func] - fn get_scene_id(&self) -> u32 { + fn get_scene_id(&self) -> i32 { self.scene_id } diff --git a/rust/decentraland-godot-lib/src/godot_classes/dcl_video_player.rs b/rust/decentraland-godot-lib/src/godot_classes/dcl_video_player.rs index 9d17cc2e..4d832fec 100644 --- a/rust/decentraland-godot-lib/src/godot_classes/dcl_video_player.rs +++ b/rust/decentraland-godot-lib/src/godot_classes/dcl_video_player.rs @@ -18,7 +18,7 @@ pub struct DclVideoPlayer { base: Base, #[var] - dcl_scene_id: u32, + dcl_scene_id: i32, pub resolve_resource_sender: Option>, } diff --git a/rust/decentraland-godot-lib/src/godot_classes/mod.rs b/rust/decentraland-godot-lib/src/godot_classes/mod.rs index 7890f30e..1cd71d44 100644 --- a/rust/decentraland-godot-lib/src/godot_classes/mod.rs +++ b/rust/decentraland-godot-lib/src/godot_classes/mod.rs @@ -1,5 +1,6 @@ pub mod dcl_audio_source; pub mod dcl_audio_stream; +pub mod dcl_avatar; pub mod dcl_avatar_modifier_area_3d; pub mod dcl_camera_3d; pub mod dcl_camera_mode_area_3d; diff --git a/rust/decentraland-godot-lib/src/godot_classes/portables/mod.rs b/rust/decentraland-godot-lib/src/godot_classes/portables/mod.rs index f9d98cb1..24631f30 100644 --- a/rust/decentraland-godot-lib/src/godot_classes/portables/mod.rs +++ b/rust/decentraland-godot-lib/src/godot_classes/portables/mod.rs @@ -208,7 +208,7 @@ impl DclPortableExperienceController { } #[func] - pub fn announce_killed_by_scene_id(&mut self, killed_scene_id: u32) -> GodotString { + pub fn announce_killed_by_scene_id(&mut self, killed_scene_id: i32) -> GodotString { let killed_scene_id = SceneId(killed_scene_id); let Some(portable_experience) = self.portable_experiences @@ -248,7 +248,7 @@ impl DclPortableExperienceController { pid: GodotString, success: bool, name: GodotString, - scene_id: u32, + scene_id: i32, ) { let Some(portable_experience) = self.portable_experiences.get_mut(&pid.to_string()) else { tracing::error!("announce_spawned: portable experience not found"); diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/animator.rs b/rust/decentraland-godot-lib/src/scene_runner/components/animator.rs index b629e3a6..7583d7c7 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/animator.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/animator.rs @@ -29,7 +29,7 @@ pub fn update_animator(scene: &mut Scene, crdt_state: &mut SceneCrdtState) { let animator_component = SceneCrdtStateProtoComponents::get_animator(crdt_state); for entity in animator_dirty { - let new_value = animator_component.get(*entity); + let new_value = animator_component.get(entity); if new_value.is_none() { continue; } diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/audio_source.rs b/rust/decentraland-godot-lib/src/scene_runner/components/audio_source.rs index 12e283f5..f15072e1 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/audio_source.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/audio_source.rs @@ -23,7 +23,7 @@ pub fn update_audio_source( if let Some(audio_source_dirty) = dirty_lww_components.get(&SceneComponentId::AUDIO_SOURCE) { for entity in audio_source_dirty { - let new_value = audio_source_component.get(*entity); + let new_value = audio_source_component.get(entity); if new_value.is_none() { scene.audio_sources.remove(entity); continue; diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/audio_stream.rs b/rust/decentraland-godot-lib/src/scene_runner/components/audio_stream.rs index a0802ca7..b3a93bd1 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/audio_stream.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/audio_stream.rs @@ -31,7 +31,7 @@ pub fn update_audio_stream( for entity in audio_stream_dirty { let exist_current_node = godot_dcl_scene.get_godot_entity_node(entity).is_some(); - let next_value = if let Some(new_value) = audio_stream_component.get(*entity) { + let next_value = if let Some(new_value) = audio_stream_component.get(entity) { new_value.value.as_ref() } else { None diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/avatar_attach.rs b/rust/decentraland-godot-lib/src/scene_runner/components/avatar_attach.rs index 5d7e79f2..bf5a5f64 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/avatar_attach.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/avatar_attach.rs @@ -17,7 +17,7 @@ pub fn update_avatar_attach(scene: &mut Scene, crdt_state: &mut SceneCrdtState) if let Some(avatar_attach_dirty) = dirty_lww_components.get(&SceneComponentId::AVATAR_ATTACH) { for entity in avatar_attach_dirty { - let new_value = avatar_attach_component.get(*entity); + let new_value = avatar_attach_component.get(entity); if new_value.is_none() { continue; } diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/avatar_data.rs b/rust/decentraland-godot-lib/src/scene_runner/components/avatar_data.rs new file mode 100644 index 00000000..052da8cc --- /dev/null +++ b/rust/decentraland-godot-lib/src/scene_runner/components/avatar_data.rs @@ -0,0 +1,53 @@ +use crate::{ + dcl::crdt::{ + last_write_wins::LastWriteWinsComponentOperation, SceneCrdtState, + SceneCrdtStateProtoComponents, + }, + scene_runner::scene::Scene, +}; + +pub fn update_avatar_scene_updates(scene: &mut Scene, crdt_state: &mut SceneCrdtState) { + for entity_id in scene.avatar_scene_updates.deleted_entities.drain() { + crdt_state.entities.kill(entity_id); + } + + { + let transform_component = crdt_state.get_transform_mut(); + for (entity_id, value) in scene.avatar_scene_updates.transform.drain() { + transform_component.put(entity_id, value); + } + } + + { + let avatar_base_component = SceneCrdtStateProtoComponents::get_avatar_base_mut(crdt_state); + for (entity_id, value) in scene.avatar_scene_updates.avatar_base.drain() { + avatar_base_component.put(entity_id, Some(value)); + } + } + + { + let player_identity_data_component = + SceneCrdtStateProtoComponents::get_player_identity_data_mut(crdt_state); + for (entity_id, value) in scene.avatar_scene_updates.player_identity_data.drain() { + player_identity_data_component.put(entity_id, Some(value)); + } + } + + { + let avatar_equipped_data_component = + SceneCrdtStateProtoComponents::get_avatar_equipped_data_mut(crdt_state); + for (entity_id, value) in scene.avatar_scene_updates.avatar_equipped_data.drain() { + avatar_equipped_data_component.put(entity_id, Some(value)); + } + } + + // { + // let avatar_emote_command_component = + // SceneCrdtStateProtoComponents::get_avatar_emote_command_mut(crdt_state); + // for (entity_id, value) in + // scene.avatar_scene_updates.avatar_emote_command.drain() + // { + // avatar_emote_command_component.put(entity_id, Some(value)); + // } + // } +} diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/avatar_modifier_area.rs b/rust/decentraland-godot-lib/src/scene_runner/components/avatar_modifier_area.rs index 2033cad0..71fdc4c9 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/avatar_modifier_area.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/avatar_modifier_area.rs @@ -21,7 +21,7 @@ pub fn update_avatar_modifier_area(scene: &mut Scene, crdt_state: &mut SceneCrdt dirty_lww_components.get(&SceneComponentId::AVATAR_MODIFIER_AREA) { for entity in avatar_modifier_area_dirty { - let new_value = avatar_modifier_area_component.get(*entity); + let new_value = avatar_modifier_area_component.get(entity); let Some(new_value) = new_value else { continue; // no value, continue @@ -31,8 +31,7 @@ pub fn update_avatar_modifier_area(scene: &mut Scene, crdt_state: &mut SceneCrdt let new_value = new_value.value.clone(); - let existing = - node_3d.try_get_node_as::(NodePath::from("DCLAvatarModifierArea3D")); + let existing = node_3d.try_get_node_as::(NodePath::from("AvatarModifierArea")); if new_value.is_none() { if let Some(avatar_modifier_area_node) = existing { @@ -77,7 +76,7 @@ pub fn update_avatar_modifier_area(scene: &mut Scene, crdt_state: &mut SceneCrdt .bind_mut() .set_avatar_modifiers(modifiers); avatar_modifier_area.bind_mut().set_exclude_ids(exclude_ids); - avatar_modifier_area.set_name(GodotString::from("DCLAvatarModifierArea3D")); + avatar_modifier_area.set_name(GodotString::from("AvatarModifierArea")); node_3d.add_child(avatar_modifier_area.clone().upcast()); } } diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/avatar_shape.rs b/rust/decentraland-godot-lib/src/scene_runner/components/avatar_shape.rs index c9260859..932d6d97 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/avatar_shape.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/avatar_shape.rs @@ -17,7 +17,7 @@ pub fn update_avatar_shape(scene: &mut Scene, crdt_state: &mut SceneCrdtState) { if let Some(avatar_shape_dirty) = dirty_lww_components.get(&SceneComponentId::AVATAR_SHAPE) { for entity in avatar_shape_dirty { - let new_value = avatar_shape_component.get(*entity); + let new_value = avatar_shape_component.get(entity); if new_value.is_none() { continue; } diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/camera_mode_area.rs b/rust/decentraland-godot-lib/src/scene_runner/components/camera_mode_area.rs index 9062f98f..7c5b1f70 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/camera_mode_area.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/camera_mode_area.rs @@ -21,7 +21,7 @@ pub fn update_camera_mode_area(scene: &mut Scene, crdt_state: &mut SceneCrdtStat dirty_lww_components.get(&SceneComponentId::CAMERA_MODE_AREA) { for entity in camera_mode_area_dirty { - let new_value = camera_mode_area_component.get(*entity); + let new_value = camera_mode_area_component.get(entity); let Some(new_value) = new_value else { continue; // no value, continue diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/gltf_container.rs b/rust/decentraland-godot-lib/src/scene_runner/components/gltf_container.rs index e7c7f1ee..d35ecfaa 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/gltf_container.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/gltf_container.rs @@ -20,7 +20,7 @@ pub fn update_gltf_container(scene: &mut Scene, crdt_state: &mut SceneCrdtState) if let Some(gltf_container_dirty) = dirty_lww_components.get(&SceneComponentId::GLTF_CONTAINER) { for entity in gltf_container_dirty { - let new_value = gltf_container_component.get(*entity); + let new_value = gltf_container_component.get(entity); if new_value.is_none() { continue; } @@ -63,7 +63,7 @@ pub fn update_gltf_container(scene: &mut Scene, crdt_state: &mut SceneCrdtState) new_gltf .bind_mut() .set_dcl_gltf_src(GodotString::from(new_value.src)); - new_gltf.bind_mut().set_dcl_scene_id(scene_id as i32); + new_gltf.bind_mut().set_dcl_scene_id(scene_id); new_gltf.bind_mut().set_dcl_entity_id(entity.as_i32()); new_gltf .bind_mut() @@ -84,7 +84,6 @@ pub fn update_gltf_container(scene: &mut Scene, crdt_state: &mut SceneCrdtState) pub fn sync_gltf_loading_state(scene: &mut Scene, crdt_state: &mut SceneCrdtState) { let godot_dcl_scene = &mut scene.godot_dcl_scene; - let scene_id = scene.scene_id.0; let gltf_container_loading_state_component = SceneCrdtStateProtoComponents::get_gltf_container_loading_state_mut(crdt_state); @@ -94,7 +93,7 @@ pub fn sync_gltf_loading_state(scene: &mut Scene, crdt_state: &mut SceneCrdtStat .1 .try_get_node_as::(NodePath::from("GltfContainer")); - let current_state = match gltf_container_loading_state_component.get(*entity) { + let current_state = match gltf_container_loading_state_component.get(entity) { Some(state) => match state.value.as_ref() { Some(value) => GltfContainerLoadingState::from_proto(value.current_state()), _ => GltfContainerLoadingState::Unknown, @@ -109,14 +108,6 @@ pub fn sync_gltf_loading_state(scene: &mut Scene, crdt_state: &mut SceneCrdtStat None => GltfContainerLoadingState::Unknown, }; - tracing::info!( - " scene_id {:?} \t entity_id{:?} \t current_state {:?} \t current_state_godot: {:?}", - scene_id, - entity, - current_state, - current_state_godot - ); - if current_state_godot != current_state { gltf_container_loading_state_component.put(*entity, Some(crate::dcl::components::proto_components::sdk::components::PbGltfContainerLoadingState { current_state: current_state_godot.to_i32() })); } diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/material.rs b/rust/decentraland-godot-lib/src/scene_runner/components/material.rs index 5a30e878..016836d5 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/material.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/material.rs @@ -35,7 +35,7 @@ pub fn update_material(scene: &mut Scene, crdt_state: &mut SceneCrdtState) { if let Some(material_dirty) = dirty_lww_components.get(&SceneComponentId::MATERIAL) { for entity in material_dirty { - let new_value = material_component.get(*entity); + let new_value = material_component.get(entity); if new_value.is_none() { continue; } diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/mesh_collider.rs b/rust/decentraland-godot-lib/src/scene_runner/components/mesh_collider.rs index 286c2e22..50b6fa62 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/mesh_collider.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/mesh_collider.rs @@ -102,7 +102,7 @@ pub fn update_mesh_collider(scene: &mut Scene, crdt_state: &mut SceneCrdtState) let mesh_collider_component = SceneCrdtStateProtoComponents::get_mesh_collider(crdt_state); for entity in mesh_collider_dirty { - let new_value = mesh_collider_component.get(*entity); + let new_value = mesh_collider_component.get(entity); if new_value.is_none() { continue; } @@ -135,14 +135,10 @@ pub fn update_mesh_collider(scene: &mut Scene, crdt_state: &mut SceneCrdtState) if add_to_base { animatable_body_3d.set_name(GodotString::from("MeshCollider")); - animatable_body_3d.set_meta( - "dcl_entity_id".into(), - (entity.as_usize() as i32).to_variant(), - ); - animatable_body_3d.set_meta( - "dcl_scene_id".into(), - (scene.scene_id.0 as i32).to_variant(), - ); + animatable_body_3d + .set_meta("dcl_entity_id".into(), (entity.as_i32()).to_variant()); + animatable_body_3d + .set_meta("dcl_scene_id".into(), (scene.scene_id.0).to_variant()); node_3d.add_child(animatable_body_3d.upcast()); } diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/mesh_renderer.rs b/rust/decentraland-godot-lib/src/scene_runner/components/mesh_renderer.rs index 367175c2..0cf81e14 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/mesh_renderer.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/mesh_renderer.rs @@ -70,7 +70,7 @@ pub fn update_mesh_renderer(scene: &mut Scene, crdt_state: &mut SceneCrdtState) let mesh_renderer_component = SceneCrdtStateProtoComponents::get_mesh_renderer(crdt_state); for entity in mesh_renderer_dirty { - let new_value = mesh_renderer_component.get(*entity); + let new_value = mesh_renderer_component.get(entity); if new_value.is_none() { continue; } diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/mod.rs b/rust/decentraland-godot-lib/src/scene_runner/components/mod.rs index 1e112387..2637d52d 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/mod.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/mod.rs @@ -2,6 +2,7 @@ pub mod animator; pub mod audio_source; pub mod audio_stream; pub mod avatar_attach; +pub mod avatar_data; pub mod avatar_modifier_area; pub mod avatar_shape; pub mod billboard; diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/pointer_events.rs b/rust/decentraland-godot-lib/src/scene_runner/components/pointer_events.rs index d2eb5705..23adb9d2 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/pointer_events.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/pointer_events.rs @@ -84,7 +84,7 @@ pub fn update_scene_pointer_events(scene: &mut Scene, crdt_state: &mut SceneCrdt SceneCrdtStateProtoComponents::get_pointer_events(crdt_state); for entity in pointer_events_dirty { - let new_value = pointer_events_component.get(*entity); + let new_value = pointer_events_component.get(entity); let new_value = if let Some(value) = new_value { value.value.clone() } else { diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/raycast.rs b/rust/decentraland-godot-lib/src/scene_runner/components/raycast.rs index 99aa21a7..5a7c374b 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/raycast.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/raycast.rs @@ -29,7 +29,7 @@ pub fn update_raycasts(scene: &mut Scene, crdt_state: &mut SceneCrdtState) { if let Some(raycast_dirty) = dirty_lww_components.get(&SceneComponentId::RAYCAST) { for entity in raycast_dirty { - let new_value = raycast_component.get(*entity); + let new_value = raycast_component.get(entity); if new_value.is_none() { scene.continuos_raycast.remove(entity); continue; @@ -59,7 +59,7 @@ pub fn update_raycasts(scene: &mut Scene, crdt_state: &mut SceneCrdtState) { let Some(entity_node) = scene.godot_dcl_scene.get_node_3d(entity) else { continue; }; - let Some(raycast) = raycast_component.get(*entity) else { + let Some(raycast) = raycast_component.get(entity) else { continue; }; @@ -253,7 +253,7 @@ fn get_raycast_hit( ) .to::(); - if dcl_scene_id != scene.scene_id.0 as i32 { + if dcl_scene_id != scene.scene_id.0 { return None; } diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/text_shape.rs b/rust/decentraland-godot-lib/src/scene_runner/components/text_shape.rs index a7527544..6e605810 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/text_shape.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/text_shape.rs @@ -17,7 +17,7 @@ pub fn update_text_shape(scene: &mut Scene, crdt_state: &mut SceneCrdtState) { let text_shape_component = SceneCrdtStateProtoComponents::get_text_shape(crdt_state); for entity in text_shape_dirty { - let new_value = text_shape_component.get(*entity); + let new_value = text_shape_component.get(entity); if new_value.is_none() { continue; } diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/transform_and_parent.rs b/rust/decentraland-godot-lib/src/scene_runner/components/transform_and_parent.rs index 36c7b12a..e025a9a8 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/transform_and_parent.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/transform_and_parent.rs @@ -41,7 +41,7 @@ pub fn update_transform_and_parent(scene: &mut Scene, crdt_state: &mut SceneCrdt if let Some(dirty_transform) = dirty_lww_components.get(&SceneComponentId::TRANSFORM) { for entity in dirty_transform { - let value = if let Some(entry) = transform_component.get(*entity) { + let value = if let Some(entry) = transform_component.get(entity) { entry.value.clone() } else { None diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/ui/scene_ui.rs b/rust/decentraland-godot-lib/src/scene_runner/components/ui/scene_ui.rs index de1d234a..b687359a 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/ui/scene_ui.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/ui/scene_ui.rs @@ -227,7 +227,7 @@ pub fn update_scene_ui( let need_update_ui_canvas = { let ui_canvas_information_component = SceneCrdtStateProtoComponents::get_ui_canvas_information(crdt_state); - if let Some(entry) = ui_canvas_information_component.get(SceneEntityId::ROOT) { + if let Some(entry) = ui_canvas_information_component.get(&SceneEntityId::ROOT) { if let Some(current_value) = entry.value.as_ref() { current_value != ui_canvas_information } else { diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_background.rs b/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_background.rs index 65f87163..5314819a 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_background.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_background.rs @@ -19,7 +19,7 @@ pub fn update_ui_background(scene: &mut Scene, crdt_state: &mut SceneCrdtState) if let Some(dirty_ui_background) = dirty_lww_components.get(&SceneComponentId::UI_BACKGROUND) { for entity in dirty_ui_background { - let value = if let Some(entry) = ui_background_component.get(*entity) { + let value = if let Some(entry) = ui_background_component.get(entity) { entry.value.clone() } else { None diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_dropdown.rs b/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_dropdown.rs index 7af54715..976e3adf 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_dropdown.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_dropdown.rs @@ -19,7 +19,7 @@ pub fn update_ui_dropdown(scene: &mut Scene, crdt_state: &mut SceneCrdtState) { if let Some(dirty_ui_dropdown) = dirty_lww_components.get(&SceneComponentId::UI_DROPDOWN) { for entity in dirty_ui_dropdown { - let value = if let Some(entry) = ui_dropdown_component.get(*entity) { + let value = if let Some(entry) = ui_dropdown_component.get(entity) { entry.value.clone() } else { None diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_input.rs b/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_input.rs index 63d9936c..ce113ca8 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_input.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_input.rs @@ -19,7 +19,7 @@ pub fn update_ui_input(scene: &mut Scene, crdt_state: &mut SceneCrdtState) { if let Some(dirty_ui_input) = dirty_lww_components.get(&SceneComponentId::UI_INPUT) { for entity in dirty_ui_input { - let value = if let Some(entry) = ui_input_component.get(*entity) { + let value = if let Some(entry) = ui_input_component.get(entity) { entry.value.clone() } else { None diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_text.rs b/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_text.rs index 99ee8295..c41ca989 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_text.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_text.rs @@ -19,7 +19,7 @@ pub fn update_ui_text(scene: &mut Scene, crdt_state: &mut SceneCrdtState) { if let Some(dirty_ui_text) = dirty_lww_components.get(&SceneComponentId::UI_TEXT) { for entity in dirty_ui_text { - let value = if let Some(entry) = ui_text_component.get(*entity) { + let value = if let Some(entry) = ui_text_component.get(entity) { entry.value.clone() } else { None diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_transform.rs b/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_transform.rs index 05e7619b..4ff1aea1 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_transform.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/ui/ui_transform.rs @@ -18,7 +18,7 @@ pub fn update_ui_transform(scene: &mut Scene, crdt_state: &mut SceneCrdtState) { if let Some(dirty_transform) = dirty_lww_components.get(&SceneComponentId::UI_TRANSFORM) { for entity in dirty_transform { - let ui_transform = if let Some(entry) = ui_transform_component.get(*entity) { + let ui_transform = if let Some(entry) = ui_transform_component.get(entity) { entry.value.as_ref() } else { None diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/video_player.rs b/rust/decentraland-godot-lib/src/scene_runner/components/video_player.rs index 76be67b7..adc95549 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/video_player.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/video_player.rs @@ -55,7 +55,7 @@ pub fn update_video_player( for entity in video_player_dirty { let exist_current_node = godot_dcl_scene.get_godot_entity_node(entity).is_some(); - let next_value = if let Some(new_value) = video_player_component.get(*entity) { + let next_value = if let Some(new_value) = video_player_component.get(entity) { new_value.value.as_ref() } else { None diff --git a/rust/decentraland-godot-lib/src/scene_runner/components/visibility.rs b/rust/decentraland-godot-lib/src/scene_runner/components/visibility.rs index e50b16fd..37ad8f43 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/components/visibility.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/components/visibility.rs @@ -20,7 +20,7 @@ pub fn update_visibility(scene: &mut Scene, crdt_state: &mut SceneCrdtState) { }; for entity in visibility_dirty { - let new_value = visibility_component.get(*entity); + let new_value = visibility_component.get(entity); let Some(new_value) = new_value else { continue; diff --git a/rust/decentraland-godot-lib/src/scene_runner/scene.rs b/rust/decentraland-godot-lib/src/scene_runner/scene.rs index 84efffa2..1e93c3bd 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/scene.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/scene.rs @@ -10,7 +10,11 @@ use crate::{ dcl::{ components::{ material::DclMaterial, - proto_components::sdk::components::{common::RaycastHit, PbPointerEventsResult}, + proto_components::sdk::components::{ + common::RaycastHit, PbAvatarBase, PbAvatarEmoteCommand, PbAvatarEquippedData, + PbPlayerIdentityData, PbPointerEventsResult, + }, + transform_and_parent::DclTransformAndParent, SceneEntityId, }, js::SceneLogMessage, @@ -131,6 +135,17 @@ pub enum GlobalSceneType { PortableExperience, } +#[derive(Default)] +pub struct SceneAvatarUpdates { + pub transform: HashMap>, + pub player_identity_data: HashMap, + pub avatar_base: HashMap, + pub avatar_equipped_data: HashMap, + pub pointer_events_result: HashMap>, + pub avatar_emote_command: HashMap>, + pub deleted_entities: HashSet, +} + pub struct Scene { pub scene_id: SceneId, pub godot_dcl_scene: GodotDclScene, @@ -162,6 +177,8 @@ pub struct Scene { // Used by VideoPlayer and AudioStream pub audio_streams: HashMap>, pub video_players: HashMap>, + + pub avatar_scene_updates: SceneAvatarUpdates, } #[derive(Debug)] @@ -195,7 +212,7 @@ impl GodotDclRaycastResult { // } } -static SCENE_ID_MONOTONIC_COUNTER: once_cell::sync::Lazy = +static SCENE_ID_MONOTONIC_COUNTER: once_cell::sync::Lazy = once_cell::sync::Lazy::new(Default::default); impl Scene { @@ -245,6 +262,7 @@ impl Scene { audio_streams: HashMap::new(), video_players: HashMap::new(), scene_type, + avatar_scene_updates: Default::default(), } } @@ -297,6 +315,7 @@ impl Scene { audio_sources: HashMap::new(), audio_streams: HashMap::new(), video_players: HashMap::new(), + avatar_scene_updates: Default::default(), } } } diff --git a/rust/decentraland-godot-lib/src/scene_runner/scene_manager.rs b/rust/decentraland-godot-lib/src/scene_runner/scene_manager.rs index 73f94817..d828e781 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/scene_manager.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/scene_manager.rs @@ -88,7 +88,7 @@ impl SceneManager { // Testing a comment for the API #[func] - fn start_scene(&mut self, scene_definition: Dictionary, content_mapping: Dictionary) -> u32 { + fn start_scene(&mut self, scene_definition: Dictionary, content_mapping: Dictionary) -> i32 { // TODO: Inject wallet from creator let wallet = Wallet::new_local_wallet(); @@ -154,7 +154,7 @@ impl SceneManager { } #[func] - fn kill_scene(&mut self, scene_id: u32) -> bool { + fn kill_scene(&mut self, scene_id: i32) -> bool { let scene_id = SceneId(scene_id); if let Some(scene) = self.scenes.get_mut(&scene_id) { if let SceneState::Alive = scene.state { @@ -180,7 +180,7 @@ impl SceneManager { #[func] fn get_scene_content_mapping(&self, scene_id: i32) -> Dictionary { - if let Some(scene) = self.scenes.get(&SceneId(scene_id as u32)) { + if let Some(scene) = self.scenes.get(&SceneId(scene_id)) { return scene.content_mapping.clone(); } Dictionary::default() @@ -188,37 +188,37 @@ impl SceneManager { #[func] fn get_scene_title(&self, scene_id: i32) -> GodotString { - if let Some(scene) = self.scenes.get(&SceneId(scene_id as u32)) { + if let Some(scene) = self.scenes.get(&SceneId(scene_id)) { return GodotString::from(scene.definition.title.clone()); } GodotString::default() } #[func] - fn get_scene_id_by_parcel_position(&self, parcel_position: Vector2i) -> i32 { + pub fn get_scene_id_by_parcel_position(&self, parcel_position: Vector2i) -> i32 { for scene in self.scenes.values() { if let SceneType::Global(_) = scene.scene_type { continue; } if scene.definition.parcels.contains(&parcel_position) { - return scene.scene_id.0 as i32; + return scene.scene_id.0; } } - -1 + SceneId::INVALID.0 } #[func] fn get_scene_base_parcel(&self, scene_id: i32) -> Vector2i { - if let Some(scene) = self.scenes.get(&SceneId(scene_id as u32)) { + if let Some(scene) = self.scenes.get(&SceneId(scene_id)) { return scene.definition.base; } Vector2i::default() } fn compute_scene_distance(&mut self) { - self.current_parcel_scene_id = SceneId(u32::MAX); + self.current_parcel_scene_id = SceneId::INVALID; let mut player_global_position = self.player_node.get_global_transform().origin; player_global_position.x *= 0.0625; @@ -415,7 +415,7 @@ impl SceneManager { Ok(response) => match response { SceneResponse::Error(scene_id, msg) => { let mut arguments = VariantArray::new(); - arguments.push((scene_id.0 as i32).to_variant()); + arguments.push((scene_id.0).to_variant()); arguments.push((SceneLogLevel::SystemError as i32).to_variant()); arguments.push(self.total_time_seconds_time.to_variant()); arguments.push(GodotString::from(&msg).to_variant()); @@ -457,7 +457,7 @@ impl SceneManager { // enable logs for log in &logs { let mut arguments = VariantArray::new(); - arguments.push((scene_id.0 as i32).to_variant()); + arguments.push(scene_id.0.to_variant()); arguments.push((log.level as i32).to_variant()); arguments.push((log.timestamp as f32).to_variant()); arguments.push(GodotString::from(&log.message).to_variant()); @@ -519,7 +519,7 @@ impl SceneManager { ) .to::(); - let scene = self.scenes.get(&SceneId(dcl_scene_id as u32))?; + let scene = self.scenes.get(&SceneId(dcl_scene_id))?; let scene_position = scene.godot_dcl_scene.root_node_3d.get_position(); let raycast_data = RaycastHit::from_godot_raycast( scene_position, @@ -529,7 +529,7 @@ impl SceneManager { )?; Some(GodotDclRaycastResult { - scene_id: SceneId(dcl_scene_id as u32), + scene_id: SceneId(dcl_scene_id), entity_id: SceneEntityId::from_i32(dcl_entity_id), hit: raycast_data, }) @@ -602,7 +602,29 @@ impl SceneManager { } #[signal] - fn on_change_scene_id(scene_id: u32) {} + fn on_change_scene_id(scene_id: i32) {} + + pub fn get_all_scenes_mut(&mut self) -> &mut HashMap { + &mut self.scenes + } + + pub fn get_scene_mut(&mut self, scene_id: &SceneId) -> Option<&mut Scene> { + self.scenes.get_mut(scene_id) + } + + // this could be cached + pub fn get_global_scenes(&self) -> Vec { + self.scenes + .iter() + .filter(|(_scene_id, scene)| { + if let SceneType::Global(_) = scene.scene_type { + return true; + } + false + }) + .map(|(scene_id, _)| *scene_id) + .collect::>() + } } #[godot_api] @@ -621,7 +643,7 @@ impl NodeVirtual for SceneManager { sorted_scene_ids: vec![], dying_scene_ids: vec![], current_parcel_scene_id: SceneId(0), - last_current_parcel_scene_id: SceneId(u32::MAX), + last_current_parcel_scene_id: SceneId::INVALID, main_receiver_from_thread, thread_sender_to_main, diff --git a/rust/decentraland-godot-lib/src/scene_runner/update_scene.rs b/rust/decentraland-godot-lib/src/scene_runner/update_scene.rs index bb154e51..73ae0a24 100644 --- a/rust/decentraland-godot-lib/src/scene_runner/update_scene.rs +++ b/rust/decentraland-godot-lib/src/scene_runner/update_scene.rs @@ -8,6 +8,7 @@ use super::{ audio_source::update_audio_source, audio_stream::update_audio_stream, avatar_attach::update_avatar_attach, + avatar_data::update_avatar_scene_updates, avatar_modifier_area::update_avatar_modifier_area, avatar_shape::update_avatar_shape, billboard::update_billboard, @@ -26,7 +27,7 @@ use super::{ }, deleted_entities::update_deleted_entities, rpc_calls::process_rpcs, - scene::{Dirty, Scene, SceneUpdateState}, + scene::{Dirty, Scene, SceneType, SceneUpdateState}, }; use crate::{ common::rpc::RpcCalls, @@ -44,6 +45,7 @@ use crate::{ }, RendererResponse, SceneId, }, + godot_classes::dcl_global::DclGlobal, }; // @returns true if the scene was full processed, or false if it remains something to process @@ -73,7 +75,7 @@ pub fn _process_scene( let engine_info_component = SceneCrdtStateProtoComponents::get_engine_info_mut(crdt_state); let tick_number = - if let Some(entry) = engine_info_component.get(SceneEntityId::ROOT) { + if let Some(entry) = engine_info_component.get(&SceneEntityId::ROOT) { if let Some(value) = entry.value.as_ref() { value.tick_number + 1 } else { @@ -104,13 +106,28 @@ pub fn _process_scene( total_runtime: (Instant::now() - scene.start_time).as_secs_f32(), }), ); + + if tick_number == 0 { + let filter_by_scene_id = if let SceneType::Parcel = scene.scene_type { + Some(*current_parcel_scene_id) + } else { + None + }; + + DclGlobal::singleton() + .bind() + .avatars + .bind() + .first_sync_crdt_state(crdt_state, filter_by_scene_id); + } + false } SceneUpdateState::PrintLogs => { // enable logs for log in &scene.current_dirty.logs { let mut arguments = VariantArray::new(); - arguments.push((scene.scene_id.0 as i32).to_variant()); + arguments.push((scene.scene_id.0).to_variant()); arguments.push((log.level as i32).to_variant()); arguments.push((log.timestamp as f32).to_variant()); arguments.push(GodotString::from(&log.message).to_variant()); @@ -205,6 +222,8 @@ pub fn _process_scene( false } SceneUpdateState::ComputeCrdtState => { + update_avatar_scene_updates(scene, crdt_state); + // Set transform let camera_transform = DclTransformAndParent::from_godot( camera_global_transform, @@ -224,7 +243,7 @@ pub fn _process_scene( // Set camera mode let maybe_current_camera_mode = SceneCrdtStateProtoComponents::get_camera_mode(crdt_state) - .get(SceneEntityId::CAMERA) + .get(&SceneEntityId::CAMERA) .and_then(|camera_mode_value| { camera_mode_value.value.as_ref().map(|v| v.mode) }); @@ -238,7 +257,7 @@ pub fn _process_scene( // Set PointerLock let maybe_is_pointer_locked = SceneCrdtStateProtoComponents::get_pointer_lock(crdt_state) - .get(SceneEntityId::CAMERA) + .get(&SceneEntityId::CAMERA) .and_then(|pointer_lock_value| { pointer_lock_value .value