this repo has no description
1use std::collections::BTreeSet;
2
3use bevy_ecs::world::EntityWorldMut;
4
5use crate::client::{ViewDistance, VisibleEntityLayers};
6use crate::entity::cow::CowEntityBundle;
7use crate::entity::{EntityLayerId, Position};
8use crate::layer::chunk::UnloadedChunk;
9use crate::layer::{ChunkLayer, EntityLayer};
10use crate::protocol::packets::play::{
11 BlockEntityUpdateS2c, ChunkDataS2c, ChunkDeltaUpdateS2c, EntitiesDestroyS2c, EntitySpawnS2c,
12 MoveRelativeS2c, UnloadChunkS2c,
13};
14use crate::protocol::Packet;
15use crate::testing::ScenarioSingleClient;
16use crate::{BlockState, ChunkView, Despawned, Server};
17
18#[test]
19fn block_create_destroy() {
20 let ScenarioSingleClient {
21 mut app,
22 mut helper,
23 layer: layer_ent,
24 ..
25 } = ScenarioSingleClient::new();
26
27 let mut layer = app.world_mut().get_mut::<ChunkLayer>(layer_ent).unwrap();
28
29 // Insert an empty chunk at (0, 0).
30 layer.insert_chunk([0, 0], UnloadedChunk::new());
31
32 // Wait until the next tick to start sending changes.
33 app.update();
34
35 let mut layer = app.world_mut().get_mut::<ChunkLayer>(layer_ent).unwrap();
36
37 // Set some blocks.
38 layer.set_block([1, 1, 1], BlockState::CHEST);
39 layer.set_block([1, 2, 1], BlockState::PLAYER_HEAD);
40 layer.set_block([1, 3, 1], BlockState::OAK_SIGN);
41
42 app.update();
43
44 {
45 let recvd = helper.collect_received();
46
47 recvd.assert_count::<ChunkDeltaUpdateS2c>(1);
48 recvd.assert_count::<BlockEntityUpdateS2c>(3)
49 };
50
51 let mut layer = app.world_mut().get_mut::<ChunkLayer>(layer_ent).unwrap();
52
53 layer.set_block([1, 1, 1], BlockState::AIR);
54 layer.set_block([1, 2, 1], BlockState::AIR);
55 layer.set_block([1, 3, 1], BlockState::AIR);
56
57 app.update();
58
59 {
60 let recvd = helper.collect_received();
61
62 recvd.assert_count::<ChunkDeltaUpdateS2c>(1);
63 recvd.assert_count::<BlockEntityUpdateS2c>(0);
64 }
65}
66
67#[test]
68fn layer_chunk_view_change() {
69 fn view(client: &EntityWorldMut) -> ChunkView {
70 let chunk_pos = client.get::<Position>().unwrap().0.into();
71 let view_dist = client.get::<ViewDistance>().unwrap().get();
72
73 ChunkView::new(chunk_pos, view_dist)
74 }
75
76 let ScenarioSingleClient {
77 mut app,
78 client: client_ent,
79 mut helper,
80 layer: layer_ent,
81 } = ScenarioSingleClient::new();
82
83 let mut layer = app.world_mut().get_mut::<ChunkLayer>(layer_ent).unwrap();
84
85 for z in -30..30 {
86 for x in -30..30 {
87 layer.insert_chunk([x, z], UnloadedChunk::new());
88 }
89 }
90
91 let mut client = app.world_mut().entity_mut(client_ent);
92
93 client.get_mut::<Position>().unwrap().set([8.0, 0.0, 8.0]);
94 client.get_mut::<ViewDistance>().unwrap().set(6);
95
96 // Tick
97 app.update();
98 let mut client = app.world_mut().entity_mut(client_ent);
99
100 let mut loaded_chunks = BTreeSet::new();
101
102 // Collect all chunks received on join.
103 for f in helper.collect_received().0 {
104 if f.id == ChunkDataS2c::ID {
105 let ChunkDataS2c { pos, .. } = f.decode::<ChunkDataS2c>().unwrap();
106 // Newly received chunk was not previously loaded.
107 assert!(loaded_chunks.insert(pos), "({pos:?})");
108 }
109 }
110
111 // Check that all the received chunks are in the client's view.
112 for pos in view(&client).iter() {
113 assert!(loaded_chunks.contains(&pos), "{pos:?}");
114 }
115
116 assert!(!loaded_chunks.is_empty());
117
118 // Move the client to the adjacent chunk.
119 client.get_mut::<Position>().unwrap().set([24.0, 0.0, 24.0]);
120
121 // Tick
122 app.update();
123 let client = app.world_mut().entity_mut(client_ent);
124
125 // For all chunks received this tick...
126 for f in helper.collect_received().0 {
127 match f.id {
128 ChunkDataS2c::ID => {
129 let ChunkDataS2c { pos, .. } = f.decode().unwrap();
130 // Newly received chunk was not previously loaded.
131 assert!(loaded_chunks.insert(pos), "({pos:?})");
132 }
133 UnloadChunkS2c::ID => {
134 let UnloadChunkS2c { pos } = f.decode().unwrap();
135 // Newly removed chunk was previously loaded.
136 assert!(loaded_chunks.remove(&pos), "({pos:?})");
137 }
138 _ => {}
139 }
140 }
141
142 // Check that all chunks loaded now are within the client's view.
143 for pos in view(&client).iter() {
144 assert!(loaded_chunks.contains(&pos), "{pos:?}");
145 }
146}
147
148#[test]
149fn chunk_viewer_count() {
150 let ScenarioSingleClient {
151 mut app,
152 client: client_ent,
153 mut helper,
154 layer: layer_ent,
155 } = ScenarioSingleClient::new();
156
157 let mut client = app.world_mut().entity_mut(client_ent);
158
159 client.get_mut::<Position>().unwrap().set([8.0, 64.0, 8.0]);
160 client.get_mut::<ViewDistance>().unwrap().set(2);
161
162 let mut layer = app.world_mut().get_mut::<ChunkLayer>(layer_ent).unwrap();
163
164 // Create chunk at (0, 0).
165 layer.insert_chunk([0, 0], UnloadedChunk::new());
166
167 app.update(); // Tick.
168
169 helper.collect_received().assert_count::<ChunkDataS2c>(1);
170
171 let mut layer = app.world_mut().get_mut::<ChunkLayer>(layer_ent).unwrap();
172
173 assert_eq!(layer.chunk_mut([0, 0]).unwrap().viewer_count(), 1);
174
175 // Create new chunk next to the first chunk and move the client away from it on
176 // the same tick.
177 layer.insert_chunk([0, 1], UnloadedChunk::new());
178
179 let mut client = app.world_mut().entity_mut(client_ent);
180 client.get_mut::<Position>().unwrap().set([100.0, 0.0, 0.0]);
181
182 app.update(); // Tick.
183
184 {
185 let recvd = helper.collect_received();
186
187 recvd.assert_count::<ChunkDataS2c>(1);
188 recvd.assert_count::<UnloadChunkS2c>(2)
189 };
190
191 let mut layer = app.world_mut().get_mut::<ChunkLayer>(layer_ent).unwrap();
192
193 // Viewer count of both chunks should be zero.
194 assert_eq!(layer.chunk([0, 0]).unwrap().viewer_count(), 0);
195 assert_eq!(layer.chunk([0, 1]).unwrap().viewer_count(), 0);
196
197 // Create a third chunk adjacent to the others.
198 layer.insert_chunk([1, 0], UnloadedChunk::new());
199
200 // Move the client back in view of all three chunks.
201 let mut client = app.world_mut().entity_mut(client_ent);
202 client.get_mut::<Position>().unwrap().set([8.0, 0.0, 8.0]);
203
204 app.update(); // Tick.
205
206 let mut layer = app.world_mut().get_mut::<ChunkLayer>(layer_ent).unwrap();
207
208 // All three chunks should have viewer count of one.
209 assert_eq!(layer.chunk_mut([0, 0]).unwrap().viewer_count(), 1);
210 assert_eq!(layer.chunk_mut([1, 0]).unwrap().viewer_count(), 1);
211 assert_eq!(layer.chunk_mut([0, 1]).unwrap().viewer_count(), 1);
212
213 // Client should have received load packet for all three.
214 helper.collect_received().assert_count::<ChunkDataS2c>(3);
215}
216
217#[test]
218fn entity_layer_switching() {
219 let ScenarioSingleClient {
220 mut app,
221 client: client_ent,
222 mut helper,
223 layer: l1,
224 } = ScenarioSingleClient::new();
225
226 let server = app.world_mut().resource::<Server>();
227
228 let l2 = EntityLayer::new(server);
229 let l3 = EntityLayer::new(server);
230
231 let l2 = app.world_mut().spawn(l2).id();
232 let _l3 = app.world_mut().spawn(l3).id();
233
234 // Spawn three entities and put them all on the main layer to start.
235
236 let e1 = CowEntityBundle {
237 layer: EntityLayerId(l1),
238 ..Default::default()
239 };
240
241 let e2 = CowEntityBundle {
242 layer: EntityLayerId(l1),
243 ..Default::default()
244 };
245
246 let e3 = CowEntityBundle {
247 layer: EntityLayerId(l1),
248 ..Default::default()
249 };
250
251 let e1 = app.world_mut().spawn(e1).id();
252 let _e2 = app.world_mut().spawn(e2).id();
253 let _e3 = app.world_mut().spawn(e3).id();
254
255 app.update(); // Tick.
256
257 // Can the client see all the new entities?
258 helper.collect_received().assert_count::<EntitySpawnS2c>(3);
259
260 // Move e1 to l2 and add l2 to the visible layers set.
261 app.world_mut().get_mut::<EntityLayerId>(e1).unwrap().0 = l2;
262 app.world_mut()
263 .get_mut::<VisibleEntityLayers>(client_ent)
264 .unwrap()
265 .0
266 .insert(l2);
267
268 app.update(); // Tick.
269
270 {
271 let recvd = helper.collect_received();
272
273 // Client received packets to despawn and then spawn the entity in the new
274 // layer. (this could be optimized away in the future)
275 recvd.assert_count::<EntitiesDestroyS2c>(1);
276 recvd.assert_count::<EntitySpawnS2c>(1);
277 recvd.assert_order::<(EntitiesDestroyS2c, EntitySpawnS2c)>()
278 };
279
280 // Remove the original layer from the visible layer set.
281 assert!(app
282 .world_mut()
283 .get_mut::<VisibleEntityLayers>(client_ent)
284 .unwrap()
285 .0
286 .remove(&l1));
287
288 app.update(); // Tick.
289
290 // Both entities on the original layer should be removed.
291 {
292 let recvd = helper.collect_received();
293 recvd.assert_count::<EntitiesDestroyS2c>(1)
294 };
295
296 // Despawn l2.
297 app.world_mut().entity_mut(l2).insert(Despawned);
298
299 app.update(); // Tick.
300
301 // e1 should be removed.
302 helper
303 .collect_received()
304 .assert_count::<EntitiesDestroyS2c>(1);
305
306 let mut visible_entity_layers = app
307 .world_mut()
308 .get_mut::<VisibleEntityLayers>(client_ent)
309 .unwrap();
310
311 // l2 should be automatically removed from the visible layers set.
312 assert!(!visible_entity_layers.0.contains(&e1));
313
314 // Add back the original layer.
315 assert!(visible_entity_layers.0.insert(l1));
316
317 app.update(); // Tick.
318
319 // e2 and e3 should be spawned.
320
321 {
322 let recvd = helper.collect_received();
323
324 recvd.assert_count::<EntitySpawnS2c>(2);
325 }
326}
327
328#[test]
329fn chunk_entity_spawn_despawn() {
330 let ScenarioSingleClient {
331 mut app,
332 client: client_ent,
333 mut helper,
334 layer: layer_ent,
335 } = ScenarioSingleClient::new();
336
337 let mut layer = app.world_mut().get_mut::<ChunkLayer>(layer_ent).unwrap();
338
339 // Insert an empty chunk at (0, 0).
340 layer.insert_chunk([0, 0], UnloadedChunk::new());
341
342 // Put an entity in the new chunk.
343 let cow_ent = app
344 .world_mut()
345 .spawn(CowEntityBundle {
346 position: Position::new([8.0, 0.0, 8.0]),
347 layer: EntityLayerId(layer_ent),
348 ..Default::default()
349 })
350 .id();
351
352 app.update();
353
354 // Client is in view of the chunk, so they should receive exactly one chunk
355 // spawn packet and entity spawn packet.
356 {
357 let recvd = helper.collect_received();
358
359 recvd.assert_count::<ChunkDataS2c>(1);
360 recvd.assert_count::<EntitySpawnS2c>(1);
361 recvd.assert_count::<UnloadChunkS2c>(0);
362 recvd.assert_count::<EntitiesDestroyS2c>(0)
363 };
364
365 // Move the entity. Client should receive entity move packet.
366 app.world_mut().get_mut::<Position>(cow_ent).unwrap().0.x += 0.1;
367
368 app.update();
369
370 helper.collect_received().assert_count::<MoveRelativeS2c>(1);
371
372 // Despawning the chunk should delete the chunk and not the entity contained
373 // within.
374 let mut layer = app.world_mut().get_mut::<ChunkLayer>(layer_ent).unwrap();
375
376 layer.remove_chunk([0, 0]).unwrap();
377
378 app.update();
379
380 {
381 let recvd = helper.collect_received();
382
383 recvd.assert_count::<UnloadChunkS2c>(1);
384 recvd.assert_count::<EntitiesDestroyS2c>(0);
385 recvd.assert_count::<ChunkDataS2c>(0);
386 recvd.assert_count::<EntitySpawnS2c>(0)
387 };
388
389 // Placing the chunk back should respawn the chunk and not the entity.
390
391 let mut layer = app.world_mut().get_mut::<ChunkLayer>(layer_ent).unwrap();
392
393 assert!(layer.insert_chunk([0, 0], UnloadedChunk::new()).is_none());
394
395 app.update();
396
397 {
398 let recvd = helper.collect_received();
399
400 recvd.assert_count::<ChunkDataS2c>(1);
401 recvd.assert_count::<EntitySpawnS2c>(0);
402 recvd.assert_count::<UnloadChunkS2c>(0);
403 recvd.assert_count::<EntitiesDestroyS2c>(0)
404 };
405
406 // Move player and entity away from the chunk on the same tick.
407
408 app.world_mut().get_mut::<Position>(client_ent).unwrap().0.x = 1000.0;
409 app.world_mut().get_mut::<Position>(cow_ent).unwrap().0.x = -1000.0;
410
411 app.update();
412
413 {
414 let recvd = helper.collect_received();
415
416 recvd.assert_count::<UnloadChunkS2c>(1);
417 recvd.assert_count::<EntitiesDestroyS2c>(1);
418 recvd.assert_count::<ChunkDataS2c>(0);
419 recvd.assert_count::<EntitySpawnS2c>(0)
420 };
421
422 // Put the client and entity back on the same tick.
423
424 app.world_mut()
425 .get_mut::<Position>(client_ent)
426 .unwrap()
427 .set([8.0, 0.0, 8.0]);
428 app.world_mut()
429 .get_mut::<Position>(cow_ent)
430 .unwrap()
431 .set([8.0, 0.0, 8.0]);
432
433 app.update();
434
435 {
436 let recvd = helper.collect_received();
437
438 recvd.assert_count::<ChunkDataS2c>(1);
439 recvd.assert_count::<EntitySpawnS2c>(1);
440 recvd.assert_count::<UnloadChunkS2c>(0);
441 recvd.assert_count::<EntitiesDestroyS2c>(0)
442 };
443
444 // Adding and removing a chunk on the same tick should have no effect on
445 // the client.
446
447 let mut layer = app.world_mut().get_mut::<ChunkLayer>(layer_ent).unwrap();
448
449 layer.insert_chunk([0, 1], UnloadedChunk::new());
450 layer.remove_chunk([0, 1]).unwrap();
451
452 app.world_mut()
453 .get_mut::<Position>(cow_ent)
454 .unwrap()
455 .set([24.0, 0.0, 24.0]);
456
457 app.update();
458
459 {
460 let recvd = helper.collect_received();
461
462 recvd.assert_count::<ChunkDataS2c>(0);
463 recvd.assert_count::<EntitySpawnS2c>(0);
464 recvd.assert_count::<UnloadChunkS2c>(0);
465 recvd.assert_count::<EntitiesDestroyS2c>(0)
466 };
467}