tiger_lib/
game.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//! Dealing with which game we are validating

use std::fmt::{Display, Formatter};
use std::sync::OnceLock;

use anyhow::{anyhow, Result};
use bitflags::bitflags;

use crate::helpers::display_choices;

/// Records at runtime which game we are validating, in case there are multiple feature flags set.
static GAME: OnceLock<Game> = OnceLock::new();

/// Enum specifying which game we are validating.
///
/// This enum is meant to be optimized away entirely when there is only one feature flag set.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Game {
    #[cfg(feature = "ck3")]
    Ck3,
    #[cfg(feature = "vic3")]
    Vic3,
    #[cfg(feature = "imperator")]
    Imperator,
}

impl Game {
    /// Decide which game we are validating. Should be called as early as possible.
    /// Returns an error if called more than once.
    pub fn set(game: Game) -> Result<()> {
        GAME.set(game).map_err(|_| anyhow!("tried to set game type twice"))?;
        Ok(())
    }

    /// Return which game we are validating. Should only be called after [`Game::set`].
    ///
    /// ## Panics
    /// Will panic if called before [`Game::set`].
    #[allow(clippy::self_named_constructors)] // not a constructor
    #[allow(unreachable_code)]
    pub fn game() -> Game {
        #[cfg(all(feature = "ck3", not(feature = "vic3"), not(feature = "imperator")))]
        return Game::Ck3;
        #[cfg(all(feature = "vic3", not(feature = "ck3"), not(feature = "imperator")))]
        return Game::Vic3;
        #[cfg(all(feature = "imperator", not(feature = "ck3"), not(feature = "vic3")))]
        return Game::Imperator;
        *GAME.get().expect("internal error: don't know which game we are validating")
    }

    /// Convenience function indicating whether we are validating Crusader Kings 3 mods.
    pub(crate) fn is_ck3() -> bool {
        #[cfg(not(feature = "ck3"))]
        return false;
        #[cfg(all(feature = "ck3", not(feature = "vic3"), not(feature = "imperator")))]
        return true;
        #[cfg(all(feature = "ck3", any(feature = "vic3", feature = "imperator")))]
        return GAME.get() == Some(&Game::Ck3);
    }

    /// Convenience function indicating whether we are validating Victoria 3 mods.
    pub(crate) fn is_vic3() -> bool {
        #[cfg(not(feature = "vic3"))]
        return false;
        #[cfg(all(feature = "vic3", not(feature = "ck3"), not(feature = "imperator")))]
        return true;
        #[cfg(all(feature = "vic3", any(feature = "ck3", feature = "imperator")))]
        return GAME.get() == Some(&Game::Vic3);
    }

    /// Convenience function indicating whether we are validating Imperator: Rome mods.
    pub(crate) fn is_imperator() -> bool {
        #[cfg(not(feature = "imperator"))]
        return false;
        #[cfg(all(feature = "imperator", not(feature = "ck3"), not(feature = "vic3")))]
        return true;
        #[cfg(all(feature = "imperator", any(feature = "ck3", feature = "vic3")))]
        return GAME.get() == Some(&Game::Imperator);
    }
}

bitflags! {
    /// A set of bitflags to indicate for which game something is intended,
    /// independent of which game we are validating.
    ///
    /// This way, error messages about things being used in the wrong game can be given at runtime.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
    pub struct GameFlags: u8 {
        const Ck3 = 0x01;
        const Vic3 = 0x02;
        const Imperator = 0x04;
    }
}

impl GameFlags {
    /// Get a [`GameFlags`] value representing the game being validated.
    /// Useful for checking with `.contains`.
    pub fn game() -> Self {
        // Unfortunately we have to translate between the types here.
        match Game::game() {
            #[cfg(feature = "ck3")]
            Game::Ck3 => GameFlags::Ck3,
            #[cfg(feature = "vic3")]
            Game::Vic3 => GameFlags::Vic3,
            #[cfg(feature = "imperator")]
            Game::Imperator => GameFlags::Imperator,
        }
    }
}

impl Display for GameFlags {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        let mut vec = Vec::new();
        if self.contains(Self::Ck3) {
            vec.push("Crusader Kings 3");
        }
        if self.contains(Self::Vic3) {
            vec.push("Victoria 3");
        }
        if self.contains(Self::Imperator) {
            vec.push("Imperator: Rome");
        }
        display_choices(f, &vec, "and")
    }
}

impl From<Game> for GameFlags {
    /// Convert a [`Game`] into a [`GameFlags`] with just that game's flag set.
    fn from(game: Game) -> Self {
        match game {
            #[cfg(feature = "ck3")]
            Game::Ck3 => GameFlags::Ck3,
            #[cfg(feature = "vic3")]
            Game::Vic3 => GameFlags::Vic3,
            #[cfg(feature = "imperator")]
            Game::Imperator => GameFlags::Imperator,
        }
    }
}