1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
use std::collections::HashMap;
use specs;
use grid::{GridPoint3, PosInOwningRoot, Neighbors};
use super::{origin_of_chunk_owning, origin_of_chunk_in_same_root_containing};
use super::ChunkOrigin;
use super::chunk::{Chunk, Cell};
use super::spec::Spec;
use super::gen::Gen;
use super::chunk_pair::{ChunkPairOrigins, ChunkPair};
// TODO: split out a WorldGen type that handles all the procedural
// generation, because none of that really needs to be tangled
// with the realised Globe.
pub struct Globe {
spec: Spec,
// TODO: temporarily making this public because I'm planning to
// rip it out of `Globe` anyway.
pub gen: Gen,
// Map chunk origins to chunks.
//
// TODO: you'll probably also want to store some lower-res
// pseudo-chunks for rendering planets at a distance.
// But maybe you can put that off? Or maybe that's an entirely
// different type of Globe-oid?
chunks: HashMap<ChunkOrigin, Chunk>,
// Track which chunks are up-to-date with authoritative data for cells
// they share with a neighbor.
chunk_pairs: HashMap<ChunkPairOrigins, ChunkPair>,
}
// Allowing sibling modules to reach into semi-private parts
// of the Globe struct.
pub trait GlobeGuts<'a> {
fn chunks(&'a self) -> &'a HashMap<ChunkOrigin, Chunk>;
fn chunks_mut(&'a mut self) -> &'a mut HashMap<ChunkOrigin, Chunk>;
}
impl<'a> GlobeGuts<'a> for Globe {
fn chunks(&'a self) -> &'a HashMap<ChunkOrigin, Chunk> {
&self.chunks
}
fn chunks_mut(&'a mut self) -> &'a mut HashMap<ChunkOrigin, Chunk> {
&mut self.chunks
}
}
impl Globe {
pub fn new(spec: Spec) -> Globe {
Globe {
spec: spec,
gen: Gen::new(spec),
chunks: HashMap::new(),
chunk_pairs: HashMap::new(),
}
}
pub fn new_example() -> Globe {
Globe::new(Spec {
seed: 14,
floor_radius: 25.0,
ocean_radius: 66.6,
block_height: 0.65,
root_resolution: [64, 128],
// Chunks should probably be taller, but short chunks are a bit
// better for now in exposing bugs visually.
chunk_resolution: [16, 16, 4],
})
}
pub fn new_earth_scale_example() -> Globe {
Globe::new(Spec::new_earth_scale_example())
}
pub fn spec(&self) -> Spec {
self.spec
}
/// Copy shared cells owned by a chunk for any loaded downstream chunks
/// that have an outdated copy.
///
/// Panics if the given chunk is not loaded.
pub fn push_shared_cells_for_chunk(&mut self, source_chunk_origin: ChunkOrigin) {
// BEWARE: HACKS BELOW to get around borrowck. There has to be
// a better way around this!
// Temporarily remove the source chunk from the globe, so that we can simultaneously
// read from it and write to a bunch of other chunks.
//
// TODO: at very least avoid this most of the time by doing a read-only pass over
// all neighbours and bailing out if they're completely up-to-date.
let source_chunk = self.chunks.remove(&source_chunk_origin).expect(
"Tried to push shared cells for a chunk that isn't loaded.",
);
// For each of this chunk's downstream neighbors, see if it has up-to-date
// copies of the data we share with that neighbor. Otherwise, copy it over.
for downstream_neighbor in &source_chunk.downstream_neighbors {
let sink_chunk = match self.chunks.get_mut(&downstream_neighbor.origin) {
None => {
// No worries; the chunk isn't loaded. Do nothing.
continue;
}
Some(chunk) => chunk,
};
// Look it up to see if it is already up-to-date.
let chunk_pair_origins = ChunkPairOrigins {
source: source_chunk.origin,
sink: sink_chunk.origin,
};
let chunk_pair = self.chunk_pairs.get_mut(&chunk_pair_origins).expect(
"Chunk pair for chunk should have been present.",
);
if chunk_pair.last_upstream_edge_version_known_downstream ==
source_chunk.owned_edge_version
{
// Sink chunk is already up-to-date; move on to next neighbor.
continue;
}
// Copy over each cell, one-by-one.
for point_pair in &chunk_pair.point_pairs {
let source_cell = *source_chunk.cell(point_pair.source.into());
let target_cell = sink_chunk.cell_mut(point_pair.sink);
// Copy source -> target.
*target_cell = source_cell;
}
// Downstream chunk now has most recent changes from upstream.
chunk_pair.last_upstream_edge_version_known_downstream = source_chunk
.owned_edge_version;
// If we got this far, then it means we needed to update something.
// So mark the downstream chunk as having its view out-of-date.
sink_chunk.mark_view_as_dirty();
}
// Put the source chunk back into the world!
self.chunks.insert(source_chunk_origin, source_chunk);
}
/// Copy shared cells not owned by a chunk from any loaded upstream chunks
/// that have a more current copy.
///
/// Panics if the given chunk is not loaded.
pub fn pull_shared_cells_for_chunk(&mut self, sink_chunk_origin: ChunkOrigin) {
// BEWARE: HACKS BELOW to get around borrowck. There has to be
// a better way around this!
// Temporarily remove the sink chunk from the globe, so that we can simultaneously
// write to it and read from a bunch of other chunks.
//
// TODO: at very least avoid this most of the time by doing a read-only pass over
// all neighbours and bailing out if we're completely up-to-date.
let mut sink_chunk = self.chunks.remove(&sink_chunk_origin).expect(
"Tried to pull shared cells for a chunk that isn't loaded.",
);
// Temporarily remove list of neighbours from sink chunk so that we can
// both read from it and update the chunk's data.
let mut upstream_neighbors = Vec::<super::chunk::UpstreamNeighbor>::new();
use std::mem::swap;
swap(&mut upstream_neighbors, &mut sink_chunk.upstream_neighbors);
// For each of this chunk's upstream neighbors, see if it has a newer copy of the data
// we share with that neighbor. Otherwise, copy it into the sink chunk.
for upstream_neighbor in &upstream_neighbors {
let source_chunk = match self.chunks.get_mut(&upstream_neighbor.origin) {
None => {
// No worries; the chunk isn't loaded. Do nothing.
continue;
}
Some(chunk) => chunk,
};
// Look it up to see if it has any newer data.
let chunk_pair_origins = ChunkPairOrigins {
source: source_chunk.origin,
sink: sink_chunk.origin,
};
let chunk_pair = self.chunk_pairs.get_mut(&chunk_pair_origins).expect(
"Chunk pair for chunk should have been present.",
);
if chunk_pair.last_upstream_edge_version_known_downstream ==
source_chunk.owned_edge_version
{
// Sink chunk is already up-to-date; move on to next neighbor.
continue;
}
// Copy over each cell, one-by-one.
for point_pair in &chunk_pair.point_pairs {
let source_cell = *source_chunk.cell(point_pair.source.into());
let target_cell = sink_chunk.cell_mut(point_pair.sink);
// Copy source -> target.
*target_cell = source_cell;
}
// Downstream chunk now has most recent changes from upstream.
chunk_pair.last_upstream_edge_version_known_downstream = source_chunk
.owned_edge_version;
// If we got this far, then it means we needed to update something.
// So mark the downstream chunk as having its view out-of-date.
sink_chunk.mark_view_as_dirty();
}
// Put the list of neighbors back into the sink chunk.
swap(&mut upstream_neighbors, &mut sink_chunk.upstream_neighbors);
// Put the sink chunk back into the world!
self.chunks.insert(sink_chunk_origin, sink_chunk);
}
pub fn origin_of_chunk_owning(&self, pos: PosInOwningRoot) -> ChunkOrigin {
origin_of_chunk_owning(pos, self.spec.root_resolution, self.spec.chunk_resolution)
}
// NOTE: chunk returned probably won't _own_ `pos`.
pub fn origin_of_chunk_in_same_root_containing(&self, pos: GridPoint3) -> ChunkOrigin {
// Figure out what chunk this is in.
origin_of_chunk_in_same_root_containing(
pos,
self.spec.root_resolution,
self.spec.chunk_resolution,
)
}
/// Most `Chunks`s will have an associated `ChunkView`. Indicate that the
/// chunk (or something else affecting its visibility) has been modified
/// since the view was last updated.
pub fn mark_chunk_views_affected_by_cell_as_dirty(&mut self, pos: GridPoint3) {
// Translate into owning root.
// TODO: wrapper types so we don't have to do
// this sort of thing defensively!
// TODO: is it even necessary at all here? All we really care about
// is consistency in which chunk we look at for the centre cell,
// and the one.....
// TODO: really, just rewrite this whole function. It doesn't really work.
let pos_in_owning_root = PosInOwningRoot::new(pos, self.spec.root_resolution);
// TODO: this (Vec) is super slow! Replace with a less brain-dead solution.
// Just committing this one now to patch over a kinda-regression in that
// the existing bug of not doing this at all just become a lot more
// obvious now that I'm doing a better job of culling cells.
let mut dirty_cells: Vec<PosInOwningRoot> = Vec::new();
dirty_cells.push(pos_in_owning_root);
dirty_cells.extend(
Neighbors::new(pos_in_owning_root.into(), self.spec.root_resolution)
.map(|neighbor_pos| {
PosInOwningRoot::new(neighbor_pos, self.spec.root_resolution)
}),
);
// Gah, filthy hacks. This is to get around not having a way to query for
// "all chunks containing this cell".
//
// TODO: replace now that you DO have that. See other comment about `AllChunksContainingPoint`.
let mut cells_in_dirty_chunks: Vec<PosInOwningRoot> = Vec::new();
for dirty_cell in dirty_cells {
cells_in_dirty_chunks.extend(
Neighbors::new(dirty_cell.into(), self.spec.root_resolution)
.map(|neighbor_pos| {
PosInOwningRoot::new(neighbor_pos, self.spec.root_resolution)
}),
);
}
for dirty_pos in cells_in_dirty_chunks {
let chunk_origin = self.origin_of_chunk_owning(dirty_pos);
// It's fine for the chunk to not be loaded.
if let Some(chunk) = self.chunks.get_mut(&chunk_origin) {
chunk.mark_view_as_dirty();
}
}
}
pub fn increment_chunk_owned_edge_version_for_cell(&mut self, pos: PosInOwningRoot) {
let chunk_origin = self.origin_of_chunk_owning(pos.into());
let chunk = self.chunks.get_mut(&chunk_origin).expect(
"Uh oh, I don't know how to handle chunks that aren't loaded yet.",
);
chunk.owned_edge_version += 1;
}
/// Add the given chunk to the globe.
///
/// This may have been freshly generated, or loaded from disk.
///
/// # Panics
///
/// Panics if there was already a chunk loaded for the same chunk origin.
pub fn add_chunk(&mut self, chunk: Chunk) {
// TODO: You could assert that the chunk actually belongs to _this globe_.
// It could store a UUID associated with its generator and complain
// if you try to load a chunk for the wrong globe even if they have
// the same resolution.
self.ensure_all_chunk_pairs_present_for(&chunk);
let chunk_origin = chunk.origin;
if self.chunks.insert(chunk_origin, chunk).is_some() {
panic!("There was already a chunk loaded at the same origin!");
}
}
/// Remove the chunk at the given chunk origin. Returns the removed chunk.
///
/// # Panics
///
/// Panics if there was no chunk loaded at the given chunk origin.
pub fn remove_chunk(&mut self, chunk_origin: ChunkOrigin) -> Chunk {
let chunk = self.chunks.remove(&chunk_origin).expect(
"Attempted to remove a chunk that was not loaded",
);
self.remove_all_chunk_pairs_for(&chunk);
chunk
}
// TODO: consider moving `load_or_build_chunk`, `ensure_chunk_present`,
// and `find_lowest_cell_containing` back out into a smarter component
// so that `Globe` can be dumber, or move more of `Globe` down into a new
// dumber component, e.g., `GlobeVoxMap`.
pub fn load_or_build_chunk(&mut self, origin: ChunkOrigin) {
use rand;
use rand::Rng;
let spec = self.spec();
let mut cells: Vec<Cell> = Vec::new();
// Include cells _on_ the far edge of the chunk;
// even though we don't own them we'll need to draw part of them.
let end_x = origin.pos().x + spec.chunk_resolution[0];
let end_y = origin.pos().y + spec.chunk_resolution[1];
// Chunks don't share cells in the z-direction,
// but do in the x- and y-directions.
let end_z = origin.pos().z + spec.chunk_resolution[2] - 1;
for cell_z in origin.pos().z..(end_z + 1) {
for cell_y in origin.pos().y..(end_y + 1) {
for cell_x in origin.pos().x..(end_x + 1) {
let grid_point = GridPoint3::new(origin.pos().root, cell_x, cell_y, cell_z);
let mut cell = self.gen.cell_at(grid_point);
// Temp hax?
let mut rng = rand::thread_rng();
cell.shade = 1.0 - 0.5 * rng.next_f32();
cells.push(cell);
}
}
}
self.add_chunk(Chunk::new(
origin,
cells,
spec.root_resolution,
spec.chunk_resolution,
));
}
/// Ensures the specified chunk is present.
///
/// If the chunk is already present, then do nothing. Otherwise, the chunk
/// may be either loaded from disk, or generated fresh if it has never been
/// saved.
///
/// This pays no regard to preferred limits on the number of chunks that should
/// be loaded, and chunks added through this mechanism may well be unloaded
/// immediately the next time this system is invoked, making this only suitable
/// for immediate actions.
//
// TODO: this definitely belongs elsewhere; somewhere that knows about
// loading things from disk. The simpler version of just making sure the
// voxmap buffer exists should exist on a dumber struct extracted from `Globe`.
pub fn ensure_chunk_present(&mut self, chunk_origin: ChunkOrigin) {
if self.chunk_at(chunk_origin).is_some() {
return;
}
self.load_or_build_chunk(chunk_origin);
// Make sure this chunk has up-to-date data for edge cells that it doesn't own.
self.pull_shared_cells_for_chunk(chunk_origin);
// Make sure that neighboring chunks have up-to-date data for edge cells owned
// by this chunk.
self.push_shared_cells_for_chunk(chunk_origin);
}
/// Make sure we are tracking the currency of shared data in all chunks
/// upstream or downstream of this chunk.
fn ensure_all_chunk_pairs_present_for(&mut self, chunk: &Chunk) {
use globe::chunk_pair::{ChunkPairOrigins, ChunkPair};
// Copy cached upstream and downstream neighbors from
// chunks rather than re-computing them for each pair now.
for upstream_neighbor in &chunk.upstream_neighbors {
let chunk_pair_origins = ChunkPairOrigins {
source: upstream_neighbor.origin,
sink: chunk.origin,
};
self.chunk_pairs.entry(chunk_pair_origins).or_insert(
ChunkPair {
point_pairs: upstream_neighbor.shared_cells.clone(),
last_upstream_edge_version_known_downstream: 0,
},
);
}
for downstream_neighbor in &chunk.downstream_neighbors {
let chunk_pair_origins = ChunkPairOrigins {
source: chunk.origin,
sink: downstream_neighbor.origin,
};
self.chunk_pairs.entry(chunk_pair_origins).or_insert(
ChunkPair {
point_pairs: downstream_neighbor.shared_cells.clone(),
last_upstream_edge_version_known_downstream: 0,
},
);
}
}
/// Clean up any chunk pairs where either the source or sink is this chunk.
fn remove_all_chunk_pairs_for(&mut self, chunk: &Chunk) {
// TODO: HashMap::retain was stabilised in Rust 1.18;
// replace this as soon as you update.
for upstream_neighbor in &chunk.upstream_neighbors {
let chunk_pair_origins = ChunkPairOrigins {
source: upstream_neighbor.origin,
sink: chunk.origin,
};
// Note that chunk pair will only be present if _both_ chunks it refers to were loaded.
self.chunk_pairs.remove(&chunk_pair_origins);
}
for downstream_neighbor in &chunk.downstream_neighbors {
let chunk_pair_origins = ChunkPairOrigins {
source: chunk.origin,
sink: downstream_neighbor.origin,
};
// Note that chunk pair will only be present if _both_ chunks it refers to were loaded.
self.chunk_pairs.remove(&chunk_pair_origins);
}
}
}
impl<'a> Globe {
pub fn chunk_at(&'a self, chunk_origin: ChunkOrigin) -> Option<&'a Chunk> {
self.chunks.get(&chunk_origin)
}
// TODO: this naming is horrible. :)
pub fn authoritative_cell(&'a self, pos: PosInOwningRoot) -> &'a Cell {
let chunk_origin = self.origin_of_chunk_owning(pos);
let chunk = self.chunks.get(&chunk_origin).expect(
"Uh oh, I don't know how to handle chunks that aren't loaded yet.",
);
chunk.cell(pos.into())
}
pub fn authoritative_cell_mut(&'a mut self, pos: PosInOwningRoot) -> &'a mut Cell {
let chunk_origin = self.origin_of_chunk_owning(pos);
let chunk = self.chunks.get_mut(&chunk_origin).expect(
"Uh oh, I don't know how to handle chunks that aren't loaded yet.",
);
chunk.cell_mut(pos.into())
}
pub fn maybe_non_authoritative_cell(&'a self, pos: GridPoint3) -> &'a Cell {
let chunk_origin = self.origin_of_chunk_in_same_root_containing(pos);
let chunk = self.chunks.get(&chunk_origin).expect(
"Uh oh, I don't know how to handle chunks that aren't loaded yet.",
);
chunk.cell(pos)
}
}
impl specs::Component for Globe {
type Storage = specs::HashMapStorage<Globe>;
}