rnd(M03): extract streaming target planner
Work-Package: M03-RND-STREAMING-PLANNER-001 Agent: sindo-main-codex Tests: planner/facade/focus/coordinate/manifest/shutdown contracts, seven-checkpoint dry-run, repository gates Fidelity: preserves wanted/retained radii, boundary prefetch, editor preview, queue priority and cache/render behavior
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
class_name StreamingTargetPlan
|
||||
extends RefCounted
|
||||
|
||||
## Immutable result of one runtime or editor target-planning operation.
|
||||
## Tile-key sets use the streamer's existing `<tile_x>_<tile_y>` representation.
|
||||
|
||||
var focus_tile: AdtTileCoordinate:
|
||||
get:
|
||||
return _focus_tile
|
||||
|
||||
var _focus_tile: AdtTileCoordinate
|
||||
var _wanted_tile_keys: Dictionary
|
||||
var _retained_tile_keys: Dictionary
|
||||
|
||||
|
||||
## Takes ownership of planner-created sets and freezes them. The planner creates
|
||||
## these dictionaries independently from its catalog input, so no copy is needed.
|
||||
func _init(
|
||||
focus_tile_value: AdtTileCoordinate,
|
||||
wanted_tile_keys_value: Dictionary,
|
||||
retained_tile_keys_value: Dictionary
|
||||
) -> void:
|
||||
_focus_tile = focus_tile_value
|
||||
_wanted_tile_keys = wanted_tile_keys_value
|
||||
_retained_tile_keys = retained_tile_keys_value
|
||||
_wanted_tile_keys.make_read_only()
|
||||
_retained_tile_keys.make_read_only()
|
||||
|
||||
|
||||
## Returns the immutable wanted tile-key set.
|
||||
func wanted_tile_keys() -> Dictionary:
|
||||
return _wanted_tile_keys
|
||||
|
||||
|
||||
## Returns the immutable retained tile-key set.
|
||||
func retained_tile_keys() -> Dictionary:
|
||||
return _retained_tile_keys
|
||||
@@ -0,0 +1 @@
|
||||
uid://fq1ax8tdju2q
|
||||
@@ -0,0 +1,104 @@
|
||||
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: StreamingTargetPolicy
|
||||
) -> StreamingTargetPlan:
|
||||
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
|
||||
) -> StreamingTargetPlan:
|
||||
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: StreamingTargetPolicy
|
||||
) -> Array[AdtTileCoordinate]:
|
||||
var centers: Array[AdtTileCoordinate] = [focus_tile]
|
||||
var threshold := 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]
|
||||
@@ -0,0 +1 @@
|
||||
uid://crqjbgoe3f6ea
|
||||
@@ -0,0 +1,108 @@
|
||||
class_name StreamingTargetPolicy
|
||||
extends RefCounted
|
||||
|
||||
## Immutable runtime tile-radius and boundary-prefetch policy consumed by
|
||||
## [StreamingTargetPlanner]. Values preserve scene configuration exactly;
|
||||
## effective radii and thresholds apply the existing renderer clamps.
|
||||
|
||||
const CHUNKS_PER_ADT_TILE := 16
|
||||
|
||||
var lod2_radius_chunks: int:
|
||||
get:
|
||||
return _lod2_radius_chunks
|
||||
|
||||
var lod2_tile_radius: int:
|
||||
get:
|
||||
return _lod2_tile_radius
|
||||
|
||||
var warm_tile_margin: int:
|
||||
get:
|
||||
return _warm_tile_margin
|
||||
|
||||
var prewarm_tile_margin: int:
|
||||
get:
|
||||
return _prewarm_tile_margin
|
||||
|
||||
var retain_tile_margin: int:
|
||||
get:
|
||||
return _retain_tile_margin
|
||||
|
||||
var boundary_prefetch_threshold: float:
|
||||
get:
|
||||
return _boundary_prefetch_threshold
|
||||
|
||||
var _lod2_radius_chunks: int
|
||||
var _lod2_tile_radius: int
|
||||
var _warm_tile_margin: int
|
||||
var _prewarm_tile_margin: int
|
||||
var _retain_tile_margin: int
|
||||
var _boundary_prefetch_threshold: float
|
||||
|
||||
|
||||
## Captures one renderer configuration snapshot for a deterministic plan.
|
||||
func _init(
|
||||
lod2_radius_chunks_value: int,
|
||||
lod2_tile_radius_value: int,
|
||||
warm_tile_margin_value: int,
|
||||
prewarm_tile_margin_value: int,
|
||||
retain_tile_margin_value: int,
|
||||
boundary_prefetch_threshold_value: float
|
||||
) -> void:
|
||||
_lod2_radius_chunks = lod2_radius_chunks_value
|
||||
_lod2_tile_radius = lod2_tile_radius_value
|
||||
_warm_tile_margin = warm_tile_margin_value
|
||||
_prewarm_tile_margin = prewarm_tile_margin_value
|
||||
_retain_tile_margin = retain_tile_margin_value
|
||||
_boundary_prefetch_threshold = boundary_prefetch_threshold_value
|
||||
|
||||
|
||||
## Returns the current visible ADT radius derived from chunk and tile settings.
|
||||
func visible_tile_radius() -> int:
|
||||
return calculate_visible_tile_radius(_lod2_radius_chunks, _lod2_tile_radius, _warm_tile_margin)
|
||||
|
||||
|
||||
## Returns the visible radius plus the non-negative prewarm margin.
|
||||
func load_tile_radius() -> int:
|
||||
return calculate_load_tile_radius(
|
||||
_lod2_radius_chunks,
|
||||
_lod2_tile_radius,
|
||||
_warm_tile_margin,
|
||||
_prewarm_tile_margin
|
||||
)
|
||||
|
||||
|
||||
## Returns the load radius plus the non-negative hysteresis margin.
|
||||
func retained_tile_radius() -> int:
|
||||
return load_tile_radius() + maxi(0, _retain_tile_margin)
|
||||
|
||||
|
||||
## Returns the edge fraction accepted by the existing prefetch policy.
|
||||
func clamped_boundary_prefetch_threshold() -> float:
|
||||
return clampf(_boundary_prefetch_threshold, 0.0, 0.49)
|
||||
|
||||
|
||||
## Calculates the visible radius without allocating a policy. The streamer uses
|
||||
## this hot-path form while finalizing tiles between planning refreshes.
|
||||
static func calculate_visible_tile_radius(
|
||||
lod2_radius_chunks_value: int,
|
||||
lod2_tile_radius_value: int,
|
||||
warm_tile_margin_value: int
|
||||
) -> int:
|
||||
var chunk_tile_radius := int(ceil(
|
||||
float(lod2_radius_chunks_value) / float(CHUNKS_PER_ADT_TILE)
|
||||
)) + warm_tile_margin_value
|
||||
return maxi(chunk_tile_radius, lod2_tile_radius_value)
|
||||
|
||||
|
||||
## Calculates the load radius without allocating a policy.
|
||||
static func calculate_load_tile_radius(
|
||||
lod2_radius_chunks_value: int,
|
||||
lod2_tile_radius_value: int,
|
||||
warm_tile_margin_value: int,
|
||||
prewarm_tile_margin_value: int
|
||||
) -> int:
|
||||
return calculate_visible_tile_radius(
|
||||
lod2_radius_chunks_value,
|
||||
lod2_tile_radius_value,
|
||||
warm_tile_margin_value
|
||||
) + maxi(0, prewarm_tile_margin_value)
|
||||
@@ -0,0 +1 @@
|
||||
uid://cfrad5wr668w5
|
||||
Reference in New Issue
Block a user