class_name StreamingTargetPlanner extends RefCounted ## Scene-free deterministic calculator for runtime/editor ADT target sets. ## It creates no tasks or renderer resources and never mutates its inputs. const COORDINATE_MAPPER_SCRIPT := preload("res://src/domain/coordinates/coordinate_mapper.gd") const ADT_TILE_COORDINATE_SCRIPT := preload("res://src/domain/coordinates/adt_tile_coordinate.gd") const TARGET_PLAN_SCRIPT := preload("res://src/render/streaming/streaming_target_plan.gd") ## Builds runtime wanted and retained sets, including the existing edge/corner ## prefetch centers. `available_tile_paths_by_key` is read only for membership. func plan_runtime( available_tile_paths_by_key: Dictionary, focus_position: GodotWorldPosition, policy ): var focus_tile = COORDINATE_MAPPER_SCRIPT.godot_to_adt_tile(focus_position) var prefetch_centers := _prefetch_centers(focus_position, focus_tile, policy) var wanted_tile_keys := _available_keys_within_radius( available_tile_paths_by_key, prefetch_centers, policy.load_tile_radius() ) var retained_tile_keys := _available_keys_within_radius( available_tile_paths_by_key, prefetch_centers, policy.retained_tile_radius() ) return TARGET_PLAN_SCRIPT.new(focus_tile, wanted_tile_keys, retained_tile_keys) ## Builds the editor square preview set. Editor wanted and retained sets are ## identical and a negative radius is clamped to the focus tile. func plan_editor( available_tile_paths_by_key: Dictionary, focus_position: GodotWorldPosition, editor_preview_tile_radius: int ): var focus_tile = COORDINATE_MAPPER_SCRIPT.godot_to_adt_tile(focus_position) var centers: Array[AdtTileCoordinate] = [focus_tile] var wanted_tile_keys := _available_keys_within_radius( available_tile_paths_by_key, centers, maxi(0, editor_preview_tile_radius) ) return TARGET_PLAN_SCRIPT.new(focus_tile, wanted_tile_keys, wanted_tile_keys) func _prefetch_centers( focus_position: GodotWorldPosition, focus_tile: AdtTileCoordinate, policy ) -> Array[AdtTileCoordinate]: var centers: Array[AdtTileCoordinate] = [focus_tile] var threshold: float = policy.clamped_boundary_prefetch_threshold() if threshold <= 0.0: return centers var tile_local_position = COORDINATE_MAPPER_SCRIPT.godot_to_adt_tile_local(focus_position) var local_x_fraction: float = tile_local_position.east_yards / COORDINATE_MAPPER_SCRIPT.ADT_TILE_SIZE_YARDS var local_z_fraction: float = tile_local_position.south_yards / COORDINATE_MAPPER_SCRIPT.ADT_TILE_SIZE_YARDS var tile_x_offsets: Array[int] = [0] var tile_y_offsets: Array[int] = [0] if local_x_fraction <= threshold: tile_x_offsets.append(-1) elif local_x_fraction >= 1.0 - threshold: tile_x_offsets.append(1) if local_z_fraction <= threshold: tile_y_offsets.append(-1) elif local_z_fraction >= 1.0 - threshold: tile_y_offsets.append(1) for tile_x_offset in tile_x_offsets: for tile_y_offset in tile_y_offsets: if tile_x_offset == 0 and tile_y_offset == 0: continue centers.append(ADT_TILE_COORDINATE_SCRIPT.new( focus_tile.tile_x + tile_x_offset, focus_tile.tile_y + tile_y_offset )) return centers func _available_keys_within_radius( available_tile_paths_by_key: Dictionary, centers: Array[AdtTileCoordinate], tile_radius: int ) -> Dictionary: var available_tile_keys: Dictionary = {} for center in centers: for tile_y in range(center.tile_y - tile_radius, center.tile_y + tile_radius + 1): for tile_x in range(center.tile_x - tile_radius, center.tile_x + tile_radius + 1): var tile_key := _tile_key(tile_x, tile_y) if available_tile_paths_by_key.has(tile_key): available_tile_keys[tile_key] = true return available_tile_keys func _tile_key(tile_x: int, tile_y: int) -> String: return "%d_%d" % [tile_x, tile_y]