class_name LoadedResourceDiscoveryBenchmark extends RefCounted const SCHEMA_VERSION := 1 const WORKLOAD_ID := &"loaded_resource_target_resolution" const RESOURCE_SPACING := 20.0 const DEFAULT_QUERY_COUNT := 400 const DEFAULT_SAMPLE_COUNT := 7 const WARMUP_QUERY_COUNT := 40 const SimulationManagerScript := preload("res://simulation/SimulationManager.gd") class LinearResourceAdapter: extends Node func get_resource_candidates(action_id: StringName) -> Array[Dictionary]: var candidates: Array[Dictionary] = [] for node in ResourceNode.get_all(): if node.action_id != action_id or node.interaction_point == null: continue ( candidates . append( { "target_id": String(node.node_id), "position": node.interaction_point.global_position, "resource_id": String(node.resource_id), "safety_risk": node.safety_risk, "comfort_distance": node.comfort_distance, "discovery_priority": node.discovery_priority, } ) ) return candidates func create_fixture(parent: Node, resource_count: int, seed_value: int) -> Dictionary: if parent == null or resource_count <= 0: return {} var fixture_root := Node.new() fixture_root.name = "LoadedResourceDiscoveryFixture" parent.add_child(fixture_root) var resource_root := Node3D.new() resource_root.name = "ResourceNodes" fixture_root.add_child(resource_root) for resource_index in resource_count: resource_root.add_child(_create_resource(resource_index, resource_count)) var adapter := ActiveWorldAdapter.new() adapter.name = "ActiveWorldAdapter" fixture_root.add_child(adapter) var manager: Node = SimulationManagerScript.new() manager.name = "SimulationManager" manager.debug_logs = false manager.simulation_seed = seed_value manager.active_world_adapter = adapter fixture_root.add_child(manager) manager.set_process(false) manager.register_loaded_resource_nodes() var npc: SimNPC = manager.npcs[0] npc.energy = 62.0 npc.set_task(SimulationIds.ACTION_GATHER_FOOD) return { "root": fixture_root, "resource_root": resource_root, "adapter": adapter, "manager": manager, "npc": npc, "resource_count": resource_count, } func create_linear_adapter() -> Node: return LinearResourceAdapter.new() func measure_fixture( fixture: Dictionary, query_count: int = DEFAULT_QUERY_COUNT, sample_count: int = DEFAULT_SAMPLE_COUNT, include_spatial: bool = true ) -> Dictionary: if fixture.is_empty() or query_count <= 0 or sample_count <= 0: return {} var adapter: ActiveWorldAdapter = fixture["adapter"] var origins := _build_origins(int(fixture["resource_count"]), query_count) var linear_adapter := create_linear_adapter() (fixture["root"] as Node).add_child(linear_adapter) _run_queries(fixture, linear_adapter, origins, mini(WARMUP_QUERY_COUNT, query_count), false) var linear := _measure_mode(fixture, linear_adapter, origins, sample_count, false) if linear.is_empty(): return {} var result := { "schema_version": SCHEMA_VERSION, "workload_id": String(WORKLOAD_ID), "resource_count": int(fixture["resource_count"]), "query_count": query_count, "sample_count": sample_count, "linear": linear, } if ( include_spatial and adapter.has_method("get_resource_candidates_in_radius") and adapter.has_method("get_resource_query_profile") ): _run_queries(fixture, adapter, origins, mini(WARMUP_QUERY_COUNT, query_count), false) var spatial := _measure_mode(fixture, adapter, origins, sample_count, true) if spatial.is_empty() or spatial["selected_checksum"] != linear["selected_checksum"]: return {} result["spatial"] = spatial result["speedup"] = ( float(linear["usec_per_query_median"]) / maxf(float(spatial["usec_per_query_median"]), 0.0001) ) result["inspected_reduction_percent"] = ( ( 1.0 - float(spatial["candidates_inspected_average"]) / float(fixture["resource_count"]) ) * 100.0 ) return result func free_fixture(fixture: Dictionary) -> void: if fixture.has("root") and is_instance_valid(fixture["root"]): (fixture["root"] as Node).free() func _measure_mode( fixture: Dictionary, query_adapter: Object, origins: Array[Vector3], sample_count: int, spatial: bool ) -> Dictionary: var elapsed_samples: Array[int] = [] var inspected_samples: Array[float] = [] var checksum := "" for _sample_index in sample_count: var sample := _run_queries(fixture, query_adapter, origins, origins.size(), spatial) if sample.is_empty(): return {} if checksum.is_empty(): checksum = sample["selected_checksum"] elif checksum != sample["selected_checksum"]: return {} elapsed_samples.append(int(sample["elapsed_usec"])) inspected_samples.append(float(sample["candidates_inspected_average"])) elapsed_samples.sort() inspected_samples.sort() var elapsed_median := elapsed_samples[elapsed_samples.size() / 2] return { "elapsed_usec_samples": elapsed_samples, "elapsed_usec_median": elapsed_median, "usec_per_query_median": float(elapsed_median) / float(origins.size()), "queries_per_second_median": float(origins.size()) * 1000000.0 / float(elapsed_median), "candidates_inspected_average": inspected_samples[inspected_samples.size() / 2], "selected_checksum": checksum, } func _run_queries( fixture: Dictionary, query_adapter: Object, origins: Array[Vector3], query_count: int, spatial: bool ) -> Dictionary: var manager: Node = fixture["manager"] var npc: SimNPC = fixture["npc"] var selected_ids := PackedStringArray() var inspected_total := 0 var started_usec := Time.get_ticks_usec() for query_index in query_count: var result: Dictionary = manager.target_resolver.resolve( npc, origins[query_index], manager, query_adapter ) if result.is_empty(): return {} var target_id := StringName(result["target_id"]) selected_ids.append(String(target_id)) manager.release_resource(target_id, npc.id) if spatial and not manager.target_resolver.last_resource_query_stats.is_empty(): inspected_total += int( manager.target_resolver.last_resource_query_stats.get("candidates_inspected", 0) ) else: inspected_total += int(fixture["resource_count"]) var elapsed_usec := maxi(Time.get_ticks_usec() - started_usec, 1) return { "elapsed_usec": elapsed_usec, "candidates_inspected_average": float(inspected_total) / float(query_count), "selected_checksum": "|".join(selected_ids).sha256_text(), } func _create_resource(resource_index: int, resource_count: int) -> ResourceNode: var node := ResourceNode.new() node.name = "Resource_%04d" % resource_index node.node_id = StringName("benchmark_resource_%04d" % resource_index) node.action_id = SimulationIds.ACTION_GATHER_FOOD node.resource_id = SimulationIds.RESOURCE_FOOD node.initial_amount = 100000.0 node.initial_enabled = resource_index % 29 != 0 node.safety_risk = float(resource_index % 6) * 0.06 node.comfort_distance = 16.0 + float(resource_index % 5) * 5.0 node.discovery_priority = float(resource_index % 9) * 0.35 node.debug_label_enabled = false node.position = _resource_position(resource_index, resource_count) var interaction_point := Marker3D.new() interaction_point.name = "InteractionPoint" node.add_child(interaction_point) return node func _build_origins(resource_count: int, query_count: int) -> Array[Vector3]: var origins: Array[Vector3] = [] for query_index in query_count: var resource_index := (query_index * 37 + 11) % resource_count var offset := Vector3( float((query_index * 7) % 13) - 6.0, 0.0, float((query_index * 11) % 17) - 8.0 ) origins.append(_resource_position(resource_index, resource_count) + offset) return origins func _resource_position(resource_index: int, resource_count: int) -> Vector3: var columns := ceili(sqrt(float(resource_count))) var column := resource_index % columns var row := resource_index / columns var centered_column := float(column) - float(columns - 1) * 0.5 var row_count := ceili(float(resource_count) / float(columns)) var centered_row := float(row) - float(row_count - 1) * 0.5 return Vector3( centered_column * RESOURCE_SPACING, sin(float(resource_index) * 0.37) * 2.5, centered_row * RESOURCE_SPACING )