khora_core/utils/
bitflags.rs

1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! A macro to define bitflags in a structured way.
16#[macro_export]
17#[doc(hidden)]
18macro_rules! khora_bitflags {
19    (
20        $(#[$attr:meta])*
21        $vis:vis struct $name:ident: $ty:ty {
22            $(
23                $(#[$flag_attr:meta])*
24                const $flag_name:ident = $flag_value:expr;
25            )*
26        }
27    ) => {
28        $(#[$attr])*
29        #[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
30        $vis struct $name {
31            pub(crate) bits: $ty,
32        }
33
34        impl $name {
35            /// An empty set of flags.
36            pub const EMPTY: Self = Self { bits: 0 };
37
38            /// Creates a new bitflag set from the given raw bits.
39            /// Bits not corresponding to any defined flag are kept.
40            pub const fn from_bits_truncate(bits: $ty) -> Self {
41                Self { bits }
42            }
43
44            /// Returns the raw value of the bitflag set.
45            pub const fn bits(&self) -> $ty {
46                self.bits
47            }
48
49            /// Returns `true` if all flags in `other` are contained within `self`.
50            pub const fn contains(&self, other: Self) -> bool {
51                (self.bits & other.bits) == other.bits
52            }
53
54            /// Returns `true` if any flag in `other` is contained within `self`.
55            pub const fn intersects(&self, other: Self) -> bool {
56                (self.bits & other.bits) != 0
57            }
58
59            /// Inserts the flags in `other` into `self`.
60            pub fn insert(&mut self, other: Self) {
61                self.bits |= other.bits;
62            }
63
64            /// Removes the flags in `other` from `self`.
65            pub fn remove(&mut self, other: Self) {
66                self.bits &= !other.bits;
67            }
68
69            /// Toggles the flags in `other` in `self`.
70            pub fn toggle(&mut self, other: Self) {
71                self.bits ^= other.bits;
72            }
73
74            /// Returns a new `Self` with `other` flags inserted.
75            #[must_use]
76            pub const fn with(mut self, other: Self) -> Self {
77                self.bits |= other.bits;
78                self
79            }
80
81            /// Returns a new `Self` with `other` flags removed.
82            #[must_use]
83            pub const fn without(mut self, other: Self) -> Self {
84                self.bits &= !other.bits;
85                self
86            }
87
88            // Define the individual flag constants
89            $(
90                $(#[$flag_attr])*
91                pub const $flag_name: Self = Self { bits: $flag_value };
92            )*
93        }
94
95        // Implement bitwise operators
96        impl core::ops::BitOr for $name {
97            type Output = Self;
98            fn bitor(self, other: Self) -> Self {
99                Self { bits: self.bits | other.bits }
100            }
101        }
102
103        impl core::ops::BitAnd for $name {
104            type Output = Self;
105            fn bitand(self, other: Self) -> Self {
106                Self { bits: self.bits & other.bits }
107            }
108        }
109
110        impl core::ops::BitXor for $name {
111            type Output = Self;
112            fn bitxor(self, other: Self) -> Self {
113                Self { bits: self.bits ^ other.bits }
114            }
115        }
116
117        impl core::ops::Not for $name {
118            type Output = Self;
119            fn not(self) -> Self {
120                Self { bits: !self.bits }
121            }
122        }
123
124        impl core::ops::BitOrAssign for $name {
125            fn bitor_assign(&mut self, other: Self) {
126                self.bits |= other.bits;
127            }
128        }
129
130        impl core::ops::BitAndAssign for $name {
131            fn bitand_assign(&mut self, other: Self) {
132                self.bits &= other.bits;
133            }
134        }
135
136        impl core::ops::BitXorAssign for $name {
137            fn bitxor_assign(&mut self, other: Self) {
138                self.bits ^= other.bits;
139            }
140        }
141
142        // Optimized Debug implementation (no runtime allocations)
143        impl core::fmt::Debug for $name {
144            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
145                let mut bits = self.bits;
146                let mut first_flag = true;
147
148                write!(f, "{} {{ ", stringify!($name))?;
149
150                $(
151                    // Only process flags that are non-zero.
152                    // Check if the flag's bits are actually present in the current_bits.
153                    if ($flag_value != 0) && (bits & $flag_value) == $flag_value {
154                        if !first_flag {
155                            write!(f, " | ")?;
156                        }
157                        write!(f, "{}", stringify!($flag_name))?;
158                        bits &= !$flag_value; // Clear these bits from the remaining set
159                        first_flag = false;
160                    }
161                )*
162
163                // Handle any remaining unknown bits
164                if bits != 0 {
165                    if !first_flag {
166                        write!(f, " | ")?;
167                    }
168                    write!(f, "UNKNOWN({:#x})", bits)?;
169                    first_flag = false;
170                }
171
172                // If after checking all flags (and unknown bits), the original value was 0,
173                // and no named flags were printed (meaning `first_flag` is still true),
174                // then explicitly print "EMPTY".
175                if self.bits == 0 && first_flag {
176                    write!(f, "EMPTY")?;
177                }
178
179                write!(f, " }}")
180            }
181        }
182    };
183}
184
185#[cfg(test)]
186mod tests {
187    // Import the macro for testing within this module
188    use crate::khora_bitflags;
189
190    // Define a test bitflag type using the macro
191    khora_bitflags! {
192        /// TestFlags for macro verification
193        pub struct TestFlags: u32 {
194            const FLAG_A = 1 << 0;
195            const FLAG_B = 1 << 1;
196            const FLAG_C = 1 << 2;
197            const FLAG_D = 1 << 3;
198            const COMBINED_AC = Self::FLAG_A.bits() | Self::FLAG_C.bits();
199            const CUSTOM_HIGH_BIT = 1 << 20;
200            const NONE_FLAG = 0; // A flag with value 0, should behave like EMPTY
201        }
202    }
203
204    #[test]
205    fn test_empty_flags() {
206        let flags = TestFlags::EMPTY;
207        assert_eq!(flags.bits(), 0);
208        assert!(flags.contains(TestFlags::EMPTY));
209        assert!(!flags.contains(TestFlags::FLAG_A));
210        assert_eq!(TestFlags::default().bits(), 0, "Default should be empty");
211        assert_eq!(format!("{flags:?}"), "TestFlags { EMPTY }");
212    }
213
214    #[test]
215    fn test_single_flag() {
216        let flags = TestFlags::FLAG_A;
217        assert_eq!(flags.bits(), 1);
218        assert!(flags.contains(TestFlags::FLAG_A));
219        assert!(!flags.contains(TestFlags::FLAG_B));
220        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A }");
221    }
222
223    #[test]
224    fn test_multiple_flags() {
225        let flags = TestFlags::FLAG_A | TestFlags::FLAG_C;
226        assert_eq!(flags.bits(), 0b101); // 1 | 4 = 5
227        assert!(flags.contains(TestFlags::FLAG_A));
228        assert!(!flags.contains(TestFlags::FLAG_B));
229        assert!(flags.contains(TestFlags::FLAG_C));
230        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A | FLAG_C }");
231    }
232
233    #[test]
234    fn test_combined_constant() {
235        let flags = TestFlags::COMBINED_AC;
236        assert_eq!(
237            flags.bits(),
238            TestFlags::FLAG_A.bits() | TestFlags::FLAG_C.bits()
239        );
240        assert!(flags.contains(TestFlags::FLAG_A));
241        assert!(flags.contains(TestFlags::FLAG_C));
242        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A | FLAG_C }");
243    }
244
245    #[test]
246    fn test_from_bits_truncate_and_bits() {
247        let flags = TestFlags::from_bits_truncate(5);
248        assert_eq!(flags.bits(), 5);
249        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A | FLAG_C }");
250
251        // Test with unknown bits
252        let unknown_bits = TestFlags::from_bits_truncate(0b10000); // 1 << 4 = 16, not a defined flag
253        assert_eq!(unknown_bits.bits(), 16);
254        assert_eq!(format!("{unknown_bits:?}"), "TestFlags { UNKNOWN(0x10) }");
255    }
256
257    #[test]
258    fn test_contains() {
259        let all_defined =
260            TestFlags::FLAG_A | TestFlags::FLAG_B | TestFlags::FLAG_C | TestFlags::FLAG_D;
261        assert!(all_defined.contains(TestFlags::FLAG_A));
262        assert!(all_defined.contains(TestFlags::FLAG_A | TestFlags::FLAG_C));
263        assert!(!all_defined.contains(TestFlags::CUSTOM_HIGH_BIT));
264        assert!(!all_defined.contains(TestFlags::FLAG_A | TestFlags::CUSTOM_HIGH_BIT));
265        assert!(all_defined.contains(TestFlags::EMPTY));
266    }
267
268    #[test]
269    fn test_intersects() {
270        let flags1 = TestFlags::FLAG_A | TestFlags::FLAG_B; // 0b0011
271        let flags2 = TestFlags::FLAG_B | TestFlags::FLAG_C; // 0b0110
272        let flags3 = TestFlags::FLAG_C | TestFlags::FLAG_D; // 0b1100
273
274        assert!(flags1.intersects(flags2)); // Common FLAG_B
275        assert!(!flags1.intersects(flags3)); // No common flags
276        assert!(flags1.intersects(TestFlags::FLAG_A));
277        assert!(!flags1.intersects(TestFlags::EMPTY)); // Empty does not intersect anything (by definition)
278    }
279
280    #[test]
281    fn test_mutable_operations_insert() {
282        let mut flags = TestFlags::FLAG_A;
283        flags.insert(TestFlags::FLAG_B);
284        assert_eq!(
285            flags.bits(),
286            TestFlags::FLAG_A.bits() | TestFlags::FLAG_B.bits()
287        );
288        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A | FLAG_B }");
289    }
290
291    #[test]
292    fn test_mutable_operations_remove() {
293        let mut flags = TestFlags::FLAG_A | TestFlags::FLAG_B;
294        flags.remove(TestFlags::FLAG_A);
295        assert_eq!(flags.bits(), TestFlags::FLAG_B.bits());
296        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_B }");
297
298        flags.remove(TestFlags::FLAG_B | TestFlags::FLAG_D); // Remove B, D not present
299        assert_eq!(flags.bits(), TestFlags::EMPTY.bits());
300        assert_eq!(format!("{flags:?}"), "TestFlags { EMPTY }");
301    }
302
303    #[test]
304    fn test_mutable_operations_toggle() {
305        let mut flags = TestFlags::FLAG_A;
306        flags.toggle(TestFlags::FLAG_C); // Add C
307        assert_eq!(
308            flags.bits(),
309            TestFlags::FLAG_A.bits() | TestFlags::FLAG_C.bits()
310        );
311        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A | FLAG_C }");
312
313        flags.toggle(TestFlags::FLAG_A); // Remove A
314        assert_eq!(flags.bits(), TestFlags::FLAG_C.bits());
315        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_C }");
316    }
317
318    #[test]
319    fn test_immutable_operations_with() {
320        let initial = TestFlags::FLAG_A;
321        let with_b = initial.with(TestFlags::FLAG_B);
322        assert_eq!(
323            with_b.bits(),
324            TestFlags::FLAG_A.bits() | TestFlags::FLAG_B.bits()
325        );
326        assert_eq!(format!("{with_b:?}"), "TestFlags { FLAG_A | FLAG_B }");
327        assert_eq!(
328            initial.bits(),
329            TestFlags::FLAG_A.bits(),
330            "Original should be unchanged"
331        );
332    }
333
334    #[test]
335    fn test_immutable_operations_without() {
336        let initial = TestFlags::FLAG_A | TestFlags::FLAG_B;
337        let without_a = initial.without(TestFlags::FLAG_A);
338        assert_eq!(without_a.bits(), TestFlags::FLAG_B.bits());
339        assert_eq!(format!("{without_a:?}"), "TestFlags { FLAG_B }");
340        assert_eq!(
341            initial.bits(),
342            (TestFlags::FLAG_A | TestFlags::FLAG_B).bits(),
343            "Original should be unchanged"
344        );
345    }
346
347    #[test]
348    fn test_bitwise_or_operator() {
349        let f1 = TestFlags::FLAG_A | TestFlags::FLAG_B;
350        let f2 = TestFlags::FLAG_B | TestFlags::FLAG_C;
351        let result = f1 | f2;
352        assert_eq!(
353            result.bits(),
354            TestFlags::FLAG_A.bits() | TestFlags::FLAG_B.bits() | TestFlags::FLAG_C.bits()
355        );
356        assert_eq!(
357            format!("{result:?}"),
358            "TestFlags { FLAG_A | FLAG_B | FLAG_C }"
359        );
360    }
361
362    #[test]
363    fn test_bitwise_and_operator() {
364        let f1 = TestFlags::FLAG_A | TestFlags::FLAG_B;
365        let f2 = TestFlags::FLAG_B | TestFlags::FLAG_C;
366        let result = f1 & f2;
367        assert_eq!(result.bits(), TestFlags::FLAG_B.bits());
368        assert_eq!(format!("{result:?}"), "TestFlags { FLAG_B }");
369    }
370
371    #[test]
372    fn test_bitwise_xor_operator() {
373        let f1 = TestFlags::FLAG_A | TestFlags::FLAG_B;
374        let f2 = TestFlags::FLAG_B | TestFlags::FLAG_C;
375        let result = f1 ^ f2;
376        assert_eq!(
377            result.bits(),
378            TestFlags::FLAG_A.bits() | TestFlags::FLAG_C.bits()
379        );
380        assert_eq!(format!("{result:?}"), "TestFlags { FLAG_A | FLAG_C }");
381    }
382
383    #[test]
384    fn test_bitwise_not_operator() {
385        let flags = TestFlags::FLAG_A;
386        let result = !flags;
387        assert_eq!(result.bits(), !TestFlags::FLAG_A.bits()); // Value depends on the underlying integer type
388                                                              // Debug output for NOT might be complex due to UNKNOWN bits
389                                                              // For u32, !1 is 0xFFFFFFFE, which is a lot of unknown bits.
390                                                              // We'll just assert the raw bits for now.
391    }
392
393    #[test]
394    fn test_assign_or_operator() {
395        let mut flags = TestFlags::FLAG_A;
396        flags |= TestFlags::FLAG_B;
397        assert_eq!(
398            flags.bits(),
399            TestFlags::FLAG_A.bits() | TestFlags::FLAG_B.bits()
400        );
401        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A | FLAG_B }");
402    }
403
404    #[test]
405    fn test_assign_and_operator() {
406        let mut flags = TestFlags::FLAG_A | TestFlags::FLAG_B | TestFlags::FLAG_C; // 0b0111 = 7
407                                                                                   // AND with (FLAG_B | FLAG_D) = 0b0010 | 0b1000 = 0b1010 = 10
408                                                                                   // Expected result: 0b0111 & 0b1010 = 0b0010 (FLAG_B) = 2
409        flags &= TestFlags::FLAG_B | TestFlags::FLAG_D;
410        assert_eq!(flags.bits(), TestFlags::FLAG_B.bits());
411        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_B }");
412    }
413
414    #[test]
415    fn test_assign_xor_operator() {
416        let mut flags = TestFlags::FLAG_A | TestFlags::FLAG_B | TestFlags::FLAG_C;
417        flags ^= TestFlags::FLAG_B; // Toggle B (remove it)
418        assert_eq!(
419            flags.bits(),
420            TestFlags::FLAG_A.bits() | TestFlags::FLAG_C.bits()
421        );
422        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A | FLAG_C }");
423    }
424
425    #[test]
426    fn test_debug_formatting_high_bit() {
427        let flags = TestFlags::CUSTOM_HIGH_BIT;
428        assert_eq!(format!("{flags:?}"), "TestFlags { CUSTOM_HIGH_BIT }");
429    }
430
431    #[test]
432    fn test_debug_formatting_mixed_known_and_unknown() {
433        // Test a combination of known and unknown bits
434        let flags = TestFlags::FLAG_A | TestFlags::from_bits_truncate(1 << 8); // FLAG_A (0x1) | 0x100
435        assert_eq!(
436            format!("{flags:?}"),
437            "TestFlags { FLAG_A | UNKNOWN(0x100) }"
438        );
439
440        let flags_more_unknown =
441            TestFlags::FLAG_B | TestFlags::from_bits_truncate((1 << 8) | (1 << 9)); // FLAG_B (0x2) | 0x100 | 0x200
442        assert_eq!(
443            format!("{flags_more_unknown:?}"),
444            "TestFlags { FLAG_B | UNKNOWN(0x300) }"
445        );
446    }
447
448    #[test]
449    fn test_debug_formatting_only_unknown() {
450        // This test case needs to ensure NO known flags are set.
451        // FLAG_A = 1<<0, FLAG_B = 1<<1, FLAG_C = 1<<2, FLAG_D = 1<<3
452        // So, 0xFF (0b11111111) is actually *not* only unknown; it contains A, B, C, D.
453        // A truly "only unknown" value would be one where bits 0-3 are zero,
454        // and 1<<20 is zero, but other bits are set.
455        // Example: (1 << 4) | (1 << 5) = 0b110000 = 48 (0x30)
456        let flags = TestFlags::from_bits_truncate((1 << 4) | (1 << 5));
457        assert_eq!(format!("{flags:?}"), "TestFlags { UNKNOWN(0x30) }");
458
459        // Another example: a single unknown bit that isn't one of the defined ones
460        let flags_single_unknown = TestFlags::from_bits_truncate(1 << 4); // 0b10000 = 16 (0x10)
461        assert_eq!(
462            format!("{flags_single_unknown:?}"),
463            "TestFlags { UNKNOWN(0x10) }"
464        );
465    }
466
467    #[test]
468    fn test_none_flag_value() {
469        // A flag with value 0 (NONE_FLAG) should not affect operations or appear in debug output
470        let flags = TestFlags::FLAG_A | TestFlags::NONE_FLAG;
471        assert_eq!(flags.bits(), TestFlags::FLAG_A.bits());
472        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A }");
473
474        let flags_empty = TestFlags::NONE_FLAG;
475        assert_eq!(flags_empty.bits(), 0);
476        assert_eq!(format!("{flags_empty:?}"), "TestFlags { EMPTY }");
477    }
478}