From 99e028e4e29830aa55e16659133b719fd69e95de Mon Sep 17 00:00:00 2001 From: Lean Mendoza Date: Mon, 25 Sep 2023 17:38:36 -0300 Subject: [PATCH] improve: move colliders generation to the content loading thread (#51) --- .github/workflows/ci.yml | 6 + godot/project.godot | 2 + .../decentraland_components/gltf_container.gd | 76 ++++----- godot/src/logic/content_manager.gd | 156 +++++++++++++++++- 4 files changed, 194 insertions(+), 46 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a3bb43a3..fd757d9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -217,6 +217,12 @@ jobs: # args: --manifest-path rust/decentraland-godot-lib/Cargo.toml --release --target=aarch64-apple-darwin - name: cargo test + if: runner.os == 'windows' + working-directory: rust/decentraland-godot-lib + run: cargo test --release + + - name: cargo test + if: runner.os != 'windows' working-directory: rust/decentraland-godot-lib run: cargo test diff --git a/godot/project.godot b/godot/project.godot index b6530f6a..b137f893 100644 --- a/godot/project.godot +++ b/godot/project.godot @@ -13,6 +13,8 @@ config_version=5 config/name="Decentraland Godot Rust" config/description="Decentraland Godot Client - Protocol Squad" run/main_scene="res://src/main.tscn" +run/disable_stdout=true +run/disable_stderr=true config/features=PackedStringArray("4.1") config/icon="res://decentraland_logo.png" diff --git a/godot/src/decentraland_components/gltf_container.gd b/godot/src/decentraland_components/gltf_container.gd index a9c84e3f..9ac50b81 100644 --- a/godot/src/decentraland_components/gltf_container.gd +++ b/godot/src/decentraland_components/gltf_container.gd @@ -17,8 +17,7 @@ const GodotGltfState = { Finished = 4, } var gltf_state: int = 0 - -var gltf_start_loading_time: int = 0 +var gltf_instance_req_id: int = 0 func _ready(): @@ -35,8 +34,6 @@ func load_gltf(): gltf_state = GodotGltfState.NotFound return - gltf_start_loading_time = Time.get_ticks_usec() - var fetching_resource = Global.content_manager.fetch_gltf(dcl_gltf_src, content_mapping) # TODO: should we set a timeout? @@ -62,30 +59,34 @@ func _on_gltf_loaded(resource_hash: String): gltf_state = GodotGltfState.FinishedWithError return - gltf_state = GodotGltfState.Finished - gltf_node = node.duplicate() + gltf_instance_req_id = Global.content_manager.instance_gltf_colliders( + node, dcl_visible_cmask, dcl_invisible_cmask, dcl_scene_id, dcl_entity_id + ) + Global.content_manager.gltf_node_collider_finishes.connect(self._on_gltf_instanced) -# var colliders_timestamp = Time.get_ticks_usec() - create_and_set_mask_colliders(gltf_node) - add_child.call_deferred(gltf_node) +func _on_gltf_instanced(req_id: int, node: Node): + if req_id != gltf_instance_req_id: + return -# -# var now = Time.get_ticks_usec() -# var loading_time = now - gltf_start_loading_time -# var colliders_time = now - colliders_timestamp -# print("gltf ", dcl_gltf_src, " loaded in ", roundf(float(loading_time) / 1000.0), " ms", " collider in ", roundf(float(colliders_time) / 1000.0)) + Global.content_manager.gltf_node_collider_finishes.disconnect(self._on_gltf_instanced) + + gltf_node = node + gltf_state = GodotGltfState.Finished + + add_child.call_deferred(gltf_node) -func get_collider(mesh_instance: MeshInstance3D): +func get_animatable_body_3d(mesh_instance: MeshInstance3D): for maybe_static_body in mesh_instance.get_children(): - if maybe_static_body is StaticBody3D: + if maybe_static_body is AnimatableBody3D: return maybe_static_body return null -func create_and_set_mask_colliders(node_to_inspect: Node): +func update_mask_colliders(node_to_inspect: Node): + print("updating mask colliders") for node in node_to_inspect.get_children(): if node is MeshInstance3D: var mask: int = 0 @@ -94,31 +95,17 @@ func create_and_set_mask_colliders(node_to_inspect: Node): else: mask = dcl_invisible_cmask - var static_body_3d: StaticBody3D = get_collider(node) - if static_body_3d == null and mask > 0: - node.create_trimesh_collision() - static_body_3d = get_collider(node) - - if static_body_3d != null: - var parent = static_body_3d.get_parent() - var new_animatable = AnimatableBody3D.new() - parent.add_child(new_animatable) - parent.remove_child(static_body_3d) - - for child in static_body_3d.get_children(true): - static_body_3d.remove_child(child) - new_animatable.add_child(child) - if child is CollisionShape3D and child.shape is ConcavePolygonShape3D: - # TODO: workaround, the face's normals probably need to be inverted in some meshes - child.shape.backface_collision = true - - new_animatable.collision_layer = mask - new_animatable.sync_to_physics = false - new_animatable.set_meta("dcl_scene_id", dcl_scene_id) - new_animatable.set_meta("dcl_entity_id", dcl_entity_id) + var animatable_body_3d = get_animatable_body_3d(node) + if animatable_body_3d != null: + if mask == 0: + animatable_body_3d.process_mode = Node.PROCESS_MODE_DISABLED + animatable_body_3d.mask = 0 + else: + animatable_body_3d.process_mode = Node.PROCESS_MODE_INHERIT + animatable_body_3d.mask = mask if node is Node: - create_and_set_mask_colliders(node) + update_mask_colliders(node) func change_gltf(new_gltf, visible_meshes_collision_mask, invisible_meshes_collision_mask): @@ -135,9 +122,12 @@ func change_gltf(new_gltf, visible_meshes_collision_mask, invisible_meshes_colli self.load_gltf.call_deferred() else: if ( - visible_meshes_collision_mask != dcl_visible_cmask - or invisible_meshes_collision_mask != dcl_invisible_cmask + ( + visible_meshes_collision_mask != dcl_visible_cmask + or invisible_meshes_collision_mask != dcl_invisible_cmask + ) + and gltf_node != null ): dcl_visible_cmask = visible_meshes_collision_mask dcl_invisible_cmask = invisible_meshes_collision_mask - create_and_set_mask_colliders(gltf_node) + update_mask_colliders(gltf_node) diff --git a/godot/src/logic/content_manager.gd b/godot/src/logic/content_manager.gd index 2f5c6b28..c6ea1c74 100644 --- a/godot/src/logic/content_manager.gd +++ b/godot/src/logic/content_manager.gd @@ -4,8 +4,15 @@ class_name ContentManager signal content_loading_finished(hash: String) signal wearable_data_loaded(id: String) signal meshes_material_finished(id: int) +signal gltf_node_collider_finishes(id: int, gltf_node: Node) -enum ContentType { CT_GLTF_GLB = 1, CT_TEXTURE = 2, CT_WEARABLE_EMOTE = 3, CT_MESHES_MATERIAL = 4 } +enum ContentType { + CT_GLTF_GLB = 1, + CT_TEXTURE = 2, + CT_WEARABLE_EMOTE = 3, + CT_MESHES_MATERIAL = 4, + CT_INSTACE_GLTF = 5 +} var loading_content: Array[Dictionary] = [] var pending_content: Array[Dictionary] = [] @@ -95,6 +102,31 @@ func duplicate_materials(target_meshes: Array[Dictionary]) -> int: return id +func instance_gltf_colliders( + gltf_node: Node, + dcl_visible_cmask: int, + dcl_invisible_cmask: int, + dcl_scene_id: int, + dcl_entity_id: int +) -> int: + var id = request_monotonic_counter + 1 + request_monotonic_counter = id + + pending_content.push_back( + { + "id": id, + "content_type": ContentType.CT_INSTACE_GLTF, + "gltf_node": gltf_node, + "dcl_visible_cmask": dcl_visible_cmask, + "dcl_invisible_cmask": dcl_invisible_cmask, + "dcl_scene_id": dcl_scene_id, + "dcl_entity_id": dcl_entity_id + } + ) + + return id + + # Public function # @returns true if the resource was added to queue to fetch, false if it had already been fetched func fetch_gltf(file_path: String, content_mapping: Dictionary): @@ -187,6 +219,10 @@ func _th_poll(): if not _process_meshes_material(content): _th_to_delete.push_back(content) + ContentType.CT_INSTACE_GLTF: + if not _process_instance_gltf(content): + _th_to_delete.push_back(content) + _: printerr("Fetching invalid content type ", _th_content_type) @@ -407,8 +443,7 @@ func _process_loading_gltf(content: Dictionary, finished_downloads: Array[Reques var node = new_gltf.generate_scene(new_gltf_state) if node != null: node.rotate_y(PI) - _hide_colliders(node) - split_animations(node) + create_colliders(node) if err != OK: push_warning("resource with errors ", file_path, " : ", err) else: @@ -520,3 +555,118 @@ func _hide_colliders(gltf_node): if maybe_collider is Node: _hide_colliders(maybe_collider) + + +func create_colliders(node_to_inspect: Node): + for node in node_to_inspect.get_children(): + if node is MeshInstance3D: + var invisible_mesh = node.name.find("_collider") != -1 + var static_body_3d: StaticBody3D = get_collider(node) + if static_body_3d == null: + node.create_trimesh_collision() + static_body_3d = get_collider(node) + static_body_3d.name = node.name + "_colgen" + + if static_body_3d != null: + var parent = static_body_3d.get_parent() + var new_animatable = AnimatableBody3D.new() + parent.add_child(new_animatable) + parent.remove_child(static_body_3d) + + for child in static_body_3d.get_children(true): + static_body_3d.remove_child(child) + new_animatable.add_child(child) + if child is CollisionShape3D and child.shape is ConcavePolygonShape3D: + # TODO: workaround, the face's normals probably need to be inverted in some meshes + child.shape.backface_collision = true + + new_animatable.sync_to_physics = false + new_animatable.process_mode = Node.PROCESS_MODE_DISABLED + new_animatable.collision_layer = 0 + + new_animatable.set_meta("invisible_mesh", invisible_mesh) + + if invisible_mesh: + node.visible = false + + if node is Node: + create_colliders(node) + + +func _process_instance_gltf(content: Dictionary): + var gltf_node: Node = content.get("gltf_node") + var dcl_visible_cmask: int = content.get("dcl_visible_cmask") + var dcl_invisible_cmask: int = content.get("dcl_invisible_cmask") + var dcl_scene_id: int = content.get("dcl_scene_id") + var dcl_entity_id: int = content.get("dcl_entity_id") + + gltf_node = gltf_node.duplicate() + + var to_remove_nodes = [] + update_set_mask_colliders( + gltf_node, + dcl_visible_cmask, + dcl_invisible_cmask, + dcl_scene_id, + dcl_entity_id, + to_remove_nodes + ) + + for node in to_remove_nodes: + node.get_parent().remove_child(node) + + self.emit_signal.call_deferred("gltf_node_collider_finishes", content["id"], gltf_node) + return false + + +func get_collider(mesh_instance: MeshInstance3D): + for maybe_static_body in mesh_instance.get_children(): + if maybe_static_body is StaticBody3D: + return maybe_static_body + return null + + +func update_set_mask_colliders( + node_to_inspect: Node, + dcl_visible_cmask: int, + dcl_invisible_cmask: int, + dcl_scene_id: int, + dcl_entity_id: int, + to_remove_nodes: Array +): + for node in node_to_inspect.get_children(): + if node is AnimatableBody3D: + var mask: int = 0 + var invisible_mesh = node.has_meta("invisible_mesh") and node.get_meta("invisible_mesh") + if invisible_mesh: + mask = dcl_invisible_cmask + else: + mask = dcl_visible_cmask + + var resolved_node = node + if not node.has_meta("dcl_scene_id"): + var parent = node.get_parent() + resolved_node = node.duplicate() + resolved_node.name = node.name + "_instanced" + resolved_node.set_meta("dcl_scene_id", dcl_scene_id) + resolved_node.set_meta("dcl_entity_id", dcl_entity_id) + + parent.add_child(resolved_node) + to_remove_nodes.push_back(node) + + if mask == 0: + resolved_node.process_mode = Node.PROCESS_MODE_DISABLED + resolved_node.collision_layer = 0 + else: + resolved_node.process_mode = Node.PROCESS_MODE_INHERIT + resolved_node.collision_layer = mask + + if node is Node: + update_set_mask_colliders( + node, + dcl_visible_cmask, + dcl_invisible_cmask, + dcl_scene_id, + dcl_entity_id, + to_remove_nodes + )