pub struct StreamRadius {
pub r_active: f64,
pub r_evict: f64,
}Expand description
Per-grid streaming activity / eviction radii (S7.1).
Both values are in grid-local voxel units — the same scale
as a GridLocalPos::voxel coordinate. The math falls out
cleanly from there: chunks span fixed integer voxel extents and
the camera’s grid-local position is also expressed in voxels.
Semantics inside crate::Scene::pump_streaming_sync:
- A chunk whose AABB-to-camera distance is
≤ r_activeMUST be loaded; if absent + a generator is attached, it gets streamed in viacrate::Grid::ensure_chunk_generated. - A chunk whose AABB-to-camera distance is
> r_evictis dropped from the chunk map. - Chunks in the hysteresis band
(r_active, r_evict]are neither streamed in nor evicted — they’re left as-is. The gap prevents a camera oscillating near a boundary from thrashing generation + eviction.
The Default / Self::DISABLED value is r_active = 0,
r_evict = ∞: crate::Scene::pump_streaming_sync is a no-op.
Existing grids keep the pre-S7 “absent stays absent, present
stays present” behaviour until a caller opts in.
Fields§
§r_active: f64Chunks closer than this (grid-local voxels, AABB distance) are streamed in.
r_evict: f64Chunks farther than this (grid-local voxels, AABB distance)
are evicted. Must be ≥ r_active.
Implementations§
Source§impl StreamRadius
impl StreamRadius
Sourcepub const DISABLED: Self
pub const DISABLED: Self
r_active = 0, r_evict = ∞ — pump_streaming_sync
never streams a chunk in or evicts one. The default for
pre-S7.1 grids.
Sourcepub fn new(r_active: f64, r_evict: f64) -> Self
pub fn new(r_active: f64, r_evict: f64) -> Self
New radius pair. Requires r_evict >= r_active so the
hysteresis band is well-formed (or empty when ==).
§Panics
Panics if r_evict < r_active, if r_active is NaN or
negative, or if r_evict is negative. NaN and negative
radii are policy bugs — failing loud at construction beats
silently degenerating chunk-AABB tests later.
Examples found in repository?
83fn main() {
84 // ANCHOR: scene_grids
85 let mut scene = Scene::new();
86 // A static "world" grid at the origin…
87 let ground_id = scene.add_grid(GridTransform::at(DVec3::ZERO));
88 // …and a second grid placed and rotated independently — its own
89 // f64 world position + quaternion, same chunked voxel payload.
90 let ship_id = scene.add_grid(GridTransform {
91 origin: DVec3::new(300.0, -80.0, -40.0),
92 rotation: DQuat::from_rotation_z(0.5),
93 });
94 // Object grids should not paint their own (grid-local, rotating)
95 // sky — leave the sky to the world grid.
96 scene.grid_mut(ship_id).expect("just added").render_sky = false;
97 // ANCHOR_END: scene_grids
98
99 // ANCHOR: edits
100 let ground = scene.grid_mut(ground_id).expect("just added");
101 // Solid slab: grid-local voxel coords, inclusive on both ends,
102 // decomposed across as many chunks as it spans (here 4×4).
103 ground.set_rect(
104 IVec3::new(-200, -200, 200),
105 IVec3::new(199, 199, 254),
106 Some(GRASS),
107 );
108 // `Some(colour)` inserts, `None` carves back to air.
109 ground.set_sphere(IVec3::new(0, 0, 195), 12, Some(STONE));
110 ground.set_rect(IVec3::new(-3, -200, 190), IVec3::new(3, 199, 196), None);
111 // ANCHOR_END: edits
112
113 // ANCHOR: queries
114 // Solid tests: inside the slab, then inside the carved tunnel.
115 assert!(ground.voxel_solid(IVec3::new(0, 0, 210)));
116 assert!(!ground.voxel_solid(IVec3::new(0, 100, 193)));
117 // Colour queries answer on surface voxels; deep interior cells
118 // are untextured in the slab format and read as `None`.
119 assert_eq!(ground.voxel_color(IVec3::new(50, 50, 200)), Some(GRASS));
120 // ANCHOR_END: queries
121
122 // ANCHOR: recolour
123 // GOTCHA: inserting over already-solid voxels does NOT repaint
124 // them — span insertion only fills air. Recolour = carve, then
125 // insert.
126 let (lo, hi) = (IVec3::new(20, 20, 200), IVec3::new(24, 24, 204));
127 ground.set_rect(lo, hi, Some(RED));
128 assert_eq!(ground.voxel_color(IVec3::new(22, 22, 200)), Some(GRASS)); // unchanged!
129 ground.set_rect(lo, hi, None); // carve…
130 ground.set_rect(lo, hi, Some(RED)); // …then insert
131 assert_eq!(ground.voxel_color(IVec3::new(22, 22, 200)), Some(RED));
132 // ANCHOR_END: recolour
133
134 // ANCHOR: colfunc
135 // A carve exposes fresh interior walls; the plain edits paint
136 // them colour 0 (black). The `_with_colfunc` variants ask a
137 // closure instead — it sees grid-local coordinates, so gradients
138 // stay continuous across chunk seams. Here: a crater whose walls
139 // darken with depth.
140 ground.set_sphere_with_colfunc(IVec3::new(60, 60, 200), 8, SpanOp::Carve, |_x, _y, z| {
141 let depth = (z - 192).clamp(0, 15) as u8;
142 VoxColor::rgb(0x6b, 0x40 + depth * 3, 0x2f)
143 });
144 // ANCHOR_END: colfunc
145
146 // ANCHOR: snapshot
147 // Name grids before saving: ids survive the round-trip, but the
148 // generator / store hooks are host code and cannot be serialised
149 // — the name is your key for rebinding them after a load.
150 scene.grid_mut(ground_id).expect("ground exists").name = Some("ground".into());
151 let bytes = scene.save_snapshot(); // versioned envelope
152 let restored = Scene::load_snapshot(&bytes).expect("self-authored snapshot");
153 assert_eq!(restored.grid_count(), 2);
154 let (_, ground2) = restored
155 .grids()
156 .find(|(_, g)| g.name.as_deref() == Some("ground"))
157 .expect("rebind by name");
158 assert_eq!(ground2.voxel_color(IVec3::new(22, 22, 200)), Some(RED));
159 // ANCHOR_END: snapshot
160
161 // ANCHOR: streaming
162 // Streaming: attach a generator + radii to a grid, then pump once
163 // per frame with the camera's world position. Chunks within
164 // `r_active` stream in; chunks beyond `r_evict` drop out; the band
165 // between is hysteresis so a camera hovering on a boundary
166 // doesn't thrash.
167 let stream_id = scene.add_grid(GridTransform::at(DVec3::new(0.0, 4096.0, 0.0)));
168 let store = Arc::new(MemoryStore::default());
169 let grid = scene.grid_mut(stream_id).expect("just added");
170 grid.set_generator(Some(Arc::new(FloorGenerator)));
171 grid.set_chunk_store(Some(store));
172 grid.stream_radius = StreamRadius::new(256.0, 384.0);
173
174 let camera_near = DVec3::new(0.0, 4096.0, 100.0);
175 scene.pump_streaming_sync(camera_near); // real games: `pump_streaming`
176 let grid = scene.grid(stream_id).expect("still there");
177 assert!(grid.chunk_count() > 0); // floor streamed in around the camera
178 assert!(grid.voxel_solid(IVec3::new(5, 5, 240)));
179 // ANCHOR_END: streaming
180
181 // ANCHOR: streaming_edit
182 // Edit the streamed floor, walk far away, come back. The active
183 // set follows the camera: the old region evicts (edited chunks
184 // are handed to the ChunkStore first) while a fresh set streams
185 // in around the new position. On return the store wins over the
186 // generator, so the edit survives; without a store, eviction
187 // would silently revert the chunk to generator output.
188 scene
189 .grid_mut(stream_id)
190 .expect("still there")
191 .set_voxel(IVec3::new(5, 5, 240), None); // dig a hole
192 scene.pump_streaming_sync(camera_near + DVec3::new(0.0, 100_000.0, 0.0));
193 let grid = scene.grid(stream_id).expect("still there");
194 assert!(grid.chunk(IVec3::ZERO).is_none()); // home chunk evicted…
195 assert!(grid.chunk_count() > 0); // …but the far region streamed in
196 scene.pump_streaming_sync(camera_near);
197 let grid = scene.grid(stream_id).expect("re-streamed");
198 // The hole survived evict + re-stream.
199 assert!(!grid.voxel_solid(IVec3::new(5, 5, 240)));
200 // ANCHOR_END: streaming_edit
201
202 println!("book_scene_graph: all scene-graph assertions hold");
203}Sourcepub fn is_disabled(self) -> bool
pub fn is_disabled(self) -> bool
true for the Self::DISABLED sentinel pair. Lets
pump_streaming_sync skip the per-grid pass cheaply when
streaming is off.
Trait Implementations§
Source§impl Clone for StreamRadius
impl Clone for StreamRadius
Source§fn clone(&self) -> StreamRadius
fn clone(&self) -> StreamRadius
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreimpl Copy for StreamRadius
Source§impl Debug for StreamRadius
impl Debug for StreamRadius
Source§impl Default for StreamRadius
impl Default for StreamRadius
Source§impl<'de> Deserialize<'de> for StreamRadius
impl<'de> Deserialize<'de> for StreamRadius
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl PartialEq for StreamRadius
impl PartialEq for StreamRadius
Source§fn eq(&self, other: &StreamRadius) -> bool
fn eq(&self, other: &StreamRadius) -> bool
self and other values to be equal, and is used by ==.Source§impl Serialize for StreamRadius
impl Serialize for StreamRadius
impl StructuralPartialEq for StreamRadius
Auto Trait Implementations§
impl Freeze for StreamRadius
impl RefUnwindSafe for StreamRadius
impl Send for StreamRadius
impl Sync for StreamRadius
impl Unpin for StreamRadius
impl UnsafeUnpin for StreamRadius
impl UnwindSafe for StreamRadius
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more