Skip to main content

steel_core/chunk/chunk_map/
player_tracking.rs

1use super::{CSetChunkCenter, ChunkMap, ChunkPos, Entity, Player, PlayerChunkView};
2use crate::chunk::chunk_scheduler::PlayerTicketOperation;
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 player_id = player.gameprofile.id;
14        let mut last_view_guard = player.last_tracking_view.lock();
15
16        if last_view_guard.as_ref() != Some(&new_view) {
17            if let Some(last_view) = last_view_guard.as_ref() {
18                if last_view.center != new_view.center {
19                    let _ = self.scheduling.queue_player_ticket_operations([
20                        PlayerTicketOperation::Remove {
21                            pos: last_view.center,
22                            player_id,
23                        },
24                        PlayerTicketOperation::Add {
25                            pos: new_view.center,
26                            player_id,
27                        },
28                    ]);
29
30                    player.send_packet(CSetChunkCenter {
31                        x: new_view.center.0.x,
32                        y: new_view.center.0.y,
33                    });
34                }
35
36                // Track chunks for PlayerAreaMap update
37                let mut added_chunks = Vec::new();
38                let mut removed_chunks = Vec::new();
39
40                // We lock here to ensure we have unique access for the duration of the diff
41                let mut chunk_sender = player.chunk_sender().lock();
42                let connection = &*player.connection;
43                PlayerChunkView::difference(
44                    last_view,
45                    &new_view,
46                    |pos, ctx: &mut (&mut _, &mut Vec<_>, &mut Vec<_>)| {
47                        ctx.0.mark_chunk_pending_to_send(pos);
48                        ctx.1.push(pos);
49                    },
50                    |pos, ctx: &mut (&mut _, &mut Vec<_>, &mut Vec<_>)| {
51                        ctx.0.drop_chunk(connection, pos);
52                        ctx.2.push(pos);
53                    },
54                    &mut (&mut chunk_sender, &mut added_chunks, &mut removed_chunks),
55                );
56                drop(chunk_sender);
57
58                // Update the player area map with the diff
59                world.player_area_map.on_player_view_change(
60                    player.id(),
61                    &added_chunks,
62                    &removed_chunks,
63                );
64            } else {
65                self.scheduling
66                    .queue_player_ticket_operation(PlayerTicketOperation::Add {
67                        pos: new_view.center,
68                        player_id,
69                    });
70
71                // Send initial chunk cache center to client
72                player.send_packet(CSetChunkCenter {
73                    x: new_view.center.0.x,
74                    y: new_view.center.0.y,
75                });
76
77                let mut chunk_sender = player.chunk_sender().lock();
78                new_view.for_each(|pos| {
79                    chunk_sender.mark_chunk_pending_to_send(pos);
80                });
81                drop(chunk_sender);
82
83                // First time - add all chunks in view to player area map
84                world.player_area_map.on_player_join(player, &new_view);
85            }
86
87            *last_view_guard = Some(new_view);
88        }
89        drop(last_view_guard);
90
91        // Entity visibility also depends on exact player position, not only
92        // chunk-view changes. Vanilla refreshes tracked entities for accepted
93        // movement within the same chunk as well.
94        let sent_chunks = player.chunk_sender().lock().sent_chunks_snapshot();
95        world
96            .entity_tracker()
97            .update_player(player, &new_view, |chunk| sent_chunks.contains(&chunk));
98    }
99
100    /// Removes a player from the chunk map.
101    pub fn remove_player(&self, player: &Player) {
102        // Keep the view guard through ticket submission so a concurrent tracking
103        // update cannot re-add the player before this removal is ordered.
104        // Keep the same view -> sender lock order as `update_player_status`.
105        let mut last_view = player.last_tracking_view.lock();
106        let removed_view = last_view.take();
107        {
108            let mut chunk_sender = player.chunk_sender().lock();
109            let mut chunk_send_epoch = player.chunk_send_epoch.lock();
110            *chunk_send_epoch = chunk_send_epoch.wrapping_add(1);
111            chunk_sender.clear_world_chunks();
112            *player.last_chunk_pos.lock() = ChunkPos::new(i32::MAX, i32::MAX);
113        }
114
115        if let Some(removed_view) = removed_view {
116            let player_id = player.gameprofile.id;
117            self.scheduling
118                .queue_player_ticket_operation(PlayerTicketOperation::Remove {
119                    pos: removed_view.center,
120                    player_id,
121                });
122        }
123    }
124}