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
use na;
use specs;
use specs::{WriteStorage, Fetch};
use specs::Entities;
use slog::Logger;
use types::*;
use globe::{Globe, View, ChunkView};
use render::{Visual, ProtoMesh, Vertex};
use Spatial;
// For now, just creates up to 1 chunk view per tick,
// until we have created views for all chunks.
pub struct ChunkViewSystem {
log: Logger,
seconds_between_geometry_creation: TimeDelta,
seconds_since_last_geometry_creation: TimeDelta,
}
impl ChunkViewSystem {
pub fn new(
parent_log: &Logger,
seconds_between_geometry_creation: TimeDelta,
) -> ChunkViewSystem {
ChunkViewSystem {
log: parent_log.new(o!()),
seconds_between_geometry_creation: seconds_between_geometry_creation,
seconds_since_last_geometry_creation: 0.0,
}
}
fn build_chunk_geometry<'a>(
&mut self,
mut globes: specs::WriteStorage<'a, Globe>,
mut visuals: specs::WriteStorage<'a, Visual>,
// TODO: Parameterise over ReadStorage/WriteStorage when we don't care?
// TODO: I made `MaybeMutStorage` for `SpatialStorage`, so just pluck
// that out somewhere public and use that.
chunk_views: specs::WriteStorage<'a, ChunkView>,
) {
// Throttle rate of geometry creation.
// We don't want to spend too much doing this.
let ready = self.seconds_since_last_geometry_creation >
self.seconds_between_geometry_creation;
if !ready {
return;
}
use specs::Join;
for (visual, chunk_view) in (&mut visuals, &chunk_views).join() {
// TODO: find the closest mesh to the player that needs
// to be generated (i.e. absent or dirty).
//
// TODO: eventually, some rules about capping how many you create.
// Get the associated globe, complaining loudly if we fail.
let globe_entity = chunk_view.globe_entity;
let globe = match globes.get_mut(globe_entity) {
Some(globe) => globe,
None => {
warn!(
self.log,
"The globe associated with this ChunkView is not alive! Can't proceed!"
);
continue;
}
};
// Only re-generate geometry if the chunk is marked as having
// been changed since last time the view was updated.
//
// Note that this will also be true if geometry has never been created for this chunk.
//
// TODO: this might also need to be done any time any of its neighboring
// chunks changes, because we cull invisible cells, and what cells are
// visible partly depends on what's in neighboring chunks.
use globe::globe::GlobeGuts;
let spec = globe.spec();
{
// Ew, can I please have non-lexical borrow scopes?
let chunk = &mut globe.chunks_mut().get(&chunk_view.origin)
.expect("Don't know how to deal with chunk not loaded yet. Why do we have a view for it anyway?");
if !chunk.is_view_dirty {
continue;
}
}
// Make a proto-mesh for the chunk.
// TODO: debounce/throttle per chunk.
trace!(self.log, "Making chunk proto-mesh"; "origin" => format!("{:?}", chunk_view.origin));
// TEMP: just use the existing globe `View` struct
// to get this done. TODO: move into `ChunkView`.
let globe_view = View::new(spec, &self.log);
// Build geometry for this chunk into vertex
// and index buffers.
let mut vertex_data: Vec<Vertex> = Vec::new();
let mut index_data: Vec<u32> = Vec::new();
globe_view.make_chunk_geometry(
globe,
chunk_view.origin,
&mut vertex_data,
&mut index_data,
);
// Mark the chunk as having a clean view.
// NOTE: we need to do this before maybe skipping
// actually building the view.
{
// Ew, can I please have non-lexical borrow scopes?
let chunk = &mut globe.chunks_mut().get_mut(&chunk_view.origin)
.expect("Don't know how to deal with chunk not loaded yet. Why do we have a view for it anyway?");
chunk.mark_view_as_clean();
}
// Don't attempt to create an empty mesh.
// Back-end doesn't seem to like this, and there's no point
// in wasting the VBOs etc. for nothing.
if vertex_data.is_empty() || index_data.is_empty() {
trace!(self.log, "Skipping chunk proto-mesh that would be empty"; "origin" => format!("{:?}", chunk_view.origin));
// TODO: is there anything that will assume we need to make the
// mesh again just because there's no mesh for the view?
// Maybe we need to make the case of an empty `Visual` explicit
// in that type to avoid mistakes.
continue;
}
visual.proto_mesh = ProtoMesh::new(vertex_data, index_data).into();
trace!(self.log, "Made chunk proto-mesh"; "origin" => format!("{:?}", chunk_view.origin));
// Do at most 1 per frame; probably far less.
self.seconds_since_last_geometry_creation = 0.0;
return;
}
}
pub fn remove_views_for_dead_chunks<'a>(
&mut self,
entities: &Entities<'a>,
globe: &mut Globe,
globe_entity: specs::Entity,
visuals: &mut specs::WriteStorage<'a, Visual>,
chunk_views: &mut specs::WriteStorage<'a, ChunkView>,
) {
use specs::Join;
let mut entities_to_remove: Vec<specs::Entity> = Vec::new();
for (chunk_view, chunk_view_ent) in (&*chunk_views, &**entities).join() {
// Ignore chunks not belonging to this globe.
if chunk_view.globe_entity != globe_entity {
continue;
}
if globe.chunk_at(chunk_view.origin).is_none() {
debug!(self.log, "Removing a chunk view"; "origin" => format!("{:?}", chunk_view.origin));
entities_to_remove.push(chunk_view_ent);
}
}
for chunk_view_ent in entities_to_remove {
// TODO: don't forget to remove the MESH.
// TODO: don't forget to remove the MESH.
// TODO: don't forget to remove the MESH.
// TODO: don't forget to remove the MESH.
// TODO: don't forget to remove the MESH.
//
// If you don't do that, then we'll slowly leak VBOs etc.
//
// But you can get away with not doing that for now because
// the tests don't start the render system, and so will never
// make those meshes to begin with.
// Remove Visual and ChunkView components (to prevent accidentally
// iterating over them later within the same frame) and then queue
// the entity itself up for deletion.
visuals.remove(chunk_view_ent);
chunk_views.remove(chunk_view_ent);
entities.delete(chunk_view_ent).expect("Somehow tried to use an entity with the wrong generation!");
// TODO: maintain? Do we need to do that here?
// Or are these entities immediately inaccessible?
// (I'm unclear on when it's necessary / exactly what it does.)
}
}
pub fn ensure_chunk_view_entities<'a>(
&mut self,
entities: &Entities<'a>,
globe: &mut Globe,
globe_entity: specs::Entity,
chunk_views: &mut specs::WriteStorage<'a, ChunkView>,
visuals: &mut specs::WriteStorage<'a, Visual>,
spatials: &mut specs::WriteStorage<'a, Spatial>,
) {
use globe::globe::GlobeGuts;
let globe_spec = globe.spec();
for chunk in globe.chunks_mut().values_mut() {
if chunk.view_entity.is_some() {
continue;
}
trace!(self.log, "Making a chunk view"; "origin" => format!("{:?}", chunk.origin));
let chunk_view = ChunkView::new(globe_entity, chunk.origin);
// We store the geometry relative to the bottom-center of the chunk origin cell.
let chunk_origin_pos = globe_spec.cell_bottom_center(*chunk.origin.pos());
let chunk_transform = Iso3::new(chunk_origin_pos.coords, na::zero());
// We'll fill it in later.
let empty_visual = ::render::Visual::new_empty();
// TODO: Use `create_later_build`, now that it exists?
// Updated TODO: figure out what the new API is for that.
let new_ent = entities.create();
// TODO: run `maintain`? When do we have to do that?
chunk.view_entity = Some(new_ent);
chunk_views.insert(new_ent, chunk_view);
visuals.insert(new_ent, empty_visual);
spatials.insert(new_ent, Spatial::new(globe_entity, chunk_transform));
}
}
}
impl<'a> specs::System<'a> for ChunkViewSystem {
type SystemData = (Entities<'a>,
Fetch<'a, TimeDeltaResource>,
WriteStorage<'a, Globe>,
WriteStorage<'a, Visual>,
WriteStorage<'a, Spatial>,
WriteStorage<'a, ChunkView>);
fn run(&mut self, data: Self::SystemData) {
use specs::Join;
let (entities, dt, mut globes, mut visuals, mut spatials, mut chunk_views) = data;
self.seconds_since_last_geometry_creation += dt.0;
// Destroy views for any chunks that are no longer loaded.
for (globe, globe_entity) in (&mut globes, &*entities).join() {
self.remove_views_for_dead_chunks(
&entities,
globe,
globe_entity,
&mut visuals,
&mut chunk_views,
);
// Ensure that there is a visual for
// every chunk currently loaded in the globe.
//
// TODO: we don't actually want to do this
// long-term; it's just a first step in migrating
// to systems-based view creation. Eventually we'll
// be selective about what views to have; i.e. we might
// have 1000 chunks loaded, and only render 200 of them
// on this client.
self.ensure_chunk_view_entities(
&entities,
globe,
globe_entity,
&mut chunk_views,
&mut visuals,
&mut spatials,
);
}
// Build geometry for some chunks; throttled
// so we don't spend too much time doing this each frame.
self.build_chunk_geometry(globes, visuals, chunk_views);
}
}