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 voxel_world_size: 1.0,
94 });
95 // Object grids should not paint their own (grid-local, rotating)
96 // sky — leave the sky to the world grid.
97 scene.grid_mut(ship_id).expect("just added").render_sky = false;
98 // ANCHOR_END: scene_grids
99
100 // ANCHOR: edits
101 let ground = scene.grid_mut(ground_id).expect("just added");
102 // Solid slab: grid-local voxel coords, inclusive on both ends,
103 // decomposed across as many chunks as it spans (here 4×4).
104 ground.set_rect(
105 IVec3::new(-200, -200, 200),
106 IVec3::new(199, 199, 254),
107 Some(GRASS),
108 );
109 // `Some(colour)` inserts, `None` carves back to air.
110 ground.set_sphere(IVec3::new(0, 0, 195), 12, Some(STONE));
111 ground.set_rect(IVec3::new(-3, -200, 190), IVec3::new(3, 199, 196), None);
112 // ANCHOR_END: edits
113
114 // ANCHOR: queries
115 // Solid tests: inside the slab, then inside the carved tunnel.
116 assert!(ground.voxel_solid(IVec3::new(0, 0, 210)));
117 assert!(!ground.voxel_solid(IVec3::new(0, 100, 193)));
118 // Colour queries answer on surface voxels; deep interior cells
119 // are untextured in the slab format and read as `None`.
120 assert_eq!(ground.voxel_color(IVec3::new(50, 50, 200)), Some(GRASS));
121 // ANCHOR_END: queries
122
123 // ANCHOR: recolour
124 // GOTCHA: inserting over already-solid voxels does NOT repaint
125 // them — span insertion only fills air. Recolour = carve, then
126 // insert.
127 let (lo, hi) = (IVec3::new(20, 20, 200), IVec3::new(24, 24, 204));
128 ground.set_rect(lo, hi, Some(RED));
129 assert_eq!(ground.voxel_color(IVec3::new(22, 22, 200)), Some(GRASS)); // unchanged!
130 ground.set_rect(lo, hi, None); // carve…
131 ground.set_rect(lo, hi, Some(RED)); // …then insert
132 assert_eq!(ground.voxel_color(IVec3::new(22, 22, 200)), Some(RED));
133 // ANCHOR_END: recolour
134
135 // ANCHOR: colfunc
136 // A carve exposes fresh interior walls; the plain edits paint
137 // them colour 0 (black). The `_with_colfunc` variants ask a
138 // closure instead — it sees grid-local coordinates, so gradients
139 // stay continuous across chunk seams. Here: a crater whose walls
140 // darken with depth.
141 ground.set_sphere_with_colfunc(IVec3::new(60, 60, 200), 8, SpanOp::Carve, |_x, _y, z| {
142 let depth = (z - 192).clamp(0, 15) as u8;
143 VoxColor::rgb(0x6b, 0x40 + depth * 3, 0x2f)
144 });
145 // ANCHOR_END: colfunc
146
147 // ANCHOR: snapshot
148 // Name grids before saving: ids survive the round-trip, but the
149 // generator / store hooks are host code and cannot be serialised
150 // — the name is your key for rebinding them after a load.
151 scene.grid_mut(ground_id).expect("ground exists").name = Some("ground".into());
152 let bytes = scene.save_snapshot(); // versioned envelope
153 let restored = Scene::load_snapshot(&bytes).expect("self-authored snapshot");
154 assert_eq!(restored.grid_count(), 2);
155 let (_, ground2) = restored
156 .grids()
157 .find(|(_, g)| g.name.as_deref() == Some("ground"))
158 .expect("rebind by name");
159 assert_eq!(ground2.voxel_color(IVec3::new(22, 22, 200)), Some(RED));
160 // ANCHOR_END: snapshot
161
162 // ANCHOR: streaming
163 // Streaming: attach a generator + radii to a grid, then pump once
164 // per frame with the camera's world position. Chunks within
165 // `r_active` stream in; chunks beyond `r_evict` drop out; the band
166 // between is hysteresis so a camera hovering on a boundary
167 // doesn't thrash.
168 let stream_id = scene.add_grid(GridTransform::at(DVec3::new(0.0, 4096.0, 0.0)));
169 let store = Arc::new(MemoryStore::default());
170 let grid = scene.grid_mut(stream_id).expect("just added");
171 grid.set_generator(Some(Arc::new(FloorGenerator)));
172 grid.set_chunk_store(Some(store));
173 grid.stream_radius = StreamRadius::new(256.0, 384.0);
174
175 let camera_near = DVec3::new(0.0, 4096.0, 100.0);
176 scene.pump_streaming_sync(camera_near); // real games: `pump_streaming`
177 let grid = scene.grid(stream_id).expect("still there");
178 assert!(grid.chunk_count() > 0); // floor streamed in around the camera
179 assert!(grid.voxel_solid(IVec3::new(5, 5, 240)));
180 // ANCHOR_END: streaming
181
182 // ANCHOR: streaming_edit
183 // Edit the streamed floor, walk far away, come back. The active
184 // set follows the camera: the old region evicts (edited chunks
185 // are handed to the ChunkStore first) while a fresh set streams
186 // in around the new position. On return the store wins over the
187 // generator, so the edit survives; without a store, eviction
188 // would silently revert the chunk to generator output.
189 scene
190 .grid_mut(stream_id)
191 .expect("still there")
192 .set_voxel(IVec3::new(5, 5, 240), None); // dig a hole
193 scene.pump_streaming_sync(camera_near + DVec3::new(0.0, 100_000.0, 0.0));
194 let grid = scene.grid(stream_id).expect("still there");
195 assert!(grid.chunk(IVec3::ZERO).is_none()); // home chunk evicted…
196 assert!(grid.chunk_count() > 0); // …but the far region streamed in
197 scene.pump_streaming_sync(camera_near);
198 let grid = scene.grid(stream_id).expect("re-streamed");
199 // The hole survived evict + re-stream.
200 assert!(!grid.voxel_solid(IVec3::new(5, 5, 240)));
201 // ANCHOR_END: streaming_edit
202
203 // ANCHOR: grid_scale
204 // Per-grid scale: `voxel_world_size` is world units per voxel. A grid
205 // built with `at_scale(origin, 2.0)` has voxels twice world size — a
206 // coarse "planet" — while `0.25` gives a fine detail grid; both coexist
207 // at their true relative sizes. Only the world↔grid boundary scales:
208 // edits + queries stay in voxels, unchanged.
209 let mut scaled = Scene::new();
210 let planet = scaled.add_grid(GridTransform::at_scale(DVec3::ZERO, 2.0));
211 scaled
212 .grid_mut(planet)
213 .expect("just added")
214 // Same voxel edit API — grid-local coordinates.
215 .set_voxel(IVec3::new(5, 5, 10), Some(STONE));
216 // Grid-local voxel z=10 sits at WORLD z = 20 (10 voxels × 2.0). A world
217 // raycast down that column reports the grid-local voxel it hit AND a
218 // WORLD-space `t` — so hits across grids of different scale compare
219 // correctly (the raycaster marches voxels but returns world distance).
220 let hit = scaled
221 .raycast(DVec3::new(11.0, 11.0, 0.0), DVec3::new(0.0, 0.0, 1.0), 64.0)
222 .expect("ray hits the scaled voxel");
223 assert_eq!(hit.voxel, IVec3::new(5, 5, 10)); // grid-local, unscaled
224 assert!((hit.t - 20.0).abs() < 1e-4); // WORLD distance: 10 voxels × 2.0
225 // ANCHOR_END: grid_scale
226
227 println!("book_scene_graph: all scene-graph assertions hold");
228}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§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