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
188    // Define a test bitflag type using the macro
189    khora_bitflags! {
190        /// TestFlags for macro verification
191        pub struct TestFlags: u32 {
192            const FLAG_A = 1 << 0;
193            const FLAG_B = 1 << 1;
194            const FLAG_C = 1 << 2;
195            const FLAG_D = 1 << 3;
196            const COMBINED_AC = Self::FLAG_A.bits() | Self::FLAG_C.bits();
197            const CUSTOM_HIGH_BIT = 1 << 20;
198            const NONE_FLAG = 0; // A flag with value 0, should behave like EMPTY
199        }
200    }
201
202    #[test]
203    fn test_empty_flags() {
204        let flags = TestFlags::EMPTY;
205        assert_eq!(flags.bits(), 0);
206        assert!(flags.contains(TestFlags::EMPTY));
207        assert!(!flags.contains(TestFlags::FLAG_A));
208        assert_eq!(TestFlags::default().bits(), 0, "Default should be empty");
209        assert_eq!(format!("{flags:?}"), "TestFlags { EMPTY }");
210    }
211
212    #[test]
213    fn test_single_flag() {
214        let flags = TestFlags::FLAG_A;
215        assert_eq!(flags.bits(), 1);
216        assert!(flags.contains(TestFlags::FLAG_A));
217        assert!(!flags.contains(TestFlags::FLAG_B));
218        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A }");
219    }
220
221    #[test]
222    fn test_multiple_flags() {
223        let flags = TestFlags::FLAG_A | TestFlags::FLAG_C;
224        assert_eq!(flags.bits(), 0b101); // 1 | 4 = 5
225        assert!(flags.contains(TestFlags::FLAG_A));
226        assert!(!flags.contains(TestFlags::FLAG_B));
227        assert!(flags.contains(TestFlags::FLAG_C));
228        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A | FLAG_C }");
229    }
230
231    #[test]
232    fn test_combined_constant() {
233        let flags = TestFlags::COMBINED_AC;
234        assert_eq!(
235            flags.bits(),
236            TestFlags::FLAG_A.bits() | TestFlags::FLAG_C.bits()
237        );
238        assert!(flags.contains(TestFlags::FLAG_A));
239        assert!(flags.contains(TestFlags::FLAG_C));
240        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A | FLAG_C }");
241    }
242
243    #[test]
244    fn test_from_bits_truncate_and_bits() {
245        let flags = TestFlags::from_bits_truncate(5);
246        assert_eq!(flags.bits(), 5);
247        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A | FLAG_C }");
248
249        // Test with unknown bits
250        let unknown_bits = TestFlags::from_bits_truncate(0b10000); // 1 << 4 = 16, not a defined flag
251        assert_eq!(unknown_bits.bits(), 16);
252        assert_eq!(format!("{unknown_bits:?}"), "TestFlags { UNKNOWN(0x10) }");
253    }
254
255    #[test]
256    fn test_contains() {
257        let all_defined =
258            TestFlags::FLAG_A | TestFlags::FLAG_B | TestFlags::FLAG_C | TestFlags::FLAG_D;
259        assert!(all_defined.contains(TestFlags::FLAG_A));
260        assert!(all_defined.contains(TestFlags::FLAG_A | TestFlags::FLAG_C));
261        assert!(!all_defined.contains(TestFlags::CUSTOM_HIGH_BIT));
262        assert!(!all_defined.contains(TestFlags::FLAG_A | TestFlags::CUSTOM_HIGH_BIT));
263        assert!(all_defined.contains(TestFlags::EMPTY));
264    }
265
266    #[test]
267    fn test_intersects() {
268        let flags1 = TestFlags::FLAG_A | TestFlags::FLAG_B; // 0b0011
269        let flags2 = TestFlags::FLAG_B | TestFlags::FLAG_C; // 0b0110
270        let flags3 = TestFlags::FLAG_C | TestFlags::FLAG_D; // 0b1100
271
272        assert!(flags1.intersects(flags2)); // Common FLAG_B
273        assert!(!flags1.intersects(flags3)); // No common flags
274        assert!(flags1.intersects(TestFlags::FLAG_A));
275        assert!(!flags1.intersects(TestFlags::EMPTY)); // Empty does not intersect anything (by definition)
276    }
277
278    #[test]
279    fn test_mutable_operations_insert() {
280        let mut flags = TestFlags::FLAG_A;
281        flags.insert(TestFlags::FLAG_B);
282        assert_eq!(
283            flags.bits(),
284            TestFlags::FLAG_A.bits() | TestFlags::FLAG_B.bits()
285        );
286        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A | FLAG_B }");
287    }
288
289    #[test]
290    fn test_mutable_operations_remove() {
291        let mut flags = TestFlags::FLAG_A | TestFlags::FLAG_B;
292        flags.remove(TestFlags::FLAG_A);
293        assert_eq!(flags.bits(), TestFlags::FLAG_B.bits());
294        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_B }");
295
296        flags.remove(TestFlags::FLAG_B | TestFlags::FLAG_D); // Remove B, D not present
297        assert_eq!(flags.bits(), TestFlags::EMPTY.bits());
298        assert_eq!(format!("{flags:?}"), "TestFlags { EMPTY }");
299    }
300
301    #[test]
302    fn test_mutable_operations_toggle() {
303        let mut flags = TestFlags::FLAG_A;
304        flags.toggle(TestFlags::FLAG_C); // Add C
305        assert_eq!(
306            flags.bits(),
307            TestFlags::FLAG_A.bits() | TestFlags::FLAG_C.bits()
308        );
309        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A | FLAG_C }");
310
311        flags.toggle(TestFlags::FLAG_A); // Remove A
312        assert_eq!(flags.bits(), TestFlags::FLAG_C.bits());
313        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_C }");
314    }
315
316    #[test]
317    fn test_immutable_operations_with() {
318        let initial = TestFlags::FLAG_A;
319        let with_b = initial.with(TestFlags::FLAG_B);
320        assert_eq!(
321            with_b.bits(),
322            TestFlags::FLAG_A.bits() | TestFlags::FLAG_B.bits()
323        );
324        assert_eq!(format!("{with_b:?}"), "TestFlags { FLAG_A | FLAG_B }");
325        assert_eq!(
326            initial.bits(),
327            TestFlags::FLAG_A.bits(),
328            "Original should be unchanged"
329        );
330    }
331
332    #[test]
333    fn test_immutable_operations_without() {
334        let initial = TestFlags::FLAG_A | TestFlags::FLAG_B;
335        let without_a = initial.without(TestFlags::FLAG_A);
336        assert_eq!(without_a.bits(), TestFlags::FLAG_B.bits());
337        assert_eq!(format!("{without_a:?}"), "TestFlags { FLAG_B }");
338        assert_eq!(
339            initial.bits(),
340            (TestFlags::FLAG_A | TestFlags::FLAG_B).bits(),
341            "Original should be unchanged"
342        );
343    }
344
345    #[test]
346    fn test_bitwise_or_operator() {
347        let f1 = TestFlags::FLAG_A | TestFlags::FLAG_B;
348        let f2 = TestFlags::FLAG_B | TestFlags::FLAG_C;
349        let result = f1 | f2;
350        assert_eq!(
351            result.bits(),
352            TestFlags::FLAG_A.bits() | TestFlags::FLAG_B.bits() | TestFlags::FLAG_C.bits()
353        );
354        assert_eq!(
355            format!("{result:?}"),
356            "TestFlags { FLAG_A | FLAG_B | FLAG_C }"
357        );
358    }
359
360    #[test]
361    fn test_bitwise_and_operator() {
362        let f1 = TestFlags::FLAG_A | TestFlags::FLAG_B;
363        let f2 = TestFlags::FLAG_B | TestFlags::FLAG_C;
364        let result = f1 & f2;
365        assert_eq!(result.bits(), TestFlags::FLAG_B.bits());
366        assert_eq!(format!("{result:?}"), "TestFlags { FLAG_B }");
367    }
368
369    #[test]
370    fn test_bitwise_xor_operator() {
371        let f1 = TestFlags::FLAG_A | TestFlags::FLAG_B;
372        let f2 = TestFlags::FLAG_B | TestFlags::FLAG_C;
373        let result = f1 ^ f2;
374        assert_eq!(
375            result.bits(),
376            TestFlags::FLAG_A.bits() | TestFlags::FLAG_C.bits()
377        );
378        assert_eq!(format!("{result:?}"), "TestFlags { FLAG_A | FLAG_C }");
379    }
380
381    #[test]
382    fn test_bitwise_not_operator() {
383        let flags = TestFlags::FLAG_A;
384        let result = !flags;
385        assert_eq!(result.bits(), !TestFlags::FLAG_A.bits()); // Value depends on the underlying integer type
386                                                              // Debug output for NOT might be complex due to UNKNOWN bits
387                                                              // For u32, !1 is 0xFFFFFFFE, which is a lot of unknown bits.
388                                                              // We'll just assert the raw bits for now.
389    }
390
391    #[test]
392    fn test_assign_or_operator() {
393        let mut flags = TestFlags::FLAG_A;
394        flags |= TestFlags::FLAG_B;
395        assert_eq!(
396            flags.bits(),
397            TestFlags::FLAG_A.bits() | TestFlags::FLAG_B.bits()
398        );
399        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A | FLAG_B }");
400    }
401
402    #[test]
403    fn test_assign_and_operator() {
404        let mut flags = TestFlags::FLAG_A | TestFlags::FLAG_B | TestFlags::FLAG_C; // 0b0111 = 7
405                                                                                   // AND with (FLAG_B | FLAG_D) = 0b0010 | 0b1000 = 0b1010 = 10
406                                                                                   // Expected result: 0b0111 & 0b1010 = 0b0010 (FLAG_B) = 2
407        flags &= TestFlags::FLAG_B | TestFlags::FLAG_D;
408        assert_eq!(flags.bits(), TestFlags::FLAG_B.bits());
409        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_B }");
410    }
411
412    #[test]
413    fn test_assign_xor_operator() {
414        let mut flags = TestFlags::FLAG_A | TestFlags::FLAG_B | TestFlags::FLAG_C;
415        flags ^= TestFlags::FLAG_B; // Toggle B (remove it)
416        assert_eq!(
417            flags.bits(),
418            TestFlags::FLAG_A.bits() | TestFlags::FLAG_C.bits()
419        );
420        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A | FLAG_C }");
421    }
422
423    #[test]
424    fn test_debug_formatting_high_bit() {
425        let flags = TestFlags::CUSTOM_HIGH_BIT;
426        assert_eq!(format!("{flags:?}"), "TestFlags { CUSTOM_HIGH_BIT }");
427    }
428
429    #[test]
430    fn test_debug_formatting_mixed_known_and_unknown() {
431        // Test a combination of known and unknown bits
432        let flags = TestFlags::FLAG_A | TestFlags::from_bits_truncate(1 << 8); // FLAG_A (0x1) | 0x100
433        assert_eq!(
434            format!("{flags:?}"),
435            "TestFlags { FLAG_A | UNKNOWN(0x100) }"
436        );
437
438        let flags_more_unknown =
439            TestFlags::FLAG_B | TestFlags::from_bits_truncate((1 << 8) | (1 << 9)); // FLAG_B (0x2) | 0x100 | 0x200
440        assert_eq!(
441            format!("{flags_more_unknown:?}"),
442            "TestFlags { FLAG_B | UNKNOWN(0x300) }"
443        );
444    }
445
446    #[test]
447    fn test_debug_formatting_only_unknown() {
448        // This test case needs to ensure NO known flags are set.
449        // FLAG_A = 1<<0, FLAG_B = 1<<1, FLAG_C = 1<<2, FLAG_D = 1<<3
450        // So, 0xFF (0b11111111) is actually *not* only unknown; it contains A, B, C, D.
451        // A truly "only unknown" value would be one where bits 0-3 are zero,
452        // and 1<<20 is zero, but other bits are set.
453        // Example: (1 << 4) | (1 << 5) = 0b110000 = 48 (0x30)
454        let flags = TestFlags::from_bits_truncate((1 << 4) | (1 << 5));
455        assert_eq!(format!("{flags:?}"), "TestFlags { UNKNOWN(0x30) }");
456
457        // Another example: a single unknown bit that isn't one of the defined ones
458        let flags_single_unknown = TestFlags::from_bits_truncate(1 << 4); // 0b10000 = 16 (0x10)
459        assert_eq!(
460            format!("{flags_single_unknown:?}"),
461            "TestFlags { UNKNOWN(0x10) }"
462        );
463    }
464
465    #[test]
466    fn test_none_flag_value() {
467        // A flag with value 0 (NONE_FLAG) should not affect operations or appear in debug output
468        let flags = TestFlags::FLAG_A | TestFlags::NONE_FLAG;
469        assert_eq!(flags.bits(), TestFlags::FLAG_A.bits());
470        assert_eq!(format!("{flags:?}"), "TestFlags { FLAG_A }");
471
472        let flags_empty = TestFlags::NONE_FLAG;
473        assert_eq!(flags_empty.bits(), 0);
474        assert_eq!(format!("{flags_empty:?}"), "TestFlags { EMPTY }");
475    }
476}