steel_core/chunk/
chunk_ticket.rs1use std::ptr;
4
5use steel_registry::ticket_type::TicketTypeRef;
6
7use super::chunk_ticket_manager::ChunkTicketLevel;
8
9#[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 #[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 #[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 #[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 #[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 #[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 pub(crate) const fn reset_ticks_left(&mut self) {
81 self.ticks_left = self.ticket_type.timeout();
82 }
83
84 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}