Capture current source, content data, tests, asset licenses, and original-specification traceability. Original implementation remains incomplete. The accepted matching UI generation passed 154 suites and 12,253 checks, with eight rendered staff-contract screens reviewed. Latest native Windows and full-original acceptance remain outstanding.
1032 lines
38 KiB
GDScript
1032 lines
38 KiB
GDScript
class_name PerfMvp001Harness
|
|
extends RefCounted
|
|
|
|
## Standalone PERF-MVP-001 fixture and acceptance harness.
|
|
##
|
|
## The harness deliberately uses only public service APIs. It does not reach
|
|
## into SimulationRuntime or service-private dictionaries to manufacture load.
|
|
|
|
const WorldServiceType = preload("res://scripts/world/world_service.gd")
|
|
const PathServiceType = preload("res://scripts/world/path_service.gd")
|
|
const FacilityServiceType = preload("res://scripts/facility/facility_service.gd")
|
|
const GuestServiceType = preload("res://scripts/guest/guest_service.gd")
|
|
const StaffServiceType = preload("res://scripts/staff/staff_service.gd")
|
|
const InventoryServiceType = preload("res://scripts/inventory/inventory_service.gd")
|
|
const LogisticsRequestType = preload("res://scripts/inventory/logistics_request.gd")
|
|
const LogisticsDispatcherType = preload("res://scripts/inventory/logistics_dispatcher.gd")
|
|
const EventServiceType = preload("res://scripts/events/event_service.gd")
|
|
const EventInstanceType = preload("res://scripts/events/event_instance.gd")
|
|
const EconomyServiceType = preload("res://scripts/economy/economy_service.gd")
|
|
const PerformanceBudgetType = preload("res://scripts/core/performance_budget.gd")
|
|
|
|
const FULL_TARGET := {
|
|
"open_cells": 20_000,
|
|
"guests": 200,
|
|
"staff": 50,
|
|
"facilities": 150,
|
|
"queue_entries": 500,
|
|
"unfinished_logistics": 200,
|
|
"active_events": 50,
|
|
"minutes": 1_440,
|
|
"speed": 3,
|
|
}
|
|
|
|
const SMOKE_TARGET := {
|
|
"open_cells": 2_000,
|
|
"guests": 20,
|
|
"staff": 10,
|
|
"facilities": 15,
|
|
"queue_entries": 50,
|
|
"unfinished_logistics": 20,
|
|
"active_events": 5,
|
|
"minutes": 1_440,
|
|
"speed": 3,
|
|
}
|
|
|
|
const STALE_BACKLOG_LIMIT_MINUTES := 120
|
|
const FACILITY_SERVICE_MINUTES := 15
|
|
const EVENT_DEFINITION_ID := &"perf_mvp_waiting_event"
|
|
const WAREHOUSE_ID := &"perf_warehouse"
|
|
const DESTINATION_ID := &"perf_destination"
|
|
const SUPPLY_ITEM_ID := &"perf_supply"
|
|
const CUSTOMER_ACCOUNT_ID := &"perf_customers"
|
|
const OPERATOR_ACCOUNT_ID := &"perf_operator"
|
|
|
|
var _economy: Variant
|
|
var _runtime_errors: Array[Dictionary] = []
|
|
var _logistics_requests: Array = []
|
|
var _dispatcher: Variant
|
|
var _carriers: Dictionary = {}
|
|
var _maximum_facility_queue_age := 0
|
|
var _maximum_logistics_backlog_age := 0
|
|
var _stale_facility_queue_entries := 0
|
|
var _stale_logistics_requests := 0
|
|
var _path_query_failures := 0
|
|
var _path_duration_samples: Array[int] = []
|
|
var _domain_samples: Dictionary = {}
|
|
|
|
|
|
func run(tier: StringName = &"smoke") -> Dictionary:
|
|
_reset_run_state()
|
|
var normalized_tier := &"full" if String(tier).to_lower() == "full" else &"smoke"
|
|
var target: Dictionary = (
|
|
FULL_TARGET.duplicate(true) if normalized_tier == &"full" else SMOKE_TARGET.duplicate(true)
|
|
)
|
|
var report := {
|
|
"acceptance_id": "PERF-MVP-001",
|
|
"tier": String(normalized_tier),
|
|
"status": "INCOMPLETE",
|
|
"target": target.duplicate(true),
|
|
"actual_initial": {},
|
|
"actual_final": {},
|
|
"unmet_fixture_fields": [],
|
|
"validations": [],
|
|
"performance": {},
|
|
"backlog": {},
|
|
"runtime_errors": [],
|
|
"limitations": [
|
|
"Standalone public-API composition; this does not measure scene rendering or UI work.",
|
|
"The logistics lifecycle is orchestrated by the harness because the current domain exposes LogisticsRequest and LogisticsDispatcher, but no owning LogisticsService tick API.",
|
|
],
|
|
}
|
|
|
|
var fixture_started := Time.get_ticks_usec()
|
|
var services := _create_services()
|
|
var dimensions := _dimensions_for_cell_count(int(target["open_cells"]))
|
|
var cells := _populate_world(services["world"], dimensions)
|
|
if cells.size() != int(target["open_cells"]):
|
|
_record_runtime_error(&"WORLD_FIXTURE", "Could not create the requested walkable-cell cardinality.")
|
|
if not services["path"].rebuild_graph(cells):
|
|
_record_runtime_error(&"PATH_FIXTURE", services["path"].last_error)
|
|
_populate_economy(services["economy"], int(target["queue_entries"]))
|
|
_populate_guests(services["guests"], services["path"], int(target["guests"]), dimensions)
|
|
_populate_staff(services["staff"], services["path"], int(target["staff"]), dimensions)
|
|
_populate_facilities(
|
|
services["facilities"],
|
|
services["world"],
|
|
int(target["facilities"]),
|
|
int(target["queue_entries"]),
|
|
int(target["staff"]),
|
|
dimensions
|
|
)
|
|
_populate_facility_queues(
|
|
services["facilities"],
|
|
int(target["facilities"]),
|
|
int(target["queue_entries"]),
|
|
int(target["guests"])
|
|
)
|
|
_populate_logistics(
|
|
services["inventory"],
|
|
services["staff"],
|
|
int(target["unfinished_logistics"])
|
|
)
|
|
_populate_events(services["events"], int(target["active_events"]))
|
|
|
|
var initial := _fixture_counts(services)
|
|
report["actual_initial"] = initial
|
|
report["fixture_build_ms"] = (Time.get_ticks_usec() - fixture_started) / 1_000.0
|
|
var unmet := _compare_fixture(target, initial)
|
|
report["unmet_fixture_fields"] = unmet
|
|
|
|
if not unmet.is_empty() or not _runtime_errors.is_empty():
|
|
report["runtime_errors"] = _runtime_errors.duplicate(true)
|
|
report["status"] = "INCOMPLETE"
|
|
_finalize_check_summary(report)
|
|
return report
|
|
|
|
var profiler := PerformanceBudgetType.new(512)
|
|
services["path"].path_query_measured.connect(
|
|
func(duration_us: int) -> void:
|
|
_path_duration_samples.append(duration_us)
|
|
)
|
|
var simulation_started := Time.get_ticks_usec()
|
|
var completed_minutes := _simulate_day(services, target, dimensions, profiler)
|
|
var simulation_duration_us := Time.get_ticks_usec() - simulation_started
|
|
# Feed raw path samples after timing the simulation. PerformanceBudget returns
|
|
# a full percentile report per record, so doing this inside each path callback
|
|
# would make the profiler itself dominate the x3 measurement.
|
|
for duration_us: int in _path_duration_samples:
|
|
profiler.record_sample(&"path_query", duration_us)
|
|
|
|
report["simulation"] = {
|
|
"requested_minutes": int(target["minutes"]),
|
|
"completed_minutes": completed_minutes,
|
|
"x3_frames": int(ceil(float(completed_minutes) / 3.0)),
|
|
"duration_ms": simulation_duration_us / 1_000.0,
|
|
"game_minutes_per_real_second": (
|
|
float(completed_minutes) * 1_000_000.0 / float(maxi(simulation_duration_us, 1))
|
|
),
|
|
}
|
|
report["performance"] = _performance_report(profiler)
|
|
report["backlog"] = {
|
|
"stale_limit_minutes": STALE_BACKLOG_LIMIT_MINUTES,
|
|
"maximum_facility_queue_age": _maximum_facility_queue_age,
|
|
"maximum_logistics_backlog_age": _maximum_logistics_backlog_age,
|
|
"facility_entries_observed_over_limit": _stale_facility_queue_entries,
|
|
"logistics_requests_observed_over_limit": _stale_logistics_requests,
|
|
}
|
|
report["actual_final"] = _fixture_counts(services)
|
|
report["runtime_errors"] = _runtime_errors.duplicate(true)
|
|
|
|
var validation_started := Time.get_ticks_usec()
|
|
_run_validations(report, services, cells, target, completed_minutes, profiler)
|
|
report["validation_duration_ms"] = (Time.get_ticks_usec() - validation_started) / 1_000.0
|
|
_finalize_status(report, normalized_tier)
|
|
_finalize_check_summary(report)
|
|
return report
|
|
|
|
|
|
func _create_services() -> Dictionary:
|
|
_economy = EconomyServiceType.new()
|
|
_dispatcher = LogisticsDispatcherType.new()
|
|
var facilities := FacilityServiceType.new()
|
|
facilities.configure_callbacks(Callable(), Callable(self, "_on_facility_service_completed"))
|
|
return {
|
|
"world": WorldServiceType.new(),
|
|
"path": PathServiceType.new(),
|
|
"facilities": facilities,
|
|
"guests": GuestServiceType.new(),
|
|
"staff": StaffServiceType.new(),
|
|
"inventory": InventoryServiceType.new(),
|
|
"events": EventServiceType.new(),
|
|
"economy": _economy,
|
|
}
|
|
|
|
|
|
func _dimensions_for_cell_count(cell_count: int) -> Vector2i:
|
|
# Both acceptance tiers are exactly divisible by two layers and 50 columns.
|
|
var width := 200 if cell_count >= FULL_TARGET["open_cells"] else 50
|
|
var height := cell_count / (width * 2)
|
|
return Vector2i(width, height)
|
|
|
|
|
|
func _populate_world(world: Variant, dimensions: Vector2i) -> Array[Vector3i]:
|
|
var cells: Array[Vector3i] = []
|
|
if not world.reset({
|
|
&"min_x": 0,
|
|
&"max_x": dimensions.x - 1,
|
|
&"min_y": 0,
|
|
&"max_y": dimensions.y - 1,
|
|
}):
|
|
_record_runtime_error(&"WORLD_RESET", "WorldService rejected PERF bounds.")
|
|
return cells
|
|
for layer: int in [WorldServiceType.LAYER_FRONT, WorldServiceType.LAYER_BACK]:
|
|
for y: int in range(dimensions.y):
|
|
for x: int in range(dimensions.x):
|
|
var cell := Vector3i(x, y, layer)
|
|
if not world.set_walkable(cell, true):
|
|
_record_runtime_error(&"WORLD_WALKABLE", "WorldService rejected cell %s." % cell)
|
|
return cells
|
|
cells.append(cell)
|
|
return cells
|
|
|
|
|
|
func _populate_economy(economy: Variant, queue_count: int) -> void:
|
|
if not economy.register_account(CUSTOMER_ACCOUNT_ID):
|
|
_record_runtime_error(&"ECONOMY_ACCOUNT", "Could not register customer account.")
|
|
if not economy.register_account(OPERATOR_ACCOUNT_ID):
|
|
_record_runtime_error(&"ECONOMY_ACCOUNT", "Could not register operator account.")
|
|
var customer_seed = economy.initialize_account(
|
|
CUSTOMER_ACCOUNT_ID,
|
|
maxi(queue_count * 2, 100),
|
|
0,
|
|
&"perf_customer_seed"
|
|
)
|
|
var operator_seed = economy.initialize_account(
|
|
OPERATOR_ACCOUNT_ID,
|
|
100,
|
|
0,
|
|
&"perf_operator_seed"
|
|
)
|
|
if not customer_seed.is_success() or not operator_seed.is_success():
|
|
_record_runtime_error(&"ECONOMY_SEED", "Could not seed PERF ledger accounts.")
|
|
|
|
|
|
func _populate_guests(
|
|
guest_service: Variant,
|
|
path_service: Variant,
|
|
count: int,
|
|
dimensions: Vector2i
|
|
) -> void:
|
|
var definition = guest_service.create_definition(&"perf_guest", {
|
|
&"wealth_min": 100,
|
|
&"wealth_max": 100,
|
|
&"base_patience": 180,
|
|
&"need_rates": {
|
|
&"hunger": 0,
|
|
&"sleep": 0,
|
|
&"hygiene": 0,
|
|
&"medical": 0,
|
|
&"leisure": 0,
|
|
&"intimacy": 0,
|
|
},
|
|
})
|
|
if definition == null:
|
|
_record_runtime_error(&"GUEST_DEFINITION", guest_service.last_error)
|
|
return
|
|
for index: int in range(count):
|
|
var guest_id := StringName("perf_guest_%04d" % index)
|
|
var cell := Vector3i(index % dimensions.x, (index / dimensions.x) % dimensions.y, 0)
|
|
var guest = guest_service.spawn_guest(guest_id, &"perf_guest", 0, {
|
|
&"current_cell": cell,
|
|
&"wallet_gold": 100,
|
|
&"planned_exit_at": 2_880,
|
|
})
|
|
if guest == null:
|
|
_record_runtime_error(&"GUEST_SPAWN", guest_service.last_error)
|
|
continue
|
|
if not path_service.project_actor_position(guest_id, cell, 0):
|
|
_record_runtime_error(&"GUEST_PATH_PROJECTION", path_service.last_error)
|
|
|
|
|
|
func _populate_staff(
|
|
staff_service: Variant,
|
|
path_service: Variant,
|
|
count: int,
|
|
dimensions: Vector2i
|
|
) -> void:
|
|
_carriers.clear()
|
|
var carrier_capacity := maxi(int(ceil(float(maxi(_target_logistics_hint(count), 1)) / float(maxi(count, 1)))), 1)
|
|
for index: int in range(count):
|
|
var staff_id := StringName("perf_staff_%04d" % index)
|
|
var cell := Vector3i(
|
|
(index * 3) % dimensions.x,
|
|
((index * 3) / dimensions.x) % dimensions.y,
|
|
0
|
|
)
|
|
var result = staff_service.create_staff(staff_id, &"logistics", {
|
|
&"current_cell": cell,
|
|
&"state": &"AVAILABLE",
|
|
&"role_tags": [&"logistics"],
|
|
&"permitted_task_tags": [&"delivery"],
|
|
})
|
|
if not result.is_success():
|
|
_record_runtime_error(&"STAFF_CREATE", staff_service.last_error)
|
|
continue
|
|
if not path_service.project_actor_position(staff_id, cell, 0):
|
|
_record_runtime_error(&"STAFF_PATH_PROJECTION", path_service.last_error)
|
|
_carriers[staff_id] = {
|
|
&"position": cell,
|
|
&"capacity": carrier_capacity,
|
|
&"current_load": 0,
|
|
&"role": &"logistics",
|
|
&"role_fit": 1,
|
|
&"path_minutes": 1,
|
|
}
|
|
|
|
|
|
func _target_logistics_hint(staff_count: int) -> int:
|
|
return FULL_TARGET["unfinished_logistics"] if staff_count >= FULL_TARGET["staff"] else SMOKE_TARGET["unfinished_logistics"]
|
|
|
|
|
|
func _populate_facilities(
|
|
facility_service: Variant,
|
|
world: Variant,
|
|
count: int,
|
|
queue_count: int,
|
|
staff_count: int,
|
|
dimensions: Vector2i
|
|
) -> void:
|
|
var queue_capacity := int(ceil(float(queue_count) / float(maxi(count, 1)))) + 1
|
|
for index: int in range(count):
|
|
var facility_id := StringName("perf_facility_%04d" % index)
|
|
var cell := Vector3i(index % dimensions.x, (index / dimensions.x) % dimensions.y, 1)
|
|
var active_staff: Array[StringName] = []
|
|
if staff_count > 0:
|
|
active_staff.append(StringName("perf_staff_%04d" % (index % staff_count)))
|
|
var result = facility_service.create_instance({
|
|
&"instance_id": facility_id,
|
|
&"definition_id": &"perf_service_room",
|
|
&"origin": cell,
|
|
&"occupied_cells": [cell],
|
|
&"capacity": 1,
|
|
&"queue_capacity": queue_capacity,
|
|
&"base_service_minutes": FACILITY_SERVICE_MINUTES,
|
|
&"base_quality": 50,
|
|
&"base_price": 1,
|
|
&"staff_min": 1 if not active_staff.is_empty() else 0,
|
|
&"staff_max": 1 if not active_staff.is_empty() else 0,
|
|
&"active_staff_ids": active_staff,
|
|
})
|
|
if not result.is_success():
|
|
_record_runtime_error(&"FACILITY_CREATE", facility_service.last_error)
|
|
continue
|
|
if not world.occupy(facility_id, [cell], &"facility"):
|
|
_record_runtime_error(&"FACILITY_OCCUPANCY", "World occupancy rejected %s." % facility_id)
|
|
|
|
|
|
func _populate_facility_queues(
|
|
facility_service: Variant,
|
|
facility_count: int,
|
|
queue_count: int,
|
|
guest_count: int
|
|
) -> void:
|
|
for index: int in range(queue_count):
|
|
var facility_id := StringName("perf_facility_%04d" % (index % facility_count))
|
|
var guest_ids: Array[StringName] = [
|
|
StringName("perf_guest_%04d" % (index % guest_count)),
|
|
]
|
|
var request_id := StringName("perf_service_request_%05d" % index)
|
|
var request = facility_service.create_request(
|
|
request_id,
|
|
facility_id,
|
|
guest_ids,
|
|
&"perf_service",
|
|
0,
|
|
{
|
|
&"quoted_price": 1,
|
|
&"service_minutes": FACILITY_SERVICE_MINUTES,
|
|
&"priority": index % 3,
|
|
}
|
|
)
|
|
if request == null:
|
|
_record_runtime_error(&"FACILITY_REQUEST", "Could not create %s." % request_id)
|
|
continue
|
|
var result = facility_service.enqueue_request(request, 0)
|
|
if not result.is_success():
|
|
_record_runtime_error(&"FACILITY_QUEUE", facility_service.last_error)
|
|
|
|
|
|
func _populate_logistics(
|
|
inventory: Variant,
|
|
staff_service: Variant,
|
|
count: int
|
|
) -> void:
|
|
_logistics_requests.clear()
|
|
var stock = inventory.add_stack(
|
|
WAREHOUSE_ID,
|
|
SUPPLY_ITEM_ID,
|
|
count + 100,
|
|
100,
|
|
-1,
|
|
0,
|
|
1,
|
|
&"perf_supply_stack"
|
|
)
|
|
if not bool(stock.get(&"ok", false)):
|
|
_record_runtime_error(&"INVENTORY_STOCK", String(stock.get(&"message", "Stock creation failed.")))
|
|
return
|
|
for index: int in range(count):
|
|
var request_id := StringName("perf_logistics_%04d" % index)
|
|
var reservation = inventory.reserve_item(
|
|
WAREHOUSE_ID,
|
|
SUPPLY_ITEM_ID,
|
|
1,
|
|
request_id,
|
|
&"perf_delivery",
|
|
0,
|
|
STALE_BACKLOG_LIMIT_MINUTES,
|
|
StringName("perf_reserve_%04d" % index)
|
|
)
|
|
if not bool(reservation.get(&"ok", false)):
|
|
_record_runtime_error(&"LOGISTICS_RESERVE", String(reservation.get(&"message", "Reservation failed.")))
|
|
continue
|
|
var reservation_ids: Array[StringName] = []
|
|
for raw_id: Variant in reservation.get(&"reservation_ids", []):
|
|
reservation_ids.append(StringName(raw_id))
|
|
var stack_ids: Array[StringName] = []
|
|
for raw_id: Variant in reservation.get(&"stack_ids", []):
|
|
stack_ids.append(StringName(raw_id))
|
|
var request = LogisticsRequestType.new().configure(
|
|
request_id,
|
|
WAREHOUSE_ID,
|
|
DESTINATION_ID,
|
|
{SUPPLY_ITEM_ID: 1},
|
|
100 - (index % 20),
|
|
0,
|
|
STALE_BACKLOG_LIMIT_MINUTES,
|
|
1
|
|
)
|
|
if not request.mark_reserved(reservation_ids, stack_ids, 0):
|
|
_record_runtime_error(&"LOGISTICS_REQUEST", "Could not reserve %s." % request_id)
|
|
continue
|
|
_logistics_requests.append(request)
|
|
# Ensure the carrier adapter was derived from real registered Staff records.
|
|
if _carriers.size() != staff_service.get_staff_members().size():
|
|
_record_runtime_error(&"LOGISTICS_CARRIERS", "Carrier projection does not match Staff cardinality.")
|
|
|
|
|
|
func _populate_events(event_service: Variant, count: int) -> void:
|
|
var definition_result = _register_perf_event_definition(event_service)
|
|
if not definition_result.is_success():
|
|
_record_runtime_error(&"EVENT_DEFINITION", event_service.last_error)
|
|
return
|
|
for index: int in range(count):
|
|
var event_instance_id := StringName("perf_event_instance_%04d" % index)
|
|
var enqueue = event_service.enqueue_candidate(
|
|
EVENT_DEFINITION_ID,
|
|
[],
|
|
0,
|
|
{
|
|
&"event_instance_id": event_instance_id,
|
|
&"deduplication_key": StringName("perf_event_key_%04d" % index),
|
|
}
|
|
)
|
|
if not enqueue.is_success():
|
|
_record_runtime_error(&"EVENT_ENQUEUE", event_service.last_error)
|
|
continue
|
|
var started = event_service.start_next_event(0)
|
|
if not started.is_success() or not bool(started.details.get(&"waiting_for_choice", false)):
|
|
_record_runtime_error(&"EVENT_START", event_service.last_error)
|
|
|
|
|
|
func _register_perf_event_definition(event_service: Variant) -> Variant:
|
|
return event_service.create_definition(EVENT_DEFINITION_ID, {
|
|
&"category": &"PERFORMANCE",
|
|
&"priority": 10,
|
|
&"foreground": false,
|
|
&"choices": [
|
|
{
|
|
&"choice_id": &"hold",
|
|
&"text_key": &"perf.hold",
|
|
&"cost": {},
|
|
&"actions": [],
|
|
},
|
|
],
|
|
})
|
|
|
|
|
|
func _simulate_day(
|
|
services: Dictionary,
|
|
target: Dictionary,
|
|
dimensions: Vector2i,
|
|
profiler: Variant
|
|
) -> int:
|
|
var requested_minutes := int(target["minutes"])
|
|
var current_minute := 0
|
|
var paths_per_minute := 4 if int(target["guests"]) >= FULL_TARGET["guests"] else 1
|
|
while current_minute < requested_minutes and _runtime_errors.is_empty():
|
|
var frame_started := Time.get_ticks_usec()
|
|
var ticks_this_frame := mini(int(target["speed"]), requested_minutes - current_minute)
|
|
for _tick: int in range(ticks_this_frame):
|
|
current_minute += 1
|
|
_advance_one_minute(services, current_minute, dimensions, paths_per_minute)
|
|
if not _runtime_errors.is_empty():
|
|
break
|
|
var frame_duration := Time.get_ticks_usec() - frame_started
|
|
profiler.record_simulation_frame(frame_duration, int(target["speed"]), ticks_this_frame)
|
|
return current_minute
|
|
|
|
|
|
func _advance_one_minute(
|
|
services: Dictionary,
|
|
absolute_minute: int,
|
|
dimensions: Vector2i,
|
|
paths_per_minute: int
|
|
) -> void:
|
|
var domain_started := Time.get_ticks_usec()
|
|
_track_backlog(services["facilities"], absolute_minute)
|
|
services["economy"].set_current_minute(absolute_minute)
|
|
_record_domain_sample(&"backlog_and_clock", Time.get_ticks_usec() - domain_started)
|
|
|
|
domain_started = Time.get_ticks_usec()
|
|
var facility_results: Array = services["facilities"].advance_one_minute(absolute_minute)
|
|
_record_domain_sample(&"facility", Time.get_ticks_usec() - domain_started)
|
|
for result: Variant in facility_results:
|
|
if result != null and result.has_method("is_success") and not result.is_success():
|
|
_record_runtime_error(&"FACILITY_TICK", result.message)
|
|
return
|
|
domain_started = Time.get_ticks_usec()
|
|
var guest_report: Dictionary = services["guests"].advance_one_minute(absolute_minute)
|
|
_record_domain_sample(&"guest", Time.get_ticks_usec() - domain_started)
|
|
if not bool(guest_report.get(&"ok", false)):
|
|
_record_runtime_error(&"GUEST_TICK", services["guests"].last_error)
|
|
return
|
|
domain_started = Time.get_ticks_usec()
|
|
var staff_report: Dictionary = services["staff"].advance_one_minute(absolute_minute)
|
|
_record_domain_sample(&"staff", Time.get_ticks_usec() - domain_started)
|
|
if not bool(staff_report.get(&"ok", false)):
|
|
_record_runtime_error(&"STAFF_TICK", services["staff"].last_error)
|
|
return
|
|
domain_started = Time.get_ticks_usec()
|
|
services["inventory"].process_expired(absolute_minute)
|
|
services["events"].process_expired(absolute_minute)
|
|
services["events"].audit_actor_availability(absolute_minute)
|
|
_record_domain_sample(&"inventory_and_events", Time.get_ticks_usec() - domain_started)
|
|
domain_started = Time.get_ticks_usec()
|
|
_advance_logistics(services["inventory"], absolute_minute)
|
|
_record_domain_sample(&"logistics", Time.get_ticks_usec() - domain_started)
|
|
domain_started = Time.get_ticks_usec()
|
|
_run_path_queries(services["path"], absolute_minute, dimensions, paths_per_minute)
|
|
_record_domain_sample(&"path", Time.get_ticks_usec() - domain_started)
|
|
|
|
|
|
func _track_backlog(facility_service: Variant, absolute_minute: int) -> void:
|
|
for facility: Variant in facility_service.get_instances():
|
|
for request: Variant in facility_service.get_queue(facility.instance_id):
|
|
var age := maxi(absolute_minute - int(request.enqueued_at), 0)
|
|
_maximum_facility_queue_age = maxi(_maximum_facility_queue_age, age)
|
|
if age > STALE_BACKLOG_LIMIT_MINUTES:
|
|
_stale_facility_queue_entries += 1
|
|
for request: Variant in _logistics_requests:
|
|
if request.is_terminal():
|
|
continue
|
|
var age := maxi(absolute_minute - int(request.created_at), 0)
|
|
_maximum_logistics_backlog_age = maxi(_maximum_logistics_backlog_age, age)
|
|
if age > STALE_BACKLOG_LIMIT_MINUTES:
|
|
_stale_logistics_requests += 1
|
|
|
|
|
|
func _advance_logistics(inventory: Variant, absolute_minute: int) -> void:
|
|
var dispatch_started := Time.get_ticks_usec()
|
|
if absolute_minute == 1:
|
|
var assignments: Array[Dictionary] = _dispatcher.dispatch(
|
|
_logistics_requests,
|
|
_carriers,
|
|
absolute_minute
|
|
)
|
|
if assignments.size() != _logistics_requests.size():
|
|
_record_runtime_error(
|
|
&"LOGISTICS_DISPATCH",
|
|
"Assigned %d of %d requests." % [assignments.size(), _logistics_requests.size()]
|
|
)
|
|
return
|
|
_record_domain_sample(&"logistics_dispatch", Time.get_ticks_usec() - dispatch_started)
|
|
var lifecycle_started := Time.get_ticks_usec()
|
|
var pickup_requests: Array = []
|
|
var delivery_requests: Array = []
|
|
for request: Variant in _logistics_requests:
|
|
var state_age := absolute_minute - int(request.state_changed_at)
|
|
match request.state:
|
|
LogisticsRequestType.State.ASSIGNED:
|
|
if state_age >= 1:
|
|
pickup_requests.append(request)
|
|
LogisticsRequestType.State.PICKING_UP:
|
|
if state_age >= 1 and not request.transition_to(
|
|
LogisticsRequestType.State.DELIVERING,
|
|
"",
|
|
absolute_minute
|
|
):
|
|
_record_runtime_error(&"LOGISTICS_DELIVERY_STATE", "Could not begin delivery for %s." % request.request_id)
|
|
return
|
|
LogisticsRequestType.State.DELIVERING:
|
|
if state_age >= 1:
|
|
delivery_requests.append(request)
|
|
|
|
if not pickup_requests.is_empty():
|
|
var pickup_entries: Array[Dictionary] = []
|
|
for request: Variant in pickup_requests:
|
|
pickup_entries.append({
|
|
&"reservation_ids": request.reservation_ids,
|
|
&"destination_inventory_id": _cargo_inventory_id(request.assigned_carrier_id),
|
|
&"operation_id": StringName("perf_pickup_%s" % request.request_id),
|
|
})
|
|
var pickup_started := Time.get_ticks_usec()
|
|
var pickup: Dictionary = inventory.transfer_reserved_batch(pickup_entries, absolute_minute)
|
|
_record_domain_sample(&"logistics_pickup_batch", Time.get_ticks_usec() - pickup_started)
|
|
if not bool(pickup.get(&"ok", false)):
|
|
_record_runtime_error(&"LOGISTICS_PICKUP", String(pickup.get(&"message", "Pickup failed.")))
|
|
return
|
|
for request: Variant in pickup_requests:
|
|
if not request.transition_to(LogisticsRequestType.State.PICKING_UP, "", absolute_minute):
|
|
_record_runtime_error(&"LOGISTICS_PICKUP_STATE", "Could not begin pickup for %s." % request.request_id)
|
|
return
|
|
|
|
if not delivery_requests.is_empty():
|
|
var delivery_entries: Array[Dictionary] = []
|
|
for request: Variant in delivery_requests:
|
|
delivery_entries.append({
|
|
&"source_inventory_id": _cargo_inventory_id(request.assigned_carrier_id),
|
|
&"destination_inventory_id": request.destination_inventory_id,
|
|
&"item_id": SUPPLY_ITEM_ID,
|
|
&"quantity": 1,
|
|
&"operation_id": StringName("perf_delivery_%s" % request.request_id),
|
|
})
|
|
var delivery_started := Time.get_ticks_usec()
|
|
var delivered: Dictionary = inventory.transfer_available_batch(delivery_entries, absolute_minute)
|
|
_record_domain_sample(&"logistics_delivery_batch", Time.get_ticks_usec() - delivery_started)
|
|
if not bool(delivered.get(&"ok", false)):
|
|
_record_runtime_error(&"LOGISTICS_DELIVERY", String(delivered.get(&"message", "Delivery failed.")))
|
|
return
|
|
for request: Variant in delivery_requests:
|
|
var carrier_id: StringName = request.assigned_carrier_id
|
|
if not request.transition_to(LogisticsRequestType.State.COMPLETED, "", absolute_minute):
|
|
_record_runtime_error(&"LOGISTICS_COMPLETE", "Could not complete %s." % request.request_id)
|
|
return
|
|
_dispatcher.release_assignment_for_request(
|
|
request.request_id,
|
|
carrier_id,
|
|
request.total_weight
|
|
)
|
|
_record_domain_sample(&"logistics_lifecycle", Time.get_ticks_usec() - lifecycle_started)
|
|
|
|
|
|
func _run_path_queries(
|
|
path_service: Variant,
|
|
absolute_minute: int,
|
|
dimensions: Vector2i,
|
|
query_count: int
|
|
) -> void:
|
|
for query_index: int in range(query_count):
|
|
var x := (absolute_minute * 7 + query_index * 17) % dimensions.x
|
|
var y := (absolute_minute * 3 + query_index * 11) % dimensions.y
|
|
var goal_x := mini(x + 4, dimensions.x - 1)
|
|
var start := Vector3i(x, y, 0)
|
|
var goal := Vector3i(goal_x, y, 0)
|
|
var path: Array[Vector3i] = path_service.find_path(start, goal)
|
|
if path.is_empty():
|
|
_path_query_failures += 1
|
|
_record_runtime_error(
|
|
&"PATH_QUERY",
|
|
"No route from %s to %s (%s)." % [start, goal, path_service.last_no_route_reason]
|
|
)
|
|
return
|
|
|
|
|
|
func _on_facility_service_completed(
|
|
_service: Variant,
|
|
request: Variant,
|
|
_instance: Variant,
|
|
absolute_minute: int
|
|
) -> Variant:
|
|
return _economy.transfer(
|
|
CUSTOMER_ACCOUNT_ID,
|
|
OPERATOR_ACCOUNT_ID,
|
|
int(request.quoted_price),
|
|
absolute_minute,
|
|
&"FACILITY_REVENUE",
|
|
request.facility_id,
|
|
StringName("perf_revenue_%s" % request.request_id),
|
|
&"perf.facility_revenue"
|
|
)
|
|
|
|
|
|
func _cargo_inventory_id(carrier_id: StringName) -> StringName:
|
|
return StringName("perf_cargo_%s" % carrier_id)
|
|
|
|
|
|
func _fixture_counts(services: Dictionary) -> Dictionary:
|
|
var queue_entries := 0
|
|
var active_services := 0
|
|
for facility: Variant in services["facilities"].get_instances():
|
|
queue_entries += services["facilities"].get_queue(facility.instance_id).size()
|
|
active_services += services["facilities"].get_active_services(facility.instance_id).size()
|
|
var unfinished_logistics := 0
|
|
var completed_logistics := 0
|
|
for request: Variant in _logistics_requests:
|
|
if request.is_terminal():
|
|
completed_logistics += 1
|
|
else:
|
|
unfinished_logistics += 1
|
|
var active_events := 0
|
|
var waiting_events := 0
|
|
var queued_events := 0
|
|
for instance: Variant in services["events"].get_instances():
|
|
if not instance.is_terminal():
|
|
active_events += 1
|
|
if instance.state == EventInstanceType.State.WAITING_CHOICE:
|
|
waiting_events += 1
|
|
elif instance.state == EventInstanceType.State.QUEUED:
|
|
queued_events += 1
|
|
return {
|
|
"open_cells": services["world"].get_walkable_cells().size(),
|
|
"path_cells": services["path"].create_snapshot().get(&"cell_traffic", []).size(),
|
|
"guests": services["guests"].guest_count(),
|
|
"staff": services["staff"].get_staff_members().size(),
|
|
"facilities": services["facilities"].get_instances().size(),
|
|
"queue_entries": queue_entries,
|
|
"active_services": active_services,
|
|
"unfinished_logistics": unfinished_logistics,
|
|
"completed_logistics": completed_logistics,
|
|
"active_events": active_events,
|
|
"waiting_choice_events": waiting_events,
|
|
"queued_events": queued_events,
|
|
}
|
|
|
|
|
|
func _compare_fixture(target: Dictionary, actual: Dictionary) -> Array[Dictionary]:
|
|
var unmet: Array[Dictionary] = []
|
|
for field: String in [
|
|
"open_cells",
|
|
"guests",
|
|
"staff",
|
|
"facilities",
|
|
"queue_entries",
|
|
"unfinished_logistics",
|
|
"active_events",
|
|
]:
|
|
if int(actual.get(field, -1)) != int(target[field]):
|
|
unmet.append({
|
|
"field": field,
|
|
"target": int(target[field]),
|
|
"actual": int(actual.get(field, -1)),
|
|
})
|
|
return unmet
|
|
|
|
|
|
func _run_validations(
|
|
report: Dictionary,
|
|
services: Dictionary,
|
|
cells: Array[Vector3i],
|
|
target: Dictionary,
|
|
completed_minutes: int,
|
|
profiler: Variant
|
|
) -> void:
|
|
var validations: Array = report["validations"]
|
|
_add_validation(
|
|
validations,
|
|
&"day_completed_without_crash",
|
|
completed_minutes == int(target["minutes"]) and _runtime_errors.is_empty(),
|
|
{"completed_minutes": completed_minutes, "runtime_error_count": _runtime_errors.size()}
|
|
)
|
|
_add_validation(
|
|
validations,
|
|
&"fixture_cardinality_exact",
|
|
(report["unmet_fixture_fields"] as Array).is_empty(),
|
|
{"actual_initial": report["actual_initial"]}
|
|
)
|
|
_add_validation(
|
|
validations,
|
|
&"facility_queue_not_stale",
|
|
_stale_facility_queue_entries == 0 and _maximum_facility_queue_age <= STALE_BACKLOG_LIMIT_MINUTES,
|
|
{"maximum_age": _maximum_facility_queue_age, "observations_over_limit": _stale_facility_queue_entries}
|
|
)
|
|
_add_validation(
|
|
validations,
|
|
&"logistics_backlog_not_stale",
|
|
_stale_logistics_requests == 0 and _maximum_logistics_backlog_age <= STALE_BACKLOG_LIMIT_MINUTES,
|
|
{"maximum_age": _maximum_logistics_backlog_age, "observations_over_limit": _stale_logistics_requests}
|
|
)
|
|
_add_validation(
|
|
validations,
|
|
&"logistics_all_completed",
|
|
_count_terminal_logistics() == _logistics_requests.size(),
|
|
{"completed": _count_terminal_logistics(), "total": _logistics_requests.size()}
|
|
)
|
|
|
|
var inventory_validation: Dictionary = services["inventory"].validate_invariants()
|
|
var inventory_nonnegative := _inventory_is_nonnegative(services["inventory"])
|
|
_add_validation(
|
|
validations,
|
|
&"inventory_invariants",
|
|
bool(inventory_validation.get(&"ok", false)) and inventory_nonnegative,
|
|
{"service_report": inventory_validation, "nonnegative": inventory_nonnegative}
|
|
)
|
|
var ledger_validation: Dictionary = services["economy"].validate_ledger()
|
|
_add_validation(
|
|
validations,
|
|
&"ledger_balanced",
|
|
bool(ledger_validation.get(&"ok", false))
|
|
and int(ledger_validation.get(&"total_debit", -1)) == int(ledger_validation.get(&"total_credit", -2)),
|
|
ledger_validation
|
|
)
|
|
_add_validation(validations, &"guest_invariants", bool(services["guests"].validate_invariants().get(&"valid", false)), services["guests"].validate_invariants())
|
|
_add_validation(validations, &"staff_invariants", bool(services["staff"].validate_invariants().get(&"ok", false)), services["staff"].validate_invariants())
|
|
_add_validation(validations, &"event_invariants", bool(services["events"].validate_invariants().get(&"ok", false)), services["events"].validate_invariants())
|
|
_add_validation(validations, &"path_queries_reachable", _path_query_failures == 0, {"failures": _path_query_failures})
|
|
|
|
_validate_snapshot_round_trips(validations, services, cells)
|
|
var x3_report: Dictionary = profiler.get_report(&"simulation_x3")
|
|
var path_report: Dictionary = profiler.get_report(&"path_query")
|
|
var stall_report: Dictionary = profiler.get_report(&"main_thread_stall")
|
|
_add_validation(validations, &"simulation_x3_budget", not bool(x3_report[&"budget_exceeded"]), _primitive_dictionary(x3_report))
|
|
_add_validation(validations, &"path_query_budget", not bool(path_report[&"budget_exceeded"]), _primitive_dictionary(path_report))
|
|
_add_validation(validations, &"main_thread_stall_budget", not bool(stall_report[&"budget_exceeded"]), _primitive_dictionary(stall_report))
|
|
|
|
|
|
func _validate_snapshot_round_trips(
|
|
validations: Array,
|
|
services: Dictionary,
|
|
cells: Array[Vector3i]
|
|
) -> void:
|
|
var world_snapshot: Dictionary = services["world"].create_snapshot()
|
|
var world_clone = WorldServiceType.new()
|
|
var world_ok := world_clone.restore_snapshot(world_snapshot)
|
|
_add_validation(validations, &"world_snapshot", world_ok and world_clone.create_snapshot() == world_snapshot, {"restored": world_ok})
|
|
|
|
var path_snapshot: Dictionary = services["path"].create_snapshot()
|
|
var path_clone = PathServiceType.new()
|
|
var path_restored := path_clone.restore_snapshot(path_snapshot)
|
|
var path_rebuilt := path_restored and path_clone.rebuild_graph(cells)
|
|
_add_validation(validations, &"path_snapshot", path_rebuilt and path_clone.create_snapshot() == path_snapshot, {"restored": path_restored, "rebuilt": path_rebuilt})
|
|
|
|
var facility_snapshot: Dictionary = services["facilities"].create_snapshot()
|
|
var facility_clone = FacilityServiceType.new()
|
|
var facility_ok := facility_clone.restore_snapshot(facility_snapshot)
|
|
_add_validation(validations, &"facility_snapshot", facility_ok and facility_clone.create_snapshot() == facility_snapshot, {"restored": facility_ok})
|
|
|
|
var guest_snapshot: Dictionary = services["guests"].create_snapshot()
|
|
var guest_clone = GuestServiceType.new()
|
|
var guest_ok := guest_clone.restore_snapshot(guest_snapshot)
|
|
_add_validation(validations, &"guest_snapshot", guest_ok and guest_clone.create_snapshot() == guest_snapshot, {"restored": guest_ok})
|
|
|
|
var staff_snapshot: Dictionary = services["staff"].create_snapshot()
|
|
var staff_clone = StaffServiceType.new()
|
|
var staff_ok := staff_clone.restore_snapshot(staff_snapshot)
|
|
_add_validation(validations, &"staff_snapshot", staff_ok and staff_clone.create_snapshot() == staff_snapshot, {"restored": staff_ok})
|
|
|
|
var inventory_snapshot: Dictionary = services["inventory"].create_snapshot()
|
|
var inventory_clone = InventoryServiceType.new()
|
|
var inventory_ok := inventory_clone.restore_snapshot(inventory_snapshot)
|
|
_add_validation(validations, &"inventory_snapshot", inventory_ok and inventory_clone.create_snapshot() == inventory_snapshot, {"restored": inventory_ok})
|
|
|
|
var event_snapshot: Dictionary = services["events"].create_snapshot()
|
|
var event_clone = EventServiceType.new()
|
|
var event_definition_ok: bool = _register_perf_event_definition(event_clone).is_success()
|
|
var event_ok: bool = event_definition_ok and event_clone.restore_snapshot(event_snapshot)
|
|
_add_validation(validations, &"event_snapshot", event_ok and event_clone.create_snapshot() == event_snapshot, {"definition_registered": event_definition_ok, "restored": event_ok})
|
|
|
|
var economy_snapshot: Dictionary = services["economy"].create_snapshot()
|
|
var economy_clone = EconomyServiceType.new()
|
|
var economy_ok := economy_clone.restore_snapshot(economy_snapshot)
|
|
_add_validation(validations, &"economy_snapshot", economy_ok and economy_clone.create_snapshot() == economy_snapshot, {"restored": economy_ok})
|
|
|
|
var dispatcher_snapshot: Dictionary = _dispatcher.create_snapshot()
|
|
var dispatcher_clone = LogisticsDispatcherType.new()
|
|
var dispatcher_ok := dispatcher_clone.restore_snapshot(dispatcher_snapshot)
|
|
_add_validation(validations, &"logistics_dispatcher_snapshot", dispatcher_ok and dispatcher_clone.create_snapshot() == dispatcher_snapshot, {"restored": dispatcher_ok})
|
|
|
|
var logistics_round_trip_ok := true
|
|
for request: Variant in _logistics_requests:
|
|
var clone = LogisticsRequestType.from_snapshot(request.create_snapshot())
|
|
if clone == null or clone.create_snapshot() != request.create_snapshot():
|
|
logistics_round_trip_ok = false
|
|
break
|
|
_add_validation(validations, &"logistics_request_snapshots", logistics_round_trip_ok, {"request_count": _logistics_requests.size()})
|
|
|
|
|
|
func _inventory_is_nonnegative(inventory: Variant) -> bool:
|
|
for stack: Dictionary in inventory.get_stacks():
|
|
if int(stack.get(&"quantity", -1)) < 0:
|
|
return false
|
|
if int(stack.get(&"reserved_quantity", -1)) < 0:
|
|
return false
|
|
if int(stack.get(&"reserved_quantity", 0)) > int(stack.get(&"quantity", -1)):
|
|
return false
|
|
return true
|
|
|
|
|
|
func _count_terminal_logistics() -> int:
|
|
var count := 0
|
|
for request: Variant in _logistics_requests:
|
|
if request.is_terminal():
|
|
count += 1
|
|
return count
|
|
|
|
|
|
func _performance_report(profiler: Variant) -> Dictionary:
|
|
return {
|
|
"simulation_x3": _primitive_dictionary(profiler.get_report(&"simulation_x3")),
|
|
"path_query": _primitive_dictionary(profiler.get_report(&"path_query")),
|
|
"main_thread_stall": _primitive_dictionary(profiler.get_report(&"main_thread_stall")),
|
|
"domain_timings_per_game_minute": {
|
|
"backlog_and_clock": _raw_sample_report(&"backlog_and_clock"),
|
|
"facility": _raw_sample_report(&"facility"),
|
|
"guest": _raw_sample_report(&"guest"),
|
|
"staff": _raw_sample_report(&"staff"),
|
|
"inventory_and_events": _raw_sample_report(&"inventory_and_events"),
|
|
"logistics": _raw_sample_report(&"logistics"),
|
|
"logistics_dispatch": _raw_sample_report(&"logistics_dispatch"),
|
|
"logistics_lifecycle": _raw_sample_report(&"logistics_lifecycle"),
|
|
"logistics_pickup_batch": _raw_sample_report(&"logistics_pickup_batch"),
|
|
"logistics_delivery_batch": _raw_sample_report(&"logistics_delivery_batch"),
|
|
"path": _raw_sample_report(&"path"),
|
|
},
|
|
}
|
|
|
|
|
|
func _record_domain_sample(metric_id: StringName, duration_us: int) -> void:
|
|
if not _domain_samples.has(metric_id):
|
|
_domain_samples[metric_id] = []
|
|
(_domain_samples[metric_id] as Array).append(maxi(duration_us, 0))
|
|
|
|
|
|
func _raw_sample_report(metric_id: StringName) -> Dictionary:
|
|
var raw_values: Array = _domain_samples.get(metric_id, [])
|
|
var values: Array[int] = []
|
|
var total := 0
|
|
var maximum := 0
|
|
for raw_value: Variant in raw_values:
|
|
var value := int(raw_value)
|
|
values.append(value)
|
|
total += value
|
|
maximum = maxi(maximum, value)
|
|
values.sort()
|
|
var p95 := 0
|
|
if not values.is_empty():
|
|
var index := maxi((95 * values.size() + 99) / 100 - 1, 0)
|
|
p95 = values[mini(index, values.size() - 1)]
|
|
return {
|
|
"sample_count": values.size(),
|
|
"average_us": total / values.size() if not values.is_empty() else 0,
|
|
"p95_us": p95,
|
|
"maximum_us": maximum,
|
|
}
|
|
|
|
|
|
func _primitive_dictionary(source: Dictionary) -> Dictionary:
|
|
var result: Dictionary = {}
|
|
for raw_key: Variant in source:
|
|
result[String(raw_key)] = source[raw_key]
|
|
return result
|
|
|
|
|
|
func _add_validation(
|
|
validations: Array,
|
|
validation_id: StringName,
|
|
ok: bool,
|
|
details: Dictionary = {},
|
|
skipped := false
|
|
) -> void:
|
|
validations.append({
|
|
"id": String(validation_id),
|
|
"status": "SKIPPED" if skipped else ("PASS" if ok else "FAIL"),
|
|
"details": details,
|
|
})
|
|
|
|
|
|
func _finalize_status(report: Dictionary, tier: StringName) -> void:
|
|
if not (report["unmet_fixture_fields"] as Array).is_empty():
|
|
report["status"] = "INCOMPLETE"
|
|
return
|
|
if not _runtime_errors.is_empty():
|
|
report["status"] = "FAILED"
|
|
return
|
|
for validation: Dictionary in report["validations"]:
|
|
if validation["status"] == "SKIPPED":
|
|
report["status"] = "INCOMPLETE"
|
|
return
|
|
if validation["status"] == "FAIL":
|
|
report["status"] = "FAILED"
|
|
return
|
|
report["status"] = "PASS" if tier == &"full" else "SMOKE_PASS"
|
|
|
|
|
|
func _finalize_check_summary(report: Dictionary) -> void:
|
|
var passed := 0
|
|
var failed := 0
|
|
var skipped := 0
|
|
for validation: Dictionary in report["validations"]:
|
|
match String(validation.get("status", "FAIL")):
|
|
"PASS":
|
|
passed += 1
|
|
"SKIPPED":
|
|
skipped += 1
|
|
_:
|
|
failed += 1
|
|
report["check_summary"] = {
|
|
"total": passed + failed + skipped,
|
|
"passed": passed,
|
|
"failed": failed,
|
|
"skipped": skipped,
|
|
}
|
|
|
|
|
|
func _record_runtime_error(code: StringName, message: String) -> void:
|
|
_runtime_errors.append({"code": String(code), "message": message})
|
|
|
|
|
|
func _reset_run_state() -> void:
|
|
_economy = null
|
|
_dispatcher = null
|
|
_runtime_errors.clear()
|
|
_logistics_requests.clear()
|
|
_carriers.clear()
|
|
_maximum_facility_queue_age = 0
|
|
_maximum_logistics_backlog_age = 0
|
|
_stale_facility_queue_entries = 0
|
|
_stale_logistics_requests = 0
|
|
_path_query_failures = 0
|
|
_path_duration_samples.clear()
|
|
_domain_samples.clear()
|