Skip to main content

steel_core/chunk/
chunk_ticket.rs

1//! A typed chunk ticket matching Vanilla's `Ticket` value.
2
3use std::ptr;
4
5use steel_registry::ticket_type::TicketTypeRef;
6
7use super::chunk_ticket_manager::ChunkTicketLevel;
8
9/// One ticket type and level, with its remaining timeout.
10#[derive(Debug, Clone, Copy)]
11pub(crate) struct ChunkTicket {
12    ticket_type: TicketTypeRef,
13    ticket_level: ChunkTicketLevel,
14    ticks_left: i64,
15}
16
17impl ChunkTicket {
18    /// Creates a ticket with the type's full timeout.
19    #[must_use]
20    pub(crate) const fn new(ticket_type: TicketTypeRef, ticket_level: ChunkTicketLevel) -> Self {
21        Self::from_saved(ticket_type, ticket_level, ticket_type.timeout())
22    }
23
24    /// Restores a ticket's serialized remaining timeout.
25    #[must_use]
26    pub(crate) const fn from_saved(
27        ticket_type: TicketTypeRef,
28        ticket_level: ChunkTicketLevel,
29        ticks_left: i64,
30    ) -> Self {
31        Self {
32            ticket_type,
33            ticket_level,
34            ticks_left,
35        }
36    }
37
38    /// Creates a ticket that makes chunks full within `radius`.
39    #[must_use]
40    pub(crate) const fn for_full_chunk_radius(ticket_type: TicketTypeRef, radius: u8) -> Self {
41        Self::new(ticket_type, ChunkTicketLevel::for_full_chunk_radius(radius))
42    }
43
44    #[must_use]
45    pub(crate) const fn ticket_type(&self) -> TicketTypeRef {
46        self.ticket_type
47    }
48
49    #[must_use]
50    pub(crate) const fn ticket_level(&self) -> ChunkTicketLevel {
51        self.ticket_level
52    }
53
54    #[must_use]
55    pub(crate) const fn ticks_left(&self) -> i64 {
56        self.ticks_left
57    }
58
59    /// Returns this ticket's contribution to loading propagation.
60    #[must_use]
61    pub(crate) const fn loading_level(&self) -> Option<ChunkTicketLevel> {
62        if self.ticket_type.does_load() {
63            Some(self.ticket_level)
64        } else {
65            None
66        }
67    }
68
69    /// Returns this ticket's contribution to simulation propagation.
70    #[must_use]
71    pub(crate) const fn simulation_level(&self) -> Option<ChunkTicketLevel> {
72        if self.ticket_type.does_simulate() {
73            Some(self.ticket_level)
74        } else {
75            None
76        }
77    }
78
79    /// Restores the type's full timeout.
80    pub(crate) const fn reset_ticks_left(&mut self) {
81        self.ticks_left = self.ticket_type.timeout();
82    }
83
84    /// Ages a timed ticket by one tick using Java `long` overflow behavior.
85    pub(crate) const fn decrease_ticks_left(&mut self) {
86        if self.ticket_type.has_timeout() {
87            self.ticks_left = self.ticks_left.wrapping_sub(1);
88        }
89    }
90
91    #[must_use]
92    pub(crate) const fn is_timed_out(&self) -> bool {
93        self.ticket_type.has_timeout() && self.ticks_left < 0
94    }
95}
96
97impl PartialEq for ChunkTicket {
98    fn eq(&self, other: &Self) -> bool {
99        ptr::eq(self.ticket_type, other.ticket_type) && self.ticket_level == other.ticket_level
100    }
101}
102
103impl Eq for ChunkTicket {}
104
105#[cfg(test)]
106mod tests {
107    use steel_registry::vanilla_ticket_types::{DRAGON, PLAYER_LOADING, PORTAL};
108
109    use super::*;
110
111    #[test]
112    fn identity_is_type_pointer_and_level_not_timeout_state() {
113        let mut first = ChunkTicket::new(&PORTAL, ChunkTicketLevel::FULL_CHUNK);
114        let second = first;
115
116        first.decrease_ticks_left();
117
118        assert_eq!(first, second);
119        assert_ne!(
120            first,
121            ChunkTicket::new(&DRAGON, ChunkTicketLevel::FULL_CHUNK)
122        );
123        assert_ne!(
124            first,
125            ChunkTicket::new(&PORTAL, ChunkTicketLevel::BLOCK_TICKING_CHUNK)
126        );
127    }
128
129    #[test]
130    fn timeout_aging_matches_java_long_behavior() {
131        let mut timed = ChunkTicket::new(&PORTAL, ChunkTicketLevel::FULL_CHUNK);
132        assert_eq!(timed.ticks_left(), PORTAL.timeout());
133
134        timed.ticks_left = 0;
135        timed.decrease_ticks_left();
136        assert!(timed.is_timed_out());
137        timed.reset_ticks_left();
138        assert_eq!(timed.ticks_left(), PORTAL.timeout());
139
140        timed.ticks_left = i64::MIN;
141        timed.decrease_ticks_left();
142        assert_eq!(timed.ticks_left(), i64::MAX);
143        assert!(!timed.is_timed_out());
144
145        let mut untimed = ChunkTicket::new(&PLAYER_LOADING, ChunkTicketLevel::FULL_CHUNK);
146        untimed.decrease_ticks_left();
147        assert_eq!(untimed.ticks_left(), 0);
148        assert!(!untimed.is_timed_out());
149    }
150}