feat(simulation): T-1151 window par_iter + T-1150 granularity carrier (five touch points + aliasing tests)
T-1151: build_district_window_layer dispatches one Rayon task per row (pure derive_window_cell via derive_at_metres), scattered row-major into the flat arrays; a cfg(test) serial path backs the bit-identical parallel-vs-serial golden. T-1150: serde-default window_granularity (1=district, 4=quarter) + window_min_wl_m on AtlasLayerRequest — additive, no sixth demux shape, old frames decode unchanged (tested). Quarter mode = full reclassification at 512m spacing over the same world rect ((4n)x(4n) cells); WIRE_CAP_CELLS=4096 enforces n*granularity <= cap (quarter clamps n to 16, the design doc's worked example). Granularity + min_wl key ALL five touch points: DistrictWindowLayer echo, server FIFO-256 cache key (now a 5-tuple), per-connection coalescing key, client request codec (omitted-at-default wire fields), client LRU key. Mandatory aliasing regressions on both ends: identical (body, center, n) at granularity 1 vs 4 produce distinct cache entries and correct per-granularity payload shapes (server, 3-thread queue to avoid the AnalyzeBody thread contention found while writing it) and distinct client cache keys (gdUnit). Replay fixture regenerated — the layer struct grew two echoed fields (231->254 bytes, content verified). Client requests stay district-granularity by default — quarter requests arrive with T-1153's rung selection.
This commit is contained in:
@@ -203,10 +203,17 @@ pub enum GenWorkItem {
|
||||
/// reason `AnalyzeBody.body_params` is boxed.
|
||||
body_params: Box<BodyParams>,
|
||||
/// Window centre + side length in districts. `n` is ALREADY clamped to
|
||||
/// `[1, DISTRICT_WINDOW_MAX_N]` by the caller (`handle_atlas_request`)
|
||||
/// `[1, DISTRICT_WINDOW_MAX_N]` AND the granularity-aware
|
||||
/// `WIRE_CAP_CELLS` ceiling by the caller (`handle_atlas_request`)
|
||||
/// before this item is built — never trusted from the wire again here.
|
||||
center: DistrictPos,
|
||||
n: u32,
|
||||
/// Derivation granularity (T-1150) — `WINDOW_GRANULARITY_DISTRICT` (1)
|
||||
/// or `WINDOW_GRANULARITY_QUARTER` (4). Already resolved via
|
||||
/// `resolve_window_granularity` by the caller.
|
||||
granularity: u32,
|
||||
/// Octave cutoff in whole metres (T-1149/T-1150), `0` = no cutoff.
|
||||
min_wl_m: u32,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -218,14 +225,23 @@ impl GenWorkItem {
|
||||
}
|
||||
}
|
||||
|
||||
/// Coalescing key for `DeriveWindow` items only — `(connection, body)`.
|
||||
/// Coalescing key for `DeriveWindow` items only — `(connection, body,
|
||||
/// granularity)` (T-1150, design doc §3 [SOFT] recommendation, extending
|
||||
/// T-1137's `(connection, body)`). `granularity` is part of the key so an
|
||||
/// in-flight district-spacing (granularity 1) pan-burst is never
|
||||
/// superseded by an unrelated quarter-spacing (granularity 4) request for
|
||||
/// the same connection+body, and vice versa — the two rungs are separate
|
||||
/// in-flight derives, not competing updates to the same one.
|
||||
/// `None` for every other variant (they don't coalesce this way).
|
||||
pub fn window_supersede_key(&self) -> Option<(ConnectionId, &str)> {
|
||||
pub fn window_supersede_key(&self) -> Option<(ConnectionId, &str, u32)> {
|
||||
if let GenWorkItem::DeriveWindow {
|
||||
body_id, conn_id, ..
|
||||
body_id,
|
||||
conn_id,
|
||||
granularity,
|
||||
..
|
||||
} = self
|
||||
{
|
||||
Some((*conn_id, body_id))
|
||||
Some((*conn_id, body_id, *granularity))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -403,11 +419,15 @@ impl GenerationQueue {
|
||||
}
|
||||
|
||||
/// Submit a `DeriveWindow` item with per-connection coalescing (D-226
|
||||
/// T-1124 amendment §1, "recommended"): if a `DeriveWindow` item for the
|
||||
/// SAME `(connection, body)` is still sitting in the pending queue
|
||||
/// (not yet dispatched to a Rayon worker), it is replaced in place by the
|
||||
/// new one — a pan-burst that queues several window requests for the same
|
||||
/// connection+body before the first is dispatched collapses to one derive.
|
||||
/// T-1124 amendment §1, "recommended"; extended T-1150 to key on
|
||||
/// granularity too): if a `DeriveWindow` item for the SAME `(connection,
|
||||
/// body, granularity)` is still sitting in the pending queue (not yet
|
||||
/// dispatched to a Rayon worker), it is replaced in place by the new one
|
||||
/// — a pan-burst that queues several window requests for the same
|
||||
/// connection+body+granularity before the first is dispatched collapses
|
||||
/// to one derive. A district-spacing and quarter-spacing request for the
|
||||
/// same connection+body do NOT coalesce with each other — they're
|
||||
/// separate in-flight derives, not competing updates to the same rung.
|
||||
///
|
||||
/// Deliberately does **not** attempt to cancel an item already dispatched
|
||||
/// to a Rayon worker (no cancellation channel exists, and the amendment
|
||||
@@ -419,12 +439,12 @@ impl GenerationQueue {
|
||||
/// `window_supersede_key()` returns `None`).
|
||||
pub fn submit_window(&self, item: GenWorkItem, priority: GenPriority) {
|
||||
if let Some(key) = item.window_supersede_key() {
|
||||
let key = (key.0, key.1.to_string());
|
||||
let key = (key.0, key.1.to_string(), key.2);
|
||||
let mut pending = self.pending.lock().unwrap();
|
||||
pending.retain(|q| {
|
||||
q.item
|
||||
.window_supersede_key()
|
||||
.map(|k| (k.0, k.1.to_string()) != key)
|
||||
.map(|k| (k.0, k.1.to_string(), k.2) != key)
|
||||
.unwrap_or(true)
|
||||
});
|
||||
let pos = pending
|
||||
@@ -747,6 +767,8 @@ fn run_work_item(
|
||||
body_params,
|
||||
center,
|
||||
n,
|
||||
granularity,
|
||||
min_wl_m,
|
||||
} => match load_heightmap_png(heightmap_path, body_id, *sea_level) {
|
||||
Ok(hm) => {
|
||||
// Same GRID_W×GRID_H downsample AnalyzeBody applies (D-202) — the
|
||||
@@ -780,6 +802,8 @@ fn run_work_item(
|
||||
*center,
|
||||
*n,
|
||||
&climate,
|
||||
*granularity,
|
||||
*min_wl_m,
|
||||
);
|
||||
GenCompletion::WindowDerived {
|
||||
body_id: body_id.clone(),
|
||||
@@ -1137,6 +1161,8 @@ mod tests {
|
||||
}),
|
||||
center,
|
||||
n: 4,
|
||||
granularity: crate::atlas::layer_proxy::WINDOW_GRANULARITY_DISTRICT,
|
||||
min_wl_m: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+753
-68
File diff suppressed because it is too large
Load Diff
@@ -413,7 +413,10 @@ fn drain_generation_completions(
|
||||
// NEXT poll (the existing D-225 re-request loop) hits
|
||||
// `handle_atlas_request`'s window branch, which finds this
|
||||
// entry via `DistrictWindowCache::get` and serves it.
|
||||
window_cache.insert((body_id, layer.center, layer.n), *layer);
|
||||
window_cache.insert(
|
||||
(body_id, layer.center, layer.n, layer.granularity, layer.min_wl_m),
|
||||
*layer,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -904,6 +907,8 @@ mod tests {
|
||||
up_to: CascadeLayer::Topography,
|
||||
window_center: None,
|
||||
window_n: 0,
|
||||
window_granularity: 0,
|
||||
window_min_wl_m: 0,
|
||||
},
|
||||
)]));
|
||||
world.insert_resource(AtlasResponseBuffer::default());
|
||||
|
||||
@@ -1201,6 +1201,8 @@ mod inbound_tests {
|
||||
up_to: CascadeLayer::Topography,
|
||||
window_center: None,
|
||||
window_n: 0,
|
||||
window_granularity: 0,
|
||||
window_min_wl_m: 0,
|
||||
};
|
||||
let frame = rmp_serde::to_vec_named(&req).unwrap();
|
||||
assert!(
|
||||
@@ -1246,6 +1248,8 @@ mod inbound_tests {
|
||||
up_to: CascadeLayer::Topography,
|
||||
window_center: None,
|
||||
window_n: 0,
|
||||
window_granularity: 0,
|
||||
window_min_wl_m: 0,
|
||||
})
|
||||
.unwrap();
|
||||
let star_map_frame = rmp_serde::to_vec_named(&StarMapRequest { star_map: true }).unwrap();
|
||||
|
||||
Reference in New Issue
Block a user