feat: index loaded resource discovery
This commit is contained in:
@@ -0,0 +1,239 @@
|
||||
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
|
||||
)
|
||||
Reference in New Issue
Block a user