Skip to main content

steel_core/chunk/chunk_map/
player_tracking.rs

1use super::{CSetChunkCenter, ChunkMap, ChunkPos, ChunkTicket, Entity, Player, PlayerChunkView};
2use crate::player::chunk_sender::ChunkSender;
3
4impl ChunkMap {
5    /// Updates the player's status in the chunk map.
6    pub fn update_player_status(&self, player: &Player) {
7        let current_chunk_pos = ChunkPos::from_entity_pos(player.position());
8        *player.last_chunk_pos.lock() = current_chunk_pos;
9        let view_distance = player.view_distance();
10
11        let new_view = PlayerChunkView::new(current_chunk_pos, view_distance);
12        let world = self.world_gen_context.world();
13        let mut last_view_guard = player.last_tracking_view.lock();
14
15        if last_view_guard.as_ref() != Some(&new_view) {
16            let new_ticket = ChunkTicket::player(new_view.view_distance, world.simulation_distance);
17
18            if let Some(last_view) = last_view_guard.as_ref() {
19                if last_view.center != new_view.center
20                    || last_view.view_distance != new_view.view_distance
21                {
22                    let old_ticket =
23                        ChunkTicket::player(last_view.view_distance, world.simulation_distance);
24                    self.replace_chunk_ticket(
25                        last_view.center,
26                        old_ticket,
27                        new_view.center,
28                        new_ticket,
29                    );
30
31                    player.send_packet(CSetChunkCenter {
32                        x: new_view.center.0.x,
33                        y: new_view.center.0.y,
34                    });
35                }
36
37                // Track chunks for PlayerAreaMap update
38                let mut added_chunks = Vec::new();
39                let mut removed_chunks = Vec::new();
40
41                // We lock here to ensure we have unique access for the duration of the diff
42                let mut chunk_sender = player.chunk_sender.lock();
43                let connection = &*player.connection;
44                PlayerChunkView::difference(
45                    last_view,
46                    &new_view,
47                    |pos, ctx: &mut (&mut _, &mut Vec<_>, &mut Vec<_>)| {
48                        ctx.0.mark_chunk_pending_to_send(pos);
49                        ctx.1.push(pos);
50                    },
51                    |pos, ctx: &mut (&mut _, &mut Vec<_>, &mut Vec<_>)| {
52                        ctx.0.drop_chunk(connection, pos);
53                        ctx.2.push(pos);
54                    },
55                    &mut (&mut chunk_sender, &mut added_chunks, &mut removed_chunks),
56                );
57                drop(chunk_sender);
58
59                // Update the player area map with the diff
60                world.player_area_map.on_player_view_change(
61                    player.id(),
62                    &added_chunks,
63                    &removed_chunks,
64                );
65            } else {
66                self.add_chunk_ticket(new_view.center, new_ticket);
67
68                // Send initial chunk cache center to client
69                player.send_packet(CSetChunkCenter {
70                    x: new_view.center.0.x,
71                    y: new_view.center.0.y,
72                });
73
74                let mut chunk_sender = player.chunk_sender.lock();
75                new_view.for_each(|pos| {
76                    chunk_sender.mark_chunk_pending_to_send(pos);
77                });
78                drop(chunk_sender);
79
80                // First time - add all chunks in view to player area map
81                world.player_area_map.on_player_join(player, &new_view);
82            }
83
84            *last_view_guard = Some(new_view);
85        }
86        drop(last_view_guard);
87
88        // Entity visibility also depends on exact player position, not only
89        // chunk-view changes. Vanilla refreshes tracked entities for accepted
90        // movement within the same chunk as well.
91        let sent_chunks = player.chunk_sender.lock().sent_chunks_snapshot();
92        world
93            .entity_tracker()
94            .update_player(player, &new_view, |chunk| sent_chunks.contains(&chunk));
95    }
96
97    /// Removes a player from the chunk map.
98    pub fn remove_player(&self, player: &Player) {
99        let last_view = {
100            // Keep the same view -> sender lock order as `update_player_status`.
101            // The independent chunk-sending loop holds the view through its commit,
102            // making this the linearization point for detaching chunk state.
103            let mut last_view = player.last_tracking_view.lock();
104            let mut chunk_sender = player.chunk_sender.lock();
105            let mut chunk_send_epoch = player.chunk_send_epoch.lock();
106            *chunk_send_epoch = chunk_send_epoch.wrapping_add(1);
107            *chunk_sender = ChunkSender::default();
108            *player.last_chunk_pos.lock() = ChunkPos::new(i32::MAX, i32::MAX);
109            last_view.take()
110        };
111
112        if let Some(last_view) = last_view {
113            let world = self.world_gen_context.world();
114            let ticket = ChunkTicket::player(last_view.view_distance, world.simulation_distance);
115            self.remove_chunk_ticket(last_view.center, ticket);
116        }
117    }
118}